Skip to content

Instantly share code, notes, and snippets.

@cohenItay
Created June 23, 2022 08:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cohenItay/05d3f8b41c8ecd25bbecf8e4b1b30e73 to your computer and use it in GitHub Desktop.
Save cohenItay/05d3f8b41c8ecd25bbecf8e4b1b30e73 to your computer and use it in GitHub Desktop.
class DiskLoggerImpl(
private val appContext: Context,
private val logsFileProvider: LogsFileProvider
) : DiskLogger {
private val TAG = DiskLogger::class.simpleName!!
private val timeFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault())
private var bufferWriter: BufferedWriter? = null
private val loggerScope = CoroutineScope(Dispatchers.Default + Job())
private val channel = Channel<Operation>()
private var closeBufferJob: Job? = null
init {
loggerScope.launch {
for (op in channel) {
when (op) {
is Operation.Write ->
processWriteLog(op.logContent)
is Operation.Delete ->
processCleanLogs(op.from)
}
}
}
}
@Suppress("BlockingMethodInNonBlockingContext") // It is inside withContext(Dispatchers.IO), probably a bug in the IDE
override fun log(logContent: String) {
loggerScope.launch {
// Working with the channel makes the communications between the coroutines sequential
// and thus works like a queue
channel.send(Operation.Write(logContent))
}
}
override fun cleanLogs(from: Date) {
val diff = TimeUnit.MILLISECONDS.toDays(Calendar.getInstance().timeInMillis - from.time)
if (diff < 1L)
error("Less then one day time difference")
loggerScope.launch {
channel.send(Operation.Delete(from))
}
}
private suspend fun processCleanLogs(from: Date) = withContext(Dispatchers.IO) {
if (Calendar.getInstance().time.time <= from.time)
return@withContext
val directory = logsFileProvider.provideLogsDirectory(appContext)
if (!directory.exists())
return@withContext
val allInnerFiles = FileUtils.getAllFilesInDir(directory)
val filesToDelete = allInnerFiles.filter { file ->
val lastModified = file.lastModified()
if (lastModified <= 0L) {
false
} else {
Date(lastModified).before(from)
}
}
filesToDelete.forEach { file ->
if (!file.delete()) {
Log.e(TAG, "processCleanLogs: Was not able to delete file ${file.name}")
}
}
}
/**
* Process the log, which is write it to the disk.
*/
private suspend fun processWriteLog(logContent: String) = withContext(Dispatchers.IO) {
val writer = validateWriterIsReady() ?: return@withContext
val time = timeFormat.format(Calendar.getInstance().time)
val log = "$time $logContent\n"
try {
@Suppress("BlockingMethodInNonBlockingContext") // lint bug, it is inside IO Context
writer.write(log)
} catch (e: IOException) {
PLogger.e(TAG, "couldn't write the log: '$log'", e)
}
closeBufferJob?.cancelAndJoin()
closeBufferJob = createCloseBufferJob()
}
@Suppress( "BlockingMethodInNonBlockingContext") // This lint is shown for withContext while it should not, bug.
private suspend fun validateWriterIsReady() : BufferedWriter? =
bufferWriter ?: withContext(Dispatchers.IO) {
val file = logsFileProvider.provideFile(appContext)
?: return@withContext null
try {
bufferWriter = BufferedWriter(FileWriter(file, true))
} catch (e: IOException) {
PLogger.e(TAG, "$file, is a directory rather then a file", e)
bufferWriter?.close()
}
bufferWriter!!
}
private fun createCloseBufferJob() = loggerScope.launch(Dispatchers.IO) {
delay(2 * 60 * 1_000L) // 2 minutes
if (isActive) {
bufferWriter?.close()
bufferWriter = null
}
}
private sealed class Operation {
data class Write(val logContent: String) : Operation()
data class Delete(val from: Date) : Operation()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment