diff --git a/app/src/main/java/com/dazuoye/filemanager/BaseActivity.kt b/app/src/main/java/com/dazuoye/filemanager/BaseActivity.kt index d38ec39..5443875 100644 --- a/app/src/main/java/com/dazuoye/filemanager/BaseActivity.kt +++ b/app/src/main/java/com/dazuoye/filemanager/BaseActivity.kt @@ -4,15 +4,28 @@ import androidx.appcompat.app.AppCompatActivity import com.dazuoye.filemanager.fileSystem.DeleteHelper.Companion.delete import java.io.File -open class BaseActivity: AppCompatActivity() { - +/** + * BaseActivity 继承自 AppCompatActivity,提供了一些基础功能。 + * 主要功能是在销毁 Activity 时清理缓存和释放系统资源。 + */ +open class BaseActivity : AppCompatActivity() { + /** + * 在 Activity 销毁时调用的方法。 + * 此方法用于删除缓存中的 "clipboard" 文件并触发垃圾回收。 + */ override fun onDestroy() { super.onDestroy() - val clipFile = File(this.cacheDir,"clipboard") - if (clipFile.exists()){ + + // 创建一个指向缓存目录中 "clipboard" 文件的引用 + val clipFile = File(this.cacheDir, "clipboard") + + // 如果 "clipboard" 文件存在,调用 delete 方法删除文件 + if (clipFile.exists()) { delete(clipFile.path) } + + // 显式调用垃圾回收器,提示系统进行内存回收 System.gc() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dazuoye/filemanager/SettingStorage.kt b/app/src/main/java/com/dazuoye/filemanager/SettingStorage.kt index f0bb594..af04337 100644 --- a/app/src/main/java/com/dazuoye/filemanager/SettingStorage.kt +++ b/app/src/main/java/com/dazuoye/filemanager/SettingStorage.kt @@ -13,26 +13,36 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +// 扩展 Context,定义一个 DataStore,用于存储应用设置 val Context.settingStore: DataStore by preferencesDataStore(name = "app_settings") +/** + * SettingStorage 类用于管理和访问应用的设置存储。 + * 它封装了 DataStore 访问逻辑,支持异步获取和设置偏好数据。 + */ class SettingStorage(private val context: Context) { val hideExtension = booleanPreferencesKey("hide_extension") val hideHiddenFile = booleanPreferencesKey("hide_hidden_file") + /** + * 获取给定偏好设置键的值。 + * @param key 偏好设置键 + * @return 偏好设置的值,如果未设置则返回 null + */ + fun get(key: Preferences.Key): T? = runBlocking { + // 使用 DataStore 获取偏好数据,并获取与给定键关联的值 + context.settingStore.data + .map { value -> value[key] } + .first() // 获取流的第一个值 + } - fun get(key: Preferences.Key): T? = - runBlocking { - context.settingStore.data - .map { value -> - value[key] - } - .first() - } - - fun set(key: Preferences.Key, value: T) = - CoroutineScope(Dispatchers.Default).launch { - context.settingStore.edit { - it[key] = value - } - } -} \ No newline at end of file + /** + * 设置给定偏好设置键的值。 + * @param key 偏好设置键 + * @param value 要存储的值 + */ + fun set(key: Preferences.Key, value: T) = CoroutineScope(Dispatchers.Default).launch { + // 使用 DataStore 更新偏好数据 + context.settingStore.edit { it[key] = value } + } +} diff --git a/app/src/main/java/com/dazuoye/filemanager/compose/PasteHelper.kt b/app/src/main/java/com/dazuoye/filemanager/compose/PasteHelper.kt index a7f2982..52f8c7d 100644 --- a/app/src/main/java/com/dazuoye/filemanager/compose/PasteHelper.kt +++ b/app/src/main/java/com/dazuoye/filemanager/compose/PasteHelper.kt @@ -5,34 +5,52 @@ import java.io.FileInputStream import java.io.FileOutputStream import java.nio.channels.FileChannel +/** + * PasteHelper 类提供了静态方法,用于复制文件和目录。 + */ class PasteHelper { companion object { + + /** + * 复制目录及其所有内容到目标目录。 + * @param sourceDir 源目录 + * @param destDir 目标目录 + * 如果目标目录不存在,则创建它。 + * 如果源目录不存在或参数不是目录,则抛出异常。 + */ fun copyDirectory(sourceDir: File, destDir: File) { - // creates the destination directory if it does not exist + // 如果目标目录不存在,则创建它 if (!destDir.exists()) { destDir.mkdirs() } - // throws exception if the source does not exist + // 如果源目录不存在,则抛出异常 require(sourceDir.exists()) { "sourceDir does not exist" } - // throws exception if the arguments are not directories + // 如果参数不是目录,则抛出异常 require(!(sourceDir.isFile || destDir.isFile)) { "Either sourceDir or destDir is not a directory" } + // 调用内部方法递归复制目录 copyDirectoryImpl(sourceDir, destDir) } + /** + * 实现目录复制的内部方法。 + * @param sourceDir 源目录 + * @param destDir 目标目录 + * 遍历源目录中的所有文件和子目录,并递归复制它们。 + */ private fun copyDirectoryImpl(sourceDir: File, destDir: File) { val items = sourceDir.listFiles() if (items != null && items.isNotEmpty()) { for (anItem: File in items) { if (anItem.isDirectory) { val newDir = File(destDir, anItem.name) - newDir.mkdir() - // copy the directory (recursive call) + newDir.mkdir() // 创建子目录 + // 递归复制目录 copyDirectory(anItem, newDir) } else { - // copy the file + // 复制单个文件 val destFile = File(destDir, anItem.name) copySingleFile(anItem, destFile) } @@ -40,21 +58,30 @@ class PasteHelper { } } + /** + * 复制单个文件到目标位置。 + * @param sourceFile 源文件 + * @param destFile 目标文件 + * 如果目标文件不存在,则创建它。 + */ private fun copySingleFile(sourceFile: File, destFile: File) { if (!destFile.exists()) { - destFile.createNewFile() + destFile.createNewFile() // 创建目标文件 } + var sourceChannel: FileChannel? = null var destChannel: FileChannel? = null try { + // 使用文件通道进行文件复制 sourceChannel = FileInputStream(sourceFile).channel destChannel = FileOutputStream(destFile).channel sourceChannel.transferTo(0, sourceChannel.size(), destChannel) } finally { + // 关闭文件通道,释放资源 sourceChannel?.close() destChannel?.close() } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dazuoye/filemanager/compose/RequirePermissionActivity.kt b/app/src/main/java/com/dazuoye/filemanager/compose/RequirePermissionActivity.kt index 84e70a3..144b504 100644 --- a/app/src/main/java/com/dazuoye/filemanager/compose/RequirePermissionActivity.kt +++ b/app/src/main/java/com/dazuoye/filemanager/compose/RequirePermissionActivity.kt @@ -36,84 +36,115 @@ import com.dazuoye.filemanager.fileSystem.byTypeFileLister.MusicLister import com.dazuoye.filemanager.fileSystem.byTypeFileLister.VideoLister import com.dazuoye.filemanager.main_page +/** + * RequirePermissionActivity 继承自 ComponentActivity,用于请求用户的存储权限。 + * 如果权限已经被授予,应用将导航到主页面并初始化系统。 + */ class RequirePermissionActivity : ComponentActivity() { + + /** + * 在 Activity 创建时调用的方法。 + * 该方法启用全屏模式,设置状态栏颜色,并构建一个界面来提示用户授予存储权限。 + */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - enableEdgeToEdge() + enableEdgeToEdge() // 启用全屏边到边显示 val activity = this - window.statusBarColor = getColor(R.color.WhiteSmoke) + window.statusBarColor = getColor(R.color.WhiteSmoke) // 设置状态栏颜色 + + // 使用 Jetpack Compose 构建 UI setContent { Column( modifier = Modifier - .statusBarsPadding() - .fillMaxHeight(0.9f) - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + .statusBarsPadding() // 为状态栏留出空间 + .fillMaxHeight(0.9f) // 设置列高度为 90% + .fillMaxWidth(), // 填满宽度 + horizontalAlignment = Alignment.CenterHorizontally, // 水平居中 + verticalArrangement = Arrangement.Center // 垂直居中 ) { + // 显示提示文本,根据系统版本显示不同的提示信息 Text( text = getString( if (VERSION.SDK_INT >= VERSION_CODES.R) { - R.string.require_manage_storage + R.string.require_manage_storage // Android R 及以上需要的权限 } else { - R.string.require_permission_readwrite + R.string.require_permission_readwrite // 较低版本需要的权限 } ), - modifier = Modifier.padding(vertical = 10.dp), - fontSize = 30.sp + modifier = Modifier.padding(vertical = 10.dp), // 设置垂直内边距 + fontSize = 30.sp // 设置字体大小 ) + + // 按钮,用于请求用户授予存储权限 Button( - onClick = { // Ask for permission + onClick = { // 按钮点击事件 if (VERSION.SDK_INT >= VERSION_CODES.R) { + // Android R 及以上版本 if (!Environment.isExternalStorageManager()) { val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) val uri = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null) intent.setData(uri) - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) // 清空栈顶 startActivity(intent) } - } else { // for legacy system - val permissions = - arrayOf(permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE) + } else { + // 针对较旧版本的系统请求权限 + val permissions = arrayOf( + permission.READ_EXTERNAL_STORAGE, + permission.WRITE_EXTERNAL_STORAGE + ) ActivityCompat.requestPermissions( activity, permissions, 100 ) } }, colors = ButtonColors( - containerColor = Color(0xFF039BE5), - contentColor = Color.White, - disabledContainerColor = Color.Gray, - disabledContentColor = Color.White + containerColor = Color(0xFF039BE5), // 按钮背景颜色 + contentColor = Color.White, // 按钮文本颜色 + disabledContainerColor = Color.Gray, // 按钮禁用状态背景颜色 + disabledContentColor = Color.White // 按钮禁用状态文本颜色 ) ) { + // 按钮文本 Text(text = getString(R.string.give_permission)) } } } } + /** + * 在 Activity 恢复时调用的方法。 + * 检查是否已经授予权限,如果是,则导航到主页面并初始化系统。 + */ override fun onResume() { super.onResume() if (checkPermissions(this)) { val intent = Intent(this, main_page::class.java) - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) // 清空栈顶 startActivity(intent) - initSystem() + initSystem() // 初始化系统 finish() } } } +/** + * 检查应用是否已获得所需的存储权限。 + * @param context 上下文,用于访问权限检查方法 + * @return Boolean 如果权限已被授予,返回 true;否则返回 false + */ fun checkPermissions(context: Context): Boolean { - // Check storage permission + // 对于 Android R 及以上版本,检查是否具有管理所有文件的权限 if (VERSION.SDK_INT >= VERSION_CODES.R) { - // Check manage storage on R+ if (!Environment.isExternalStorageManager()) { return false } } else { - val permissions = arrayOf(permission.READ_EXTERNAL_STORAGE, permission.WRITE_EXTERNAL_STORAGE) + // 对于较低版本,逐个检查读写权限 + val permissions = arrayOf( + permission.READ_EXTERNAL_STORAGE, + permission.WRITE_EXTERNAL_STORAGE + ) permissions.forEach { if (context.checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED) { return false @@ -123,9 +154,12 @@ fun checkPermissions(context: Context): Boolean { return true } +/** + * 初始化系统的资源列表,如图像、视频、音乐和文档。 + */ fun initSystem() { - ImageLister.instance.initialize() - VideoLister.instance.initialize() - MusicLister.instance.initialize() - DocumentLister.instance.initialize() + ImageLister.instance.initialize() // 初始化图像列表 + VideoLister.instance.initialize() // 初始化视频列表 + MusicLister.instance.initialize() // 初始化音乐列表 + DocumentLister.instance.initialize() // 初始化文档列表 } diff --git a/app/src/main/java/com/dazuoye/filemanager/compose/ui/SearchFileColumn.kt b/app/src/main/java/com/dazuoye/filemanager/compose/ui/SearchFileColumn.kt index 4380d5e..b2dff6a 100644 --- a/app/src/main/java/com/dazuoye/filemanager/compose/ui/SearchFileColumn.kt +++ b/app/src/main/java/com/dazuoye/filemanager/compose/ui/SearchFileColumn.kt @@ -4,33 +4,12 @@ import android.content.Context import android.content.Intent import android.widget.Toast import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AlertDialog.Builder -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.IconButton -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -52,20 +31,32 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.File +/** + * 用于在搜索结果中显示文件列表的类 + * @param context 上下文对象 + * @param searchTypeName 搜索类型名称,用于显示在UI中 + * @param searchRegex 搜索的正则表达式 + */ class SearchFileColumn( val context: Context, private val searchTypeName: String, private val searchRegex: Regex? ) { + // 保存搜索到的文件列表 private val fileList = mutableStateListOf() + // 搜索输入的文本状态 private val searchText = mutableStateOf("") + /** + * Composable函数,用于绘制搜索文件的主界面 + */ @Composable fun Draw() { var list by remember { mutableStateOf>(emptyList()) } var isOkay by remember { mutableStateOf(false) } var sortByTime by remember { mutableStateOf(true) } + // 当搜索结果或排序方式改变时,重新加载并排序文件列表 LaunchedEffect(isOkay, list, sortByTime) { isOkay = false fileList.clear() @@ -78,6 +69,7 @@ class SearchFileColumn( isOkay = true } + // 主界面布局,包含返回按钮、标题和排序按钮 Column( modifier = Modifier .fillMaxSize() @@ -91,6 +83,7 @@ class SearchFileColumn( .padding(horizontal = 10.dp), verticalAlignment = Alignment.CenterVertically ) { + // 返回按钮,点击时返回主页面 IconButton( onClick = { val intent = Intent( @@ -106,6 +99,7 @@ class SearchFileColumn( ) } + // 显示搜索标题或搜索结果 Text( text = if (list.isEmpty()) { context.getString(R.string.search_here, searchTypeName) @@ -122,6 +116,7 @@ class SearchFileColumn( Spacer(modifier = Modifier.weight(1f)) + // 排序按钮,切换按时间或大小排序 IconButton( onClick = { sortByTime = !sortByTime @@ -148,20 +143,23 @@ class SearchFileColumn( ), "sortMethod" ) } - } + + // 加载或显示搜索结果 if (!isOkay) { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { + // 显示“加载中”的文本 Text( text = context.getString(R.string.loading), fontSize = 34.sp ) } } else { + // 显示搜索结果的文件列表 DrawColumns( fileList, searchText = searchText.value, @@ -186,6 +184,7 @@ class SearchFileColumn( } } ) { + // 文件点击事件,打开对应的文件 val file = File(it) if (file.isFile) { val uri = FileProvider.getUriForFile( @@ -203,6 +202,13 @@ class SearchFileColumn( } } + /** + * Composable函数,用于绘制文件列表 + * @param fileList 文件列表 + * @param searchText 搜索输入文本 + * @param onSearch 搜索按钮点击事件 + * @param onItemClick 文件点击事件 + */ @Composable private fun DrawColumns( fileList: List, @@ -213,7 +219,7 @@ class SearchFileColumn( LazyColumn( modifier = Modifier.padding(vertical = 5.dp) ) { - // 最顶上那个 + // 搜索输入框 item { Row( modifier = Modifier @@ -223,6 +229,7 @@ class SearchFileColumn( ) { var searchInput by remember { mutableStateOf(searchText) } + // 搜索输入框 TextField( value = searchInput, maxLines = 1, @@ -238,6 +245,7 @@ class SearchFileColumn( ), textStyle = TextStyle(fontSize = 18.sp), trailingIcon = { + // 点击图标进行搜索 Image( ImageVector.vectorResource(R.drawable.ic_search), context.getString(R.string.search), @@ -245,6 +253,7 @@ class SearchFileColumn( .clickable { if (searchInput.isNotEmpty()) { if (searchInput == "." || searchInput == "..") { + // 输入非法时提示错误 Toast .makeText( context, @@ -269,7 +278,7 @@ class SearchFileColumn( } } - // 下面的内容 + // 显示每个文件的视图 items(fileList) { file -> FileSingleView( file, @@ -279,12 +288,16 @@ class SearchFileColumn( } } + /** + * Composable函数,用于绘制单个文件项 + * @param file 文件对象 + * @param onItemClick 文件点击事件 + */ @Composable private fun FileSingleView( file: WrappedFile, onItemClick: ((String) -> Unit)? = null ) { - Row( modifier = Modifier .fillMaxWidth() @@ -299,6 +312,7 @@ class SearchFileColumn( }, verticalAlignment = Alignment.CenterVertically, ) { + // 显示文件图标 Image( ImageVector.vectorResource( when (file.mime.split('/').first()) { @@ -317,12 +331,14 @@ class SearchFileColumn( .padding(horizontal = 15.dp) .fillMaxWidth(0.8f) ) { + // 文件名称 Text( text = file.name, fontSize = 24.sp, maxLines = 1, overflow = TextOverflow.Ellipsis ) + // 文件最后修改时间 Text( text = file.getModifiedTimeString(context), fontSize = 15.sp, @@ -333,6 +349,7 @@ class SearchFileColumn( Spacer(Modifier.weight(1f)) + // 显示文件信息的按钮 IconButton( onClick = { showFileInfoAlert(context, file.path) @@ -346,6 +363,11 @@ class SearchFileColumn( } } + /** + * 显示文件详细信息的弹窗 + * @param context 上下文对象 + * @param file 文件路径 + */ fun showFileInfoAlert(context: Context, file: String) { val f = File(file) if (!f.exists()) { @@ -369,4 +391,4 @@ class SearchFileColumn( } .show() } -} \ No newline at end of file +} diff --git a/app/src/main/rust/libfshelper/src/lib.rs b/app/src/main/rust/libfshelper/src/lib.rs index 3ba2d42..54f46c8 100644 --- a/app/src/main/rust/libfshelper/src/lib.rs +++ b/app/src/main/rust/libfshelper/src/lib.rs @@ -4,18 +4,30 @@ use jni::JNIEnv; use std::{fs, i64}; use walkdir::WalkDir; +/// 获取指定文件夹的总大小,并将其格式化为字符串后返回给 Java +/// +/// # 参数 +/// - `env`: JNI 环境对象,用于与 Java 交互 +/// - `_`: `JClass` 类型,表示调用此方法的 Java 类(未使用) +/// - `input`: Java 传递的字符串,代表文件夹路径 +/// +/// # 返回 +/// - `jstring`: 表示文件夹大小的格式化字符串,如果发生错误,返回错误信息 #[no_mangle] pub extern "system" fn Java_com_dazuoye_filemanager_utils_FSHelper_getFolderSizeNative<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, input: JString<'local>, ) -> jstring { + // 将 Java 字符串转换为 Rust 字符串 let dir: String = env .get_string(&input) .expect("failed to parse input") .into(); + // 检查目录是否存在 if !fs::exists(&dir).expect(format!("Cannot stat {dir}").as_str()) { + // 如果目录不存在,返回相应的错误信息 return env .new_string(format!("{} not exists!", dir)) .expect("Couldn't create java string!") @@ -24,25 +36,29 @@ pub extern "system" fn Java_com_dazuoye_filemanager_utils_FSHelper_getFolderSize // 从这里保证文件至少存在了 let mut size: u64 = 0; + // 使用 WalkDir 遍历目录中的所有文件和子目录 for entry in WalkDir::new(dir) { match entry { Ok(item) => { + // 尝试获取每个文件的元数据并累加其大小 match item.metadata() { Ok(metadata) => size += metadata.len(), Err(e) => { + // 处理元数据获取失败的情况 eprintln!("Error getting metadata for item: {e:?}"); continue; } } }, Err(e) => { + // 处理遍历目录时的错误 eprintln!("Error walking directory: {:?}", e); continue; } } } - + // 将计算出的文件夹大小格式化为字符串并返回给 Java let output = env .new_string(format_size(size)) .expect("Couldn't create java string!"); @@ -50,48 +66,70 @@ pub extern "system" fn Java_com_dazuoye_filemanager_utils_FSHelper_getFolderSize output.into_raw() } +/// 获取指定文件夹的总大小(以字节为单位),返回给 Java +/// +/// # 参数 +/// - `env`: JNI 环境对象,用于与 Java 交互 +/// - `_`: `JClass` 类型,表示调用此方法的 Java 类(未使用) +/// - `input`: Java 传递的字符串,代表文件夹路径 +/// +/// # 返回 +/// - `jlong`: 文件夹的总大小(以字节为单位),如果发生错误则返回 0 #[no_mangle] pub extern "system" fn Java_com_dazuoye_filemanager_utils_FSHelper_getFolderSizeBytesNative<'local>( mut env: JNIEnv<'local>, _: JClass<'local>, input: JString<'local>, ) -> jlong { + // 将 Java 字符串转换为 Rust 字符串 let dir: String = env .get_string(&input) .expect("failed to parse input") .into(); + // 检查目录是否存在 if !fs::exists(&dir).expect(format!("Cannot stat {}", dir).as_str()) { return 0; } // 从这里保证文件至少存在了 let mut size: u64 = 0; + // 使用 WalkDir 遍历目录中的所有文件和子目录 for entry in WalkDir::new(dir) { match entry { Ok(item) => { + // 尝试获取每个文件的元数据并累加其大小 match item.metadata() { Ok(metadata) => size += metadata.len(), Err(e) => { + // 处理元数据获取失败的情况 eprintln!("Error getting metadata for item: {:?}", e); continue; } } }, Err(e) => { + // 处理遍历目录时的错误 eprintln!("Error walking directory: {:?}", e); continue; } } } - + // 将文件夹大小转换为 `jlong`,如果溢出则返回 `i64::MAX` match i64::try_from(size) { Ok(compatible) => compatible, Err(_) => i64::MAX } } +/// 格式化文件大小为更易读的字符串格式 +/// +/// # 参数 +/// - `size`: 文件大小,以字节为单位 +/// +/// # 返回 +/// - `String`: 格式化后的文件大小字符串(带单位,如 "KB", "MB" 等) fn format_size(size: u64) -> String { // 定义单位 let units = ["B", "KB", "MB", "GB", "TB", "PB"]; @@ -106,4 +144,4 @@ fn format_size(size: u64) -> String { // 保留两位小数格式化输出 format!("{:.2} {}", size, units[unit_index]) -} \ No newline at end of file +}