Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}")
Comment on lines +56 to +73
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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
-        Log.d(tag, "5. 삭제 로직 시작")
+        Log.d(tag, "4. 삭제 로직 시작")
         deleteMissingImages(alreadyAnalyzedPaths, currentGalleryUrisString, dbName)
-        Log.d(tag, "6. 삭제 로직 완료")
+        Log.d(tag, "5. 삭제 로직 완료")
 
         val addUris = currentGalleryUris.filter { uri ->
             uri.toString() !in alreadyAnalyzedPaths
         }
-        Log.d(tag, "7. 추가할 이미지 수: ${addUris.size}")
+        Log.d(tag, "6. 추가할 이미지 수: ${addUris.size}")

And update line 87:

         } else {
-            Log.d(tag, "8. 추가할 이미지가 없어 종료함")
+            Log.d(tag, "7. 추가할 이미지가 없어 종료함")
         }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/ImageAnalysisRepositoryImpl.kt
around lines 54 to 71, the debug log numbering skips "4" (it goes 1,2,3,5...).
Rename the log strings so the sequence is consecutive: change "5. 삭제 로직 시작" →
"4. 삭제 로직 시작", "6. 삭제 로직 완료" → "5. 삭제 로직 완료", and "7. 추가할 이미지 수" → "6. 추가할 이미지
수"; also scan nearby logs (e.g., around line 87) and adjust any further step
numbers to maintain a consistent incremental sequence.


if (addUris.isNotEmpty()) {
val allSuccessfulPaths = mutableListOf<String>()
Expand All @@ -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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix potential file extension duplication and incorrect extension assignment.

The current logic has critical flaws:

  1. Extension duplication: If originalFileName or the derived fileName already contains an extension (e.g., "photo.jpg"), appending ".jpg" results in "photo.jpg.jpg".
  2. Incorrect extension: Non-JPEG images (PNG, WEBP, etc.) will be incorrectly labeled with a ".jpg" extension, potentially causing server-side lookup failures.
🔎 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val fileName = pathString.substringAfterLast('/')
Log.d(tag, "AI 서버로 보낼 파일명: $fileName")
val finalFileName = if (originalFileName != null) {
"$originalFileName.jpg"
} else {
"$fileName.jpg"
}
val fileName = pathString.substringAfterLast('/')
Log.d(tag, "AI 서버로 보낼 파일명: $fileName")
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"
}
🤖 Prompt for AI Agents
In
core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/ImageAnalysisRepositoryImpl.kt
around lines 105–112, the code naively appends ".jpg" causing duplicate
extensions or incorrect labeling for non-JPEGs; instead, extract the filename
base and its existing extension (prefer originalFileName's extension, else
derive from pathString), normalize the extension (lowercase, ensure it starts
with a '.'), and if no extension is found fall back to ".jpg"; then build
finalFileName by combining the base name (without any extension) plus the chosen
extension so you never produce "photo.jpg.jpg" or mislabel PNG/WEBP files.


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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Web and AI services receive different filenames.

Line 114 sends finalFileName (which includes the .jpg extension) to the web service, while Line 119 sends fileName (path-derived name without the added extension) to the AI service. This inconsistency will likely cause deletion to fail on one service while succeeding on the other, leading to data synchronization issues.

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())

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/ImageAnalysisRepositoryImpl.kt
around lines 114 to 119, the web service call uses finalFileName (with the .jpg
extension) while the AI multipart part uses fileName (path-derived, without the
appended extension), causing inconsistent deletion; update the multipart
creation to use finalFileName for the form-data filename so both web and AI
services receive the identical filename (keep the rest of the
MultipartBody.Part.createFormData call and request body unchanged).

val aiSuccess = runCatching { aiService.uploadDeleteImage(aiPart, dbName.toRequestBody()) }.isSuccess

Comment on lines +114 to 121
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 -5

Repository: 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.kt

Repository: 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 3

Repository: komodgn/meta-android

Length of output: 705


Fix API contract mismatch in AI service deletion call.

The AI service's uploadDeleteImage method expects a multipart part named "filename", but the code creates one named "deleteImage". Change:

val aiPart = MultipartBody.Part.createFormData("deleteImage", fileName, "".toRequestBody())

to:

val aiPart = MultipartBody.Part.createFormData("filename", fileName, "".toRequestBody())

The web service contract (DeleteImageRequest with dbName and deleteImage fields) correctly matches its usage.

🤖 Prompt for AI Agents
In
core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/ImageAnalysisRepositoryImpl.kt
around lines 114-121, the multipart part name for the AI service delete call is
incorrect ("deleteImage"); change the multipart part name to "filename" so it
matches the AI service contract (create the MultipartBody.Part with name
"filename" using the existing fileName and empty request body) and keep the web
service call as-is.

tempFile.delete()
if (webResponse.isSuccess && aiSuccess) {
analyzedImageDao.deletePath(pathString)
Log.d(tag, "삭제 성공: $pathString")
} else {
Log.e(tag, "삭제 실패 (Web: $webResponse, AI: $aiSuccess): $pathString")
}
}
}

Expand Down
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
Expand Up @@ -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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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' -C2

Repository: 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' -C2

Repository: 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.kt

Repository: 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 with the old signature. Line 105 in ImageAnalysisRepositoryImpl.kt calls:

webService.uploadWebDeleteImage(fileNamePart, dbName)

But WebService.uploadWebDeleteImage expects a single @Body DeleteImageRequest parameter (lines 70–72). This signature mismatch would cause a runtime/compilation failure.

To complete the refactoring, both services must be aligned and the repository must be updated:

  1. AIService: Remove @Multipart and update to accept @Body request: DeleteImageRequest (matching WebService)
  2. Repository: Create DeleteImageRequest instance and pass it correctly to both services

The inconsistency across AIService, WebService, and the repository layer breaks the entire delete flow.

🤖 Prompt for AI Agents
In
core/network/src/main/java/com/example/metasearch/core/network/service/AIService.kt
around lines 36 to 41, the uploadDeleteImage method remains multipart but the
WebService and repository were refactored to expect a single DeleteImageRequest
body; update AIService to remove @Multipart and change the signature to suspend
fun uploadDeleteImage(@Body request: DeleteImageRequest): CommonResponse so it
matches WebService, then update the repository call (ImageAnalysisRepositoryImpl
line ~105) to construct a DeleteImageRequest instance and pass that single
request object to both webService.uploadWebDeleteImage(...) and
aiService.uploadDeleteImage(...), ensuring parameter types and imports are
consistent.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.example.metasearch.core.network.service

import com.example.metasearch.core.network.request.ChangeNameRequest
import com.example.metasearch.core.network.request.DeleteEntityRequest
import com.example.metasearch.core.network.request.DeleteImageRequest
import com.example.metasearch.core.network.request.DetectedObjectsRequest
import com.example.metasearch.core.network.request.NLQueryRequest
import com.example.metasearch.core.network.request.PersonFrequencyRequest
Expand Down Expand Up @@ -65,10 +66,8 @@ public interface WebService {
@Query("dbName") dbName: String,
)

@Multipart
@POST("android/deleteimg")
@POST("deleteImage/")
suspend fun uploadWebDeleteImage(
@Part filename: MultipartBody.Part,
@Part("dbName") dbName: String,
@Body request: DeleteImageRequest,
)
}