Skip to content

Commit 0f82ceb

Browse files
committed
stability: stream expanded file cache hashing
1 parent 4f4d4aa commit 0f82ceb

3 files changed

Lines changed: 66 additions & 9 deletions

File tree

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,37 @@
11
package com.kyhsgeekcode.filechooser.model
22

3-
import chan.murmur.Three
43
import kotlinx.coroutines.Dispatchers
54
import kotlinx.coroutines.withContext
65
import java.io.File
6+
import java.io.InputStream
7+
import java.security.MessageDigest
78

89
//class ExpandedFileCache(private val cacheDir: File) {
910
suspend fun getExpandedFile(cacheDir: File, sourceFile: File): File {
10-
val hash = withContext(Dispatchers.Default) {
11-
Three.hash128(withContext(Dispatchers.IO) {
12-
sourceFile.readBytes()
13-
})
14-
}
15-
val title = hash.joinToString("") {
16-
it.toString(16)
11+
val title = withContext(Dispatchers.IO) {
12+
buildExpandedFileCacheKey(sourceFile)
1713
}
1814
return File(cacheDir, title)
1915
}
20-
//}
16+
17+
internal fun buildExpandedFileCacheKey(sourceFile: File): String {
18+
sourceFile.inputStream().buffered().use { input ->
19+
return buildExpandedFileCacheKey(input)
20+
}
21+
}
22+
23+
internal fun buildExpandedFileCacheKey(inputStream: InputStream): String {
24+
val digest = MessageDigest.getInstance("SHA-256")
25+
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
26+
while (true) {
27+
val read = inputStream.read(buffer)
28+
if (read < 0) {
29+
break
30+
}
31+
if (read > 0) {
32+
digest.update(buffer, 0, read)
33+
}
34+
}
35+
return digest.digest().joinToString("") { byte -> "%02x".format(byte) }
36+
}
37+
//}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.kyhsgeekcode.filechooser.model
2+
3+
import java.io.ByteArrayInputStream
4+
import java.io.File
5+
import kotlin.test.Test
6+
import kotlin.test.assertEquals
7+
import kotlin.test.assertNotEquals
8+
9+
class ExpandedFileCacheTest {
10+
@Test
11+
fun `buildExpandedFileCacheKey is stable for equal content`() {
12+
val first = buildExpandedFileCacheKey(ByteArrayInputStream("same-content".encodeToByteArray()))
13+
val second = buildExpandedFileCacheKey(ByteArrayInputStream("same-content".encodeToByteArray()))
14+
15+
assertEquals(first, second)
16+
}
17+
18+
@Test
19+
fun `buildExpandedFileCacheKey changes for different content`() {
20+
val first = buildExpandedFileCacheKey(ByteArrayInputStream("same-content".encodeToByteArray()))
21+
val second = buildExpandedFileCacheKey(ByteArrayInputStream("different-content".encodeToByteArray()))
22+
23+
assertNotEquals(first, second)
24+
}
25+
26+
@Test
27+
fun `buildExpandedFileCacheKeyFromFile matches stream key`() {
28+
val tempFile = File.createTempFile("expanded-cache", ".bin").apply {
29+
writeBytes("file-content".encodeToByteArray())
30+
deleteOnExit()
31+
}
32+
33+
val streamKey = buildExpandedFileCacheKey(ByteArrayInputStream("file-content".encodeToByteArray()))
34+
val fileKey = buildExpandedFileCacheKey(tempFile)
35+
36+
assertEquals(streamKey, fileKey)
37+
}
38+
}

docs/maintenance/implementation-log.ko.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
| 이슈 `#514`, `#543`, `#576` `.so`/ELF 아키텍처와 override autosetup | `x86_64`, `PPC64` ELF machine type을 64-bit disassembly mode로 매핑하고, binary overview에서 수동 설정을 적용하면 disassembly handle을 다시 열도록 바꿨다 | 일부 `.so`가 32-bit mode로 잘못 열리던 경로를 줄이고, `Override autosetup`이 값만 바꾸고 실제 disassembly는 안 바뀌던 문제를 수정했다 | `app/src/main/java/com/kyhsgeekcode/disassembler/models/Architecture.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/ui/tabs/BinaryOverviewTab.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/ui/tabs/BinaryTab.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/models/ArchitectureTest.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/ui/tabs/BinaryManualSetupConfigTest.kt` | 완료 |
3636
| 이슈 `#123`, `#720` 프로젝트 export와 details 저장 부재 | ZIP 생성기를 재귀 상대경로 방식으로 고치고, 프로젝트 overview에 SAF `CreateDocument` 기반 `Export project` 버튼을 추가했으며, binary detail 탭에는 `Save Details to file` 버튼을 구현했다 | 깨져 있던 project ZIP export 경로를 실제로 복구하고, 최신 Android 저장소 모델에 맞게 상세 정보 텍스트를 `.txt` 문서로 저장할 수 있게 했다 | `app/src/main/java/com/kyhsgeekcode/Util.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/exporting/ExportDocuments.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/project/ProjectManager.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/viewmodel/MainViewModel.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/ui/MainTab.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/ui/tabs/BinaryDetailTab.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/ProjectExportArchiveTest.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/exporting/BinaryDetailsExportTest.kt` | 완료 |
3737
| 이슈 `#219` 대용량 파일 detail 렌더링 OOM 완화 | binary detail 화면이 전체 문자열을 그대로 레이아웃하지 않도록 preview helper를 추가하고, 화면에는 잘린 preview만 유지하며 전체 내용은 export 시점에만 다시 생성하도록 바꿨다 | 큰 ELF/PE detail 문자열이 화면 레이아웃 단계에서 메모리를 과도하게 잡는 경로를 줄이고, 저장 기능은 유지한 채 UI 메모리 압박을 낮췄다 | `app/src/main/java/com/kyhsgeekcode/disassembler/ui/tabs/BinaryDetailTab.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/ui/tabs/BinaryDetailsPreviewTest.kt` | 완료 |
38+
| 이슈 `#219`, `#523` 대용량 파일 확장 cache key OOM 완화 | archive/dex/app expansion cache 경로가 더 이상 파일 전체를 `readBytes()`로 읽지 않고, streaming SHA-256으로 cache key를 계산하도록 바꿨다 | 큰 파일을 확장하기 전에 해시 계산만으로 메모리를 크게 잡아먹던 경계를 제거해서 대용량 import의 선행 OOM 위험을 줄였다 | `app/src/main/java/com/kyhsgeekcode/filechooser/model/ExpandedFileCache.kt`, `app/src/test/java/com/kyhsgeekcode/filechooser/model/ExpandedFileCacheTest.kt` | 완료 |
3839
| 이슈 `#129` `.ar` archive 지원 | 공용 archive extractor를 추가하고, Compose/legacy drawer의 archive 확장 경로를 ZIP 전용 구현에서 generic archive extractor로 교체했다 | archive 판정은 되는데 실제 확장은 ZIP만 되던 불일치를 제거해서 `.ar` 같은 지원 가능한 archive도 실제로 탐색 가능하게 만들었다 | `app/src/main/java/com/kyhsgeekcode/Util.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/ui/FileDrawerTree.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/FileDrawerListItem.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/FileDrawerListAdapter.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/ArchiveExtractionTest.kt` | 완료 |
3940
| 프로젝트 경로/파일명 회귀 방지 | 프로젝트 상대경로 계산과 import 파일명 정규화를 pure helper로 분리 | 단위 테스트가 가능하도록 로직을 분리하고 경계 케이스를 줄였다 | `app/src/main/java/com/kyhsgeekcode/disassembler/project/ProjectManager.kt`, `app/src/main/java/com/kyhsgeekcode/disassembler/viewmodel/MainViewModel.kt` | 완료 |
4041
| 회귀 테스트 부재 | `ProjectManager`, 저장소 권한, Hex 레이아웃, import 파일명 테스트 추가 | 최소한의 유지보수 안전망을 확보했다 | `app/src/test/java/com/kyhsgeekcode/disassembler/ProjectManagerTest.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/PermissionUtilsTest.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/ui/components/HexViewLayoutTest.kt`, `app/src/test/java/com/kyhsgeekcode/disassembler/viewmodel/ImportedFileNameTest.kt` | 완료 |
@@ -59,6 +60,7 @@
5960
| large-file cache policy 테스트 | 통과 | 큰 파일은 메모리 캐시에 남기지 않는 규칙을 고정 |
6061
| string search accumulator 테스트 | 통과 | 문자열 결과 상한과 truncation 동작을 고정 |
6162
| binary detail preview 테스트 | 통과 | 긴 detail 문자열은 preview만 렌더링하고 notice를 붙이는 규칙을 고정 |
63+
| expanded file cache key 테스트 | 통과 | 같은 파일 내용은 같은 streaming hash를 만들고, 전체 `readBytes()` 없이 key를 계산하는 규칙을 고정 |
6264
| architecture mapping 테스트 | 통과 | `x86_64`, `PPC64`가 64-bit mode로 매핑되는 규칙을 고정 |
6365
| binary manual setup reload 테스트 | 통과 | override autosetup 변경 시 disassembly 재로드 필요 여부를 고정 |
6466
| project export archive 테스트 | 통과 | ZIP 엔트리가 절대경로를 포함하지 않고 `sourceFilePath`, `baseFolder/...`로 묶이는지 확인 |

0 commit comments

Comments
 (0)