Created
November 11, 2022 14:44
-
-
Save marius-m/d78fbc57cf22bb01a7baa93168f384f0 to your computer and use it in GitHub Desktop.
Logging export mechanism (With zip funct)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context | |
import android.os.Environment | |
import androidx.lifecycle.LifecycleCoroutineScope | |
import com.spotos.core.LogUtils.withLogInstance | |
import com.spotos.core.LogUtils.withLogKlass | |
import com.spotos.core.Tags | |
import com.spotos.data.SentryInteractor | |
import com.spotos.data.ViewProvider | |
import kotlinx.coroutines.CoroutineDispatcher | |
import kotlinx.coroutines.Dispatchers | |
import kotlinx.coroutines.launch | |
import kotlinx.coroutines.withContext | |
import timber.log.Timber | |
import java.io.BufferedOutputStream | |
import java.io.File | |
import java.io.FileOutputStream | |
import java.util.zip.ZipEntry | |
import java.util.zip.ZipOutputStream | |
class LogsExportDefault( | |
private val context: Context, | |
private val viewProvider: ViewProvider<LogsExport.View>, | |
private val sentryInteractor: SentryInteractor, | |
private val lifecycleCoroutineScope: LifecycleCoroutineScope, | |
private val dispatcherIo: CoroutineDispatcher = Dispatchers.IO, | |
private val dispatcherMain: CoroutineDispatcher = Dispatchers.Main, | |
) : LogsExport { | |
override fun exportLogs(logName: String) { | |
lifecycleCoroutineScope.launch { | |
Timber.tag(Tags.DEBUG).d( | |
"exportLogs.init(thread: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
) | |
viewProvider.get()?.showProgress() | |
withContext(dispatcherIo) { | |
try { | |
val dirInternal = internalDir(context, logName) | |
val dirExternal = externalDirWithCleanUp(context, logName) | |
Timber.tag(Tags.DEBUG).d( | |
"exportLogs.copy(thread: %s, target: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
dirExternal.absolutePath, | |
) | |
dirInternal.copyRecursively(target = dirExternal) | |
withContext(dispatcherMain) { | |
viewProvider.get()?.exportSuccessMessage(dirExternal.absolutePath) | |
} | |
} catch (e: Exception) { | |
withContext(dispatcherMain) { | |
Timber.tag(Tags.DEBUG).w( | |
e, | |
"exportLogs.error(thread: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
) | |
viewProvider.get()?.exportErrorMessage(e.message ?: "Unknown") | |
} | |
} finally { | |
withContext(dispatcherMain) { | |
Timber.tag(Tags.DEBUG).d( | |
"exportLogs.finally(thread: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
) | |
viewProvider.get()?.hideProgress() | |
} | |
} | |
} | |
} | |
} | |
override fun exportLogsAsZip(logName: String) { | |
lifecycleCoroutineScope.launch { | |
Timber.tag(Tags.DEBUG).d( | |
"exportLogsAsZip.init(thread: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
) | |
viewProvider.get()?.showProgress() | |
withContext(dispatcherIo) { | |
try { | |
val dirInternal = internalDir(context, logName) | |
val dirExternal = externalDirWithCleanUp(context, logName) | |
Timber.tag(Tags.DEBUG).d( | |
"exportLogsAsZip.copyInit(thread: %s, target: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
dirExternal.absolutePath, | |
) | |
val fileLogsAsZip = zipDir(dirInput = dirInternal, logName = logName) | |
val fileExternalTarget = File(dirExternal, "${logName}.zip") | |
fileLogsAsZip | |
.copyTo(target = fileExternalTarget, overwrite = true) | |
Timber.tag(Tags.DEBUG).d( | |
"exportLogsAsZip.copyResult(thread: %s, target: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
fileExternalTarget, | |
) | |
dirCleanZips(dirInput = dirInternal) | |
withContext(dispatcherMain) { | |
viewProvider.get()?.exportSuccessMessage(dirExternal.absolutePath) | |
} | |
} catch (e: Exception) { | |
withContext(dispatcherMain) { | |
Timber.tag(Tags.DEBUG).w( | |
e, | |
"exportLogsAsZip.error(thread: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
) | |
viewProvider.get()?.exportErrorMessage(e.message ?: "Unknown") | |
} | |
} finally { | |
withContext(dispatcherMain) { | |
Timber.tag(Tags.DEBUG).d( | |
"exportLogsAsZip.finally(thread: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
) | |
viewProvider.get()?.hideProgress() | |
} | |
} | |
} | |
} | |
} | |
override fun reportAsZip(logName: String) { | |
lifecycleCoroutineScope.launch { | |
Timber.tag(Tags.DEBUG).d( | |
"reportAsZip.init(thread: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
) | |
viewProvider.get()?.showProgress() | |
withContext(dispatcherIo) { | |
try { | |
val dirInternal = internalDir(context, logName) | |
val dirExternal = externalDirWithCleanUp(context, logName) | |
Timber.tag(Tags.DEBUG).d( | |
"reportAsZip.copyInit(thread: %s, target: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
dirExternal.absolutePath, | |
) | |
dirCleanZips(dirInput = dirInternal) | |
val fileLogsAsZip = zipDir(dirInput = dirInternal, logName = logName) | |
sentryInteractor.reportWithLogs("Manual report", fileLogsAsZip) | |
Timber.tag(Tags.DEBUG).d( | |
"reportAsZip.copyResult(thread: %s, target: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
fileLogsAsZip, | |
) | |
withContext(dispatcherMain) { | |
viewProvider.get()?.reportSuccessMessage(dirExternal.absolutePath) | |
} | |
} catch (e: Exception) { | |
withContext(dispatcherMain) { | |
Timber.tag(Tags.DEBUG).w( | |
e, | |
"reportAsZip.error(thread: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
) | |
viewProvider.get()?.reportErrorMessage(e.message ?: "Unknown") | |
} | |
} finally { | |
withContext(dispatcherMain) { | |
Timber.tag(Tags.DEBUG).d( | |
"reportAsZip.finally(thread: %s)".withLogInstance(this@LogsExportDefault), | |
Thread.currentThread(), | |
) | |
viewProvider.get()?.hideProgress() | |
} | |
} | |
} | |
} | |
} | |
companion object { | |
private fun internalDir( | |
context: Context, | |
logName: String, | |
): File { | |
return File(context.filesDir, logName) | |
} | |
@Throws(IllegalStateException::class) | |
private fun externalDirWithCleanUp( | |
context: Context, | |
logName: String, | |
): File { | |
val externalDirRoot = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) | |
checkNotNull(externalDirRoot) { "External directory is null" } | |
val externalDirLogs = File(externalDirRoot, logName) | |
if (externalDirLogs.exists()) { | |
externalDirLogs.deleteRecursively() | |
} | |
if (!externalDirLogs.exists()) { | |
val mkdirResult = externalDirLogs.mkdirs() | |
check(mkdirResult) { "Nowhere to save logs!" } | |
} | |
return externalDirLogs | |
} | |
fun lastLogAsFile( | |
context: Context, | |
logName: String, | |
): File { | |
val dirInternal = internalDir(context, logName) | |
return File(dirInternal, "$logName.log") | |
} | |
/** | |
* Zips output directory and its contents | |
* Source: https://stackoverflow.com/questions/52215496/how-to-zip-folders-subfolders-with-files-in-it-in-kotlin-using-zipoutputstream | |
* @return zipped file | |
*/ | |
private fun zipDir( | |
dirInput: File, | |
logName: String, | |
): File { | |
Timber.tag(Tags.DEBUG).d( | |
"zipDir.init(dirInput: %s, logName: %s)" | |
.withLogKlass(LogsExportDefault::class), | |
dirInput.absolutePath, | |
logName, | |
) | |
val fileZipOutput = File.createTempFile(logName, ".zip", dirInput) | |
ZipOutputStream(BufferedOutputStream(FileOutputStream(fileZipOutput))).use { zos -> | |
dirInput.walkTopDown().forEach { file -> | |
val zipFileName = file.absolutePath | |
.removePrefix(dirInput.absolutePath).removePrefix("/") | |
val zipFileNameSuffix = if (file.isDirectory) "/" else "" | |
val entry = ZipEntry( "$zipFileName$zipFileNameSuffix") | |
when { | |
file.extension.endsWith("zip") -> { | |
Timber.tag(Tags.DEBUG).d( | |
"zipDir.zip.ignoreZip(file: %s)" | |
.withLogKlass(LogsExportDefault::class), | |
file.absolutePath, | |
) | |
} | |
file.isFile -> { | |
Timber.tag(Tags.DEBUG).d( | |
"zipDir.zip.appendEntryFile(file: %s)" | |
.withLogKlass(LogsExportDefault::class), | |
file.absolutePath, | |
) | |
zos.putNextEntry(entry) | |
file.inputStream().use { | |
it.copyTo(zos) | |
} | |
} | |
file.isDirectory -> { | |
Timber.tag(Tags.DEBUG).d( | |
"zipDir.zip.appendEntryDir(file: %s)" | |
.withLogKlass(LogsExportDefault::class), | |
file.absolutePath, | |
) | |
zos.putNextEntry(entry) | |
} | |
} | |
} | |
} | |
Timber.tag(Tags.DEBUG).d( | |
"zipDir.finish(fileZipOutput: %s)" | |
.withLogKlass(LogsExportDefault::class), | |
fileZipOutput.absolutePath, | |
) | |
return fileZipOutput | |
} | |
/** | |
* Removes *.zip files from directory | |
*/ | |
private fun dirCleanZips( | |
dirInput: File, | |
) { | |
Timber.tag(Tags.DEBUG).d( | |
"dirCleanZips.init(dirInput: %s)" | |
.withLogKlass(LogsExportDefault::class), | |
dirInput.absolutePath, | |
) | |
val filesMarkedDelete = dirInput.walkTopDown().mapNotNull { file -> | |
when { | |
file.extension.endsWith("zip") -> { | |
Timber.tag(Tags.DEBUG).d( | |
"dirCleanZips.markRemove(file: %s)" | |
.withLogKlass(LogsExportDefault::class), | |
file.absolutePath, | |
) | |
file | |
} | |
file.isFile -> { | |
Timber.tag(Tags.DEBUG).d( | |
"dirCleanZips.ignoreFile(file: %s)" | |
.withLogKlass(LogsExportDefault::class), | |
file.absolutePath, | |
) | |
null | |
} | |
file.isDirectory -> { | |
Timber.tag(Tags.DEBUG).d( | |
"dirCleanZips.ignoreDir(file: %s)" | |
.withLogKlass(LogsExportDefault::class), | |
file.absolutePath, | |
) | |
null | |
} | |
else -> null | |
} | |
} | |
filesMarkedDelete.forEach { file -> | |
Timber.tag(Tags.DEBUG).d( | |
"dirCleanZips.remove(file: %s)" | |
.withLogKlass(LogsExportDefault::class), | |
file.absolutePath, | |
) | |
file.delete() | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment