Skip to content

Commit 25fa61c

Browse files
authored
Merge pull request #969 from synonymdev/refactor/logs-polish
refactor: polish logs helpers
2 parents 4de3fff + 8c08481 commit 25fa61c

8 files changed

Lines changed: 419 additions & 151 deletions

File tree

app/src/main/java/to/bitkit/appwidget/config/AppWidgetConfigViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ import to.bitkit.data.dto.price.TradingPair
2626
import to.bitkit.models.widget.ArticleModel
2727
import to.bitkit.models.widget.BlocksPreferences
2828
import to.bitkit.models.widget.BlocksWidgetField
29-
import to.bitkit.models.widget.toggleField
3029
import to.bitkit.models.widget.HeadlinePreferences
3130
import to.bitkit.models.widget.PricePreferences
3231
import to.bitkit.models.widget.WeatherDataOption
3332
import to.bitkit.models.widget.WeatherPreferences
3433
import to.bitkit.models.widget.toArticleModel
34+
import to.bitkit.models.widget.toggleField
3535
import to.bitkit.repositories.CurrencyRepo
3636
import to.bitkit.ui.screens.widgets.blocks.WeatherModel
3737
import to.bitkit.ui.screens.widgets.blocks.toWeatherModel

app/src/main/java/to/bitkit/repositories/LogsRepo.kt

Lines changed: 102 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class LogsRepo @Inject constructor(
4444
suspend fun postQuestion(email: String, message: String): Result<Unit> = withContext(bgDispatcher) {
4545
runCatching {
4646
val logsBase64 = zipLogs(maxEncodedBytes = MAX_SUPPORT_UPLOAD_BASE64_BYTES).getOrDefault("")
47-
val logsFileName = createLogsArchiveFileName(SUPPORT_LOGS_ARCHIVE_PREFIX)
47+
val logsArchiveBaseName = currentLogsArchiveName(SUPPORT_LOGS_ARCHIVE_PREFIX).baseName
4848

4949
chatwootHttpClient.postQuestion(
5050
message = ChatwootMessage(
@@ -53,7 +53,7 @@ class LogsRepo @Inject constructor(
5353
platform = Env.platform,
5454
version = Env.version,
5555
logs = logsBase64,
56-
logsFileName = logsFileName,
56+
logsFileName = logsArchiveBaseName,
5757
)
5858
)
5959
}.onFailure {
@@ -108,7 +108,7 @@ class LogsRepo @Inject constructor(
108108
val file = withContext(ioDispatcher) {
109109
val tempDir = context.cacheDir.resolve("logs").apply { mkdirs() }
110110

111-
val zipFileName = createLogsArchiveFileName()
111+
val zipFileName = currentLogsArchiveName().fileName
112112
val tempFile = File(tempDir, zipFileName)
113113

114114
// Convert base64 back to bytes and write to file
@@ -143,74 +143,12 @@ class LogsRepo @Inject constructor(
143143
allLogs.take(limit)
144144
}
145145

146-
return@runCatching createZipBase64(logsToZip, maxEncodedBytes)
146+
return@runCatching createZipBase64(logsToZip, maxEncodedBytes, ::createSupportSnapshot)
147147
}.onFailure {
148148
Logger.error("Failed to zip logs", it, context = TAG)
149149
}
150150
}
151151

152-
@Suppress("NestedBlockDepth")
153-
private fun createZipBase64(logFiles: List<LogFile>, maxEncodedBytes: Int?): String {
154-
val selectedLogFiles = logFiles.toMutableList()
155-
156-
while (true) {
157-
val encoded = createZipBytes(selectedLogFiles).toBase64()
158-
if (maxEncodedBytes == null || encoded.length <= maxEncodedBytes || selectedLogFiles.isEmpty()) {
159-
Logger.info("Created support logs archive with '${selectedLogFiles.size}' log file(s)", context = TAG)
160-
return encoded
161-
}
162-
163-
selectedLogFiles.removeAt(selectedLogFiles.lastIndex)
164-
}
165-
}
166-
167-
@Suppress("NestedBlockDepth")
168-
private fun createZipBytes(logFiles: List<LogFile>): ByteArray {
169-
return ByteArrayOutputStream().use { byteArrayOut ->
170-
ZipOutputStream(byteArrayOut).use { zipOut ->
171-
zipOut.putNextEntry(ZipEntry(SUPPORT_SNAPSHOT_FILE_NAME))
172-
zipOut.write(createSupportSnapshot().toByteArray())
173-
zipOut.closeEntry()
174-
175-
logFiles.forEach { logFile ->
176-
if (logFile.file.exists()) {
177-
val zipEntry = ZipEntry("${logFile.source.name.lowercase()}/${logFile.fileName}")
178-
zipOut.putNextEntry(zipEntry)
179-
180-
FileInputStream(logFile.file).use { fileIn ->
181-
fileIn.copyTo(zipOut)
182-
}
183-
zipOut.closeEntry()
184-
}
185-
}
186-
}
187-
byteArrayOut.toByteArray()
188-
}
189-
}
190-
191-
private fun File.toLogFile(): LogFile {
192-
val match = LOG_FILE_NAME_REGEX.matchEntire(name)
193-
val serviceName = match
194-
?.groupValues
195-
?.getOrNull(1)
196-
?.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
197-
?: LogSource.Unknown.name
198-
val timestamp = match?.groupValues?.getOrNull(2)?.replace("_", " ")
199-
val part = match?.groupValues?.getOrNull(3)?.ifBlank { null }
200-
val partSuffix = part?.let { " part $it" }.orEmpty()
201-
val displayName = if (timestamp != null) {
202-
"$serviceName Log: $timestamp$partSuffix"
203-
} else {
204-
"$serviceName Log: $name"
205-
}
206-
207-
return LogFile(
208-
displayName = displayName,
209-
file = this,
210-
source = getEnumValueOf<LogSource>(serviceName).getOrDefault(LogSource.Unknown),
211-
)
212-
}
213-
214152
private fun createSupportSnapshot(): String {
215153
val state = lightningRepo.lightningState.value
216154
val snapshot = SupportSnapshot(
@@ -262,26 +200,85 @@ class LogsRepo @Inject constructor(
262200
return appJson.encodeToString(snapshot)
263201
}
264202

265-
private fun createLogsArchiveFileName(prefix: String = LOGS_ARCHIVE_PREFIX): String {
266-
return "${prefix}_${currentLogTimestamp()}.zip"
203+
private fun currentLogsArchiveName(prefix: String = LOGS_ARCHIVE_PREFIX): LogsArchiveName {
204+
return createLogsArchiveName(prefix, currentLogTimestamp())
267205
}
268206

269207
private fun currentLogTimestamp(): String {
270208
return utcDateFormatterOf(DatePattern.LOG_FILE).format(Date())
271209
}
210+
}
272211

273-
private companion object {
274-
const val TAG = "SupportRepo"
275-
const val LOGS_ARCHIVE_PREFIX = "bitkit_logs"
276-
const val MAX_SUPPORT_UPLOAD_BASE64_BYTES = 900 * 1024
277-
const val SUPPORT_LOGS_ARCHIVE_PREFIX = "bitkit_support_logs"
278-
const val SUPPORT_SNAPSHOT_FILE_NAME = "support_snapshot.json"
279-
val LOG_FILE_NAME_REGEX = Regex(
280-
"^([A-Za-z]+)_(\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2})(?:\\.part_(\\d{3}))?\\.log$"
281-
)
212+
internal fun createZipBase64(
213+
logFiles: List<LogFile>,
214+
maxEncodedBytes: Int?,
215+
supportSnapshot: () -> String,
216+
): String {
217+
val selectedLogFiles = logFiles.toMutableList()
218+
219+
while (true) {
220+
val encoded = createZipBytes(selectedLogFiles, supportSnapshot).toBase64()
221+
if (maxEncodedBytes == null || encoded.length <= maxEncodedBytes || selectedLogFiles.isEmpty()) {
222+
Logger.info("Created support logs archive with '${selectedLogFiles.size}' log file(s)", context = TAG)
223+
return encoded
224+
}
225+
226+
selectedLogFiles.removeAt(selectedLogFiles.lastIndex)
282227
}
283228
}
284229

230+
internal fun createZipBytes(
231+
logFiles: List<LogFile>,
232+
supportSnapshot: () -> String,
233+
): ByteArray {
234+
return ByteArrayOutputStream().use { byteArrayOut ->
235+
ZipOutputStream(byteArrayOut).use { zipOut ->
236+
zipOut.writeSupportSnapshot(supportSnapshot())
237+
logFiles.filter { it.file.exists() }.forEach { logFile ->
238+
zipOut.writeLogFile(logFile)
239+
}
240+
}
241+
byteArrayOut.toByteArray()
242+
}
243+
}
244+
245+
private fun ZipOutputStream.writeSupportSnapshot(supportSnapshot: String) {
246+
putNextEntry(ZipEntry(SUPPORT_SNAPSHOT_FILE_NAME))
247+
write(supportSnapshot.toByteArray())
248+
closeEntry()
249+
}
250+
251+
private fun ZipOutputStream.writeLogFile(logFile: LogFile) {
252+
putNextEntry(ZipEntry("${logFile.source.name.lowercase()}/${logFile.fileName}"))
253+
FileInputStream(logFile.file).use { fileIn ->
254+
fileIn.copyTo(this)
255+
}
256+
closeEntry()
257+
}
258+
259+
internal fun File.toLogFile(): LogFile {
260+
val match = LOG_FILE_NAME_REGEX.matchEntire(name)
261+
val serviceName = match
262+
?.groupValues
263+
?.getOrNull(1)
264+
?.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
265+
?: LogSource.Unknown.name
266+
val timestamp = match?.groupValues?.getOrNull(2)?.replace("_", " ")
267+
val part = match?.groupValues?.getOrNull(3)?.ifBlank { null }
268+
val partSuffix = part?.let { " part $it" }.orEmpty()
269+
val displayName = if (timestamp != null) {
270+
"$serviceName Log: $timestamp$partSuffix"
271+
} else {
272+
"$serviceName Log: $name"
273+
}
274+
275+
return LogFile(
276+
displayName = displayName,
277+
file = this,
278+
source = getEnumValueOf<LogSource>(serviceName).getOrDefault(LogSource.Unknown),
279+
)
280+
}
281+
285282
data class LogFile(
286283
val displayName: String,
287284
val file: File,
@@ -290,6 +287,24 @@ data class LogFile(
290287
val fileName: String get() = file.name
291288
}
292289

290+
internal data class LogsArchiveName(
291+
val baseName: String,
292+
) {
293+
val fileName: String get() = "$baseName$ZIP_EXTENSION"
294+
}
295+
296+
internal fun createLogsArchiveName(prefix: String, timestamp: String): LogsArchiveName {
297+
return LogsArchiveName("${prefix}_$timestamp".withoutZipExtension())
298+
}
299+
300+
internal fun String.withoutZipExtension(): String {
301+
var name = this
302+
while (name.endsWith(ZIP_EXTENSION, ignoreCase = true)) {
303+
name = name.dropLast(ZIP_EXTENSION.length)
304+
}
305+
return name
306+
}
307+
293308
private fun NodeLifecycleState.supportName(): String = when (this) {
294309
is NodeLifecycleState.Stopped -> "Stopped"
295310
is NodeLifecycleState.Starting -> "Starting"
@@ -348,3 +363,13 @@ private data class SupportBalanceSnapshot(
348363
val lightningBalancesCount: Int,
349364
val pendingChannelClosureBalancesCount: Int,
350365
)
366+
367+
private const val TAG = "LogsRepo"
368+
private const val LOGS_ARCHIVE_PREFIX = "bitkit_logs"
369+
private const val MAX_SUPPORT_UPLOAD_BASE64_BYTES = 900 * 1024
370+
private const val SUPPORT_LOGS_ARCHIVE_PREFIX = "bitkit_support_logs"
371+
private const val SUPPORT_SNAPSHOT_FILE_NAME = "support_snapshot.json"
372+
private const val ZIP_EXTENSION = ".zip"
373+
private val LOG_FILE_NAME_REGEX = Regex(
374+
"^([A-Za-z]+)_(\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2})(?:\\.part_(\\d{3}))?\\.log$"
375+
)

app/src/main/java/to/bitkit/services/MigrationService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ import to.bitkit.models.WidgetType
5858
import to.bitkit.models.WidgetWithPosition
5959
import to.bitkit.models.toSettingsString
6060
import to.bitkit.models.widget.BlocksPreferences
61-
import to.bitkit.models.widget.limitedToMax
6261
import to.bitkit.models.widget.HeadlinePreferences
6362
import to.bitkit.models.widget.PricePreferences
6463
import to.bitkit.models.widget.WeatherDataOption
6564
import to.bitkit.models.widget.WeatherPreferences
65+
import to.bitkit.models.widget.limitedToMax
6666
import to.bitkit.repositories.ActivityRepo
6767
import to.bitkit.services.core.Bip39Service
6868
import to.bitkit.utils.AppError

0 commit comments

Comments
 (0)