Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-24 22:18:50 +03:00
parent a774347c70
commit a71e17561f
4 changed files with 56 additions and 10 deletions

View file

@ -4,18 +4,15 @@ import android.content.Context
import com.tangem.datasource.BuildConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormatterBuilder
import timber.log.Timber
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.io.*
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.inject.Inject
import javax.inject.Singleton
@ -30,7 +27,7 @@ import javax.inject.Singleton
@Singleton
class AppLogsStore @Inject constructor(
@ApplicationContext private val applicationContext: Context,
dispatchers: CoroutineDispatcherProvider,
private val dispatchers: CoroutineDispatcherProvider,
) {
private val scope = CoroutineScope(
@ -38,8 +35,10 @@ class AppLogsStore @Inject constructor(
CoroutineExceptionHandler { _, error -> Timber.e("AppLogsStore.scope is failed $error") },
)
private val mutex = Mutex()
private val zipMutex = Mutex()
private val file = File(applicationContext.filesDir, NEW_LOG_FILE_NAME)
private val file = File(applicationContext.filesDir, PERMITTED_FILE_NAME)
private val fileZip = File(applicationContext.filesDir, PERMITTED_FILE_NAME_ZIP)
private val formatter = DateTimeFormatterBuilder()
.appendDayOfMonth(2)
@ -58,6 +57,16 @@ class AppLogsStore @Inject constructor(
/** Get log file */
fun getFile(): File? = if (file.exists()) file else null
suspend fun getZipFile(): File? {
return zipMutex.withLock {
if (file.exists()) {
zip(listOf(file), fileZip)
} else {
null
}
}
}
/** Save log [message] */
fun saveLogMessage(tag: String, message: String) {
// Temporally logs are not saved in prod environment
@ -133,8 +142,41 @@ class AppLogsStore @Inject constructor(
}
}
@Suppress("NestedBlockDepth")
private suspend fun zip(filesToCompress: List<File>, outputZipFile: File): File? {
return withContext(dispatchers.io) {
if (outputZipFile.exists() && !outputZipFile.delete()) {
return@withContext null
}
val buffer = ByteArray(BUFFER_SIZE)
FileOutputStream(outputZipFile).use { fos ->
ZipOutputStream(fos).use { zos ->
filesToCompress.forEach { file ->
FileInputStream(file).use { inStream ->
val ze = ZipEntry(file.name)
zos.putNextEntry(ze)
var len: Int
while (inStream.read(buffer).also { len = it } > 0) {
zos.write(buffer, 0, len)
}
}
}
zos.finish() // Ensures the zip output is finalized
}
}
outputZipFile
}
}
private companion object {
const val BUFFER_SIZE = 1024
const val LOG_FILE_NAME = "logs.txt"
const val NEW_LOG_FILE_NAME = "app_logs.txt"
// the only name that we allow to send as email to company addresses
const val PERMITTED_FILE_NAME = "log.txt"
const val PERMITTED_FILE_NAME_ZIP = "log.zip"
}
}

View file

@ -94,6 +94,8 @@ internal class DefaultFeedbackRepository(
override fun getLogFile(): File? = appLogsStore.getFile()
override suspend fun getZipLogFile(): File? = appLogsStore.getZipFile()
override fun sendEmail(feedbackEmail: FeedbackEmail) {
emailSender.send(
EmailSender.Email(

View file

@ -31,7 +31,7 @@ class SendFeedbackEmailUseCase(
subject = emailSubjectResolver.resolve(type),
message = createMessage(type),
// Temporally user data is not sent
file = null, // feedbackRepository.getLogFile(),
file = null, // feedbackRepository.getZipLogFile(),
)
feedbackRepository.sendEmail(email)

View file

@ -27,5 +27,7 @@ interface FeedbackRepository {
fun getLogFile(): File?
suspend fun getZipLogFile(): File?
fun sendEmail(feedbackEmail: FeedbackEmail)
}