diff --git a/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt b/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt index 17220f2c..06ed319a 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt @@ -541,7 +541,11 @@ class ForeGroundService : Service() { url?.let { CoroutineScope(Dispatchers.IO).launch { try { - val youtubeDLDir = File(getExternalFilesDir(null), "completed_torrents") + val baseDir = File(getExternalFilesDir(null), "completed_torrents") + + // 2. youtube 전용 폴더 생성 + val youtubeDLDir = File(baseDir, "Youtube") + if (!youtubeDLDir.exists()) youtubeDLDir.mkdirs() val command = YoutubeDLRequest(url).apply { addOption("-q") // 로그 최소화 addOption("--no-warnings") // 경고 숨김 diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/CompletedFilesFragment.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/CompletedFilesFragment.kt index f4dab324..02d8ae73 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/CompletedFilesFragment.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/CompletedFilesFragment.kt @@ -232,7 +232,11 @@ class CompletedFilesFragment : Fragment() { loadFiles() } .setNeutralButton("폴더 전체 삭제") { _, _ -> - // 💡 여기서도 다이얼로그 안에서 다이얼로그를 부릅니다! (안전한 패턴) + if (isProtectedFile(folder)) { + Toast.makeText(context, "Images/Videos 폴더는 전체 삭제할 수 없습니다.", Toast.LENGTH_SHORT).show() + return@setNeutralButton + } + android.app.AlertDialog.Builder(requireContext()) .setMessage("정말로 폴더와 내부의 모든 파일을 삭제하시겠습니까?") .setPositiveButton("예") { _, _ -> @@ -468,13 +472,32 @@ class CompletedFilesFragment : Fragment() { view.findViewById(R.id.btnRenameSelected)?.setOnClickListener { showBatchRenameDialog() } view.findViewById(R.id.btnDeleteSelected)?.setOnClickListener { if (selectedFiles.isEmpty()) return@setOnClickListener + + // 💡 보호 대상 필터링 + val protectedCount = selectedFiles.count { isProtectedFile(it) } + val filesToDelete = selectedFiles.filter { !isProtectedFile(it) } + + if (protectedCount > 0 && filesToDelete.isEmpty()) { + Toast.makeText(context, "Images 및 Videos 폴더 내 항목은 개별 삭제만 가능합니다.", Toast.LENGTH_LONG).show() + return@setOnClickListener + } + + val message = if (protectedCount > 0) { + "보호된 항목 ${protectedCount}개를 제외하고 나머지 ${filesToDelete.size}개 항목을 삭제하시겠습니까?" + } else { + "선택한 ${filesToDelete.size}개 항목을 삭제하시겠습니까?" + } + android.app.AlertDialog.Builder(requireContext()) - .setMessage("선택한 ${selectedFiles.size}개 항목을 삭제하시겠습니까?") + .setMessage(message) .setPositiveButton("삭제") { _, _ -> var delCount = 0 - selectedFiles.forEach { file -> - if (file.isDirectory) { file.deleteRecursively(); delCount++ } - else if (file.delete()) { delCount++ } + filesToDelete.forEach { file -> + if (file.isDirectory) { + if (file.deleteRecursively()) delCount++ + } else if (file.delete()) { + delCount++ + } } Toast.makeText(context, "${delCount}개 항목 삭제됨", Toast.LENGTH_SHORT).show() toggleSelectionMode(false) @@ -759,9 +782,12 @@ class CompletedFilesFragment : Fragment() { // walkBottomUp()을 사용하면 가장 깊은 하위 폴더부터 위로 올라오면서 검사합니다. rootDir.walkBottomUp().forEach { dir -> - // 최상위 루트 폴더 자체는 지우면 안 되므로 통과 if (dir != rootDir && dir.isDirectory) { - // 폴더 내부에 아무것도 없다면 삭제 + // 💡 Images와 Videos 폴더는 비어있어도 삭제 대상에서 제외 + if (dir.parentFile == rootDir && (dir.name == "Images" || dir.name == "Videos")) { + return@forEach + } + if (dir.listFiles()?.isEmpty() == true) { if (dir.delete()) { deletedFolderCount++ @@ -807,6 +833,22 @@ class CompletedFilesFragment : Fragment() { if (!hidden) loadFiles() } + private fun isProtectedFile(file: File): Boolean { + // 1. 루트의 Images, Videos 폴더 자체 보호 + val protectedFolders = setOf("Images", "Videos","Youtube") + if (file.isDirectory && file.parentFile == rootDir && protectedFolders.contains(file.name)) { + return true + } + + // 2. Images나 Videos 폴더 안에 들어있는 파일/폴더들 보호 + val parent = file.parentFile + if (parent != null && parent.parentFile == rootDir && protectedFolders.contains(parent.name)) { + return true + } + + return false + } + private fun applyFilterAndSort() { // 💡 1. 데이터 소스 결정 val baseFiles = if (searchQuery.isNotEmpty()) {