From 8a698ddf0f28cabdbccabba9ca6eaa124927f9ef Mon Sep 17 00:00:00 2001 From: Bob Yue <85574806+BobYue-01@users.noreply.github.com> Date: Sun, 29 Mar 2026 23:47:21 +0800 Subject: [PATCH] fix(app_low): backup targeted photos before delete test Limit deletion scope to test photos only and require backup first. - Replace recursive direct delete flow with backup-then-delete processing in `MainActivity`. - Only process files with prefix `fdtest_` and image extensions `jpg/jpeg/png`; skip all other media. - Add backup directory creation with fallback path and duplicate filename handling to avoid overwrite. - Update UI text to clearly warn users that only prefixed test photos are affected and backups are saved to `Pictures/FileDeleterBackup`. --- .../com/epcdiy/filedeleter/MainActivity.java | 99 +++++++++++++++---- .../src/main/res/layout/activity_main.xml | 12 ++- 2 files changed, 93 insertions(+), 18 deletions(-) diff --git a/fileDeleter/app_low/src/main/java/com/epcdiy/filedeleter/MainActivity.java b/fileDeleter/app_low/src/main/java/com/epcdiy/filedeleter/MainActivity.java index a264318..c49c06e 100644 --- a/fileDeleter/app_low/src/main/java/com/epcdiy/filedeleter/MainActivity.java +++ b/fileDeleter/app_low/src/main/java/com/epcdiy/filedeleter/MainActivity.java @@ -18,9 +18,15 @@ import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; public class MainActivity extends AppCompatActivity { + private static final String TARGET_PREFIX = "fdtest_"; + private static final String BACKUP_FOLDER_NAME = "FileDeleterBackup"; private TextView tvStatus; private StringBuilder logBuilder = new StringBuilder(); private static final int REQUEST_CODE = 100; @@ -53,7 +59,7 @@ protected void onCreate(Bundle savedInstanceState) { startBypassDeletion(); } }); - updateUI("警告!使用前务必备份照片!"); + updateUI("提示:仅处理文件名前缀为 " + TARGET_PREFIX + " 的照片(jpg/jpeg/png),且先备份后删除。备份目录:Pictures/" + BACKUP_FOLDER_NAME); } // 1. 检查权限 (Android 11+) @@ -87,43 +93,102 @@ private void startBypassDeletion() { updateUI("未找到 DCIM 目录"); return; } + File backupDir = prepareBackupDir(); + if (backupDir == null) { + updateUI("备份目录创建失败,已取消处理"); + return; + } new Thread(() -> { - traverseAndDelete(dcimDir); - runOnUiThread(() -> Toast.makeText(this, "删除模拟完成", Toast.LENGTH_SHORT).show()); + traverseAndBackupDelete(dcimDir, backupDir); + runOnUiThread(() -> Toast.makeText(this, "处理完成(仅特定命名照片)", Toast.LENGTH_SHORT).show()); }).start(); } - // 4. 递归枚举并使用底层接口删除 - private void traverseAndDelete(File dir) { + private void traverseAndBackupDelete(File dir, File backupDir) { File[] files = dir.listFiles(); if (files == null) return; for (File file : files) { if (file.isDirectory()) { - traverseAndDelete(file); // 递归进入子文件夹 + traverseAndBackupDelete(file, backupDir); } else { String fileName = file.getName(); - // 仅模拟删除图片和视频 - if (isMediaFile(fileName)) { - // 核心操作:直接调用 java.io.File.delete() - // 这不会触发 MediaStore 的数据库更新 - boolean deleted = file.delete(); - - String msg = (deleted ? "[已抹除] " : "[失败] ") + file.getAbsolutePath(); + if (isTargetPhoto(fileName)) { + boolean backedUp = backupFile(file, backupDir); + boolean deleted = backedUp && file.delete(); + String msg; + if (!backedUp) { + msg = "[跳过-备份失败] " + file.getAbsolutePath(); + } else { + msg = (deleted ? "[已备份并删除] " : "[删除失败] ") + file.getAbsolutePath(); + } updateUI(msg); } } } } - private boolean isMediaFile(String name) { + private boolean isTargetPhoto(String name) { + if (name == null) return false; String n = name.toLowerCase(); - if(n.contains("screenrecorder")) - { + return n.startsWith(TARGET_PREFIX) + && (n.endsWith(".jpg") || n.endsWith(".jpeg") || n.endsWith(".png")); + } + + private boolean backupFile(File source, File backupDir) { + File outFile = buildUniqueBackupFile(backupDir, source.getName()); + try (InputStream inputStream = new FileInputStream(source); + OutputStream outputStream = new FileOutputStream(outFile)) { + copyStream(inputStream, outputStream); + return true; + } catch (Exception e) { return false; } - return n.endsWith(".jpg") || n.endsWith(".jpeg") || n.endsWith(".png") || n.endsWith(".mp4"); + } + + private File prepareBackupDir() { + File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); + File backupDir = new File(picturesDir, BACKUP_FOLDER_NAME); + if (backupDir.exists() || backupDir.mkdirs()) { + return backupDir; + } + + File appExternal = getExternalFilesDir(Environment.DIRECTORY_PICTURES); + if (appExternal == null) { + return null; + } + File fallbackDir = new File(appExternal, BACKUP_FOLDER_NAME); + if (fallbackDir.exists() || fallbackDir.mkdirs()) { + return fallbackDir; + } + return null; + } + + private File buildUniqueBackupFile(File backupDir, String fileName) { + File target = new File(backupDir, fileName); + if (!target.exists()) { + return target; + } + + int dot = fileName.lastIndexOf('.'); + String baseName = dot > 0 ? fileName.substring(0, dot) : fileName; + String extension = dot > 0 ? fileName.substring(dot) : ""; + int index = 1; + while (target.exists()) { + target = new File(backupDir, baseName + "_" + index + extension); + index++; + } + return target; + } + + private void copyStream(InputStream inputStream, OutputStream outputStream) throws Exception { + byte[] buffer = new byte[8192]; + int len; + while ((len = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + } + outputStream.flush(); } private void updateUI(String msg) { diff --git a/fileDeleter/app_low/src/main/res/layout/activity_main.xml b/fileDeleter/app_low/src/main/res/layout/activity_main.xml index 5215f10..5f216e7 100644 --- a/fileDeleter/app_low/src/main/res/layout/activity_main.xml +++ b/fileDeleter/app_low/src/main/res/layout/activity_main.xml @@ -17,7 +17,17 @@ style="@android:style/Widget.Button" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="开始删除照片" /> + android:text="开始备份后删除测试" /> + +