mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-17 13:58:25 +00:00
fix conflict
This commit is contained in:
parent
2dcbcee66c
commit
3a9304098b
@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
|||||||
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob
|
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil
|
import org.thoughtcrime.securesms.util.MediaUtil
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider {
|
class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider {
|
||||||
|
|
||||||
@ -39,6 +40,11 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
|||||||
attachmentUploadJob.onRun()
|
attachmentUploadJob.onRun()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun insertAttachment(messageId: Long, attachmentId: Long, stream : InputStream) {
|
||||||
|
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
|
||||||
|
attachmentDatabase.insertAttachmentsForPlaceholder(messageId, AttachmentId(attachmentId,0), stream)
|
||||||
|
}
|
||||||
|
|
||||||
override fun isOutgoingMessage(timestamp: Long): Boolean {
|
override fun isOutgoingMessage(timestamp: Long): Boolean {
|
||||||
val smsDatabase = DatabaseFactory.getSmsDatabase(context)
|
val smsDatabase = DatabaseFactory.getSmsDatabase(context)
|
||||||
return smsDatabase.isOutgoingMessage(timestamp)
|
return smsDatabase.isOutgoingMessage(timestamp)
|
||||||
@ -61,10 +67,11 @@ fun DatabaseAttachment.toAttachmentStream(context: Context): SessionServiceAttac
|
|||||||
|
|
||||||
attachmentStream.key = ByteString.copyFrom(this.key?.toByteArray())
|
attachmentStream.key = ByteString.copyFrom(this.key?.toByteArray())
|
||||||
attachmentStream.digest = this.digest
|
attachmentStream.digest = this.digest
|
||||||
//attachmentStream.flags = if (this.isVoiceNote) SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE.number else 0
|
|
||||||
|
|
||||||
attachmentStream.url = this.url
|
attachmentStream.url = this.url
|
||||||
|
|
||||||
|
//TODO attachmentStream.listener
|
||||||
|
|
||||||
return attachmentStream
|
return attachmentStream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,4 +65,6 @@ dependencies {
|
|||||||
testImplementation "org.assertj:assertj-core:1.7.1"
|
testImplementation "org.assertj:assertj-core:1.7.1"
|
||||||
testImplementation "org.conscrypt:conscrypt-openjdk-uber:2.0.0"
|
testImplementation "org.conscrypt:conscrypt-openjdk-uber:2.0.0"
|
||||||
|
|
||||||
|
implementation 'org.greenrobot:eventbus:3.0.0'
|
||||||
|
|
||||||
}
|
}
|
@ -4,6 +4,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
|
|||||||
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentPointer
|
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentPointer
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentStream
|
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentStream
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
|
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
interface MessageDataProvider {
|
interface MessageDataProvider {
|
||||||
|
|
||||||
@ -20,6 +21,8 @@ interface MessageDataProvider {
|
|||||||
|
|
||||||
fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long)
|
fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long)
|
||||||
|
|
||||||
|
fun insertAttachment(messageId: Long, attachmentId: Long, stream : InputStream)
|
||||||
|
|
||||||
fun isOutgoingMessage(timestamp: Long): Boolean
|
fun isOutgoingMessage(timestamp: Long): Boolean
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package org.session.libsession.events
|
||||||
|
|
||||||
|
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachment
|
||||||
|
|
||||||
|
class AttachmentProgressEvent(attachment: SessionServiceAttachment, total: Long, progress: Long) {
|
||||||
|
}
|
@ -1,17 +1,86 @@
|
|||||||
package org.session.libsession.messaging.jobs
|
package org.session.libsession.messaging.jobs
|
||||||
|
|
||||||
class AttachmentDownloadJob: Job {
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.session.libsession.events.AttachmentProgressEvent
|
||||||
|
import org.session.libsession.messaging.MessagingConfiguration
|
||||||
|
import org.session.libsession.messaging.fileserver.FileServerAPI
|
||||||
|
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
|
||||||
|
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachment
|
||||||
|
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||||
|
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream
|
||||||
|
import org.session.libsignal.service.api.messages.SignalServiceAttachment
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
class AttachmentDownloadJob(val attachmentID: Long, val tsIncomingMessageID: Long): Job {
|
||||||
|
|
||||||
override var delegate: JobDelegate? = null
|
override var delegate: JobDelegate? = null
|
||||||
override var id: String? = null
|
override var id: String? = null
|
||||||
override var failureCount: Int = 0
|
override var failureCount: Int = 0
|
||||||
|
|
||||||
|
private val MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024
|
||||||
|
|
||||||
|
// Error
|
||||||
|
internal sealed class Error(val description: String) : Exception() {
|
||||||
|
object NoAttachment : Error("No such attachment.")
|
||||||
|
}
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
override val maxFailureCount: Int = 100
|
override val maxFailureCount: Int = 20
|
||||||
companion object {
|
companion object {
|
||||||
val collection: String = "AttachmentDownloadJobCollection"
|
val collection: String = "AttachmentDownloadJobCollection"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun execute() {
|
override fun execute() {
|
||||||
TODO("Not yet implemented")
|
val messageDataProvider = MessagingConfiguration.shared.messageDataProvider
|
||||||
|
val attachmentPointer = messageDataProvider.getAttachmentPointer(attachmentID) ?: return handleFailure(Error.NoAttachment)
|
||||||
|
messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.tsIncomingMessageID)
|
||||||
|
val tempFile = createTempFile()
|
||||||
|
val handleFailure: (java.lang.Exception) -> Unit = { exception ->
|
||||||
|
tempFile.delete()
|
||||||
|
if(exception is Error && exception == Error.NoAttachment) {
|
||||||
|
MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, tsIncomingMessageID)
|
||||||
|
this.handlePermanentFailure(exception)
|
||||||
|
} else if (exception is DotNetAPI.Error && exception == DotNetAPI.Error.ParsingFailed) {
|
||||||
|
// No need to retry if the response is invalid. Most likely this means we (incorrectly)
|
||||||
|
// got a "Cannot GET ..." error from the file server.
|
||||||
|
MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, tsIncomingMessageID)
|
||||||
|
this.handlePermanentFailure(exception)
|
||||||
|
} else {
|
||||||
|
this.handleFailure(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
//TODO find how to implement a functional interface in kotlin + use it here & on AttachmentUploadJob (see TODO in DatabaseAttachmentProvider.kt on app side)
|
||||||
|
val listener = SessionServiceAttachment.ProgressListener { override fun onAttachmentProgress(total: Long, progress: Long) { EventBus.getDefault().postSticky(AttachmentProgressEvent(attachmentPointer, total, progress)) } }
|
||||||
|
FileServerAPI.shared.downloadFile(tempFile, attachmentPointer.url, MAX_ATTACHMENT_SIZE, listener)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return handleFailure(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume we're retrieving an attachment for an open group server if the digest is not set
|
||||||
|
var stream = if (!attachmentPointer.digest.isPresent) FileInputStream(tempFile)
|
||||||
|
else AttachmentCipherInputStream.createForAttachment(tempFile, attachmentPointer.size.or(0).toLong(), attachmentPointer.key?.toByteArray(), attachmentPointer.digest.get())
|
||||||
|
|
||||||
|
messageDataProvider.insertAttachment(tsIncomingMessageID, attachmentID, stream)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSuccess() {
|
||||||
|
delegate?.handleJobSucceeded(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePermanentFailure(e: Exception) {
|
||||||
|
delegate?.handleJobFailedPermanently(this, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleFailure(e: Exception) {
|
||||||
|
delegate?.handleJobFailed(this, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTempFile(): File {
|
||||||
|
val file = File.createTempFile("push-attachment", "tmp", MessagingConfiguration.shared.context.cacheDir)
|
||||||
|
file.deleteOnExit()
|
||||||
|
return file
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -112,6 +112,20 @@ abstract class SessionServiceAttachment protected constructor(val contentType: S
|
|||||||
return Builder()
|
return Builder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface to receive progress information on upload/download of
|
||||||
|
* an attachment.
|
||||||
|
*/
|
||||||
|
interface ProgressListener {
|
||||||
|
/**
|
||||||
|
* Called on a progress change event.
|
||||||
|
*
|
||||||
|
* @param total The total amount to transmit/receive in bytes.
|
||||||
|
* @param progress The amount that has been transmitted/received in bytes thus far
|
||||||
|
*/
|
||||||
|
fun onAttachmentProgress(total: Long, progress: Long)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// matches values in AttachmentDatabase.java
|
// matches values in AttachmentDatabase.java
|
||||||
|
@ -4,19 +4,19 @@ import nl.komponents.kovenant.Promise
|
|||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import nl.komponents.kovenant.then
|
import nl.komponents.kovenant.then
|
||||||
import okhttp3.MediaType
|
import okhttp3.*
|
||||||
import okhttp3.MultipartBody
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.RequestBody
|
|
||||||
|
|
||||||
import org.session.libsession.messaging.MessagingConfiguration
|
import org.session.libsession.messaging.MessagingConfiguration
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.messaging.fileserver.FileServerAPI
|
import org.session.libsession.messaging.fileserver.FileServerAPI
|
||||||
|
import org.session.libsession.messaging.sending_receiving.MessageReceiver
|
||||||
|
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachment
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.logging.Log
|
import org.session.libsignal.libsignal.logging.Log
|
||||||
import org.session.libsignal.libsignal.loki.DiffieHellman
|
import org.session.libsignal.libsignal.loki.DiffieHellman
|
||||||
import org.session.libsignal.service.api.crypto.ProfileCipherOutputStream
|
import org.session.libsignal.service.api.crypto.ProfileCipherOutputStream
|
||||||
|
import org.session.libsignal.service.api.messages.SignalServiceAttachment
|
||||||
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException
|
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException
|
||||||
import org.session.libsignal.service.api.push.exceptions.PushNetworkException
|
import org.session.libsignal.service.api.push.exceptions.PushNetworkException
|
||||||
import org.session.libsignal.service.api.util.StreamDetails
|
import org.session.libsignal.service.api.util.StreamDetails
|
||||||
@ -29,6 +29,9 @@ import org.session.libsignal.service.internal.util.Hex
|
|||||||
import org.session.libsignal.service.internal.util.JsonUtil
|
import org.session.libsignal.service.internal.util.JsonUtil
|
||||||
import org.session.libsignal.service.loki.api.utilities.HTTP
|
import org.session.libsignal.service.loki.api.utilities.HTTP
|
||||||
import org.session.libsignal.service.loki.utilities.*
|
import org.session.libsignal.service.loki.utilities.*
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.OutputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,6 +51,8 @@ open class DotNetAPI {
|
|||||||
object DecryptionFailed : Error("Couldn't decrypt file.")
|
object DecryptionFailed : Error("Couldn't decrypt file.")
|
||||||
object MaxFileSizeExceeded : Error("Maximum file size exceeded.")
|
object MaxFileSizeExceeded : Error("Maximum file size exceeded.")
|
||||||
object TokenExpired: Error("Token expired.") // Session Android
|
object TokenExpired: Error("Token expired.") // Session Android
|
||||||
|
|
||||||
|
internal val isRetryable: Boolean = false
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -56,7 +61,7 @@ open class DotNetAPI {
|
|||||||
|
|
||||||
public data class UploadResult(val id: Long, val url: String, val digest: ByteArray?)
|
public data class UploadResult(val id: Long, val url: String, val digest: ByteArray?)
|
||||||
|
|
||||||
public fun getAuthToken(server: String): Promise<String, Exception> {
|
fun getAuthToken(server: String): Promise<String, Exception> {
|
||||||
val storage = MessagingConfiguration.shared.storage
|
val storage = MessagingConfiguration.shared.storage
|
||||||
val token = storage.getAuthToken(server)
|
val token = storage.getAuthToken(server)
|
||||||
if (token != null) { return Promise.of(token) }
|
if (token != null) { return Promise.of(token) }
|
||||||
@ -175,6 +180,80 @@ open class DotNetAPI {
|
|||||||
return execute(HTTPVerb.PATCH, server, "users/me", parameters = parameters)
|
return execute(HTTPVerb.PATCH, server, "users/me", parameters = parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DOWNLOAD
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks the calling thread.
|
||||||
|
*/
|
||||||
|
fun downloadFile(destination: File, url: String, maxSize: Int, listener: SessionServiceAttachment.ProgressListener?) {
|
||||||
|
val outputStream = FileOutputStream(destination) // Throws
|
||||||
|
var remainingAttempts = 4
|
||||||
|
var exception: Exception? = null
|
||||||
|
while (remainingAttempts > 0) {
|
||||||
|
remainingAttempts -= 1
|
||||||
|
try {
|
||||||
|
downloadFile(outputStream, url, maxSize, listener)
|
||||||
|
exception = null
|
||||||
|
break
|
||||||
|
} catch (e: Exception) {
|
||||||
|
exception = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (exception != null) { throw exception }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks the calling thread.
|
||||||
|
*/
|
||||||
|
fun downloadFile(outputStream: OutputStream, url: String, maxSize: Int, listener: SessionServiceAttachment.ProgressListener?) {
|
||||||
|
// We need to throw a PushNetworkException or NonSuccessfulResponseCodeException
|
||||||
|
// because the underlying Signal logic requires these to work correctly
|
||||||
|
val oldPrefixedHost = "https://" + HttpUrl.get(url).host()
|
||||||
|
var newPrefixedHost = oldPrefixedHost
|
||||||
|
if (oldPrefixedHost.contains(FileServerAPI.fileStorageBucketURL)) {
|
||||||
|
newPrefixedHost = FileServerAPI.shared.server
|
||||||
|
}
|
||||||
|
// Edge case that needs to work: https://file-static.lokinet.org/i1pNmpInq3w9gF3TP8TFCa1rSo38J6UM
|
||||||
|
// → https://file.getsession.org/loki/v1/f/XLxogNXVEIWHk14NVCDeppzTujPHxu35
|
||||||
|
val fileID = url.substringAfter(oldPrefixedHost).substringAfter("/f/")
|
||||||
|
val sanitizedURL = "$newPrefixedHost/loki/v1/f/$fileID"
|
||||||
|
val request = Request.Builder().url(sanitizedURL).get()
|
||||||
|
try {
|
||||||
|
val serverPublicKey = if (newPrefixedHost.contains(FileServerAPI.shared.server)) FileServerAPI.fileServerPublicKey
|
||||||
|
else FileServerAPI.shared.getPublicKeyForOpenGroupServer(newPrefixedHost).get()
|
||||||
|
val json = OnionRequestAPI.sendOnionRequest(request.build(), newPrefixedHost, serverPublicKey, isJSONRequired = false).get()
|
||||||
|
val result = json["result"] as? String
|
||||||
|
if (result == null) {
|
||||||
|
Log.d("Loki", "Couldn't parse attachment from: $json.")
|
||||||
|
throw PushNetworkException("Missing response body.")
|
||||||
|
}
|
||||||
|
val body = Base64.decode(result)
|
||||||
|
if (body.size > maxSize) {
|
||||||
|
Log.d("Loki", "Attachment size limit exceeded.")
|
||||||
|
throw PushNetworkException("Max response size exceeded.")
|
||||||
|
}
|
||||||
|
val input = body.inputStream()
|
||||||
|
val buffer = ByteArray(32768)
|
||||||
|
var count = 0
|
||||||
|
var bytes = input.read(buffer)
|
||||||
|
while (bytes >= 0) {
|
||||||
|
outputStream.write(buffer, 0, bytes)
|
||||||
|
count += bytes
|
||||||
|
if (count > maxSize) {
|
||||||
|
Log.d("Loki", "Attachment size limit exceeded.")
|
||||||
|
throw PushNetworkException("Max response size exceeded.")
|
||||||
|
}
|
||||||
|
listener?.onAttachmentProgress(body.size.toLong(), count.toLong())
|
||||||
|
bytes = input.read(buffer)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d("Loki", "Couldn't download attachment due to error: $e.")
|
||||||
|
throw if (e is NonSuccessfulResponseCodeException) e else PushNetworkException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPLOAD
|
||||||
|
|
||||||
@Throws(PushNetworkException::class, NonSuccessfulResponseCodeException::class)
|
@Throws(PushNetworkException::class, NonSuccessfulResponseCodeException::class)
|
||||||
fun uploadAttachment(server: String, attachment: PushAttachmentData): UploadResult {
|
fun uploadAttachment(server: String, attachment: PushAttachmentData): UploadResult {
|
||||||
// This function mimics what Signal does in PushServiceSocket
|
// This function mimics what Signal does in PushServiceSocket
|
||||||
|
Loading…
x
Reference in New Issue
Block a user