Fix handling of missing files during archive upload.

This commit is contained in:
Greyson Parrelli
2025-03-28 10:40:08 -04:00
parent d1accfff82
commit f48a13afc0
2 changed files with 29 additions and 9 deletions

View File

@@ -308,11 +308,7 @@ class AttachmentTable(
@Throws(IOException::class) @Throws(IOException::class)
fun getAttachmentStream(attachmentId: AttachmentId, offset: Long): InputStream { fun getAttachmentStream(attachmentId: AttachmentId, offset: Long): InputStream {
return try { return getDataStream(attachmentId, offset) ?: throw FileNotFoundException("No stream for: $attachmentId")
getDataStream(attachmentId, offset)
} catch (e: FileNotFoundException) {
throw IOException("No stream for: $attachmentId", e)
} ?: throw IOException("No stream for: $attachmentId")
} }
@Throws(IOException::class) @Throws(IOException::class)
@@ -671,6 +667,26 @@ class AttachmentTable(
} }
} }
/**
* Sets the archive transfer state for the given attachment and all other attachments that share the same data file.
*/
fun setArchiveTransferStateUnlessPermanentFailure(id: AttachmentId, state: ArchiveTransferState) {
writableDatabase.withinTransaction {
val dataFile: String = readableDatabase
.select(DATA_FILE)
.from(TABLE_NAME)
.where("$ID = ?", id.id)
.run()
.readToSingleObject { it.requireString(DATA_FILE) } ?: return@withinTransaction
writableDatabase
.update(TABLE_NAME)
.values(ARCHIVE_TRANSFER_STATE to state.value)
.where("$DATA_FILE = ? AND $ARCHIVE_TRANSFER_STATE != ${ArchiveTransferState.PERMANENT_FAILURE.value}", dataFile)
.run()
}
}
/** /**
* Marks eligible attachments as offloaded based on their received at timestamp, their last restore time, * Marks eligible attachments as offloaded based on their received at timestamp, their last restore time,
* presence of thumbnail if media, and the full file being available in the archive. * presence of thumbnail if media, and the full file being available in the archive.

View File

@@ -26,6 +26,7 @@ import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.archive.ArchiveMediaUploadFormStatusCodes import org.whispersystems.signalservice.api.archive.ArchiveMediaUploadFormStatusCodes
import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment import org.whispersystems.signalservice.api.messages.SignalServiceAttachment
import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.net.ProtocolException import java.net.ProtocolException
import kotlin.random.Random import kotlin.random.Random
@@ -74,7 +75,7 @@ class UploadAttachmentToArchiveJob private constructor(
if (transferStatus == AttachmentTable.ArchiveTransferState.NONE) { if (transferStatus == AttachmentTable.ArchiveTransferState.NONE) {
Log.d(TAG, "[$attachmentId] Updating archive transfer state to ${AttachmentTable.ArchiveTransferState.UPLOAD_IN_PROGRESS}") Log.d(TAG, "[$attachmentId] Updating archive transfer state to ${AttachmentTable.ArchiveTransferState.UPLOAD_IN_PROGRESS}")
SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.UPLOAD_IN_PROGRESS) SignalDatabase.attachments.setArchiveTransferStateUnlessPermanentFailure(attachmentId, AttachmentTable.ArchiveTransferState.UPLOAD_IN_PROGRESS)
} }
} }
@@ -110,7 +111,6 @@ class UploadAttachmentToArchiveJob private constructor(
if (attachment.remoteKey == null || attachment.remoteIv == null) { if (attachment.remoteKey == null || attachment.remoteIv == null) {
Log.w(TAG, "[$attachmentId] Attachment is missing remote key or IV! Cannot upload.") Log.w(TAG, "[$attachmentId] Attachment is missing remote key or IV! Cannot upload.")
SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.NONE)
return Result.failure() return Result.failure()
} }
@@ -143,8 +143,12 @@ class UploadAttachmentToArchiveJob private constructor(
override fun shouldCancel() = this@UploadAttachmentToArchiveJob.isCanceled override fun shouldCancel() = this@UploadAttachmentToArchiveJob.isCanceled
} }
) )
} catch (e: FileNotFoundException) {
Log.w(TAG, "[$attachmentId] No file exists for this attachment! Marking as a permanent failure.", e)
SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.PERMANENT_FAILURE)
return Result.failure()
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "[$attachmentId] Failed to get attachment stream.", e) Log.w(TAG, "[$attachmentId] Failed while reading the stream.", e)
return Result.retry(defaultBackoff()) return Result.retry(defaultBackoff())
} }
@@ -188,7 +192,7 @@ class UploadAttachmentToArchiveJob private constructor(
Log.w(TAG, "[$attachmentId] Job was canceled, updating archive transfer state to ${AttachmentTable.ArchiveTransferState.NONE}.") Log.w(TAG, "[$attachmentId] Job was canceled, updating archive transfer state to ${AttachmentTable.ArchiveTransferState.NONE}.")
SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.NONE) SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.NONE)
} else { } else {
Log.w(TAG, "[$attachmentId] Job failed, updating archive transfer state to ${AttachmentTable.ArchiveTransferState.TEMPORARY_FAILURE}.") Log.w(TAG, "[$attachmentId] Job failed, updating archive transfer state to ${AttachmentTable.ArchiveTransferState.TEMPORARY_FAILURE} (if not already a permanent failure).")
SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.TEMPORARY_FAILURE) SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.TEMPORARY_FAILURE)
} }
} }