I’m working on an Android project that involves a Worker class designed to process a ZIP file input. This ZIP file is structured to include a database file located at /databases/GazeApp.db
and various media files under multiple subfolders like /contacts/1/gallery/*.jpg
among others.
The goal within the restoreDataFromZip()
method is to unpack the ZIP file, iterating over its contents to restore a Room database from the .db
file, which is currently functioning as expected. However, I’m encountering an issue where the media files within the /contacts/
subfolder are not being restored to the application’s private storage as intended. Below is a snippet of the current implementation:
@HiltWorker
class DatabaseRestoreWorker @AssistedInject constructor(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
private val channelId = "restore_channel"
private val channelName = "Database Restore"
private val ongoingNotificationId = 3
override suspend fun doWork(): Result {
val context = applicationContext
// Retrieve the ZIP file URI from the input data
val zipFileUriString = inputData.getString(Const.ZIP_BACKUP_FILE_KEY)
val zipFileUri = zipFileUriString?.let { Uri.parse(it) }
// Check if the URI is not null
if (zipFileUri != null) {
// Step 2: Restore data from the ZIP file
val success = restoreDataFromZip(context, zipFileUri)
// Step 2: Notify user
// showRestoreCompleteNotification(context, success, notificationManager)
// Step 3: If restoration is successful, reset the database
if (success) {
GazeDatabase.resetDatabase()
// Optionally, perform any additional cleanup or reinitialization here
return Result.success()
}
}
return Result.failure()
}
private fun restoreDataFromZip(context: Context, zipFileUri: Uri): Boolean {
val contentResolver = context.contentResolver
var dbRestorationSuccess = false
var mediaFilesRestored = false
contentResolver.openInputStream(zipFileUri)?.use { inputStream ->
ZipInputStream(inputStream).use { zipInputStream ->
var entry: ZipEntry?
while (zipInputStream.nextEntry.also { entry = it } != null) {
val entryName = entry?.name
// Skip processing if entryName is null
if (entryName == null) {
zipInputStream.closeEntry()
continue
}
// Process media files before the database file
if (entryName.startsWith("contacts/")) {
try {
restoreContactsFile(context, entryName, zipInputStream)
mediaFilesRestored = true
} catch (e: Exception) {
// Decide whether to fail the entire process or just log the error
} finally {
zipInputStream.closeEntry() // Ensure the stream for this entry is fully consumed
}
}
// Process the database file
if (entryName == "databases/GazeApp.db") {
try {
restoreDatabaseFile(context, zipInputStream)
dbRestorationSuccess = true
// Break after processing the database to avoid further processing
break
} catch (e: Exception) {
Log.e("DatabaseRestoreWorker", "Failed to restore database", e)
return false // Abort the restoration process if database restoration fails
} finally {
zipInputStream.closeEntry()
}
}
}
}
}
// Restoration is considered successful if the database was successfully restored
return dbRestorationSuccess
}
private fun restoreDatabaseFile(context: Context, zipInputStream: ZipInputStream) {
// Get the path to the current Room database file
val dbPath = context.getDatabasePath(Const.DB_NAME).absolutePath
// Ensure any existing database is closed before overwriting
GazeDatabase.getDatabase(context).close()
GazeDatabase.resetDatabase()
// Replace the current database file with the extracted one
File(dbPath).outputStream().use { fileOutputStream ->
zipInputStream.copyTo(fileOutputStream)
}
}
private fun restoreContactsFile(
context: Context,
entryName: String,
zipInputStream: ZipInputStream
) {
// Construct the output file path within the app's private storage
val outputFile = File(context.filesDir, entryName)
// Ensure the parent directories exist
outputFile.parentFile?.mkdirs()
// Write the ZIP entry content to the output file
outputFile.outputStream().use { fileOutputStream ->
zipInputStream.copyTo(fileOutputStream)
}
}
}
I’ve verified the ZIP file’s integrity and confirmed the presence of .jpg
files within the specified subfolders. Attempting various strategies, I observed that directly invoking restoreContactsFile(context, entryName, zipInputStream)
immediately after checking for a null entryName
does indeed restore the images from the contacts/1/gallery/
subfolder. However, this approach contradicts my requirement to prioritize the database file restoration before proceeding with the media files in the /contacts
subfolders.
Despite experimenting with multiple passes through the ZIP file and even attempting to duplicate the ZIP for a sequential restoration process, the only successful restoration occurs with the immediate call to restoreContactsFile
, which misaligns with my intended logic of database-first restoration followed by media file restoration.
I would greatly appreciate any insights or suggestions on how to achieve the desired sequential restoration process effectively.