file_util.dart 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import 'dart:async';
  2. import 'dart:io' as io;
  3. import 'dart:typed_data';
  4. import 'package:archive/archive.dart';
  5. import 'package:dio/dio.dart';
  6. import 'package:flutter_cache_manager/flutter_cache_manager.dart';
  7. import 'package:flutter_image_compress/flutter_image_compress.dart';
  8. import 'package:logging/logging.dart';
  9. import 'package:motionphoto/motionphoto.dart';
  10. import 'package:path/path.dart';
  11. import 'package:photos/core/cache/image_cache.dart';
  12. import 'package:photos/core/cache/video_cache_manager.dart';
  13. import 'package:photos/core/configuration.dart';
  14. import 'package:photos/core/constants.dart';
  15. import 'package:photos/models/file.dart' as ente;
  16. import 'package:photos/models/file_type.dart';
  17. import 'package:photos/utils/file_download_util.dart';
  18. import 'package:photos/utils/thumbnail_util.dart';
  19. final _logger = Logger("FileUtil");
  20. void preloadFile(ente.File file) {
  21. if (file.fileType == FileType.video) {
  22. return;
  23. }
  24. getFile(file);
  25. }
  26. Future<io.File> getFile(ente.File file,
  27. {bool liveVideo = false} // only relevant for live photos
  28. ) async {
  29. if (file.isRemoteFile()) {
  30. return getFileFromServer(file, liveVideo: liveVideo);
  31. } else {
  32. String key = file.tag() + liveVideo.toString();
  33. final cachedFile = FileLruCache.get(key);
  34. if (cachedFile == null) {
  35. final diskFile = await _getLocalDiskFile(file, liveVideo: liveVideo);
  36. FileLruCache.put(key, diskFile);
  37. return diskFile;
  38. }
  39. return cachedFile;
  40. }
  41. }
  42. Future<bool> doesLocalFileExist(ente.File file) async {
  43. return await _getLocalDiskFile(file) != null;
  44. }
  45. Future<io.File> _getLocalDiskFile(ente.File file,
  46. {bool liveVideo = false}) async {
  47. if (file.isSharedMediaToAppSandbox()) {
  48. var localFile = io.File(getSharedMediaFilePath(file));
  49. return localFile.exists().then((exist) {
  50. return exist ? localFile : null;
  51. });
  52. } else if (file.fileType == FileType.livePhoto && liveVideo) {
  53. return Motionphoto.getLivePhotoFile(file.localID);
  54. } else {
  55. return file.getAsset().then((asset) async {
  56. if (asset == null || !(await asset.exists)) {
  57. return null;
  58. }
  59. return asset.file;
  60. });
  61. }
  62. }
  63. String getSharedMediaFilePath(ente.File file) {
  64. return Configuration.instance.getSharedMediaCacheDirectory() +
  65. "/" +
  66. file.localID.replaceAll(kSharedMediaIdentifier, '');
  67. }
  68. void preloadThumbnail(ente.File file) {
  69. if (file.isRemoteFile()) {
  70. getThumbnailFromServer(file);
  71. } else {
  72. getThumbnailFromLocal(file);
  73. }
  74. }
  75. final Map<int, Future<io.File>> fileDownloadsInProgress =
  76. Map<int, Future<io.File>>();
  77. Future<io.File> getFileFromServer(
  78. ente.File file, {
  79. ProgressCallback progressCallback,
  80. bool liveVideo = false, // only needed in case of live photos
  81. }) async {
  82. final cacheManager = (file.fileType == FileType.video || liveVideo)
  83. ? VideoCacheManager.instance
  84. : DefaultCacheManager();
  85. return cacheManager.getFileFromCache(file.getDownloadUrl()).then((info) {
  86. if (info == null) {
  87. if (!fileDownloadsInProgress.containsKey(file.uploadedFileID)) {
  88. if (file.fileType == FileType.livePhoto) {
  89. fileDownloadsInProgress[file.uploadedFileID] = _downloadLivePhoto(
  90. file,
  91. progressCallback: progressCallback,
  92. liveVideo: liveVideo);
  93. } else {
  94. fileDownloadsInProgress[file.uploadedFileID] = _downloadAndCache(
  95. file,
  96. cacheManager,
  97. progressCallback: progressCallback,
  98. );
  99. }
  100. }
  101. return fileDownloadsInProgress[file.uploadedFileID];
  102. } else {
  103. return info.file;
  104. }
  105. });
  106. }
  107. Future<io.File> _downloadLivePhoto(ente.File file,
  108. {ProgressCallback progressCallback, bool liveVideo = false}) async {
  109. return downloadAndDecrypt(file, progressCallback: progressCallback)
  110. .then((decryptedFile) async {
  111. if (decryptedFile == null) {
  112. return null;
  113. }
  114. _logger.fine("Decoded zipped live photo from " + decryptedFile.path);
  115. io.File imageFileCache, videoFileCache;
  116. List<int> bytes = decryptedFile.readAsBytesSync();
  117. Archive archive = ZipDecoder().decodeBytes(bytes);
  118. final tempPath = Configuration.instance.getTempDirectory();
  119. // Extract the contents of Zip compressed archive to disk
  120. for (ArchiveFile archiveFile in archive) {
  121. if (archiveFile.isFile) {
  122. String filename = archiveFile.name;
  123. String fileExtension = getExtension(archiveFile.name);
  124. String decodePath =
  125. tempPath + file.uploadedFileID.toString() + filename;
  126. List<int> data = archiveFile.content;
  127. if (filename.startsWith("image")) {
  128. final imageFile = io.File(decodePath);
  129. await imageFile.create(recursive: true);
  130. await imageFile.writeAsBytes(data);
  131. io.File imageConvertedFile = imageFile;
  132. if ((fileExtension == "unknown") ||
  133. (io.Platform.isAndroid && fileExtension == "heic")) {
  134. imageConvertedFile = await FlutterImageCompress.compressAndGetFile(
  135. decodePath,
  136. decodePath + ".jpg",
  137. keepExif: true,
  138. );
  139. await imageFile.delete();
  140. }
  141. imageFileCache = await DefaultCacheManager().putFile(
  142. file.getDownloadUrl(),
  143. await imageConvertedFile.readAsBytes(),
  144. eTag: file.getDownloadUrl(),
  145. maxAge: Duration(days: 365),
  146. fileExtension: fileExtension,
  147. );
  148. await imageConvertedFile.delete();
  149. } else if (filename.startsWith("video")) {
  150. final videoFile = io.File(decodePath);
  151. await videoFile.create(recursive: true);
  152. await videoFile.writeAsBytes(data);
  153. videoFileCache = await VideoCacheManager.instance.putFile(
  154. file.getDownloadUrl(),
  155. videoFile.readAsBytesSync(),
  156. eTag: file.getDownloadUrl(),
  157. maxAge: Duration(days: 365),
  158. fileExtension: fileExtension,
  159. );
  160. await videoFile.delete();
  161. }
  162. }
  163. }
  164. fileDownloadsInProgress.remove(file.uploadedFileID);
  165. return liveVideo ? videoFileCache : imageFileCache;
  166. }).catchError((e) {
  167. fileDownloadsInProgress.remove(file.uploadedFileID);
  168. _logger.warning("failed to download live photos" + e.toString());
  169. });
  170. }
  171. Future<io.File> _downloadAndCache(ente.File file, BaseCacheManager cacheManager,
  172. {ProgressCallback progressCallback}) async {
  173. return downloadAndDecrypt(file, progressCallback: progressCallback)
  174. .then((decryptedFile) async {
  175. if (decryptedFile == null) {
  176. return null;
  177. }
  178. var decryptedFilePath = decryptedFile.path;
  179. String fileExtension = getExtension(file.title);
  180. var outputFile = decryptedFile;
  181. if ((fileExtension == "unknown" && file.fileType == FileType.image) ||
  182. (io.Platform.isAndroid && fileExtension == "heic")) {
  183. outputFile = await FlutterImageCompress.compressAndGetFile(
  184. decryptedFilePath,
  185. decryptedFilePath + ".jpg",
  186. keepExif: true,
  187. );
  188. await decryptedFile.delete();
  189. }
  190. final cachedFile = await cacheManager.putFile(
  191. file.getDownloadUrl(),
  192. outputFile.readAsBytesSync(),
  193. eTag: file.getDownloadUrl(),
  194. maxAge: Duration(days: 365),
  195. fileExtension: fileExtension,
  196. );
  197. await outputFile.delete();
  198. fileDownloadsInProgress.remove(file.uploadedFileID);
  199. return cachedFile;
  200. }).catchError((e) {
  201. fileDownloadsInProgress.remove(file.uploadedFileID);
  202. });
  203. }
  204. String getExtension(String nameOrPath) {
  205. var fileExtension = "unknown";
  206. try {
  207. fileExtension = extension(nameOrPath).substring(1).toLowerCase();
  208. } catch (e) {
  209. _logger.severe("Could not capture file extension");
  210. }
  211. return fileExtension;
  212. }
  213. Future<Uint8List> compressThumbnail(Uint8List thumbnail) {
  214. return FlutterImageCompress.compressWithList(
  215. thumbnail,
  216. minHeight: kCompressedThumbnailResolution,
  217. minWidth: kCompressedThumbnailResolution,
  218. quality: 25,
  219. );
  220. }
  221. Future<void> clearCache(ente.File file) async {
  222. if (file.fileType == FileType.video) {
  223. VideoCacheManager.instance.removeFile(file.getDownloadUrl());
  224. } else {
  225. DefaultCacheManager().removeFile(file.getDownloadUrl());
  226. }
  227. final cachedThumbnail = io.File(
  228. Configuration.instance.getThumbnailCacheDirectory() +
  229. "/" +
  230. file.uploadedFileID.toString());
  231. if (cachedThumbnail.existsSync()) {
  232. await cachedThumbnail.delete();
  233. }
  234. }