package com.sw.inbound.utils import android.content.Context import timber.log.Timber import java.io.File /** * 模型资源管理器 * 负责从接口下载模型资源包(Zip),并解压到应用缓存目录 * 替代原有的 assets 中写死的 data 文件夹和 .pt 文件 */ object ModelResourceManager { /** * 资源下载和解压的回调接口 */ interface ResourceCallback { /** * 下载进度回调 * @param progress 进度百分比 (0-100) * @param message 进度信息 */ fun onDownloadProgress(progress: Int, message: String) /** * 解压进度回调 * @param progress 进度百分比 (0-100) * @param message 进度信息 */ fun onExtractProgress(progress: Int, message: String) /** * 完成回调 * @param resourceDir 资源所在目录 */ fun onSuccess(resourceDir: File) /** * 失败回调 * @param errorMessage 错误信息 */ fun onFailure(errorMessage: String) } /** * 下载并解压模型资源 * @param context 应用上下文 * @param downloadUrl 资源包下载链接 * @param callback 进度和结果回调 */ fun downloadAndExtractResources( context: Context, downloadUrl: String, callback: ResourceCallback ) { try { // 获取缓存目录 val cacheDir = context.cacheDir val resourcesDir = File(cacheDir, "model_resources") val zipFile = File(cacheDir, "model_resources.zip") Timber.d("资源缓存目录: ${resourcesDir.absolutePath}") Timber.d("Zip 文件路径: ${zipFile.absolutePath}") // Step 1: 下载 Zip 文件 Timber.d("开始下载资源包: $downloadUrl") ZipDownloadUtils.downloadZip( downloadUrl = downloadUrl, targetFile = zipFile, listener = object : ZipDownloadUtils.DownloadProgressListener { override fun onProgress(currentSize: Long, totalSize: Long, progress: Int) { val message = "下载中... ${formatFileSize(currentSize)}/${formatFileSize(totalSize)}" callback.onDownloadProgress(progress, message) } override fun onSuccess(file: File) { Timber.d("资源包下载完成") callback.onDownloadProgress(100, "下载完成") // Step 2: 解压 Zip 文件 extractResources(resourcesDir, zipFile, callback) } override fun onFailure(exception: Exception) { val errorMsg = "资源包下载失败: ${exception.message}" Timber.e(exception, errorMsg) callback.onFailure(errorMsg) } } ) } catch (e: Exception) { val errorMsg = "资源下载和解压过程异常: ${e.message}" Timber.e(e, errorMsg) callback.onFailure(errorMsg) } } /** * 解压资源包 */ private fun extractResources( resourcesDir: File, zipFile: File, callback: ResourceCallback ) { try { Timber.d("开始解压资源包") ZipExtractUtils.extractZip( zipFile = zipFile, targetDir = resourcesDir, listener = object : ZipExtractUtils.ExtractProgressListener { override fun onProgress( currentFile: String, currentIndex: Int, totalFiles: Int, progress: Int ) { val message = "解压中... ($currentIndex/$totalFiles) $currentFile" callback.onExtractProgress(progress, message) } override fun onSuccess(targetDir: File) { Timber.d("资源包解压完成: ${targetDir.absolutePath}") callback.onExtractProgress(100, "解压完成") // 清理 Zip 文件 zipFile.delete() Timber.d("临时 Zip 文件已删除") callback.onSuccess(targetDir) } override fun onFailure(exception: Exception) { val errorMsg = "资源包解压失败: ${exception.message}" Timber.e(exception, errorMsg) callback.onFailure(errorMsg) } } ) } catch (e: Exception) { val errorMsg = "解压过程异常: ${e.message}" Timber.e(e, errorMsg) callback.onFailure(errorMsg) } } /** * 获取已缓存的资源目录 * @param context 应用上下文 * @return 资源目录,如果不存在则返回 null */ fun getCachedResourcesDir(context: Context): File? { val resourcesDir = File(context.cacheDir, "model_resources") return if (resourcesDir.exists() && resourcesDir.isDirectory) { resourcesDir } else { null } } /** * 清理缓存的资源 * @param context 应用上下文 * @return 是否清理成功 */ fun clearCachedResources(context: Context): Boolean { return try { val resourcesDir = File(context.cacheDir, "model_resources") val zipFile = File(context.cacheDir, "model_resources.zip") val dirDeleted = resourcesDir.deleteRecursively() val zipDeleted = zipFile.delete() Timber.d("清理缓存资源: 目录=$dirDeleted, Zip=$zipDeleted") dirDeleted || zipDeleted } catch (e: Exception) { Timber.e(e, "清理缓存资源失败") false } } /** * 格式化文件大小为可读字符串 */ private fun formatFileSize(bytes: Long): String { return when { bytes <= 0 -> "0 B" bytes < 1024 -> "$bytes B" bytes < 1024 * 1024 -> String.format("%.2f KB", bytes / 1024.0) bytes < 1024 * 1024 * 1024 -> String.format("%.2f MB", bytes / (1024.0 * 1024)) else -> String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024)) } } }