file_uploader.dart 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import 'dart:convert';
  2. import 'dart:io' as io;
  3. import 'package:dio/dio.dart';
  4. import 'package:flutter_sodium/flutter_sodium.dart';
  5. import 'package:logging/logging.dart';
  6. import 'package:photos/core/configuration.dart';
  7. import 'package:photos/core/constants.dart';
  8. import 'package:photos/models/file.dart';
  9. import 'package:photos/models/upload_url.dart';
  10. import 'package:photos/services/collections_service.dart';
  11. import 'package:photos/utils/crypto_util.dart';
  12. import 'package:photos/utils/file_name_util.dart';
  13. import 'package:photos/utils/file_util.dart';
  14. class FileUploader {
  15. final _logger = Logger("FileUploader");
  16. final _dio = Dio();
  17. Future<UploadURL> getUploadURL() {
  18. return Dio()
  19. .get(
  20. Configuration.instance.getHttpEndpoint() + "/files/upload-url",
  21. options: Options(
  22. headers: {"X-Auth-Token": Configuration.instance.getToken()}),
  23. )
  24. .then((response) => UploadURL.fromMap(response.data));
  25. }
  26. Future<String> putFile(UploadURL uploadURL, io.File file) async {
  27. final fileSize = file.lengthSync().toString();
  28. final startTime = DateTime.now().millisecondsSinceEpoch;
  29. _logger.info("Putting file of size " + fileSize + " to " + uploadURL.url);
  30. return Dio()
  31. .put(uploadURL.url,
  32. data: file.openRead(),
  33. options: Options(headers: {
  34. Headers.contentLengthHeader: await file.length(),
  35. }))
  36. .catchError((e) {
  37. _logger.severe(e);
  38. throw e;
  39. }).then((value) {
  40. _logger.info("Upload speed : " +
  41. (file.lengthSync() /
  42. (DateTime.now().millisecondsSinceEpoch - startTime))
  43. .toString() +
  44. " kilo bytes per second");
  45. return uploadURL.objectKey;
  46. });
  47. }
  48. Future<File> encryptAndUploadFile(File file) async {
  49. _logger.info("Uploading " + file.toString());
  50. file.collectionID = (await CollectionsService.instance
  51. .getOrCreateForPath(file.deviceFolder))
  52. .id;
  53. final encryptedFileName = file.generatedID.toString() + ".encrypted";
  54. final tempDirectory = Configuration.instance.getTempDirectory();
  55. final encryptedFilePath = tempDirectory + encryptedFileName;
  56. final sourceFile = (await (await file.getAsset()).originFile);
  57. final encryptedFile = io.File(encryptedFilePath);
  58. final fileAttributes =
  59. await CryptoUtil.encryptFile(sourceFile.path, encryptedFilePath);
  60. final fileUploadURL = await getUploadURL();
  61. String fileObjectKey = await putFile(fileUploadURL, encryptedFile);
  62. final thumbnailData = (await (await file.getAsset()).thumbDataWithSize(
  63. THUMBNAIL_LARGE_SIZE,
  64. THUMBNAIL_LARGE_SIZE,
  65. quality: 50,
  66. ));
  67. final encryptedThumbnailName =
  68. file.generatedID.toString() + "_thumbnail.encrypted";
  69. final encryptedThumbnailPath = tempDirectory + encryptedThumbnailName;
  70. final encryptedThumbnail =
  71. CryptoUtil.encryptChaCha(thumbnailData, fileAttributes.key);
  72. io.File(encryptedThumbnailPath)
  73. .writeAsBytesSync(encryptedThumbnail.encryptedData);
  74. final thumbnailUploadURL = await getUploadURL();
  75. String thumbnailObjectKey =
  76. await putFile(thumbnailUploadURL, io.File(encryptedThumbnailPath));
  77. final encryptedMetadataData = CryptoUtil.encryptChaCha(
  78. utf8.encode(jsonEncode(file.getMetadata())), fileAttributes.key);
  79. final encryptedFileKeyData = CryptoUtil.encryptSync(
  80. fileAttributes.key,
  81. CollectionsService.instance.getCollectionKey(file.collectionID),
  82. );
  83. final encryptedKey = Sodium.bin2base64(encryptedFileKeyData.encryptedData);
  84. final keyDecryptionNonce = Sodium.bin2base64(encryptedFileKeyData.nonce);
  85. final fileDecryptionHeader = Sodium.bin2base64(fileAttributes.header);
  86. final thumbnailDecryptionHeader =
  87. Sodium.bin2base64(encryptedThumbnail.header);
  88. final encryptedMetadata =
  89. Sodium.bin2base64(encryptedMetadataData.encryptedData);
  90. final metadataDecryptionHeader =
  91. Sodium.bin2base64(encryptedMetadataData.header);
  92. final data = {
  93. "collectionID": file.collectionID,
  94. "encryptedKey": encryptedKey,
  95. "keyDecryptionNonce": keyDecryptionNonce,
  96. "file": {
  97. "objectKey": fileObjectKey,
  98. "decryptionHeader": fileDecryptionHeader,
  99. },
  100. "thumbnail": {
  101. "objectKey": thumbnailObjectKey,
  102. "decryptionHeader": thumbnailDecryptionHeader,
  103. },
  104. "metadata": {
  105. "encryptedData": encryptedMetadata,
  106. "decryptionHeader": metadataDecryptionHeader,
  107. }
  108. };
  109. return _dio
  110. .post(
  111. Configuration.instance.getHttpEndpoint() + "/files",
  112. options:
  113. Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
  114. data: data,
  115. )
  116. .then((response) {
  117. encryptedFile.deleteSync();
  118. io.File(encryptedThumbnailPath).deleteSync();
  119. final data = response.data;
  120. file.uploadedFileID = data["id"];
  121. file.updationTime = data["updationTime"];
  122. file.ownerID = data["ownerID"];
  123. file.encryptedKey = encryptedKey;
  124. file.keyDecryptionNonce = keyDecryptionNonce;
  125. file.fileDecryptionHeader = fileDecryptionHeader;
  126. file.thumbnailDecryptionHeader = thumbnailDecryptionHeader;
  127. file.metadataDecryptionHeader = metadataDecryptionHeader;
  128. return file;
  129. });
  130. }
  131. Future<File> uploadFile(File localPhoto) async {
  132. final title = getJPGFileNameForHEIC(localPhoto);
  133. final formData = FormData.fromMap({
  134. "file": MultipartFile.fromBytes(await getBytesFromDisk(localPhoto),
  135. filename: title),
  136. "deviceFileID": localPhoto.localID,
  137. "deviceFolder": localPhoto.deviceFolder,
  138. "title": title,
  139. "creationTime": localPhoto.creationTime,
  140. "modificationTime": localPhoto.modificationTime,
  141. });
  142. return _dio
  143. .post(
  144. Configuration.instance.getHttpEndpoint() + "/files",
  145. options:
  146. Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
  147. data: formData,
  148. )
  149. .then((response) {
  150. return File.fromJson(response.data);
  151. });
  152. }
  153. }