Skip to content

Commit 31c8d19

Browse files
committed
stability: reset workspace state when opening projects
1 parent abf02ce commit 31c8d19

6 files changed

Lines changed: 76 additions & 9 deletions

File tree

app/src/main/java/com/kyhsgeekcode/disassembler/project/ProjectDataStorage.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ object ProjectDataStorage {
146146
val key = Pair(keykey, DataType.FileContent)
147147
data[key] = datadata
148148
}
149+
150+
fun clear() {
151+
data.clear()
152+
}
149153
}
150154

151155
internal const val MAX_CACHED_FILE_CONTENT_BYTES = 8L * 1024 * 1024

app/src/main/java/com/kyhsgeekcode/disassembler/viewmodel/MainViewModel.kt

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@ import kotlin.collections.HashMap
4848
import kotlin.math.max
4949
import kotlin.math.min
5050

51+
internal data class OpenedProjectWorkspaceState(
52+
val openedTabs: List<TabData>,
53+
val currentTabIndex: Int
54+
)
55+
56+
internal fun openedProjectWorkspaceState(
57+
previousTabs: List<TabData>,
58+
previousCurrentTabIndex: Int
59+
): OpenedProjectWorkspaceState {
60+
val defaultTabs = listOf(TabData("Overview", TabKind.ProjectOverview))
61+
if (previousCurrentTabIndex == 0 && previousTabs == defaultTabs) {
62+
return OpenedProjectWorkspaceState(
63+
openedTabs = previousTabs,
64+
currentTabIndex = previousCurrentTabIndex
65+
)
66+
}
67+
return OpenedProjectWorkspaceState(
68+
openedTabs = defaultTabs,
69+
currentTabIndex = 0
70+
)
71+
}
5172

5273
sealed class ShowSearchForStringsDialog {
5374
object NotShown : ShowSearchForStringsDialog()
@@ -161,8 +182,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
161182
val project = withContext(Dispatchers.IO) {
162183
ProjectManager.openProject(projectInfoFile.absolutePath)
163184
}
164-
_selectedFilePath.value = project.sourceFilePath
165-
_currentProject.value = project
185+
applyOpenedProject(project)
166186
} catch (e: Exception) {
167187
eventChannel.send(Event.AlertError("Failed to open project"))
168188
}
@@ -177,8 +197,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
177197
val project = withContext(Dispatchers.IO) {
178198
ProjectManager.import(archiveFile)
179199
}
180-
_selectedFilePath.value = project.sourceFilePath
181-
_currentProject.value = project
200+
applyOpenedProject(project)
182201
} catch (e: Exception) {
183202
eventChannel.send(Event.AlertError("Failed to import project"))
184203
}
@@ -214,8 +233,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
214233

215234
is ImportedUriAction.ImportProjectArchive -> ProjectManager.import(action.archiveFile)
216235
}
217-
_selectedFilePath.value = project.sourceFilePath
218-
_currentProject.value = project
236+
applyOpenedProject(project)
219237
}
220238
} catch (e: Exception) {
221239
viewModelScope.launch {
@@ -234,8 +252,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
234252
val project = withContext(Dispatchers.IO) {
235253
onClickCopyDialog(copy)
236254
}
237-
_selectedFilePath.value = project.sourceFilePath
238-
_currentProject.value = project
255+
applyOpenedProject(project)
239256
} catch (e: Exception) {
240257
eventChannel.send(Event.AlertError("Failed to create project"))
241258
}
@@ -397,6 +414,20 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
397414
return openedTabs.value[currentTabIndex.value].title.replaceAfter("as ", with)
398415
}
399416

417+
private fun applyOpenedProject(project: ProjectModel) {
418+
val workspaceState = openedProjectWorkspaceState(
419+
previousTabs = _openedTabs.value,
420+
previousCurrentTabIndex = _currentTabIndex.value
421+
)
422+
ProjectDataStorage.clear()
423+
tabDataMap.clear()
424+
_openedTabs.value = workspaceState.openedTabs
425+
_currentTabIndex.value = workspaceState.currentTabIndex
426+
_showSearchForStrings.value = ShowSearchForStringsDialog.NotShown
427+
_selectedFilePath.value = project.sourceFilePath
428+
_currentProject.value = project
429+
}
430+
400431
private fun openNewTab(tabData: TabData) {
401432
prepareTabData(tabData)
402433
val newList = ArrayList<TabData>()

app/src/test/java/com/kyhsgeekcode/disassembler/project/ProjectDataStorageCachePolicyTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,13 @@ class ProjectDataStorageCachePolicyTest {
3737

3838
assertContentEquals(byteArrayOf(1, 2, 3, 4), readPreviewBytes(file, maxBytes = 4))
3939
}
40+
41+
@Test
42+
fun `clear removes cached file content`() {
43+
ProjectDataStorage.data["sample" to DataType.FileContent] = byteArrayOf(1, 2, 3)
44+
45+
ProjectDataStorage.clear()
46+
47+
assertTrue(ProjectDataStorage.data.isEmpty())
48+
}
4049
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.kyhsgeekcode.disassembler.viewmodel
2+
3+
import com.kyhsgeekcode.disassembler.ui.TabData
4+
import com.kyhsgeekcode.disassembler.ui.TabKind
5+
import kotlin.test.Test
6+
import kotlin.test.assertEquals
7+
8+
class OpenedProjectWorkspaceStateTest {
9+
@Test
10+
fun `openedProjectWorkspaceState resets tabs to overview`() {
11+
val previousTabs = listOf(
12+
TabData("Overview", TabKind.ProjectOverview),
13+
TabData("classes.dex as Binary", TabKind.Binary("classes.dex"))
14+
)
15+
16+
val state = openedProjectWorkspaceState(previousTabs, previousCurrentTabIndex = 1)
17+
18+
assertEquals(0, state.currentTabIndex)
19+
assertEquals(listOf(TabData("Overview", TabKind.ProjectOverview)), state.openedTabs)
20+
}
21+
}

docs/maintenance/backlog-triage.ko.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
| 작업 묶음 | 관련 이슈 | 제안 상태 | 판단 | 다음 액션 |
3737
| --- | --- | --- | --- | --- |
38-
| 대용량/메모리/RecyclerView 크래시 | `#219`, `#235`, `#442`, `#523` | `planned-fast-follow` | `#728`에서 큰 파일 byte cache 제한과 문자열 검색 결과 상한/stable key를 먼저 넣었다 | `#728` 병합 후 실제 150MB 파일과 긴 문자열 리스트로 재검증하고 나머지 OOM 경로를 분리 |
38+
| 대용량/메모리/RecyclerView 크래시 | `#219`, `#235`, `#442`, `#523` | `planned-fast-follow` | `#728`에서 큰 파일 byte cache 제한과 문자열 검색 결과 상한/stable key를 먼저 넣었고, `#741`에서 문자열 검색 입력 상한과 프로젝트 전환 시 탭/전역 캐시 리셋을 추가하고 있다 | 실제 150MB 파일과 긴 문자열 리스트로 재검증하고, 살아 있는 경로와 legacy stack trace를 더 분리 |
3939
| `.so`/ELF/autosetup | `#514`, `#543`, `#576`, `#137` | `planned-fast-follow` | `#728`에서 64-bit ELF machine type 매핑과 override autosetup 재적용 경로를 먼저 수정했다 | 실제 `.so` 샘플로 재검증하고 남는 parser 문제만 분리 |
4040
| 구형 Android archive 감지 크래시 | `#507`, `#508` | `planned-fast-follow` | `#728`에서 archive 확장자 fast path와 `NoClassDefFoundError` 방어를 넣어 구형 Android의 Commons Compress 감지 크래시 경로를 우회했다 | Android 6~7 계열에서 archive chooser와 open 경로를 재확인하고 정리 |
4141
| project-relative path assertion 크래시 | `#512` | `planned-fast-follow` | `#728`에서 drawer/tab 경로가 project 바깥 항목을 열려다 `getRelPath()` 예외로 죽지 않도록 null-safe guard를 넣었다 | stale project/drawer entry에서 실패 메시지로만 처리되는지 확인하고 정리 |

docs/maintenance/implementation-log.ko.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@
7878
| expanded file cache key 테스트 | 통과 | 같은 파일 내용은 같은 streaming hash를 만들고, 전체 `readBytes()` 없이 key를 계산하는 규칙을 고정 |
7979
| hex preview 테스트 | 통과 | Hex 탭이 큰 파일 바이트 배열을 preview 상한으로 잘라 렌더링하는 규칙을 고정 |
8080
| text preview 테스트 | 통과 | Text 탭이 큰 파일 바이트 배열을 preview 상한으로 잘라 highlight하는 규칙을 고정 |
81+
| project workspace reset 테스트 | 통과 | 새 프로젝트를 열면 열린 탭과 현재 탭 인덱스를 `Overview` 하나로 리셋하는 규칙을 고정 |
82+
| project data cache clear 테스트 | 통과 | 프로젝트 전환 시 전역 파일 content cache를 비우는 규칙을 고정 |
8183
| import destination 파일명 테스트 | 통과 | 같은 display name으로 여러 번 import해도 app-private 파일이 덮어써지지 않는 규칙을 고정 |
8284
| imported project relocation 테스트 | 통과 | project archive import 후 `sourceFilePath`, `generatedFolder`, `project_info.json` 경로를 새 프로젝트 위치로 다시 맞추는 규칙을 고정 |
8385
| project open action 테스트 | 통과 | `Open as project`가 기존 project 디렉터리와 exported project ZIP에 대해 올바른 reopen/import 동작을 고르는 규칙을 고정 |

0 commit comments

Comments
 (0)