-
Notifications
You must be signed in to change notification settings - Fork 0
삭제된 이미지 분석 요청 시 발생하는 병목 현상을 해결한다 #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,7 @@ import com.example.metasearch.core.data.api.repository.ImageAnalysisRepository | |||||||||||||||||||||||||||||||||||||
| import com.example.metasearch.core.data.api.repository.PersonRepository | ||||||||||||||||||||||||||||||||||||||
| import com.example.metasearch.core.datastore.api.datasource.PersonIndexDataSource | ||||||||||||||||||||||||||||||||||||||
| import com.example.metasearch.core.network.request.ChangeNameRequest | ||||||||||||||||||||||||||||||||||||||
| import com.example.metasearch.core.network.request.DeleteImageRequest | ||||||||||||||||||||||||||||||||||||||
| import com.example.metasearch.core.network.service.AIService | ||||||||||||||||||||||||||||||||||||||
| import com.example.metasearch.core.network.service.WebService | ||||||||||||||||||||||||||||||||||||||
| import com.example.metasearch.core.room.api.dao.AnalyzedImageDao | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -52,18 +53,24 @@ class ImageAnalysisRepositoryImpl @Inject constructor( | |||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| override suspend fun runFullAnalysis() = withContext(Dispatchers.IO) { | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "runFullAnalysis 함수 실행") | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "1. runFullAnalysis 함수 실행") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| val currentGalleryUris = galleryRepository.getAllGalleryImages() | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "2. 갤러리 이미지 로드 완료: ${currentGalleryUris.size}개") | ||||||||||||||||||||||||||||||||||||||
| val currentGalleryUrisString = currentGalleryUris.map { it.toString() } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| val alreadyAnalyzedPaths = analyzedImageDao.getAllAnalyzedPaths() | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "3. 기존 분석 경로 로드 완료: ${alreadyAnalyzedPaths.size}개") | ||||||||||||||||||||||||||||||||||||||
| val dbName = databaseNameRepository.getPersistentDeviceDatabaseName() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "5. 삭제 로직 시작") | ||||||||||||||||||||||||||||||||||||||
| deleteMissingImages(alreadyAnalyzedPaths, currentGalleryUrisString, dbName) | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "6. 삭제 로직 완료") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| val addUris = currentGalleryUris.filter { uri -> | ||||||||||||||||||||||||||||||||||||||
| uri.toString() !in alreadyAnalyzedPaths | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "7. 추가할 이미지 수: ${addUris.size}") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (addUris.isNotEmpty()) { | ||||||||||||||||||||||||||||||||||||||
| val allSuccessfulPaths = mutableListOf<String>() | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -78,27 +85,46 @@ class ImageAnalysisRepositoryImpl @Inject constructor( | |||||||||||||||||||||||||||||||||||||
| if (allSuccessfulPaths.isNotEmpty()) { | ||||||||||||||||||||||||||||||||||||||
| processAnalysisFinish(allSuccessfulPaths, dbName) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "8. 추가할 이미지가 없어 종료함") | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| syncMismatchedNames(dbName) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| private suspend fun deleteMissingImages(alreadyPaths: List<String>, currentPaths: List<String>, dbName: String) { | ||||||||||||||||||||||||||||||||||||||
| val deletePaths = alreadyPaths.filter { it !in currentPaths } | ||||||||||||||||||||||||||||||||||||||
| deletePaths.forEach { pathString -> | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "삭제 대상 개수: ${deletePaths.size}개") | ||||||||||||||||||||||||||||||||||||||
| deletePaths.forEachIndexed { index, pathString -> | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "이미지 삭제 중 (${index + 1}/${deletePaths.size}): $pathString") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| val uri = pathString.toUri() | ||||||||||||||||||||||||||||||||||||||
| val tempFile = uri.toFile(context) | ||||||||||||||||||||||||||||||||||||||
| val requestFile = tempFile.asRequestBody("image/*".toMediaTypeOrNull()) | ||||||||||||||||||||||||||||||||||||||
| val fileNamePart = MultipartBody.Part.createFormData("deleteImage", tempFile.name, requestFile) | ||||||||||||||||||||||||||||||||||||||
| val originalFileName = galleryRepository.getFileName(uri) | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "Web 서버로 보낼 파일명: $originalFileName") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| val dbNameBody = dbName.toRequestBody("text/plain".toMediaTypeOrNull()) | ||||||||||||||||||||||||||||||||||||||
| val fileName = pathString.substringAfterLast('/') | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "AI 서버로 보낼 파일명: $fileName") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| val finalFileName = if (originalFileName != null) { | ||||||||||||||||||||||||||||||||||||||
| "$originalFileName.jpg" | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| "$fileName.jpg" | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+105
to
+112
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix potential file extension duplication and incorrect extension assignment. The current logic has critical flaws:
🔎 Proposed fix to handle extensions properly- val finalFileName = if (originalFileName != null) {
- "$originalFileName.jpg"
- } else {
- "$fileName.jpg"
- }
+ val baseFileName = originalFileName ?: fileName
+ val finalFileName = if (baseFileName.contains('.')) {
+ baseFileName // Already has extension
+ } else {
+ "$baseFileName.jpg" // Add extension only if missing
+ }Or, if you need to preserve the exact original extension: - val finalFileName = if (originalFileName != null) {
- "$originalFileName.jpg"
- } else {
- "$fileName.jpg"
- }
+ val baseFileName = originalFileName ?: fileName
+ // Use the filename as-is if it has an extension, otherwise default to .jpg
+ val finalFileName = if (baseFileName.lastIndexOf('.') > 0) {
+ baseFileName
+ } else {
+ "$baseFileName.jpg"
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| val webSuccess = runCatching { webService.uploadWebDeleteImage(fileNamePart, dbName) }.isSuccess | ||||||||||||||||||||||||||||||||||||||
| val aiSuccess = runCatching { aiService.uploadDeleteImage(fileNamePart, dbNameBody) }.isSuccess | ||||||||||||||||||||||||||||||||||||||
| val webResponse = runCatching { webService.uploadWebDeleteImage(DeleteImageRequest(dbName, finalFileName)) } | ||||||||||||||||||||||||||||||||||||||
| webResponse.onFailure { | ||||||||||||||||||||||||||||||||||||||
| Log.e(tag, "Web 삭제 실패 원인: ${it.message}") | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (webSuccess && aiSuccess) analyzedImageDao.deletePath(pathString) | ||||||||||||||||||||||||||||||||||||||
| val aiPart = MultipartBody.Part.createFormData("deleteImage", fileName, "".toRequestBody()) | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+114
to
+119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Web and AI services receive different filenames. Line 114 sends Both services should receive the same filename to ensure consistent deletion across web and AI backends. 🔎 Proposed fix to use consistent filename val aiPart = MultipartBody.Part.createFormData("deleteImage", fileName, "".toRequestBody())
+ // Use finalFileName for consistency with web service
+ val aiPart = MultipartBody.Part.createFormData("deleteImage", finalFileName, "".toRequestBody())
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| val aiSuccess = runCatching { aiService.uploadDeleteImage(aiPart, dbName.toRequestBody()) }.isSuccess | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+114
to
121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find WebService file
echo "=== Finding WebService file ==="
fd -t f "WebService\.kt"
# Find AIService file
echo -e "\n=== Finding AIService file ==="
fd -t f "AIService\.kt"
# Find DeleteImageRequest
echo -e "\n=== Finding DeleteImageRequest ==="
fd -t f "\.kt" | xargs rg -l "DeleteImageRequest" | head -5Repository: komodgn/meta-android Length of output: 707 🏁 Script executed: #!/bin/bash
echo "=== WebService.kt content ==="
cat core/network/src/main/java/com/example/metasearch/core/network/service/WebService.kt
echo -e "\n\n=== AIService.kt content ==="
cat core/network/src/main/java/com/example/metasearch/core/network/service/AIService.kt
echo -e "\n\n=== DeleteImageRequest.kt content ==="
cat core/network/src/main/java/com/example/metasearch/core/network/service/DeleteImageRequest.ktRepository: komodgn/meta-android Length of output: 4898 🏁 Script executed: #!/bin/bash
echo "=== Finding DeleteImageRequest ==="
fd -t f "DeleteImageRequest\.kt"
echo -e "\n=== Searching for DeleteImageRequest definition ==="
rg "data class DeleteImageRequest|class DeleteImageRequest" -A 3Repository: komodgn/meta-android Length of output: 705 Fix API contract mismatch in AI service deletion call. The AI service's val aiPart = MultipartBody.Part.createFormData("deleteImage", fileName, "".toRequestBody())to: val aiPart = MultipartBody.Part.createFormData("filename", fileName, "".toRequestBody())The web service contract ( 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| tempFile.delete() | ||||||||||||||||||||||||||||||||||||||
| if (webResponse.isSuccess && aiSuccess) { | ||||||||||||||||||||||||||||||||||||||
| analyzedImageDao.deletePath(pathString) | ||||||||||||||||||||||||||||||||||||||
| Log.d(tag, "삭제 성공: $pathString") | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| Log.e(tag, "삭제 실패 (Web: $webResponse, AI: $aiSuccess): $pathString") | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.example.metasearch.core.network.request | ||
|
|
||
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class DeleteImageRequest( | ||
| val dbName: String, | ||
| val deleteImage: String, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,7 +36,7 @@ public interface AIService { | |
| @Multipart | ||
| @POST("android/upload_delete") | ||
| suspend fun uploadDeleteImage( | ||
| @Part("deleteImage") filename: MultipartBody.Part, | ||
| @Part filename: MultipartBody.Part, | ||
| @Part("dbName") dbName: RequestBody, | ||
| ): CommonResponse | ||
|
Comment on lines
36
to
41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Search for invocations of uploadDeleteImage
rg -nP --type=kotlin -C5 'uploadDeleteImage\s*\(' -g '!AIService.kt'Repository: komodgn/meta-android Length of output: 1896 🏁 Script executed: # Also check for DeleteImageRequest usage in the codebase
rg -nP --type=kotlin 'DeleteImageRequest' -C2Repository: komodgn/meta-android Length of output: 1997 🏁 Script executed: # Verify the WebService pattern mentioned in the review
rg -nP --type=kotlin -C5 'uploadWebDeleteImage'Repository: komodgn/meta-android Length of output: 2945 🏁 Script executed: # Check if there are other implementations or related methods
rg -nP --type=kotlin 'uploadDelete|deleteImage' -C2Repository: komodgn/meta-android Length of output: 3080 🏁 Script executed: # Check the exact WebService signature and any overloads
rg -nP --type=kotlin -A3 'fun uploadWebDeleteImage' core/network/src/main/java/com/example/metasearch/core/network/service/WebService.ktRepository: komodgn/meta-android Length of output: 167 🏁 Script executed: # Check if there might be an older version of uploadWebDeleteImage still present
rg -nP --type=kotlin 'uploadWebDeleteImage|uploadDeleteImage' core/network/src/main/java/com/example/metasearch/core/network/service/Repository: komodgn/meta-android Length of output: 312 🏁 Script executed: # Get more context from the repository to understand the invocation
cat -n core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/ImageAnalysisRepositoryImpl.kt | sed -n '95,115p'Repository: komodgn/meta-android Length of output: 1265 Critical: Incomplete refactoring across the entire call chain—WebService invocation is broken. The AIService incompleteness is real, but the larger issue is that the repository layer is invoking webService.uploadWebDeleteImage(fileNamePart, dbName)But To complete the refactoring, both services must be aligned and the repository must be updated:
The inconsistency across AIService, WebService, and the repository layer breaks the entire delete flow. 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the inconsistent log numbering sequence.
The debug logs skip from step "3" to step "5", missing step "4". This inconsistency could confuse developers tracing execution flow.
🔎 Proposed fix to correct the numbering
And update line 87:
} else { - Log.d(tag, "8. 추가할 이미지가 없어 종료함") + Log.d(tag, "7. 추가할 이미지가 없어 종료함") }🤖 Prompt for AI Agents