mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-20 02:58:45 +00:00
Fix various issues with optimized media.
This commit is contained in:
@@ -589,6 +589,10 @@ object BackupRepository {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun maybeFixAnyDanglingUploadProgress() {
|
fun maybeFixAnyDanglingUploadProgress() {
|
||||||
|
if (SignalStore.account.isLinkedDevice) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (SignalStore.backup.archiveUploadState?.backupPhase == ArchiveUploadProgressState.BackupPhase.Message && AppDependencies.jobManager.find { it.factoryKey == BackupMessagesJob.KEY }.isEmpty()) {
|
if (SignalStore.backup.archiveUploadState?.backupPhase == ArchiveUploadProgressState.BackupPhase.Message && AppDependencies.jobManager.find { it.factoryKey == BackupMessagesJob.KEY }.isEmpty()) {
|
||||||
SignalStore.backup.archiveUploadState = null
|
SignalStore.backup.archiveUploadState = null
|
||||||
BackupMessagesJob.enqueue()
|
BackupMessagesJob.enqueue()
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasSameContents(this.slide, slide)) {
|
if (hasSameContents(this.slide, slide)) {
|
||||||
Log.i(TAG, "Not re-loading slide " + slide.asAttachment().getUri());
|
Log.i(TAG, "Not re-loading slide " + slide.asAttachment().getDisplayUri());
|
||||||
return new SettableFuture<>(false);
|
return new SettableFuture<>(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -869,6 +869,7 @@ class AttachmentTable(
|
|||||||
"""
|
"""
|
||||||
${buildAttachmentsThatNeedUploadQuery("$ARCHIVE_THUMBNAIL_TRANSFER_STATE IN (${ArchiveTransferState.NONE.value}, ${ArchiveTransferState.TEMPORARY_FAILURE.value})")} AND
|
${buildAttachmentsThatNeedUploadQuery("$ARCHIVE_THUMBNAIL_TRANSFER_STATE IN (${ArchiveTransferState.NONE.value}, ${ArchiveTransferState.TEMPORARY_FAILURE.value})")} AND
|
||||||
$QUOTE = 0 AND
|
$QUOTE = 0 AND
|
||||||
|
$STICKER_ID = -1 AND
|
||||||
($CONTENT_TYPE LIKE 'image/%' OR $CONTENT_TYPE LIKE 'video/%') AND
|
($CONTENT_TYPE LIKE 'image/%' OR $CONTENT_TYPE LIKE 'video/%') AND
|
||||||
$CONTENT_TYPE != 'image/svg+xml' AND
|
$CONTENT_TYPE != 'image/svg+xml' AND
|
||||||
$MESSAGE_ID != $WALLPAPER_MESSAGE_ID
|
$MESSAGE_ID != $WALLPAPER_MESSAGE_ID
|
||||||
@@ -888,6 +889,7 @@ class AttachmentTable(
|
|||||||
"""
|
"""
|
||||||
${buildAttachmentsThatNeedUploadQuery("$ARCHIVE_THUMBNAIL_TRANSFER_STATE IN (${ArchiveTransferState.NONE.value}, ${ArchiveTransferState.TEMPORARY_FAILURE.value})")} AND
|
${buildAttachmentsThatNeedUploadQuery("$ARCHIVE_THUMBNAIL_TRANSFER_STATE IN (${ArchiveTransferState.NONE.value}, ${ArchiveTransferState.TEMPORARY_FAILURE.value})")} AND
|
||||||
$QUOTE = 0 AND
|
$QUOTE = 0 AND
|
||||||
|
$STICKER_ID = -1 AND
|
||||||
($CONTENT_TYPE LIKE 'image/%' OR $CONTENT_TYPE LIKE 'video/%') AND
|
($CONTENT_TYPE LIKE 'image/%' OR $CONTENT_TYPE LIKE 'video/%') AND
|
||||||
$CONTENT_TYPE != 'image/svg+xml' AND
|
$CONTENT_TYPE != 'image/svg+xml' AND
|
||||||
$MESSAGE_ID != $WALLPAPER_MESSAGE_ID
|
$MESSAGE_ID != $WALLPAPER_MESSAGE_ID
|
||||||
@@ -1122,11 +1124,15 @@ class AttachmentTable(
|
|||||||
$TABLE_NAME.$OFFLOAD_RESTORED_AT < ${now - 7.days.inWholeMilliseconds} AND
|
$TABLE_NAME.$OFFLOAD_RESTORED_AT < ${now - 7.days.inWholeMilliseconds} AND
|
||||||
$TABLE_NAME.$TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND
|
$TABLE_NAME.$TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND
|
||||||
$TABLE_NAME.$ARCHIVE_TRANSFER_STATE = ${ArchiveTransferState.FINISHED.value} AND
|
$TABLE_NAME.$ARCHIVE_TRANSFER_STATE = ${ArchiveTransferState.FINISHED.value} AND
|
||||||
|
$TABLE_NAME.$DATA_FILE IS NOT NULL AND
|
||||||
|
$TABLE_NAME.$STICKER_ID = -1 AND
|
||||||
|
$TABLE_NAME.$REMOTE_KEY IS NOT NULL AND
|
||||||
|
$TABLE_NAME.$DATA_HASH_END IS NOT NULL AND
|
||||||
(
|
(
|
||||||
$TABLE_NAME.$THUMBNAIL_FILE IS NOT NULL OR
|
$TABLE_NAME.$THUMBNAIL_FILE IS NOT NULL OR
|
||||||
NOT ($TABLE_NAME.$CONTENT_TYPE like 'image/%' OR $TABLE_NAME.$CONTENT_TYPE like 'video/%')
|
NOT ($TABLE_NAME.$CONTENT_TYPE LIKE 'image/%' OR $TABLE_NAME.$CONTENT_TYPE LIKE 'video/%') OR
|
||||||
) AND
|
$TABLE_NAME.$CONTENT_TYPE = 'image/svg+xml'
|
||||||
$TABLE_NAME.$DATA_FILE IS NOT NULL
|
)
|
||||||
)
|
)
|
||||||
AND
|
AND
|
||||||
(
|
(
|
||||||
@@ -1134,7 +1140,7 @@ class AttachmentTable(
|
|||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
writableDatabase
|
val count = writableDatabase
|
||||||
.update(TABLE_NAME)
|
.update(TABLE_NAME)
|
||||||
.values(
|
.values(
|
||||||
TRANSFER_STATE to TRANSFER_RESTORE_OFFLOADED,
|
TRANSFER_STATE to TRANSFER_RESTORE_OFFLOADED,
|
||||||
@@ -1142,11 +1148,12 @@ class AttachmentTable(
|
|||||||
DATA_RANDOM to null,
|
DATA_RANDOM to null,
|
||||||
TRANSFORM_PROPERTIES to null,
|
TRANSFORM_PROPERTIES to null,
|
||||||
DATA_HASH_START to null,
|
DATA_HASH_START to null,
|
||||||
DATA_HASH_END to null,
|
|
||||||
OFFLOAD_RESTORED_AT to 0
|
OFFLOAD_RESTORED_AT to 0
|
||||||
)
|
)
|
||||||
.where("$ID in ($subSelect)")
|
.where("$ID in ($subSelect)")
|
||||||
.run()
|
.run()
|
||||||
|
|
||||||
|
Log.i(TAG, "Marked $count attachments as optimized")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ class OptimizeMediaJob private constructor(parameters: Parameters) : Job(paramet
|
|||||||
SignalDatabase.attachments.markEligibleAttachmentsAsOptimized()
|
SignalDatabase.attachments.markEligibleAttachmentsAsOptimized()
|
||||||
|
|
||||||
Log.i(TAG, "Deleting abandoned attachment files")
|
Log.i(TAG, "Deleting abandoned attachment files")
|
||||||
SignalDatabase.attachments.deleteAbandonedAttachmentFiles()
|
val count = SignalDatabase.attachments.deleteAbandonedAttachmentFiles()
|
||||||
|
Log.i(TAG, "Deleted $count attachments")
|
||||||
|
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class RestoreAttachmentJob private constructor(
|
|||||||
messageId = attachment.mmsId,
|
messageId = attachment.mmsId,
|
||||||
attachmentId = attachment.attachmentId,
|
attachmentId = attachment.attachmentId,
|
||||||
manual = true,
|
manual = true,
|
||||||
queue = Queues.MANUAL_RESTORE.random(),
|
queue = Queues.random(Queues.MANUAL_RESTORE, attachment.dataHash?.hashCode() ?: attachment.remoteKey?.hashCode()),
|
||||||
priority = Parameters.PRIORITY_DEFAULT
|
priority = Parameters.PRIORITY_DEFAULT
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -410,7 +410,7 @@ class RestoreAttachmentJob private constructor(
|
|||||||
inputStream = input,
|
inputStream = input,
|
||||||
offloadRestoredAt = if (manual) System.currentTimeMillis().milliseconds else null,
|
offloadRestoredAt = if (manual) System.currentTimeMillis().milliseconds else null,
|
||||||
archiveRestore = true,
|
archiveRestore = true,
|
||||||
notify = false
|
notify = manual
|
||||||
)
|
)
|
||||||
ArchiveDatabaseExecutor.throttledNotifyAttachmentAndChatListObservers()
|
ArchiveDatabaseExecutor.throttledNotifyAttachmentAndChatListObservers()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,12 +251,13 @@ public abstract class Slide {
|
|||||||
this.hasImage() == that.hasImage() &&
|
this.hasImage() == that.hasImage() &&
|
||||||
this.hasVideo() == that.hasVideo() &&
|
this.hasVideo() == that.hasVideo() &&
|
||||||
this.getTransferState() == that.getTransferState() &&
|
this.getTransferState() == that.getTransferState() &&
|
||||||
Util.equals(this.getUri(), that.getUri());
|
Util.equals(this.getUri(), that.getUri()) &&
|
||||||
|
Util.equals(this.getThumbnailUri(), that.getThumbnailUri());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Util.hashCode(getContentType(), hasAudio(), hasImage(),
|
return Util.hashCode(getContentType(), hasAudio(), hasImage(),
|
||||||
hasVideo(), getUri(), getTransferState());
|
hasVideo(), getUri(), getTransferState(), getThumbnailUri());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms
|
||||||
|
|
||||||
|
import okio.IOException
|
||||||
|
import org.signal.spinner.Plugin
|
||||||
|
import org.signal.spinner.PluginResult
|
||||||
|
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||||
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
|
|
||||||
|
class AttachmentPlugin : Plugin {
|
||||||
|
companion object {
|
||||||
|
const val PATH = "/attachment"
|
||||||
|
}
|
||||||
|
|
||||||
|
override val name: String = "Attachment"
|
||||||
|
override val path: String = PATH
|
||||||
|
|
||||||
|
override fun get(parameters: Map<String, List<String>>): PluginResult {
|
||||||
|
var errorContent = ""
|
||||||
|
|
||||||
|
parameters["attachment_id"]?.firstOrNull()?.let { id ->
|
||||||
|
val attachmentId = id.toLongOrNull()?.let { AttachmentId(it) }
|
||||||
|
if (attachmentId != null) {
|
||||||
|
try {
|
||||||
|
val attachment = SignalDatabase.attachments.getAttachment(attachmentId)
|
||||||
|
if (attachment != null) {
|
||||||
|
val inputStream = if (attachment.transferState == AttachmentTable.TRANSFER_PROGRESS_DONE) {
|
||||||
|
SignalDatabase.attachments.getAttachmentStream(attachmentId, 0)
|
||||||
|
} else {
|
||||||
|
SignalDatabase.attachments.getAttachmentThumbnailStream(attachmentId, 0)
|
||||||
|
}
|
||||||
|
return PluginResult.RawFileResult(attachment.size, inputStream, attachment.contentType ?: "application/octet-stream")
|
||||||
|
} else {
|
||||||
|
throw IOException("Missing attachment, not found for: $attachmentId")
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
errorContent = "${e.javaClass}: ${e.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val formContent = """
|
||||||
|
<form action="$PATH" method="GET">
|
||||||
|
<label for="number">Enter an attachment_id:</label>
|
||||||
|
<input type="number" id="attachment_id" name="attachment_id" required>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
return PluginResult.RawHtmlResult("$formContent<br>$errorContent")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,7 +85,8 @@ class SpinnerApplicationContext : ApplicationContext() {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
linkedMapOf(
|
linkedMapOf(
|
||||||
StorageServicePlugin.PATH to StorageServicePlugin()
|
StorageServicePlugin.PATH to StorageServicePlugin(),
|
||||||
|
AttachmentPlugin.PATH to AttachmentPlugin()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class StorageServicePlugin : Plugin {
|
|||||||
override val name: String = "Storage"
|
override val name: String = "Storage"
|
||||||
override val path: String = PATH
|
override val path: String = PATH
|
||||||
|
|
||||||
override fun get(): PluginResult {
|
override fun get(parameters: Map<String, List<String>>): PluginResult {
|
||||||
val columns = listOf("Type", "Id", "Data")
|
val columns = listOf("Type", "Id", "Data")
|
||||||
val rows = mutableListOf<List<String>>()
|
val rows = mutableListOf<List<String>>()
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,9 @@
|
|||||||
{{#if (eq "string" pluginResult.type)}}
|
{{#if (eq "string" pluginResult.type)}}
|
||||||
<p>{{pluginResult.text}}</p>
|
<p>{{pluginResult.text}}</p>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if (eq "html" pluginResult.type)}}
|
||||||
|
{{{pluginResult.html}}}
|
||||||
|
{{/if}}
|
||||||
{{> partials/suffix }}
|
{{> partials/suffix }}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.signal.spinner
|
package org.signal.spinner
|
||||||
|
|
||||||
interface Plugin {
|
interface Plugin {
|
||||||
fun get(): PluginResult
|
fun get(parameters: Map<String, List<String>>): PluginResult
|
||||||
val name: String
|
val name: String
|
||||||
val path: String
|
val path: String
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.signal.spinner
|
package org.signal.spinner
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
sealed class PluginResult(val type: String) {
|
sealed class PluginResult(val type: String) {
|
||||||
data class TableResult(
|
data class TableResult(
|
||||||
val columns: List<String>,
|
val columns: List<String>,
|
||||||
@@ -10,4 +12,14 @@ sealed class PluginResult(val type: String) {
|
|||||||
data class StringResult(
|
data class StringResult(
|
||||||
val text: String
|
val text: String
|
||||||
) : PluginResult("string")
|
) : PluginResult("string")
|
||||||
|
|
||||||
|
data class RawHtmlResult(
|
||||||
|
val html: String
|
||||||
|
) : PluginResult("html")
|
||||||
|
|
||||||
|
data class RawFileResult(
|
||||||
|
val length: Long,
|
||||||
|
val data: InputStream,
|
||||||
|
val mimeType: String
|
||||||
|
) : PluginResult("file")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import org.signal.core.util.logging.Log
|
|||||||
import org.signal.core.util.tracing.Tracer
|
import org.signal.core.util.tracing.Tracer
|
||||||
import org.signal.spinner.Spinner.DatabaseConfig
|
import org.signal.spinner.Spinner.DatabaseConfig
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.lang.IllegalArgumentException
|
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
@@ -79,7 +78,7 @@ internal class SpinnerServer(
|
|||||||
else -> {
|
else -> {
|
||||||
val plugin = plugins[session.uri]
|
val plugin = plugins[session.uri]
|
||||||
if (plugin != null && session.method == Method.GET) {
|
if (plugin != null && session.method == Method.GET) {
|
||||||
getPlugin(dbParam, plugin)
|
getPlugin(dbParam, plugin, session.parameters)
|
||||||
} else {
|
} else {
|
||||||
newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_HTML, "Not found")
|
newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_HTML, "Not found")
|
||||||
}
|
}
|
||||||
@@ -280,7 +279,14 @@ internal class SpinnerServer(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPlugin(dbName: String, plugin: Plugin): Response {
|
private fun getPlugin(dbName: String, plugin: Plugin, parameters: Map<String, List<String>>): Response {
|
||||||
|
val pluginResult = plugin.get(parameters)
|
||||||
|
|
||||||
|
when (pluginResult) {
|
||||||
|
is PluginResult.RawFileResult -> {
|
||||||
|
return newFixedLengthResponse(Response.Status.OK, pluginResult.mimeType, pluginResult.data, pluginResult.length)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
return renderTemplate(
|
return renderTemplate(
|
||||||
"plugin",
|
"plugin",
|
||||||
PluginPageModel(
|
PluginPageModel(
|
||||||
@@ -290,10 +296,12 @@ internal class SpinnerServer(
|
|||||||
databases = databases.keys.toList(),
|
databases = databases.keys.toList(),
|
||||||
plugins = plugins.values.toList(),
|
plugins = plugins.values.toList(),
|
||||||
activePlugin = plugin,
|
activePlugin = plugin,
|
||||||
pluginResult = plugin.get()
|
pluginResult = plugin.get(parameters)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun internalError(throwable: Throwable): Response {
|
private fun internalError(throwable: Throwable): Response {
|
||||||
val stackTrace = ExceptionUtil.convertThrowableToString(throwable)
|
val stackTrace = ExceptionUtil.convertThrowableToString(throwable)
|
||||||
|
|||||||
Reference in New Issue
Block a user