| | |
| | | |
| | | import android.content.Context; |
| | | import android.os.storage.StorageManager; |
| | | import android.os.StatFs; |
| | | |
| | | import java.io.File; |
| | | import java.lang.reflect.Array; |
| | | import java.lang.reflect.InvocationTargetException; |
| | | import java.lang.reflect.Method; |
| | | import java.text.ParseException; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Comparator; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | |
| | | import timber.log.Timber; |
| | | |
| | | public class FileUtil { |
| | | //获取插入的TFCard目录路径 |
| | |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * 清理 TF 卡上的 h264 文件 |
| | | * 当 h264 文件总大小超过指定阈值或 TF 卡剩余空间小于指定值时,删除日期最早的文件夹 |
| | | * |
| | | * @param context Context 对象,用于获取 TF 卡路径和剩余空间 |
| | | * @param h264RootDir h264 根目录路径,例如 "/sdcard/h264" |
| | | * @param maxTotalSizeGB 最大总大小(GB),默认 5GB |
| | | * @param minFreeSpaceGB 最小剩余空间(GB),默认 1GB |
| | | */ |
| | | public static void cleanupH264Files(Context context, String h264RootDir, long maxTotalSizeGB, long minFreeSpaceGB) { |
| | | if (context == null || h264RootDir == null || h264RootDir.trim().isEmpty()) { |
| | | Timber.w("Context or h264 root directory is null, skip cleanup"); |
| | | return; |
| | | } |
| | | |
| | | File h264Root = new File(h264RootDir); |
| | | if (!h264Root.exists() || !h264Root.isDirectory()) { |
| | | Timber.d("H264 root directory does not exist: %s, skip cleanup", h264RootDir); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 获取 TF 卡路径 |
| | | String tfCardPath = getStoragePath(context, true); |
| | | if (tfCardPath == null || tfCardPath.trim().isEmpty()) { |
| | | Timber.w("TF card path not available, skip cleanup"); |
| | | return; |
| | | } |
| | | |
| | | // 获取 TF 卡剩余空间(GB) |
| | | long freeSpaceGB = getFreeSpaceGB(tfCardPath); |
| | | Timber.d("TF card free space: %d GB", freeSpaceGB); |
| | | |
| | | // 扫描所有日期文件夹 |
| | | File[] dateDirs = h264Root.listFiles(File::isDirectory); |
| | | if (dateDirs == null || dateDirs.length == 0) { |
| | | Timber.d("No date directories found in h264 root: %s", h264RootDir); |
| | | return; |
| | | } |
| | | |
| | | // 按日期排序(最早的在前) |
| | | List<DateDirInfo> dateDirList = new ArrayList<>(); |
| | | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.CHINA); |
| | | |
| | | for (File dateDir : dateDirs) { |
| | | String dirName = dateDir.getName(); |
| | | // 只处理符合日期格式的文件夹(yyyyMMdd) |
| | | if (dirName.length() == 8 && dirName.matches("\\d{8}")) { |
| | | try { |
| | | Date date = dateFormat.parse(dirName); |
| | | long totalSize = calculateH264FilesSize(dateDir); |
| | | dateDirList.add(new DateDirInfo(dateDir, date, totalSize)); |
| | | } catch (ParseException e) { |
| | | Timber.w("Invalid date directory name: %s", dirName); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 按日期排序(最早的在前) |
| | | Collections.sort(dateDirList, new Comparator<DateDirInfo>() { |
| | | @Override |
| | | public int compare(DateDirInfo o1, DateDirInfo o2) { |
| | | return o1.date.compareTo(o2.date); |
| | | } |
| | | }); |
| | | |
| | | // 计算所有 h264 文件的总大小(GB) |
| | | long totalSizeGB = 0; |
| | | for (DateDirInfo info : dateDirList) { |
| | | totalSizeGB += info.totalSize / (1024L * 1024L * 1024L); |
| | | } |
| | | Timber.d("Total h264 files size: %d GB, Max allowed: %d GB", totalSizeGB, maxTotalSizeGB); |
| | | Timber.d("TF card free space: %d GB, Min required: %d GB", freeSpaceGB, minFreeSpaceGB); |
| | | |
| | | // 检查是否需要清理 |
| | | boolean needCleanup = (totalSizeGB > maxTotalSizeGB) || (freeSpaceGB < minFreeSpaceGB); |
| | | |
| | | if (!needCleanup) { |
| | | Timber.d("No cleanup needed"); |
| | | return; |
| | | } |
| | | |
| | | // 删除最早的日期文件夹,直到满足条件 |
| | | int deletedCount = 0; |
| | | while (!dateDirList.isEmpty() && ((totalSizeGB > maxTotalSizeGB) || (freeSpaceGB < minFreeSpaceGB))) { |
| | | DateDirInfo oldestDir = dateDirList.remove(0); |
| | | |
| | | Timber.d("Deleting oldest date directory: %s (size: %d GB, date: %s)", |
| | | oldestDir.dir.getName(), oldestDir.totalSize / (1024L * 1024L * 1024L), |
| | | dateFormat.format(oldestDir.date)); |
| | | |
| | | // 删除文件夹及其所有内容 |
| | | if (deleteDirectory(oldestDir.dir)) { |
| | | deletedCount++; |
| | | totalSizeGB -= oldestDir.totalSize / (1024L * 1024L * 1024L); |
| | | |
| | | // 重新获取剩余空间 |
| | | freeSpaceGB = getFreeSpaceGB(tfCardPath); |
| | | Timber.d("After deletion - Total size: %d GB, Free space: %d GB", |
| | | totalSizeGB, freeSpaceGB); |
| | | } else { |
| | | Timber.e("Failed to delete directory: %s", oldestDir.dir.getAbsolutePath()); |
| | | break; // 删除失败,停止清理 |
| | | } |
| | | } |
| | | |
| | | Timber.i("Cleanup completed. Deleted %d date directories. Final total size: %d GB, Free space: %d GB", |
| | | deletedCount, totalSizeGB, freeSpaceGB); |
| | | |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Error during h264 files cleanup"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 清理 TF 卡上的 h264 文件(使用默认参数:最大5GB,最小剩余空间1GB) |
| | | */ |
| | | public static void cleanupH264Files(Context context, String h264RootDir) { |
| | | cleanupH264Files(context, h264RootDir, 5, 1); |
| | | } |
| | | |
| | | /** |
| | | * 计算目录下所有 .h264 文件的总大小(字节) |
| | | */ |
| | | private static long calculateH264FilesSize(File dir) { |
| | | long totalSize = 0; |
| | | File[] files = dir.listFiles(); |
| | | if (files != null) { |
| | | for (File file : files) { |
| | | if (file.isFile() && file.getName().toLowerCase().endsWith(".h264")) { |
| | | totalSize += file.length(); |
| | | } |
| | | } |
| | | } |
| | | return totalSize; |
| | | } |
| | | |
| | | /** |
| | | * 获取指定路径的剩余空间(GB) |
| | | */ |
| | | private static long getFreeSpaceGB(String path) { |
| | | try { |
| | | StatFs statFs = new StatFs(path); |
| | | long blockSize = statFs.getBlockSizeLong(); |
| | | long availableBlocks = statFs.getAvailableBlocksLong(); |
| | | long freeBytes = availableBlocks * blockSize; |
| | | return freeBytes / (1024L * 1024L * 1024L); // 转换为 GB |
| | | } catch (Exception e) { |
| | | Timber.e(e, "Error getting free space for path: %s", path); |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 递归删除目录及其所有内容 |
| | | */ |
| | | private static boolean deleteDirectory(File dir) { |
| | | if (dir == null || !dir.exists()) { |
| | | return true; |
| | | } |
| | | |
| | | if (dir.isDirectory()) { |
| | | File[] children = dir.listFiles(); |
| | | if (children != null) { |
| | | for (File child : children) { |
| | | if (!deleteDirectory(child)) { |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return dir.delete(); |
| | | } |
| | | |
| | | /** |
| | | * 日期文件夹信息 |
| | | */ |
| | | private static class DateDirInfo { |
| | | File dir; |
| | | Date date; |
| | | long totalSize; // 字节 |
| | | |
| | | DateDirInfo(File dir, Date date, long totalSize) { |
| | | this.dir = dir; |
| | | this.date = date; |
| | | this.totalSize = totalSize; |
| | | } |
| | | } |
| | | } |