diff --git a/build.gradle b/build.gradle index a9e9c3b9..63fc7f57 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,7 @@ ext { // When `android-app` is checkout out, `rootProject.uploaderVersion` in the submodules will point // to `android-app/build.gradle` instead of `submodule/build.gradle`. cyfaceSerializationVersion = "3.4.2" - cyfaceUploaderVersion = "1.5.0" + cyfaceUploaderVersion = "1.5.1" // Android SDK versions minSdkVersion = 21 // device support diff --git a/persistence/src/main/kotlin/de/cyface/persistence/DefaultPersistenceLayer.kt b/persistence/src/main/kotlin/de/cyface/persistence/DefaultPersistenceLayer.kt index 41fd9c6f..9d4c93b0 100644 --- a/persistence/src/main/kotlin/de/cyface/persistence/DefaultPersistenceLayer.kt +++ b/persistence/src/main/kotlin/de/cyface/persistence/DefaultPersistenceLayer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 Cyface GmbH + * Copyright 2017-2024 Cyface GmbH * * This file is part of the Cyface SDK for Android. * @@ -65,8 +65,6 @@ import kotlin.math.max * * @author Klemens Muthmann * @author Armin Schnabel - * @version 19.1.0 - * @since 2.0.0 * @property persistenceBehaviour The [PersistenceBehaviour] defines how the `Persistence` layer works. * We need this behaviour to differentiate if the [DefaultPersistenceLayer] is used for live capturing * and or to load existing data. @@ -319,7 +317,7 @@ class DefaultPersistenceLayer : PersistenceLayer { .file Validate.isTrue(accelerationFile.delete()) } catch (e: NoSuchFileException) { - Log.v(TAG, "markAsSynchronized: No acceleration file found to delete, nothing to do") + Log.v(TAG, "markAsSynchronized: No acceleration file found to delete, nothing to do", e) } try { val rotationFile = Point3DFile.loadFile( @@ -327,7 +325,7 @@ class DefaultPersistenceLayer : PersistenceLayer { ).file Validate.isTrue(rotationFile.delete()) } catch (e: NoSuchFileException) { - Log.v(TAG, "markAsSynchronized: No rotation file found to delete, nothing to do") + Log.v(TAG, "markAsSynchronized: No rotation file found to delete, nothing to do", e) } try { val directionFile = Point3DFile.loadFile( @@ -336,7 +334,7 @@ class DefaultPersistenceLayer : PersistenceLayer { .file Validate.isTrue(directionFile.delete()) } catch (e: NoSuchFileException) { - Log.v(TAG, "markAsSynchronized: No direction file found to delete, nothing to do") + Log.v(TAG, "markAsSynchronized: No direction file found to delete, nothing to do", e) } // Also delete syncable attachments binaries when the measurement is skipped or deprecated @@ -421,8 +419,13 @@ class DefaultPersistenceLayer : PersistenceLayer { private fun markSavedAs(newStatus: AttachmentStatus, attachment: Attachment) { // The status in the database could be different from the one in the object so load it again - Validate.isTrue(attachment.status === AttachmentStatus.SAVED, "Unexpected status: ${attachment.status}") - Validate.isTrue(newStatus == AttachmentStatus.SYNCED || newStatus == AttachmentStatus.SKIPPED || newStatus == AttachmentStatus.DEPRECATED, "Unexpected status change from ${attachment.status} to $newStatus") + require(attachment.status === AttachmentStatus.SAVED) { + "Unexpected status: ${attachment.status}" + } + require( + newStatus == AttachmentStatus.SYNCED || newStatus == AttachmentStatus.SKIPPED || + newStatus == AttachmentStatus.DEPRECATED + ) { "Unexpected status change from ${attachment.status} to $newStatus" } runBlocking { attachmentDao!!.updateStatus(attachment.id, newStatus) } diff --git a/persistence/src/main/kotlin/de/cyface/persistence/dao/AttachmentDao.kt b/persistence/src/main/kotlin/de/cyface/persistence/dao/AttachmentDao.kt index cf33d26d..9b3b1404 100644 --- a/persistence/src/main/kotlin/de/cyface/persistence/dao/AttachmentDao.kt +++ b/persistence/src/main/kotlin/de/cyface/persistence/dao/AttachmentDao.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Cyface GmbH + * Copyright 2023-2024 Cyface GmbH * * This file is part of the Cyface SDK for Android. * @@ -31,8 +31,6 @@ import de.cyface.protos.model.File.FileType * Data access object which provides the API to interact with the [Attachment] database table. * * @author Armin Schnabel - * @version 1.0.0 - * @since 7.10.0 */ @Dao interface AttachmentDao { @@ -57,16 +55,22 @@ interface AttachmentDao { /** * Ordered by timestamp for [de.cyface.persistence.DefaultPersistenceLayer.loadTracks] to work. */ - @Query("SELECT * FROM ${AttachmentTable.URI_PATH} WHERE ${BaseColumns.MEASUREMENT_ID} = :measurementId ORDER BY ${BaseColumns.TIMESTAMP} ASC") + @Query("SELECT * FROM ${AttachmentTable.URI_PATH} " + + "WHERE ${BaseColumns.MEASUREMENT_ID} = :measurementId ORDER BY ${BaseColumns.TIMESTAMP} ASC") suspend fun loadAllByMeasurementId(measurementId: Long): List - @Query("SELECT * FROM ${AttachmentTable.URI_PATH} WHERE ${BaseColumns.MEASUREMENT_ID} = :measurementId LIMIT 1") + @Query("SELECT * FROM ${AttachmentTable.URI_PATH} " + + "WHERE ${BaseColumns.MEASUREMENT_ID} = :measurementId LIMIT 1") suspend fun loadOneByMeasurementId(measurementId: Long): Attachment? - @Query("SELECT * FROM ${AttachmentTable.URI_PATH} WHERE ${BaseColumns.MEASUREMENT_ID} = :measurementId AND ${AttachmentTable.COLUMN_TYPE} = :type ORDER BY ${BaseColumns.TIMESTAMP} ASC") + @Query("SELECT * FROM ${AttachmentTable.URI_PATH} " + + "WHERE ${BaseColumns.MEASUREMENT_ID} = :measurementId " + + "AND ${AttachmentTable.COLUMN_TYPE} = :type ORDER BY ${BaseColumns.TIMESTAMP} ASC") suspend fun loadOneByMeasurementIdAndType(measurementId: Long, type: FileType): Attachment? - @Query("SELECT * FROM ${AttachmentTable.URI_PATH} WHERE ${BaseColumns.MEASUREMENT_ID} = :measurementId AND ${AttachmentTable.COLUMN_STATUS} = :status ORDER BY ${BaseColumns.TIMESTAMP} ASC") + @Query("SELECT * FROM ${AttachmentTable.URI_PATH} " + + "WHERE ${BaseColumns.MEASUREMENT_ID} = :measurementId " + + "AND ${AttachmentTable.COLUMN_STATUS} = :status ORDER BY ${BaseColumns.TIMESTAMP} ASC") suspend fun loadAllByMeasurementIdAndStatus(measurementId: Long, status: AttachmentStatus): List /** @@ -78,7 +82,8 @@ interface AttachmentDao { /** * Returns the number of files found for a specific [measurementId] and [type]. */ - @Query("SELECT COUNT(*) FROM ${AttachmentTable.URI_PATH} WHERE ${BaseColumns.MEASUREMENT_ID} = :measurementId AND ${AttachmentTable.COLUMN_TYPE} = :type") + @Query("SELECT COUNT(*) FROM ${AttachmentTable.URI_PATH} " + + "WHERE ${BaseColumns.MEASUREMENT_ID} = :measurementId AND ${AttachmentTable.COLUMN_TYPE} = :type") suspend fun countByMeasurementIdAndType(measurementId: Long, type: FileType): Int @Query("DELETE FROM ${AttachmentTable.URI_PATH} WHERE ${BaseColumns.MEASUREMENT_ID} = :measurementId") @@ -87,9 +92,11 @@ interface AttachmentDao { @Query("DELETE FROM ${AttachmentTable.URI_PATH}") suspend fun deleteAll(): Int - @Query("UPDATE ${AttachmentTable.URI_PATH} SET ${AttachmentTable.COLUMN_SIZE} = :size WHERE ${BaseColumns.ID} = :id") + @Query("UPDATE ${AttachmentTable.URI_PATH} SET ${AttachmentTable.COLUMN_SIZE} = :size " + + "WHERE ${BaseColumns.ID} = :id") suspend fun updateSize(id: Long, size: Long) - @Query("UPDATE ${AttachmentTable.URI_PATH} SET ${AttachmentTable.COLUMN_STATUS} = :status WHERE ${BaseColumns.ID} = :id") + @Query("UPDATE ${AttachmentTable.URI_PATH} SET ${AttachmentTable.COLUMN_STATUS} = :status " + + "WHERE ${BaseColumns.ID} = :id") suspend fun updateStatus(id: Long, status: AttachmentStatus) } \ No newline at end of file diff --git a/persistence/src/main/kotlin/de/cyface/persistence/serialization/MeasurementSerializer.kt b/persistence/src/main/kotlin/de/cyface/persistence/serialization/MeasurementSerializer.kt index 94be4124..2a7a9223 100644 --- a/persistence/src/main/kotlin/de/cyface/persistence/serialization/MeasurementSerializer.kt +++ b/persistence/src/main/kotlin/de/cyface/persistence/serialization/MeasurementSerializer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018-2023 Cyface GmbH + * Copyright 2018-2024 Cyface GmbH * * This file is part of the Cyface SDK for Android. * @@ -48,14 +48,13 @@ import java.util.zip.DeflaterOutputStream * * @author Klemens Muthmann * @author Armin Schnabel - * @version 9.1.0 - * @since 2.0.0 */ class MeasurementSerializer { /** - * Loads the [de.cyface.persistence.model.Measurement] with the provided identifier from the persistence layer serialized and compressed - * in the [MeasurementSerializer.TRANSFER_FILE_FORMAT_VERSION] format and writes it to a temp file, ready to - * be transferred. + * Loads the [de.cyface.persistence.model.Measurement] with the provided identifier from the + * persistence layer serialized and compressed in the + * [MeasurementSerializer.TRANSFER_FILE_FORMAT_VERSION] format and writes it to a temp file, + * ready to be transferred. * * **ATTENTION**: The caller needs to delete the file which is referenced by the returned `FileInputStream` * when no longer needed or on program crash! @@ -80,7 +79,8 @@ class MeasurementSerializer { } withContext(Dispatchers.IO) { FileOutputStream(compressedTempFile).use { fileOutputStream -> - // As we create the DeflaterOutputStream with an FileOutputStream the compressed data is written to file + // As the DeflaterOutputStream is created with an FileOutputStream, the + // compressed data is written to file loadSerializedCompressed( fileOutputStream, measurementId, @@ -119,11 +119,8 @@ class MeasurementSerializer { val cacheDir = persistenceLayer.cacheDir var tempFile: File? = null try { - tempFile = - withContext(Dispatchers.IO) { - File.createTempFile(TRANSFER_FILE_PREFIX, ".tmp", cacheDir) - } withContext(Dispatchers.IO) { + tempFile = File.createTempFile(TRANSFER_FILE_PREFIX, ".tmp", cacheDir) FileOutputStream(tempFile).use { fileOutputStream -> loadSerializedAttachment( fileOutputStream, @@ -132,8 +129,8 @@ class MeasurementSerializer { } } } catch (e: IOException) { - if (tempFile != null && tempFile.exists()) { - Validate.isTrue(tempFile.delete()) + if (tempFile != null && tempFile!!.exists()) { + require(tempFile!!.delete()) } throw IllegalStateException(e) } @@ -141,8 +138,9 @@ class MeasurementSerializer { } /** - * Writes the [de.cyface.persistence.model.Measurement] with the provided identifier from the persistence layer serialized and compressed - * in the [MeasurementSerializer.TRANSFER_FILE_FORMAT_VERSION] format, ready to be transferred. + * Writes the [de.cyface.persistence.model.Measurement] with the provided identifier from the + * persistence layer serialized and compressed in the + * [MeasurementSerializer.TRANSFER_FILE_FORMAT_VERSION] format, ready to be transferred. * * The Deflater ZLIB (RFC-1950) compression is used. * diff --git a/persistence/src/main/kotlin/de/cyface/persistence/serialization/TransferFileSerializer.kt b/persistence/src/main/kotlin/de/cyface/persistence/serialization/TransferFileSerializer.kt index 4ddf4d64..679ccddd 100644 --- a/persistence/src/main/kotlin/de/cyface/persistence/serialization/TransferFileSerializer.kt +++ b/persistence/src/main/kotlin/de/cyface/persistence/serialization/TransferFileSerializer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2023 Cyface GmbH + * Copyright 2019-2024 Cyface GmbH * * This file is part of the Cyface SDK for Android. * @@ -107,7 +107,9 @@ object TransferFileSerializer { Validate.isTrue(measurement!!.fileFormatVersion == DefaultPersistenceLayer.PERSISTENCE_FILE_FORMAT_VERSION) if (accelerationFile.exists()) { Log.v( - TAG, String.format( + TAG, + String.format( + Locale.getDefault(), "Serializing %s accelerations for synchronization.", DataSerializable.humanReadableSize(accelerationFile.length(), true) ) @@ -117,7 +119,9 @@ object TransferFileSerializer { } if (rotationFile.exists()) { Log.v( - TAG, String.format( + TAG, + String.format( + Locale.getDefault(), "Serializing %s rotations for synchronization.", DataSerializable.humanReadableSize(rotationFile.length(), true) ) @@ -127,7 +131,9 @@ object TransferFileSerializer { } if (directionFile.exists()) { Log.v( - TAG, String.format( + TAG, + String.format( + Locale.getDefault(), "Serializing %s directions for synchronization.", DataSerializable.humanReadableSize(directionFile.length(), true) ) @@ -152,7 +158,9 @@ object TransferFileSerializer { throw IllegalStateException(e) } Log.d( - TAG, String.format( + TAG, + String.format( + Locale.getDefault(), "Serialized %s", DataSerializable.humanReadableSize( (transferFileHeader.size + measurementBytes.size).toLong(), @@ -302,13 +310,13 @@ object TransferFileSerializer { // Currently loading one image per transfer file into memory (~ 2-5 MB / image). // - To load add all high-res image data or video data in the future we cannot use the pre-compiled // builder but have to stream the data without loading it into memory to avoid an OOM exception. - val transferFileHeader = DataSerializable.transferFileHeader() + //val transferFileHeader = DataSerializable.transferFileHeader() //val uploadBytes = builder.build().toByteArray() val uploadBytes = attachment.bytes.toByteArray() try { // The stream must be closed by the caller in a finally catch withContext(Dispatchers.IO) { - bufferedOutputStream.write(transferFileHeader) + //bufferedOutputStream.write(transferFileHeader) bufferedOutputStream.write(uploadBytes) bufferedOutputStream.flush() } @@ -316,11 +324,12 @@ object TransferFileSerializer { throw IllegalStateException(e) } Log.d( - TAG, String.format( + TAG, + String.format( Locale.getDefault(), "Serialized attachment: %s", DataSerializable.humanReadableSize( - (transferFileHeader.size + uploadBytes.size).toLong(), + (/*transferFileHeader.size +*/ uploadBytes.size).toLong(), true ) ) diff --git a/synchronization/src/main/java/de/cyface/synchronization/ErrorHandler.java b/synchronization/src/main/java/de/cyface/synchronization/ErrorHandler.java index dc993da8..7f464051 100644 --- a/synchronization/src/main/java/de/cyface/synchronization/ErrorHandler.java +++ b/synchronization/src/main/java/de/cyface/synchronization/ErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2023 Cyface GmbH + * Copyright 2018-2024 Cyface GmbH * * This file is part of the Cyface SDK for Android. * @@ -41,8 +41,6 @@ * support time for all involved. * * @author Armin Schnabel - * @version 2.2.0 - * @since 2.2.0 */ public class ErrorHandler extends BroadcastReceiver { @@ -121,7 +119,6 @@ public void onReceive(final Context context, final Intent intent) { Validate.notNull(errorCode); String errorMessage; switch (errorCode) { - case UNAUTHORIZED: errorMessage = context .getString(de.cyface.synchronization.R.string.error_message_credentials_incorrect); @@ -234,8 +231,6 @@ public void onReceive(final Context context, final Intent intent) { * A list of known Errors which are thrown by the Cyface SDK. * * @author Armin Schnabel - * @version 1.3.0 - * @since 1.0.0 */ public enum ErrorCode { @@ -276,8 +271,6 @@ public static ErrorCode getValueForCode(final int code) { * Interface for listeners receiving errors. * * @author Armin Schnabel - * @version 1.0.0 - * @since 1.0.0 */ public interface ErrorListener { /** diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/Constants.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/Constants.kt index c6471e14..608ba600 100644 --- a/synchronization/src/main/kotlin/de/cyface/synchronization/Constants.kt +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/Constants.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 Cyface GmbH + * Copyright 2017-2024 Cyface GmbH * * This file is part of the Cyface SDK for Android. * @@ -23,13 +23,10 @@ package de.cyface.synchronization * * @author Klemens Muthmann * @author Armin Schnabel - * @version 1.3.1 - * @since 2.0.0 */ object Constants { /** * Tag used to identify Logcat messages issued by instances of this package. */ const val TAG = "de.cyface.sync" - } \ No newline at end of file diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/NoLocationData.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/NoLocationData.kt new file mode 100644 index 00000000..57e8a9ca --- /dev/null +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/NoLocationData.kt @@ -0,0 +1,9 @@ +package de.cyface.synchronization + +/** + * Exception thrown when no location data is available but needed, e.g. to generate the upload + * path in `de.cyface.app.digural.upload.WebdavUploader`. + * + * @author Armin Schnabel + */ +class NoLocationData(message: String?) : Exception(message) diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/SyncAdapter.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/SyncAdapter.kt index 75b688b9..ded61258 100644 --- a/synchronization/src/main/kotlin/de/cyface/synchronization/SyncAdapter.kt +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/SyncAdapter.kt @@ -59,6 +59,7 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking import java.io.File +import java.util.Locale import java.util.UUID /** @@ -180,7 +181,7 @@ class SyncAdapter private constructor( syncResult: SyncResult, fromBackground: Boolean ) { - Log.w(TAG, e.javaClass.simpleName + ": " + e.message) + Log.w(TAG, e.javaClass.simpleName + ": " + e.message, e) when (e) { is CursorIsNullException -> { syncResult.databaseError = true @@ -263,7 +264,14 @@ class SyncAdapter private constructor( if (isSyncRequestAborted(account, authority)) return val indexWithinMeasurement = 0 // the core file is index 0 - val progressListener = DefaultUploadProgressListener(measurementCount, index, measurement.id, attachmentCount, indexWithinMeasurement, progressListeners) + val progressListener = DefaultUploadProgressListener( + measurementCount, + index, + measurement.id, + attachmentCount, + indexWithinMeasurement, + progressListeners + ) error = !syncMeasurement( measurement, measurementMeta, @@ -284,14 +292,18 @@ class SyncAdapter private constructor( // The status in the database could have changed due to upload, reload it val currentStatus = persistence.measurementRepository!!.loadById(measurement.id)!!.status if (currentStatus === MeasurementStatus.SYNCABLE_ATTACHMENTS) { - val syncableAttachments = persistence.attachmentDao!!.loadAllByMeasurementIdAndStatus(measurement.id, AttachmentStatus.SAVED) + val syncableAttachments = + persistence.attachmentDao!!.loadAllByMeasurementIdAndStatus( + measurement.id, + AttachmentStatus.SAVED + ) val totalAttachments = persistence.attachmentDao!!.countByMeasurementId(measurement.id) val syncedAttachments = totalAttachments - syncableAttachments.size for (attachmentIndex in syncableAttachments.indices) { val attachment = syncableAttachments[attachmentIndex] - Log.d(TAG, "Preparing to upload attachment (id ${attachment.id}).") + Log.d(TAG, "Preparing to upload attachment (id ${attachment.id} path ${attachment.path}).") validateFileFormat(attachment) var transferTempFile: File? = null @@ -339,7 +351,10 @@ class SyncAdapter private constructor( } } - private fun serializeMeasurement(measurement: Measurement, persistence: DefaultPersistenceLayer): File? { + private fun serializeMeasurement( + measurement: Measurement, + persistence: DefaultPersistenceLayer + ): File? { var compressedTransferTempFile: File? runBlocking { compressedTransferTempFile = MeasurementSerializer().writeSerializedCompressed(measurement.id, persistence) @@ -347,7 +362,10 @@ class SyncAdapter private constructor( return compressedTransferTempFile } - private fun serializeAttachment(attachment: de.cyface.persistence.model.Attachment, persistence: DefaultPersistenceLayer): File? { + private fun serializeAttachment( + attachment: de.cyface.persistence.model.Attachment, + persistence: DefaultPersistenceLayer + ): File? { var transferTempFile: File? runBlocking { transferTempFile = MeasurementSerializer().writeSerializedAttachment(attachment, persistence) @@ -380,7 +398,9 @@ class SyncAdapter private constructor( resultDeferred.complete(false) } else { val fileName = - "${uploadable.identifier.deviceIdentifier}_${uploadable.identifier.measurementIdentifier}.${COMPRESSED_TRANSFER_FILE_EXTENSION}" + "${uploadable.identifier.deviceIdentifier}_" + + "${uploadable.identifier.measurementIdentifier}" + + ".${COMPRESSED_TRANSFER_FILE_EXTENSION}" val result = syncPerformer.sendData( uploader, syncResult, @@ -410,11 +430,15 @@ class SyncAdapter private constructor( MeasurementStatus.SYNCABLE_ATTACHMENTS, measurement.id ) - Log.d(TAG, "Measurement marked as ${MeasurementStatus.SYNCABLE_ATTACHMENTS.name.lowercase()}") + Log.d( + TAG, + "Measurement marked as ${MeasurementStatus.SYNCABLE_ATTACHMENTS.name.lowercase()}" + ) } else -> { throw IllegalArgumentException( String.format( + Locale.getDefault(), "Unknown result: %s", result ) @@ -458,7 +482,9 @@ class SyncAdapter private constructor( } else { val attachmentId = attachment.identifier.attachmentIdentifier val fileName = - "${attachment.identifier.deviceIdentifier}_${attachment.identifier.measurementIdentifier}_$attachmentId.${attachmentType.name.lowercase()}" + "${attachment.identifier.deviceIdentifier}_" + + "${attachment.identifier.measurementIdentifier}" + + "_$attachmentId.${attachmentType.name.lowercase()}" val result = syncPerformer.sendData( uploader, syncResult, @@ -488,6 +514,7 @@ class SyncAdapter private constructor( else -> { throw IllegalArgumentException( String.format( + Locale.getDefault(), "Unknown result: %s", result ) @@ -528,7 +555,9 @@ class SyncAdapter private constructor( } } - private fun loadSyncableMeasurements(persistence: DefaultPersistenceLayer): List { + private fun loadSyncableMeasurements( + persistence: DefaultPersistenceLayer + ): List { val partiallyUploaded = persistence.loadMeasurements(MeasurementStatus.SYNCABLE_ATTACHMENTS) val finishedMeasurements = persistence.loadMeasurements(MeasurementStatus.FINISHED) return partiallyUploaded + finishedMeasurements // Returns the partially uploaded measurements first @@ -641,7 +670,6 @@ class SyncAdapter private constructor( "Number of unsupported attachments: $unsupportedAttachments" } val filesSize = if (allAttachments > 0) { - // TODO: support multiple attachments by zipping them // (!) we load the CSV file here as the location_metrics.csv should always exist // if not, the upload crashes with an NPE. val attachment = persistence.attachmentDao!!.loadOneByMeasurementIdAndType(measurement.id, FileType.CSV) diff --git a/synchronization/src/main/kotlin/de/cyface/synchronization/SyncPerformer.kt b/synchronization/src/main/kotlin/de/cyface/synchronization/SyncPerformer.kt index d4655cb7..5bf596cd 100644 --- a/synchronization/src/main/kotlin/de/cyface/synchronization/SyncPerformer.kt +++ b/synchronization/src/main/kotlin/de/cyface/synchronization/SyncPerformer.kt @@ -317,6 +317,12 @@ internal class SyncPerformer(private val context: Context, private val fromBackg Result.UPLOAD_SKIPPED } + is NoLocationData -> { + syncResult.stats.numSkippedEntries++ + Log.d(TAG, e.message!!) + Result.UPLOAD_SKIPPED + } + is ConflictException -> { syncResult.stats.numSkippedEntries++ // We consider the upload successful and mark the measurement as synced