diff --git a/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt b/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt index 70c12f5..ac48807 100644 --- a/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt +++ b/app/src/main/java/app/myzel394/alibi/helpers/BatchesFolder.kt @@ -3,28 +3,26 @@ package app.myzel394.alibi.helpers import android.Manifest import android.content.ContentUris import android.content.ContentValues -import app.myzel394.alibi.ui.MEDIA_RECORDINGS_PREFIX - import android.content.Context import android.database.Cursor import android.net.Uri import android.os.Build import android.provider.MediaStore import android.provider.MediaStore.Video.Media -import androidx.documentfile.provider.DocumentFile -import java.io.File -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter -import com.arthenica.ffmpegkit.FFmpegKitConfig import android.util.Log import androidx.annotation.RequiresApi import androidx.core.net.toUri +import androidx.documentfile.provider.DocumentFile +import app.myzel394.alibi.ui.MEDIA_RECORDINGS_PREFIX import app.myzel394.alibi.ui.RECORDER_INTERNAL_SELECTED_VALUE import app.myzel394.alibi.ui.RECORDER_MEDIA_SELECTED_VALUE import app.myzel394.alibi.ui.SUPPORTS_SCOPED_STORAGE import app.myzel394.alibi.ui.utils.PermissionHelper -import com.arthenica.ffmpegkit.FFprobeKit +import com.arthenica.ffmpegkit.FFmpegKitConfig import kotlinx.coroutines.CompletableDeferred +import java.io.File +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter import kotlin.reflect.KFunction4 abstract class BatchesFolder( @@ -197,7 +195,6 @@ abstract class BatchesFolder( createNewFile() } - fun checkIfOutputAlreadyExists( date: LocalDateTime, extension: String @@ -388,12 +385,12 @@ abstract class BatchesFolder( } } - fun deleteOldRecordings(earliestCounter: Long) { + fun deleteRecordings(range: LongRange) { when (type) { BatchType.INTERNAL -> getInternalFolder().listFiles()?.forEach { val fileCounter = it.nameWithoutExtension.toIntOrNull() ?: return@forEach - if (fileCounter < earliestCounter) { + if (fileCounter in range) { it.delete() } } @@ -401,7 +398,7 @@ abstract class BatchesFolder( BatchType.CUSTOM -> getCustomDefinedFolder().listFiles().forEach { val fileCounter = it.name?.substringBeforeLast(".")?.toIntOrNull() ?: return@forEach - if (fileCounter < earliestCounter) { + if (fileCounter in range) { it.delete() } } @@ -411,7 +408,7 @@ abstract class BatchesFolder( val deletableNames = mutableListOf() queryMediaContent { rawName, counter, _, _ -> - if (counter < earliestCounter) { + if (counter in range) { deletableNames.add(rawName) } } @@ -428,7 +425,7 @@ abstract class BatchesFolder( it.nameWithoutExtension.substring(mediaPrefix.length).toIntOrNull() ?: return@forEach - if (fileCounter < earliestCounter) { + if (fileCounter in range) { it.delete() } } diff --git a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt index ed8f9c7..05a6a55 100644 --- a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt @@ -11,6 +11,9 @@ abstract class IntervalRecorderService : protected var counter = 0L private set + // Tracks the index of the currently locked file + private var lockedIndex: Long? = null + lateinit var settings: AppSettings private lateinit var cycleTimer: ScheduledExecutorService @@ -21,6 +24,23 @@ abstract class IntervalRecorderService : abstract fun getRecordingInformation(): I + // When saving the recording, the files should be locked. + // This prevents the service from deleting the currently available files, so that + // they can be safely used to save the recording. + // Once finished, make sure to unlock the files using `unlockFiles`. + fun lockFiles() { + lockedIndex = counter + } + + // Unlocks and deletes the files that were locked using `lockFiles`. + fun unlockFiles(cleanupFiles: Boolean = false) { + if (cleanupFiles) { + batchesFolder.deleteRecordings(0..lockedIndex!!) + } + + lockedIndex = null + } + // Make overrideable open fun startNewCycle() { counter += 1 @@ -72,12 +92,12 @@ abstract class IntervalRecorderService : private fun deleteOldRecordings() { val timeMultiplier = settings.maxDuration / settings.intervalDuration - val earliestCounter = counter - timeMultiplier + val earliestCounter = Math.max(counter - timeMultiplier, lockedIndex ?: 0) if (earliestCounter <= 0) { return } - batchesFolder.deleteOldRecordings(earliestCounter) + batchesFolder.deleteRecordings(0..earliestCounter) } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt index 5aaf273..7f4ac2d 100644 --- a/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/VideoRecorderService.kt @@ -139,7 +139,7 @@ class VideoRecorderService : if (_cameraAvailableListener.isCompleted) { action() } else { - // Race condition of `startNewCycle` being called before `invpkeOnCompletion` + // Race condition of `startNewCycle` being called before `invokeOnCompletion` // has been called can be ignored, as the camera usually opens within 5 seconds // and the interval can't be set shorter than 10 seconds. _cameraAvailableListener.invokeOnCompletion { diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt index bd2c241..15eb477 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/RecorderEventsHandler.kt @@ -129,15 +129,17 @@ fun RecorderEventsHandler( } } - suspend fun saveRecording(recorder: RecorderModel) { + suspend fun saveRecording(recorder: RecorderModel): Thread { isProcessing = true // Give the user some time to see the processing dialog delay(100) - thread { + return thread { runBlocking { try { + recorder.recorderService?.lockFiles() + val recording = // When new recording created recorder.recorderService?.getRecordingInformation() @@ -215,6 +217,7 @@ fun RecorderEventsHandler( } catch (error: Exception) { Log.getStackTraceString(error) } finally { + recorder.recorderService?.unlockFiles() isProcessing = false } } @@ -230,7 +233,7 @@ fun RecorderEventsHandler( // instead of hoping that the coroutine from where this will be called will be alive // until the end of the saving process scope.launch { - saveRecording(audioRecorder as RecorderModel) + saveRecording(audioRecorder as RecorderModel).join() } } audioRecorder.onRecordingStart = { @@ -273,7 +276,7 @@ fun RecorderEventsHandler( // instead of hoping that the coroutine from where this will be called will be alive // until the end of the saving process scope.launch { - saveRecording(videoRecorder as RecorderModel) + saveRecording(videoRecorder as RecorderModel).join() } } videoRecorder.onRecordingStart = { diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt index e6f1b5a..ec5471b 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/RecorderScreen/organisms/VideoRecordingStatus.kt @@ -227,7 +227,7 @@ fun _PrimitiveControls(videoRecorder: VideoRecorderModel) { it.saveLastRecording(videoRecorder as RecorderModel) } - videoRecorder.onRecordingSave() + videoRecorder.onRecordingSave().join() runCatching { videoRecorder.destroyService(context)