fix conflict

pull/420/head
Brice 4 years ago
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.mms.PartAuthority
import org.thoughtcrime.securesms.util.MediaUtil
import java.io.InputStream
class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider {
@ -39,6 +40,11 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
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 {
val smsDatabase = DatabaseFactory.getSmsDatabase(context)
return smsDatabase.isOutgoingMessage(timestamp)
@ -61,10 +67,11 @@ fun DatabaseAttachment.toAttachmentStream(context: Context): SessionServiceAttac
attachmentStream.key = ByteString.copyFrom(this.key?.toByteArray())
attachmentStream.digest = this.digest
//attachmentStream.flags = if (this.isVoiceNote) SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE.number else 0
attachmentStream.url = this.url
//TODO attachmentStream.listener
return attachmentStream
}

@ -65,4 +65,6 @@ dependencies {
testImplementation "org.assertj:assertj-core:1.7.1"
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.SessionServiceAttachmentStream
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
import java.io.InputStream
interface MessageDataProvider {
@ -20,6 +21,8 @@ interface MessageDataProvider {
fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long)
fun insertAttachment(messageId: Long, attachmentId: Long, stream : InputStream)
fun isOutgoingMessage(timestamp: Long): Boolean
@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
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 id: String? = null
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
override val maxFailureCount: Int = 100
override val maxFailureCount: Int = 20
companion object {
val collection: String = "AttachmentDownloadJobCollection"
}
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()
}
}
/**
* 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

@ -4,19 +4,19 @@ import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map
import nl.komponents.kovenant.then
import okhttp3.MediaType
import okhttp3.MultipartBody
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.*
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.SnodeAPI
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.loki.DiffieHellman
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.PushNetworkException
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.loki.api.utilities.HTTP
import org.session.libsignal.service.loki.utilities.*
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.util.*
/**
@ -48,6 +51,8 @@ open class DotNetAPI {
object DecryptionFailed : Error("Couldn't decrypt file.")
object MaxFileSizeExceeded : Error("Maximum file size exceeded.")
object TokenExpired: Error("Token expired.") // Session Android
internal val isRetryable: Boolean = false
}
companion object {
@ -56,7 +61,7 @@ open class DotNetAPI {
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 token = storage.getAuthToken(server)
if (token != null) { return Promise.of(token) }
@ -175,6 +180,80 @@ open class DotNetAPI {
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)
fun uploadAttachment(server: String, attachment: PushAttachmentData): UploadResult {
// This function mimics what Signal does in PushServiceSocket

Loading…
Cancel
Save