download.dart 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'package:app_installer/app_installer.dart';
  4. import 'package:dio/dio.dart';
  5. class DownloadStatus {
  6. String tag;
  7. String path;
  8. int count;
  9. int total;
  10. bool done;
  11. bool failed;
  12. DioError? error;
  13. DownloadStatus(
  14. this.tag, this.path, this.count, this.total, this.done, this.failed,{this.error});
  15. }
  16. /*
  17. * 文件下载
  18. * 懒加载单例
  19. */
  20. class DownLoadManage {
  21. //用于记录正在下载的url,避免重复下载
  22. // var downloadingUrls = new List();
  23. var downloadingUrls = new Map<String, CancelToken>();
  24. // 单例公开访问点
  25. factory DownLoadManage() => _getInstance();
  26. // 静态私有成员,没有初始化
  27. static DownLoadManage? _instance;
  28. // 私有构造函数
  29. DownLoadManage._() {
  30. // 具体初始化代码
  31. }
  32. // 静态、同步、私有访问点
  33. static DownLoadManage _getInstance() {
  34. if (_instance == null) {
  35. _instance = DownLoadManage._();
  36. }
  37. return _instance!;
  38. }
  39. final StreamController<DownloadStatus> _controller =
  40. StreamController.broadcast();
  41. Stream<DownloadStatus> get stream => _controller.stream;
  42. /*
  43. *下载
  44. */
  45. Future<bool> download(url, savePath) async {
  46. int downloadStart = 0;
  47. bool fileExists = false;
  48. File f = File(savePath);
  49. if (await f.exists()) {
  50. downloadStart = f.lengthSync();
  51. fileExists = true;
  52. }
  53. print(
  54. "download $url $fileExists ${downloadingUrls.containsKey(url)} ${downloadingUrls[url]?.isCancelled} ${downloadingUrls.length}");
  55. if (fileExists &&
  56. downloadingUrls.containsKey(url) &&
  57. !(downloadingUrls[url]?.isCancelled ?? false)) {
  58. print("download $url $fileExists");
  59. //正在下载
  60. return false;
  61. }
  62. CancelToken cancelToken = new CancelToken();
  63. // downloadingUrls[url] = cancelToken;
  64. if (downloadingUrls[url] == null) {
  65. downloadingUrls[url] = cancelToken;
  66. }
  67. var dio = Dio();
  68. String contentLength;
  69. int _total = 0;
  70. try {
  71. contentLength = await _getContentLength(dio, url, cancelToken);
  72. print("download contentLength :$contentLength");
  73. _total = int.parse(contentLength);
  74. print("download start :$downloadStart -- contentLength: $contentLength");
  75. if (downloadStart >= _total) {
  76. stop(url);
  77. //存在本地文件,命中缓存
  78. _controller.add(
  79. DownloadStatus(url, savePath, downloadStart, _total, true, false));
  80. // done?.call();
  81. return true;
  82. }
  83. } catch (e) {
  84. print(e);
  85. stop(url);
  86. return false;
  87. }
  88. cancelToken = new CancelToken();
  89. downloadingUrls[url] = cancelToken;
  90. File tmp = File("$savePath.tmp$downloadStart");
  91. tmp.createSync();
  92. var resp = await dio
  93. .download(url, tmp.path,
  94. deleteOnError: false,
  95. onReceiveProgress: (index, total) {
  96. _controller.add(DownloadStatus(
  97. url, savePath, index + downloadStart, _total, false, false));
  98. // print("url --- > $index, $total");
  99. },
  100. // onReceiveProgress?.call(index + downloadStart, _total),
  101. cancelToken: cancelToken,
  102. options: Options(
  103. followRedirects: false,
  104. headers: {"range": "bytes=$downloadStart-$contentLength"},
  105. ))
  106. .whenComplete(() {
  107. downloadingUrls.remove(url);
  108. print("download whenComplete $url");
  109. }).catchError((e) async {
  110. print("download catch $e");
  111. downloadingUrls.remove(url);
  112. if(e is DioError){
  113. _controller.add(DownloadStatus(url, savePath, 0, _total, false, true, error: e));
  114. }else {
  115. _controller.add(DownloadStatus(url, savePath, 0, _total, false, true));
  116. }
  117. // failed?.call(e);
  118. });
  119. await append(f, tmp);
  120. if (resp != null) {
  121. // done?.call();
  122. _controller.add(DownloadStatus(url, savePath, 0, _total, true, false));
  123. return true;
  124. }
  125. return false;
  126. // downloadingUrls.remove(url);
  127. // done.call();
  128. }
  129. append(File file, File tmp) async {
  130. IOSink ioSink = file.openWrite(mode: FileMode.writeOnlyAppend);
  131. await ioSink.addStream(tmp.openRead());
  132. await tmp.delete(); //删除临时文件
  133. await ioSink.close();
  134. print("download save file ---------file --- ${file.lengthSync()}");
  135. }
  136. /*
  137. * 获取下载的文件大小
  138. */
  139. Future _getContentLength(Dio dio, url, CancelToken cancelToken) async {
  140. try {
  141. Response response = await dio.head(url, cancelToken: cancelToken);
  142. return response.headers.value(Headers.contentLengthHeader);
  143. } catch (e) {
  144. print("download _getContentLength Failed:" + e.toString());
  145. return 0;
  146. }
  147. }
  148. void stop(String url) {
  149. if (downloadingUrls.containsKey(url)) {
  150. try {
  151. downloadingUrls[url]?.cancel();
  152. } catch (e) {
  153. print(e);
  154. }
  155. }
  156. downloadingUrls.remove(url);
  157. }
  158. bool isDowning(String url) {
  159. return downloadingUrls.containsKey(url);
  160. }
  161. }