flutter:archive 暗码解压只能解压出部门文件(已解决问题) ...

打印 上一主题 下一主题

主题 975|帖子 975|积分 2925

1,背景描述

  1. 在使用flutter中的archive压缩依赖包开发一个名叫*超级密码*的全平台工具,针对加密的zip进行解压时出现如下错误,排查一波最后解决
复制代码
2,错误现象:

解压文件只能解压部门文件,其他的没有解压出来
报错日志:
  1. Failed to send request: {"jsonrpc":"2.0","id":"148","method":"lookupResolvedPackageUris","params":{"isolateId":"isolates/5812741238645739","uris":["package:archive/src/io/extract_archive_to_disk.dart"],"local":true}}
  2. Failed to send request: {"jsonrpc":"2.0","id":"147","method":"lookupResolvedPackageUris","params":{"isolateId":"isolates/5812741238645739","uris":["package:archive/src/io/extract_archive_to_disk.dart"],"local":true}}
  3. Failed to send request: {"jsonrpc":"2.0","id":"146","method":"getStack","params":{"isolateId":"isolates/5812741238645739"}}
  4. Failed to send request: {"jsonrpc":"2.0","id":"149","method":"invoke","params":{"isolateId":"isolates/5812741238645739","targetId":"objects/227/0","selector":"toString","argumentIds":[],"disableBreakpoints":true}}
  5. Failed to send request: {"jsonrpc":"2.0","id":"150","method":"invoke","params":{"isolateId":"isolates/5812741238645739","targetId":"objects/229/0","selector":"toString","argumentIds":[],"disableBreakpoints":true}}
  6. Failed to send request: {"jsonrpc":"2.0","id":"151","method":"invoke","params":{"isolateId":"isolates/5812741238645739","targetId":"objects/230/0","selector":"toString","argumentIds":[],"disableBreakpoints":true}}
复制代码
3,相干代码:

  1. Future<void> extractFileToDisk(String inputPath, String outputPath,
  2.     {String? password, int? bufferSize, ArchiveCallback? callback}) async {
  3.   Directory? tempDir;
  4.   var archivePath = inputPath;
  5.   var posixSupported = posix.isPosixSupported();
  6.   if (inputPath.endsWith('tar.gz') || inputPath.endsWith('tgz')) {
  7.     tempDir = Directory.systemTemp.createTempSync('dart_archive');
  8.     archivePath = path.join(tempDir.path, 'temp.tar');
  9.     final input = InputFileStream(inputPath);
  10.     final output = OutputFileStream(archivePath, bufferSize: bufferSize);
  11.     GZipDecoder().decodeStream(input, output);
  12.     await input.close();
  13.     await output.close();
  14.   } else if (inputPath.endsWith('tar.bz2') || inputPath.endsWith('tbz')) {
  15.     tempDir = Directory.systemTemp.createTempSync('dart_archive');
  16.     archivePath = path.join(tempDir.path, 'temp.tar');
  17.     final input = InputFileStream(inputPath);
  18.     final output = OutputFileStream(archivePath, bufferSize: bufferSize);
  19.     BZip2Decoder().decodeStream(input, output);
  20.     await input.close();
  21.     await output.close();
  22.   } else if (inputPath.endsWith('tar.xz') || inputPath.endsWith('txz')) {
  23.     tempDir = Directory.systemTemp.createTempSync('dart_archive');
  24.     archivePath = path.join(tempDir.path, 'temp.tar');
  25.     final input = InputFileStream(inputPath);
  26.     final output = OutputFileStream(archivePath, bufferSize: bufferSize);
  27.     XZDecoder().decodeStream(input, output);
  28.     await input.close();
  29.     await output.close();
  30.   }
  31.   InputStream? toClose;
  32.   Archive archive;
  33.   if (archivePath.endsWith('tar')) {
  34.     final input = InputFileStream(archivePath);
  35.     archive = TarDecoder().decodeStream(input, callback: callback);
  36.     toClose = input;
  37.   } else if (archivePath.endsWith('zip')) {
  38.     final input = InputFileStream(archivePath);
  39.     archive = ZipDecoder()
  40.         .decodeStream(input, password: password, callback: callback);
  41.     toClose = input;
  42.   } else {
  43.     throw ArgumentError.value(inputPath, 'inputPath',
  44.         'Must end tar.gz, tgz, tar.bz2, tbz, tar.xz, txz, tar or zip.');
  45.   }
  46.   for (final file in archive) {
  47.     final filePath = path.join(outputPath, path.normalize(file.name));
  48.     if (!_isWithinOutputPath(outputPath, filePath)) {
  49.       continue;
  50.     }
  51.     if (file.isSymbolicLink) {
  52.       if (!_isValidSymLink(outputPath, file)) {
  53.         continue;
  54.       }
  55.     }
  56.     if (file.isDirectory && !file.isSymbolicLink) {
  57.       Directory(filePath).createSync(recursive: true);
  58.       continue;
  59.     }
  60.     if (file.isSymbolicLink) {
  61.       final link = Link(filePath);
  62.       final p = path.normalize(file.symbolicLink ?? "");
  63.       link.createSync(p, recursive: true);
  64.     } else if (file.isFile) {
  65.       final output = OutputFileStream(filePath, bufferSize: bufferSize);
  66.       try {
  67.         file.writeContent(output);
  68.       } catch (_) {}
  69.       if (posixSupported) {
  70.         posix.chmod(filePath, file.unixPermissions.toRadixString(8));
  71.       }
  72.       await output.close();
  73.     }
  74.   }
复制代码
4,代码逻辑&问题排查

1,archive已经解析出来

2,目录正常创建
3,第一个文件正常创建
4,第二个文件创建失败
  1. 进行步进debug发现,第二个文件进入循环就直接出错了!!!
  2. 定位错误起始点
  3. plugins/archive/lib/src/io/extract_archive_to_disk.dart:227
  4.         await output.close();
复制代码
5,分析问题

5.1,先锤上一拳试试!

直接解释掉这个close部门的代码,由于在java中,这些资源不关闭一般只会浪费资源,不会堕落
解释掉后,代码运行正常!我人傻了!
5.2,为什么会有影响?

理论上final output = OutputFileStream(filePath, bufferSize: bufferSize);是相互独立的才对,会什么会有影响呢?
问一下gpt
  1. 问题原因分析
  2. 在这段代码中,如果解开 // await output.close(); 的注释,会导致文件只能解压出一个文件,第二个文件无法写入。这是因为 await output.close(); 是一个异步操作,它会阻塞当前的执行流程,直到文件流关闭完成。在异步操作完成之前,代码不会继续执行后续的循环迭代,这就导致后续的文件无法被处理。
  3. 具体来说,当第一个文件的 output.close() 被调用时,代码会等待这个操作完成,而在等待的过程中,整个 for 循环会被暂停,无法处理下一个文件,从而导致只有第一个文件能被正确解压。
  4. 修复方案
  5. 为了避免这个问题,我们需要确保每个文件的写入和关闭操作都是独立的,不会影响到后续文件的处理。可以将文件的写入和关闭操作封装到一个异步函数中,然后在 for 循环中使用 await 关键字来等待每个文件的处理完成,这样可以确保每个文件的处理都是顺序进行的,不会相互干扰。
  6. 嗯。。。。。,感觉说的有问题。阻塞下一次for循环,为什么会“中断”for循环?
复制代码
以下是修复后的代码:
  1. for (final file in archive) {
  2.   final filePath = path.join(outputPath, path.normalize(file.name));
  3.   if (!_isWithinOutputPath(outputPath, filePath)) {
  4.     continue;
  5.   }
  6.   if (file.isSymbolicLink) {
  7.     if (!_isValidSymLink(outputPath, file)) {
  8.       continue;
  9.     }
  10.   }
  11.   if (file.isDirectory && !file.isSymbolicLink) {
  12.     Directory(filePath).createSync(recursive: true);
  13.     continue;
  14.   }
  15.   if (file.isSymbolicLink) {
  16.     final link = Link(filePath);
  17.     final p = path.normalize(file.symbolicLink ?? "");
  18.     link.createSync(p, recursive: true);
  19.   } else if (file.isFile) {
  20.     // 封装文件写入和关闭操作的异步函数
  21.     await _writeFile(file, filePath, bufferSize, posixSupported);
  22.   }
  23. }
  24. // 封装文件写入和关闭操作的异步函数
  25. Future<void> _writeFile(ArchiveFile file, String filePath, int? bufferSize, bool posixSupported) async {
  26.   final output = OutputFileStream(filePath, bufferSize: bufferSize);
  27.   try {
  28.     file.writeContent(output);
  29.   } catch (_) {}
  30.   if (posixSupported) {
  31.     posix.chmod(filePath, file.unixPermissions.toRadixString(8));
  32.   }
  33.   // 关闭文件流
  34.   await output.close();
  35. }
复制代码
我尝试了一下无效!还是错误的
5.3,思考如何解决呢

方案一:直接解释掉

查一下不要这个在dart中会有什么问题
  1. 将 await output.close(); 注释掉可能会引发以下几个问题:
  2. 1. 数据丢失或不完整
  3. 缓冲区数据未完全写入:
  4. OutputFileStream 通常会使用缓冲区来提高写入性能。当你调用 writeContent 方法写入数据时,数据可能先被存储在缓冲区中,而不是立即写入到磁盘文件。close 方法的一个重要作用是将缓冲区中的剩余数据刷新到磁盘。如果不调用 close 方法,缓冲区中的数据可能不会被完全写入磁盘,导致文件内容不完整。
  5. 示例:
  6. 假设 OutputFileStream 的缓冲区大小为 4KB,而你写入的数据量为 4.5KB。前 4KB 数据会被存储在缓冲区中,当你继续写入 0.5KB 数据时,可能会触发缓冲区的部分刷新,但最后一部分数据可能仍留在缓冲区中。如果不调用 close 方法,这部分数据就不会被写入磁盘。
  7. 2. 资源泄漏
  8. 文件描述符未释放:
  9. 在操作系统层面,每个打开的文件都会占用一个文件描述符(file descriptor)。文件描述符是一种有限的系统资源。当你打开一个文件(通过 OutputFileStream)时,操作系统会分配一个文件描述符给该文件。如果不调用 close 方法,文件描述符将不会被释放,随着打开文件数量的增加,可能会耗尽系统的文件描述符资源,导致后续文件无法正常打开。
  10. 示例:
  11. 在一个长时间运行的程序中,如果不断地打开文件进行写入操作而不关闭它们,最终可能会出现 Too many open files 错误,因为系统的文件描述符资源已经被耗尽。
  12. 3. 数据不一致
  13. 文件状态未正确更新:
  14. 关闭文件时,操作系统会更新文件的元数据,如文件大小、修改时间等。如果不调用 close 方法,这些元数据可能不会被正确更新,导致文件状态不一致。例如,文件的实际大小可能与文件系统中记录的大小不一致。
  15. 示例:
  16. 当你使用 ls -l 命令查看文件信息时,文件的大小可能显示不正确,因为文件的实际大小在文件关闭时才会被正确记录。
  17. 4. 后续操作可能失败
  18. 其他程序无法访问文件:
  19. 当一个文件被打开且未关闭时,其他程序可能无法对该文件进行读取或写入操作。这是因为操作系统会对打开的文件进行锁定,以确保数据的一致性。如果不调用 close 方法,其他程序可能会因为无法获取文件锁而无法访问该文件。
  20. 示例:
  21. 如果你在程序中打开一个文件进行写入操作,并且没有关闭它,然后尝试在另一个程序中读取该文件,可能会收到 File is in use 错误。
  22. 综上所述,虽然注释掉 await output.close(); 可能在某些情况下看起来程序可以正常运行,但实际上存在潜在的风险,可能会导致数据丢失、资源泄漏、数据不一致以及后续操作失败等问题。因此,建议在完成文件写入操作后,始终调用 close 方法来确保文件的正确关闭。
复制代码
方案二:去掉同步等待

去掉同步等待后,output.close();不会壅闭for循环的进行
那没问题了,GG
另外,我自己是javaer,对于flutter还是个新手,有大牛知道此中本质的原因,可以给我留言解答,阿里嘎多
6,解决方案

  1. // await output.close();
  2. // 直接去掉await等待 修改为如下部分
  3. output.close();
复制代码
7,其他

赞助或者有偿解决技术问题,可以接洽邮箱 linkeetop@foxmail.com

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

飞不高

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表