1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-3164 Tech | Support debug log sharing (#1943)

This commit is contained in:
Konstantin Ivanov 2024-12-20 12:20:16 +01:00 committed by GitHub
parent 410a15d224
commit 147f426d5d
Signed by: github
GPG key ID: B5690EEEBB952194
20 changed files with 189 additions and 37 deletions

View file

@ -6,12 +6,15 @@ import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.di.common.ComponentDependencies
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.debugging.DebugExportLogs
import com.anytypeio.anytype.domain.device.PathProvider
import com.anytypeio.anytype.domain.networkmode.GetNetworkMode
import com.anytypeio.anytype.domain.networkmode.SetNetworkMode
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.settings.PreferencesViewModel
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.NetworkModeCopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider
import com.anytypeio.anytype.ui.onboarding.OnboardingNetworkSetupDialog
import com.anytypeio.anytype.ui.settings.system.PreferenceFragment
import dagger.Component
@ -44,6 +47,14 @@ object AppPreferencesModule {
context: Context
): CopyFileToCacheDirectory = NetworkModeCopyFileToCacheDirectory(context)
@JvmStatic
@Provides
@PerScreen
fun provideLogsExport(
repository: AuthRepository,
dispatchers: AppCoroutineDispatchers
): DebugExportLogs = DebugExportLogs(repository, dispatchers)
@JvmStatic
@Provides
@PerScreen
@ -51,12 +62,18 @@ object AppPreferencesModule {
copyFileToCacheDirectory: CopyFileToCacheDirectory,
getNetworkMode: GetNetworkMode,
setNetworkMode: SetNetworkMode,
analytics: Analytics
analytics: Analytics,
debugExportLogs: DebugExportLogs,
pathProvider: PathProvider,
urlFileProvider: UriFileProvider
): PreferencesViewModel.Factory = PreferencesViewModel.Factory(
copyFileToCacheDirectory = copyFileToCacheDirectory,
getNetworkMode = getNetworkMode,
setNetworkMode = setNetworkMode,
analytics = analytics
analytics = analytics,
debugExportLogs = debugExportLogs,
pathProvider = pathProvider,
uriFileProvider = urlFileProvider
)
}
@ -66,4 +83,6 @@ interface AppPreferencesDependencies : ComponentDependencies {
fun authRepository(): AuthRepository
fun analytics(): Analytics
fun analyticSpaceHelper(): AnalyticSpaceHelperDelegate
fun pathProvider(): PathProvider
fun provideUriFileProvider(): UriFileProvider
}

View file

@ -15,6 +15,8 @@ import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.NetworkModeConstants
import com.anytypeio.anytype.core_utils.const.FileConstants
import com.anytypeio.anytype.core_utils.ext.Mimetype
import com.anytypeio.anytype.core_utils.ext.shareFileFromPath
import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.di.common.componentManager
@ -23,6 +25,7 @@ import com.anytypeio.anytype.ui.editor.PickerDelegate
import com.anytypeio.anytype.ui.onboarding.screens.signin.NetworkSetupScreen
import com.anytypeio.anytype.ui.settings.typography
import javax.inject.Inject
import timber.log.Timber
class OnboardingNetworkSetupDialog : BaseBottomSheetComposeFragment() {
@ -62,12 +65,35 @@ class OnboardingNetworkSetupDialog : BaseBottomSheetComposeFragment() {
onAnytypeNetworkClicked = {
vm.proceedWithNetworkMode(NetworkModeConstants.NETWORK_MODE_DEFAULT)
},
onUseYamuxToggled = vm::onChangeMultiplexLibrary
onExportLogsClick = vm::onExportLogsClick
)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
subscribe(vm.commands) { command ->
when (command) {
is PreferencesViewModel.Command.ShareDebugLogs -> {
try {
shareFileFromPath(
path = command.path,
uriFileProvider = command.uriFileProvider
)
} catch (e: Exception) {
Timber.e(e, "Error while share debug logs").also {
toast("Error while share debug logs. Please try again later.")
}
}
}
is PreferencesViewModel.Command.ShowToast -> {
toast(command.message)
}
}
}
}
private fun setup() {
pickerDelegate = PickerDelegate.Impl(this) { actions ->
when (actions) {

View file

@ -52,7 +52,7 @@ fun DefaultNetworkSetupScreenPreview() {
onLocalOnlyClicked = {},
onSelfHostNetworkClicked = {},
onSetSelfHostConfigConfigClicked = {},
onUseYamuxToggled = {}
onExportLogsClick = {}
)
}
@ -69,7 +69,7 @@ fun SelfHostNetworkSetupScreenPreview() {
onLocalOnlyClicked = {},
onSelfHostNetworkClicked = {},
onSetSelfHostConfigConfigClicked = {},
onUseYamuxToggled = {}
onExportLogsClick = {}
)
}
@ -86,7 +86,7 @@ fun SelfHostNetworkWithPathSetupScreenPreview() {
onLocalOnlyClicked = {},
onSelfHostNetworkClicked = {},
onSetSelfHostConfigConfigClicked = {},
onUseYamuxToggled = {}
onExportLogsClick = {}
)
}
@ -103,7 +103,7 @@ fun LocalNetworkWithPathSetupScreenPreview() {
onLocalOnlyClicked = {},
onSelfHostNetworkClicked = {},
onSetSelfHostConfigConfigClicked = {},
onUseYamuxToggled = {}
onExportLogsClick = {}
)
}
@ -114,7 +114,7 @@ fun NetworkSetupScreen(
onAnytypeNetworkClicked: () -> Unit,
onSelfHostNetworkClicked: () -> Unit,
onSetSelfHostConfigConfigClicked: () -> Unit,
onUseYamuxToggled: () -> Unit
onExportLogsClick: () -> Unit
) {
Column(
modifier = Modifier
@ -148,19 +148,19 @@ fun NetworkSetupScreen(
color = NetworkSettingDescriptionColor,
textPaddingStart = 0.dp
)
UseYamuxCard(config = config, onUseYamuxToggled = onUseYamuxToggled)
ExportLogs(onExportLogsClick = onExportLogsClick)
Spacer(modifier = Modifier.height(24.dp))
}
}
@Composable
private fun UseYamuxCard(config: NetworkModeConfig, onUseYamuxToggled: () -> Unit) {
private fun ExportLogs(onExportLogsClick: () -> Unit) {
Column(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.fillMaxWidth()
.background(color = NetworkSettingCardColor)
.noRippleClickable { onUseYamuxToggled() }
.noRippleClickable { onExportLogsClick() }
) {
Row(
verticalAlignment = Alignment.CenterVertically,
@ -170,19 +170,11 @@ private fun UseYamuxCard(config: NetworkModeConfig, onUseYamuxToggled: () -> Uni
)
) {
Text(
text = stringResource(id = R.string.settings_use_yamux),
text = stringResource(id = R.string.settings_share_local_logs),
style = BodyCalloutRegular,
color = NetworkSettingTitleColor,
modifier = Modifier.weight(1.0f)
)
if (config.useReserveMultiplexLib) {
Image(
painter = painterResource(id = R.drawable.ic_network_settings_checked),
contentDescription = "Check icon"
)
} else {
Spacer(modifier = Modifier.size(24.dp))
}
}
}
}

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="." path="/" />
<files-path name="." path="/" />
</paths>

View file

@ -13,8 +13,7 @@ sealed class Command {
val avatarPath: String?,
val icon: Int,
val networkMode: NetworkMode = NetworkMode.DEFAULT,
val networkConfigFilePath: String? = null,
val preferYamuxTransport: Boolean? = null
val networkConfigFilePath: String? = null
) : Command()
data class AccountSelect(

View file

@ -386,6 +386,21 @@ fun Fragment.shareFirstFileFromPath(path: String, uriFileProvider: UriFileProvid
}
}
fun Fragment.shareFileFromPath(path: String, uriFileProvider: UriFileProvider) {
try {
val dirPath = File(path)
if (dirPath.exists()) {
val uri = uriFileProvider.getUriForFile(dirPath)
shareFile(uri)
} else {
toast("File does not exist.")
}
} catch (e: Exception) {
Timber.e(e, "Error while sharing file")
toast("Could not share file: ${e.message}")
}
}
fun Fragment.shareFile(uri: Uri) {
try {
val shareIntent: Intent = Intent().apply {

View file

@ -89,4 +89,8 @@ class AuthCacheDataStore(private val cache: AuthCache) : AuthDataStore {
override suspend fun setNetworkMode(modeConfig: NetworkModeConfig) {
cache.setNetworkMode(modeConfig)
}
override suspend fun debugExportLogs(dir: String): String {
throw UnsupportedOperationException()
}
}

View file

@ -100,4 +100,8 @@ class AuthDataRepository(
override suspend fun setNetworkMode(modeConfig: NetworkModeConfig) {
factory.cache.setNetworkMode(modeConfig)
}
override suspend fun debugExportLogs(dir: String): String {
return factory.remote.debugExportLogs(dir)
}
}

View file

@ -42,4 +42,5 @@ interface AuthDataStore {
suspend fun getNetworkMode(): NetworkModeConfig
suspend fun setNetworkMode(modeConfig: NetworkModeConfig)
suspend fun debugExportLogs(dir: String): String
}

View file

@ -22,4 +22,5 @@ interface AuthRemote {
suspend fun getVersion(): String
suspend fun setInitialParams(command: Command.SetInitialParams)
suspend fun debugExportLogs(dir: String): String
}

View file

@ -89,4 +89,8 @@ class AuthRemoteDataStore(
override suspend fun setNetworkMode(modeConfig: NetworkModeConfig) {
throw UnsupportedOperationException()
}
override suspend fun debugExportLogs(dir: String): String {
return authRemote.debugExportLogs(dir)
}
}

View file

@ -35,7 +35,6 @@ open class CreateAccount @Inject constructor(
icon = params.iconGradientValue,
networkMode = networkMode.networkMode,
networkConfigFilePath = networkMode.storedFilePath,
preferYamuxTransport = networkMode.useReserveMultiplexLib
)
val setup = repository.createAccount(command)
with(repository) {

View file

@ -59,4 +59,5 @@ interface AuthRepository {
suspend fun getNetworkMode(): NetworkModeConfig
suspend fun setNetworkMode(modeConfig: NetworkModeConfig)
suspend fun debugExportLogs(dir: String): String
}

View file

@ -0,0 +1,18 @@
package com.anytypeio.anytype.domain.debugging
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
import javax.inject.Inject
class DebugExportLogs @Inject constructor(
private val repo: AuthRepository,
dispatchers: AppCoroutineDispatchers
) : ResultInteractor<DebugExportLogs.Params, String>(dispatchers.io) {
override suspend fun doWork(params: Params): String {
return repo.debugExportLogs(params.dir)
}
data class Params(val dir: String)
}

View file

@ -1271,6 +1271,7 @@
<string name="dates_days_from">%1$s days from now</string>
<string name="settings_use_yamux">Use a reserve multiplex library</string>
<string name="settings_share_local_logs">Share local logs</string>
<!--endregion-->

View file

@ -75,4 +75,8 @@ class AuthMiddleware(
override suspend fun setInitialParams(command: Command.SetInitialParams) {
middleware.metricsSetParameters(command)
}
override suspend fun debugExportLogs(dir: String): String {
return middleware.debugExportLogs(dir)
}
}

View file

@ -90,8 +90,7 @@ class Middleware @Inject constructor(
avatarLocalPath = command.avatarPath,
icon = command.icon.toLong(),
networkMode = command.networkMode.toMiddlewareModel(),
networkCustomConfigFilePath = command.networkConfigFilePath.orEmpty(),
preferYamuxTransport = command.preferYamuxTransport ?: false
networkCustomConfigFilePath = command.networkConfigFilePath.orEmpty()
)
logRequestIfDebug(request)
val (response, time) = measureTimedValue { service.accountCreate(request) }
@ -2867,6 +2866,15 @@ class Middleware @Inject constructor(
logResponseIfDebug(response, time)
}
@Throws(Exception::class)
fun debugExportLogs(dir: String): String {
val request = Rpc.Debug.ExportLog.Request(dir = dir)
logRequestIfDebug(request)
val (response, time) = measureTimedValue { service.debugExportLogs(request) }
logResponseIfDebug(response, time)
return response.path
}
private fun logRequestIfDebug(request: Any) {
if (BuildConfig.DEBUG) {
logger.logRequest(request).also {

View file

@ -612,4 +612,7 @@ interface MiddlewareService {
@Throws(Exception::class)
fun deviceNetworkStateSet(request: Rpc.Device.NetworkState.Set.Request): Rpc.Device.NetworkState.Set.Response
@Throws(Exception::class)
fun debugExportLogs(request: Rpc.Debug.ExportLog.Request): Rpc.Debug.ExportLog.Response
}

View file

@ -2463,4 +2463,17 @@ class MiddlewareServiceImplementation @Inject constructor(
return response
}
}
override fun debugExportLogs(request: Rpc.Debug.ExportLog.Request): Rpc.Debug.ExportLog.Response {
val encoded = Service.debugExportLog(
Rpc.Debug.ExportLog.Request.ADAPTER.encode(request)
)
val response = Rpc.Debug.ExportLog.Response.ADAPTER.decode(encoded)
val error = response.error
if (error != null && error.code != Rpc.Debug.ExportLog.Response.Error.Code.NULL) {
throw Exception(error.description)
} else {
return response
}
}
}

View file

@ -11,7 +11,10 @@ import com.anytypeio.anytype.core_models.NetworkMode
import com.anytypeio.anytype.core_models.NetworkModeConfig
import com.anytypeio.anytype.core_models.NetworkModeConstants.NETWORK_MODE_CUSTOM
import com.anytypeio.anytype.core_models.NetworkModeConstants.NETWORK_MODE_LOCAL
import com.anytypeio.anytype.core_utils.ext.cancel
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.debugging.DebugExportLogs
import com.anytypeio.anytype.domain.device.PathProvider
import com.anytypeio.anytype.domain.networkmode.GetNetworkMode
import com.anytypeio.anytype.domain.networkmode.SetNetworkMode
import com.anytypeio.anytype.presentation.editor.picker.PickerListener
@ -19,6 +22,9 @@ import com.anytypeio.anytype.presentation.extension.sendAnalyticsSelectNetworkEv
import com.anytypeio.anytype.presentation.extension.sendAnalyticsUploadConfigFileEvent
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.CopyFileToCacheStatus
import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
@ -27,12 +33,18 @@ class PreferencesViewModel(
private val copyFileToCache: CopyFileToCacheDirectory,
private val getNetworkMode: GetNetworkMode,
private val setNetworkMode: SetNetworkMode,
private val analytics: Analytics
private val analytics: Analytics,
private val debugExportLogs: DebugExportLogs,
private val pathProvider: PathProvider,
private val uriFileProvider: UriFileProvider,
) : ViewModel(), PickerListener {
val networkModeState = MutableStateFlow(NetworkModeConfig(NetworkMode.DEFAULT, "", ""))
val reserveMultiplexSetting = MutableStateFlow(false)
private val jobs = mutableListOf<Job>()
val commands = MutableSharedFlow<Command>()
fun onStart() {
Timber.d("onStart")
viewModelScope.launch {
@ -49,6 +61,11 @@ class PreferencesViewModel(
}
}
override fun onCleared() {
super.onCleared()
jobs.cancel()
}
fun proceedWithNetworkMode(mode: String?) {
viewModelScope.launch {
val config = when (mode) {
@ -104,19 +121,30 @@ class PreferencesViewModel(
}
}
fun onChangeMultiplexLibrary() {
val newValue = !networkModeState.value.useReserveMultiplexLib
Timber.d("onChangeMultiplexLibrary: $newValue")
viewModelScope.launch {
val mode = networkModeState.value.copy(useReserveMultiplexLib = newValue)
val params = SetNetworkMode.Params(mode)
setNetworkMode.async(params).fold(
onSuccess = { networkModeState.value = mode },
onFailure = { Timber.e(it, "Failed to update network mode ") }
fun onExportLogsClick() {
Timber.d("onExportLogsClick: ")
jobs += viewModelScope.launch {
val dir = pathProvider.providePath()
val params = DebugExportLogs.Params(dir = dir)
debugExportLogs.async(params).fold(
onSuccess = { fileName ->
Timber.d("On debug logs success")
sendCommand(Command.ShareDebugLogs(fileName, uriFileProvider))
},
onFailure = {
Timber.e(it, "Error while collecting debug logs")
sendCommand(Command.ShowToast("Error while collecting debug logs: "))
}
)
}
}
private fun sendCommand(command: Command) {
viewModelScope.launch {
commands.emit(command)
}
}
override fun onStartCopyFileToCacheDir(uri: Uri) {
Timber.d("onStartCopyFileToCacheDir: $uri")
copyFileToCache.execute(
@ -145,11 +173,19 @@ class PreferencesViewModel(
}
}
sealed class Command {
data class ShowToast(val message: String) : Command()
data class ShareDebugLogs(val path: String, val uriFileProvider: UriFileProvider) : Command()
}
class Factory(
private val copyFileToCacheDirectory: CopyFileToCacheDirectory,
private val getNetworkMode: GetNetworkMode,
private val setNetworkMode: SetNetworkMode,
private val analytics: Analytics
private val analytics: Analytics,
private val debugExportLogs: DebugExportLogs,
private val pathProvider: PathProvider,
private val uriFileProvider: UriFileProvider,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
@ -158,7 +194,10 @@ class PreferencesViewModel(
copyFileToCache = copyFileToCacheDirectory,
getNetworkMode = getNetworkMode,
setNetworkMode = setNetworkMode,
analytics = analytics
analytics = analytics,
debugExportLogs = debugExportLogs,
pathProvider = pathProvider,
uriFileProvider = uriFileProvider
) as T
}
}