Skip to content

Commit 644643a

Browse files
committed
Implement cache clearing and size observation in Profile feature
- Added `getCacheSizeBytes` and `clearCacheFiles` to `FileLocationsProvider` with platform-specific implementations for Android and Desktop. - Updated `ProfileRepository` and its implementation to support cache size observation and clearing. - Enhanced `ProfileViewModel` to format cache sizes (B, KB, MB, GB) and handle `OnClearCacheClick` actions. - Introduced `OnCacheCleared` and `OnCacheClearError` events to `ProfileEvent` and integrated them into `ProfileRoot` for snackbar notifications. - Added the `cache_cleared` string resource. - Updated `SharedModule` to provide `FileLocationsProvider` to `ProfileRepositoryImpl`.
1 parent 5349a07 commit 644643a

10 files changed

Lines changed: 140 additions & 3 deletions

File tree

core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/AndroidFileLocationsProvider.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,37 @@ class AndroidFileLocationsProvider(
2020
override fun setExecutableIfNeeded(path: String) {
2121
// No-op on Android
2222
}
23+
24+
override fun getCacheSizeBytes(): Long {
25+
val dir = File(appDownloadsDir())
26+
return calculateDirSize(dir)
27+
}
28+
29+
override fun clearCacheFiles(): Boolean {
30+
val dir = File(appDownloadsDir())
31+
return deleteDirectoryContents(dir)
32+
}
33+
34+
private fun calculateDirSize(dir: File): Long {
35+
if (!dir.exists()) return 0L
36+
var size = 0L
37+
dir.listFiles()?.forEach { file ->
38+
size += if (file.isDirectory) calculateDirSize(file) else file.length()
39+
}
40+
return size
41+
}
42+
43+
private fun deleteDirectoryContents(dir: File): Boolean {
44+
if (!dir.exists()) return true
45+
var allDeleted = true
46+
dir.listFiles()?.forEach { file ->
47+
if (file.isDirectory) {
48+
if (!deleteDirectoryContents(file)) allDeleted = false
49+
if (!file.delete()) allDeleted = false
50+
} else {
51+
if (!file.delete()) allDeleted = false
52+
}
53+
}
54+
return allDeleted
55+
}
2356
}

core/data/src/commonMain/kotlin/zed/rainxch/core/data/services/FileLocationsProvider.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ interface FileLocationsProvider {
44
fun appDownloadsDir(): String
55
fun userDownloadsDir(): String
66
fun setExecutableIfNeeded(path: String)
7+
fun getCacheSizeBytes(): Long
8+
fun clearCacheFiles(): Boolean
79
}

core/data/src/jvmMain/kotlin/zed/rainxch/core/data/services/DesktopFileLocationsProvider.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,43 @@ class DesktopFileLocationsProvider(
9595
return downloadsDir.absolutePath
9696
}
9797

98+
override fun getCacheSizeBytes(): Long {
99+
val appDir = File(appDownloadsDir())
100+
val userDir = File(userDownloadsDir())
101+
return calculateDirSize(appDir) + calculateDirSize(userDir)
102+
}
103+
104+
override fun clearCacheFiles(): Boolean {
105+
val appDir = File(appDownloadsDir())
106+
val userDir = File(userDownloadsDir())
107+
val appCleared = deleteDirectoryContents(appDir)
108+
val userCleared = deleteDirectoryContents(userDir)
109+
return appCleared && userCleared
110+
}
111+
112+
private fun calculateDirSize(dir: File): Long {
113+
if (!dir.exists()) return 0L
114+
var size = 0L
115+
dir.listFiles()?.forEach { file ->
116+
size += if (file.isDirectory) calculateDirSize(file) else file.length()
117+
}
118+
return size
119+
}
120+
121+
private fun deleteDirectoryContents(dir: File): Boolean {
122+
if (!dir.exists()) return true
123+
var allDeleted = true
124+
dir.listFiles()?.forEach { file ->
125+
if (file.isDirectory) {
126+
if (!deleteDirectoryContents(file)) allDeleted = false
127+
if (!file.delete()) allDeleted = false
128+
} else {
129+
if (!file.delete()) allDeleted = false
130+
}
131+
}
132+
return allDeleted
133+
}
134+
98135
private fun getXdgDownloadsDir(): String? {
99136
return try {
100137
val userDirsFile = File(

core/presentation/src/commonMain/composeResources/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@
147147

148148
<!-- Snackbar -->
149149
<string name="logout_success">Logged out successfully, redirecting...</string>
150+
<string name="cache_cleared">Cache cleared successfully</string>
150151

151152
<!-- Dialog -->
152153
<string name="warning">Warning!</string>

feature/profile/data/src/commonMain/kotlin/zed/rainxch/profile/data/di/SharedModule.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ val settingsModule = module {
1111
tokenStore = get(),
1212
httpClient = get(),
1313
cacheManager = get(),
14-
logger = get()
14+
logger = get(),
15+
fileLocationsProvider = get()
1516
)
1617
}
1718
}

feature/profile/data/src/commonMain/kotlin/zed/rainxch/profile/data/repository/ProfileRepositoryImpl.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.ktor.client.request.get
55
import io.ktor.client.request.header
66
import io.ktor.http.HttpHeaders
77
import kotlinx.coroutines.Dispatchers
8+
import kotlinx.coroutines.delay
89
import kotlinx.coroutines.flow.Flow
910
import kotlinx.coroutines.flow.flow
1011
import kotlinx.coroutines.flow.flowOn
@@ -13,6 +14,7 @@ import zed.rainxch.core.data.cache.CacheManager.CacheTtl.USER_PROFILE
1314
import zed.rainxch.core.data.data_source.TokenStore
1415
import zed.rainxch.core.data.dto.UserProfileNetwork
1516
import zed.rainxch.core.data.network.executeRequest
17+
import zed.rainxch.core.data.services.FileLocationsProvider
1618
import zed.rainxch.core.domain.logging.GitHubStoreLogger
1719
import zed.rainxch.core.domain.repository.AuthenticationState
1820
import zed.rainxch.feature.profile.data.BuildKonfig
@@ -25,7 +27,8 @@ class ProfileRepositoryImpl(
2527
private val tokenStore: TokenStore,
2628
private val httpClient: HttpClient,
2729
private val cacheManager: CacheManager,
28-
private val logger: GitHubStoreLogger
30+
private val logger: GitHubStoreLogger,
31+
private val fileLocationsProvider: FileLocationsProvider
2932
) : ProfileRepository {
3033

3134
companion object {
@@ -84,4 +87,15 @@ class ProfileRepositoryImpl(
8487
tokenStore.clear()
8588
cacheManager.clearAll()
8689
}
90+
91+
override fun observeCacheSize(): Flow<Long> = flow {
92+
val sizeBytes = fileLocationsProvider.getCacheSizeBytes()
93+
emit(sizeBytes)
94+
}.flowOn(Dispatchers.IO)
95+
96+
override suspend fun clearCache() {
97+
fileLocationsProvider.clearCacheFiles()
98+
cacheManager.clearAll()
99+
logger.debug("Cache cleared successfully")
100+
}
87101
}

feature/profile/domain/src/commonMain/kotlin/zed/rainxch/profile/domain/repository/ProfileRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ interface ProfileRepository {
88
fun getUser(): Flow<UserProfile?>
99
fun getVersionName(): String
1010
suspend fun logout()
11+
fun observeCacheSize(): Flow<Long>
12+
suspend fun clearCache()
1113
}

feature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileEvent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ sealed interface ProfileEvent {
55
data class OnLogoutError(val message: String) : ProfileEvent
66
data object OnProxySaved : ProfileEvent
77
data class OnProxySaveError(val message: String) : ProfileEvent
8+
data object OnCacheCleared : ProfileEvent
9+
data class OnCacheClearError(val message: String) : ProfileEvent
810
}

feature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileRoot.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,18 @@ fun ProfileRoot(
8080
snackbarState.showSnackbar(event.message)
8181
}
8282
}
83+
84+
ProfileEvent.OnCacheCleared -> {
85+
coroutineScope.launch {
86+
snackbarState.showSnackbar(getString(Res.string.cache_cleared))
87+
}
88+
}
89+
90+
is ProfileEvent.OnCacheClearError -> {
91+
coroutineScope.launch {
92+
snackbarState.showSnackbar(event.message)
93+
}
94+
}
8395
}
8496
}
8597

feature/profile/presentation/src/commonMain/kotlin/zed/rainxch/profile/presentation/ProfileViewModel.kt

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,27 @@ class ProfileViewModel(
5959

6060
private fun observeCacheSize() {
6161
viewModelScope.launch {
62+
profileRepository.observeCacheSize().collect { sizeBytes ->
63+
_state.update {
64+
it.copy(cacheSize = formatCacheSize(sizeBytes))
65+
}
66+
}
67+
}
68+
}
6269

70+
private fun formatCacheSize(bytes: Long): String {
71+
if (bytes <= 0) return "0 B"
72+
val units = arrayOf("B", "KB", "MB", "GB")
73+
var size = bytes.toDouble()
74+
var unitIndex = 0
75+
while (size >= 1024 && unitIndex < units.lastIndex) {
76+
size /= 1024
77+
unitIndex++
78+
}
79+
return if (size == size.toLong().toDouble()) {
80+
"${size.toLong()} ${units[unitIndex]}"
81+
} else {
82+
"${"%.1f".format(size)} ${units[unitIndex]}"
6383
}
6484
}
6585

@@ -180,7 +200,20 @@ class ProfileViewModel(
180200
}
181201

182202
ProfileAction.OnClearCacheClick -> {
183-
203+
viewModelScope.launch {
204+
runCatching {
205+
profileRepository.clearCache()
206+
}.onSuccess {
207+
observeCacheSize()
208+
_events.send(ProfileEvent.OnCacheCleared)
209+
}.onFailure { error ->
210+
_events.send(
211+
ProfileEvent.OnCacheClearError(
212+
error.message ?: "Failed to clear cache"
213+
)
214+
)
215+
}
216+
}
184217
}
185218

186219
is ProfileAction.OnThemeColorSelected -> {

0 commit comments

Comments
 (0)