Skip to content

Commit

Permalink
Replace RequestMetaData with copy of Uploadable model
Browse files Browse the repository at this point in the history
  • Loading branch information
hb0 committed Jul 15, 2024
1 parent d89d142 commit 12ad419
Show file tree
Hide file tree
Showing 21 changed files with 1,435 additions and 128 deletions.
102 changes: 26 additions & 76 deletions src/main/kotlin/de/cyface/uploader/DefaultUploader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.http.json.JsonHttpContent
import com.google.api.client.json.JsonFactory
import com.google.api.client.json.gson.GsonFactory
import de.cyface.model.RequestMetaData
import de.cyface.uploader.exception.AccountNotActivated
import de.cyface.uploader.exception.BadRequestException
import de.cyface.uploader.exception.ConflictException
Expand All @@ -44,6 +43,9 @@ import de.cyface.uploader.exception.UnauthorizedException
import de.cyface.uploader.exception.UnexpectedResponseCode
import de.cyface.uploader.exception.UploadFailed
import de.cyface.uploader.exception.UploadSessionExpired
import de.cyface.uploader.model.Attachment
import de.cyface.uploader.model.Measurement
import de.cyface.uploader.model.Uploadable
import org.slf4j.LoggerFactory
import java.io.BufferedInputStream
import java.io.BufferedReader
Expand Down Expand Up @@ -73,25 +75,25 @@ class DefaultUploader(private val apiEndpoint: String) : Uploader {
@Suppress("unused", "CyclomaticComplexMethod", "LongMethod") // Part of the API
override fun uploadMeasurement(
jwtToken: String,
metaData: RequestMetaData<RequestMetaData.MeasurementIdentifier>,
uploadable: Measurement,
file: File,
progressListener: UploadProgressListener
): Result {
val endpoint = measurementsEndpoint()
return uploadFile(jwtToken, metaData, file, endpoint, progressListener)
return uploadFile(jwtToken, uploadable, file, endpoint, progressListener)
}

override fun uploadAttachment(
jwtToken: String,
metaData: RequestMetaData<RequestMetaData.AttachmentIdentifier>,
uploadable: Attachment,
file: File,
fileName: String,
progressListener: UploadProgressListener,
): Result {
val measurementId = metaData.identifier.measurementId.toLong()
val deviceId = metaData.identifier.deviceId
val measurementId = uploadable.identifier.measurementIdentifier
val deviceId = uploadable.identifier.deviceIdentifier.toString()
val endpoint = attachmentsEndpoint(deviceId, measurementId)
return uploadFile(jwtToken, metaData, file, endpoint, progressListener)
return uploadFile(jwtToken, uploadable, file, endpoint, progressListener)
}

override fun measurementsEndpoint(): URL {
Expand All @@ -103,16 +105,16 @@ class DefaultUploader(private val apiEndpoint: String) : Uploader {
}

@Throws(UploadFailed::class)
private fun <T : RequestMetaData.MeasurementIdentifier> uploadFile(
private fun uploadFile(
jwtToken: String,
metaData: RequestMetaData<T>,
uploadable: Uploadable,
file: File,
endpoint: URL,
progressListener: UploadProgressListener
): Result {
return try {
FileInputStream(file).use { fis ->
val uploader = initializeUploader(jwtToken, metaData, fis, file)
val uploader = initializeUploader(jwtToken, uploadable, fis, file)

// We currently cannot merge multiple upload-chunk requests into one file on server side.
// Thus, we prevent slicing the file into multiple files by increasing the chunk size.
Expand All @@ -125,8 +127,7 @@ class DefaultUploader(private val apiEndpoint: String) : Uploader {

// Add meta data to PreRequest
val jsonFactory = GsonFactory()
val preRequestBody = preRequestBody(metaData)
uploader.metadata = JsonHttpContent(jsonFactory, preRequestBody)
uploader.metadata = JsonHttpContent(jsonFactory, uploadable.toMap())

// Vert.X currently only supports compressing "down-stream" out of the box
uploader.disableGZipContent = true
Expand All @@ -148,9 +149,9 @@ class DefaultUploader(private val apiEndpoint: String) : Uploader {
}
}

private fun <T : RequestMetaData.MeasurementIdentifier> initializeUploader(
private fun initializeUploader(
jwtToken: String,
metaData: RequestMetaData<T>,
uploadable: Uploadable,
fileInputStream: FileInputStream,
file: File
): MediaHttpUploader {
Expand All @@ -160,7 +161,7 @@ class DefaultUploader(private val apiEndpoint: String) : Uploader {
LOGGER.debug("mediaContent.length: ${mediaContent.length}")
val transport = NetHttpTransport() // Use Builder to modify behaviour
val jwtBearer = "Bearer $jwtToken"
val httpRequestInitializer = RequestInitializeHandler(metaData, jwtBearer)
val httpRequestInitializer = RequestInitializeHandler(uploadable, jwtBearer)
return MediaHttpUploader(mediaContent, transport, httpRequestInitializer)
}

Expand Down Expand Up @@ -398,53 +399,6 @@ class DefaultUploader(private val apiEndpoint: String) : Uploader {
}
}

/**
* Assembles a `HttpContent` object which contains the metadata.
*
* @param metaData The metadata to convert.
* @return The meta data as `HttpContent`.
*/
fun <T : RequestMetaData.MeasurementIdentifier> preRequestBody(metaData: RequestMetaData<T>):
Map<String, String> {
val attributes: MutableMap<String, String> = HashMap()

// Location meta data
metaData.measurementMetaData.startLocation?.let { startLocation ->
attributes["startLocLat"] = startLocation.latitude.toString()
attributes["startLocLon"] = startLocation.longitude.toString()
attributes["startLocTS"] = startLocation.timestamp.toString()
}
metaData.measurementMetaData.endLocation?.let { endLocation ->
attributes["endLocLat"] = endLocation.latitude.toString()
attributes["endLocLon"] = endLocation.longitude.toString()
attributes["endLocTS"] = endLocation.timestamp.toString()
}
attributes["locationCount"] = metaData.measurementMetaData.locationCount.toString()

// Attachment meta data
when (metaData.identifier) {
is RequestMetaData.AttachmentIdentifier -> {
val identifier = metaData.identifier as RequestMetaData.AttachmentIdentifier
attributes["attachmentId"] = identifier.attachmentId
}
}
attributes["logCount"] = metaData.attachmentMetaData.logCount.toString()
attributes["imageCount"] = metaData.attachmentMetaData.imageCount.toString()
attributes["videoCount"] = metaData.attachmentMetaData.videoCount.toString()
attributes["filesSize"] = metaData.attachmentMetaData.filesSize.toString()

// Remaining meta data
attributes["deviceId"] = metaData.identifier.deviceId
attributes["measurementId"] = metaData.identifier.measurementId
attributes["deviceType"] = metaData.deviceMetaData.deviceType
attributes["osVersion"] = metaData.deviceMetaData.operatingSystemVersion
attributes["appVersion"] = metaData.applicationMetaData.applicationVersion
attributes["length"] = metaData.measurementMetaData.length.toString()
attributes["modality"] = metaData.measurementMetaData.modality
attributes["formatVersion"] = metaData.applicationMetaData.formatVersion.toString()
return attributes
}

@Suppress("MemberVisibilityCanBePrivate") // Part of the API
@JvmStatic
fun handleSuccess(response: HttpResponse): Result {
Expand Down Expand Up @@ -550,24 +504,20 @@ class DefaultUploader(private val apiEndpoint: String) : Uploader {
* @return the [String] read from the InputStream. If an I/O error occurs while reading from the stream, the
* already read string is returned which might my empty or cut short.
*/
@Suppress("MemberVisibilityCanBePrivate", "NestedBlockDepth") // Part of the API
@Suppress("MemberVisibilityCanBePrivate") // Part of the API
@JvmStatic
fun readInputStream(inputStream: InputStream): String {
try {
try {
BufferedReader(
InputStreamReader(inputStream, DEFAULT_CHARSET)
).use { bufferedReader ->
val responseString = StringBuilder()
var line: String?
while (bufferedReader.readLine().also { line = it } != null) {
responseString.append(line)
}
return responseString.toString()
return try {
BufferedReader(InputStreamReader(inputStream, DEFAULT_CHARSET)).use { bufferedReader ->
val responseString = StringBuilder()
var line: String?
while (bufferedReader.readLine().also { line = it } != null) {
responseString.append(line)
}
} catch (e: UnsupportedEncodingException) {
error(e)
responseString.toString()
}
} catch (e: UnsupportedEncodingException) {
error(e)
} catch (e: IOException) {
error(e)
}
Expand Down
43 changes: 9 additions & 34 deletions src/main/kotlin/de/cyface/uploader/RequestInitializeHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,61 +21,36 @@ package de.cyface.uploader
import com.google.api.client.http.HttpHeaders
import com.google.api.client.http.HttpRequest
import com.google.api.client.http.HttpRequestInitializer
import de.cyface.model.RequestMetaData
import de.cyface.uploader.model.Uploadable
import java.io.IOException

/**
* Assembles a request as requested to upload data.
*
* @author Armin Schnabel
* @property metaData the `MetaData` used to request the upload permission from the server
* @property uploadable the `MetaData` used to request the upload permission from the server
* @property jwtBearer the JWT token to authenticate the upload requests
*/
class RequestInitializeHandler<T : RequestMetaData.MeasurementIdentifier>(
private val metaData: RequestMetaData<T>,
class RequestInitializeHandler(
private val uploadable: Uploadable,
private val jwtBearer: String
) : HttpRequestInitializer {
@Throws(IOException::class)

override fun initialize(request: HttpRequest) {
val headers = HttpHeaders()
headers.authorization = jwtBearer
addMetaData(metaData, headers)
applyMetaToHttpHeaders(headers)
// sets the metadata in both requests but until we don't use the session-URI
// feature we can't store the metadata from the pre-request to be used in the upload
// and the library does not support just to set the upload request header
request.headers = headers
}

private fun <T : RequestMetaData.MeasurementIdentifier> addMetaData(
metaData: RequestMetaData<T>,
headers: HttpHeaders
) {
// Location meta data
metaData.measurementMetaData.startLocation?.let { startLocation ->
headers["startLocLat"] = startLocation.latitude.toString()
headers["startLocLon"] = startLocation.longitude.toString()
headers["startLocTS"] = startLocation.timestamp.toString()
private fun applyMetaToHttpHeaders(headers: HttpHeaders) {
val map = uploadable.toMap()
map.forEach { (key, value) ->
headers[key] = value
}
metaData.measurementMetaData.endLocation?.let { endLocation ->
headers["endLocLat"] = endLocation.latitude.toString()
headers["endLocLon"] = endLocation.longitude.toString()
headers["endLocTS"] = endLocation.timestamp.toString()
}
headers["locationCount"] = metaData.measurementMetaData.locationCount.toString()

// Remaining meta data
headers["deviceId"] = metaData.identifier.deviceId
headers["measurementId"] = java.lang.Long.valueOf(metaData.identifier.measurementId).toString()
headers["deviceType"] = metaData.deviceMetaData.deviceType
headers["osVersion"] = metaData.deviceMetaData.operatingSystemVersion
headers["appVersion"] = metaData.applicationMetaData.applicationVersion
headers["length"] = metaData.measurementMetaData.length.toString()
headers["modality"] = metaData.measurementMetaData.modality
headers["formatVersion"] = metaData.applicationMetaData.formatVersion.toString()
headers["logCount"] = metaData.attachmentMetaData.logCount
headers["imageCount"] = metaData.attachmentMetaData.imageCount
headers["videoCount"] = metaData.attachmentMetaData.videoCount
headers["filesSize"] = metaData.attachmentMetaData.filesSize
}
}
12 changes: 7 additions & 5 deletions src/main/kotlin/de/cyface/uploader/Uploader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
*/
package de.cyface.uploader

import de.cyface.model.RequestMetaData
import de.cyface.uploader.exception.UploadFailed
import de.cyface.uploader.model.Attachment
import de.cyface.uploader.model.Measurement
import de.cyface.uploader.model.Uploadable
import java.io.File
import java.net.MalformedURLException
import java.net.URL
Expand All @@ -35,7 +37,7 @@ interface Uploader {
* Uploads the provided measurement file to the server.
*
* @param jwtToken A String in the format "eyXyz123***".
* @param metaData The [RequestMetaData] required for the upload request.
* @param uploadable The [Uploadable] describing the data to upload.
* @param file The data file to upload via this post request.
* @param progressListener The [UploadProgressListener] to be informed about the upload progress.
* @throws UploadFailed when an error occurred.
Expand All @@ -45,7 +47,7 @@ interface Uploader {
@Throws(UploadFailed::class)
fun uploadMeasurement(
jwtToken: String,
metaData: RequestMetaData<RequestMetaData.MeasurementIdentifier>,
uploadable: Measurement,
file: File,
progressListener: UploadProgressListener
): Result
Expand All @@ -54,7 +56,7 @@ interface Uploader {
* Uploads the provided attachment file to the server, associated with a specific measurement.
*
* @param jwtToken A String in the format "eyXyz123***".
* @param metaData The [RequestMetaData] required for the upload request.
* @param uploadable The [Uploadable] describing the data to upload.
* @param file The attachment file to upload via this post request.
* @param fileName How the transfer file should be named when uploading.
* @param progressListener The [UploadProgressListener] to be informed about the upload progress.
Expand All @@ -65,7 +67,7 @@ interface Uploader {
@Throws(UploadFailed::class)
fun uploadAttachment(
jwtToken: String,
metaData: RequestMetaData<RequestMetaData.AttachmentIdentifier>,
uploadable: Attachment,
file: File,
fileName: String,
progressListener: UploadProgressListener,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2024 Cyface GmbH
*
* This file is part of the Cyface Uploader.
*
* The Cyface Uploader is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Cyface Uploader is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the Cyface Uploader. If not, see <http://www.gnu.org/licenses/>.
*/
package de.cyface.uploader.exception

/**
* Exception thrown when the client tries to upload a file in a formatVersion older than the current version.
*
* @author Armin Schnabel
* @param message Details about the reason for this exception.
*/
class DeprecatedFormatVersion(message: String) : Exception(message)
30 changes: 30 additions & 0 deletions src/main/kotlin/de/cyface/uploader/exception/InvalidMetaData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2021-2024 Cyface GmbH
*
* This file is part of the Cyface Uploader.
*
* The Cyface Uploader is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Cyface Uploader is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the Cyface Uploader. If not, see <http://www.gnu.org/licenses/>.
*/
package de.cyface.uploader.exception

/**
* Exception thrown when the upload or pre-request does not contain the expected metadata.
*
* @author Armin Schnabel
*/
class InvalidMetaData : Exception {
constructor(message: String, cause: Throwable) : super(message, cause)
constructor(message: String) : super(message)
constructor(cause: Throwable) : super(cause)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021-2023 Cyface GmbH
* Copyright 2021-2024 Cyface GmbH
*
* This file is part of the Cyface Uploader.
*
Expand All @@ -24,10 +24,13 @@ package de.cyface.uploader.exception
* This is usually indicated by OkHttp via `SSLException`.
*
* @author Armin Schnabel
* @version 1.0.2
* @since 1.0.0
*/
class NetworkUnavailableException : Exception {
/**
* @param exception The `Exception` that caused this one.
*/
constructor(exception: Exception) : super(exception)

/**
* @param detailedMessage A more detailed message explaining the context for this `Exception`.
*/
Expand Down
Loading

0 comments on commit 12ad419

Please sign in to comment.