improve sending #144 and maybe #143

This commit is contained in:
otsmr 2025-04-30 23:14:01 +02:00
parent c460befea5
commit 2bfd50ef8d
3 changed files with 136 additions and 106 deletions

View file

@ -13,6 +13,7 @@ import 'package:twonly/src/database/tables/media_uploads_table.dart';
import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/model/protobuf/api/error.pb.dart';
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart'; import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
import 'package:twonly/src/providers/api/api.dart'; import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/api_utils.dart'; import 'package:twonly/src/providers/api/api_utils.dart';
@ -49,7 +50,7 @@ Future sendMediaFile(
await File(videoFilePath.path).rename("$basePath.orginal.mp4"); await File(videoFilePath.path).rename("$basePath.orginal.mp4");
} }
await writeMediaFile(mediaUploadId, "orginal.png", imageBytes); await writeMediaFile(mediaUploadId, "orginal.png", imageBytes);
await handleSingleMediaFile(mediaUploadId); await handleSingleMediaFile(mediaUploadId, imageBytes);
} }
} }
@ -57,14 +58,15 @@ Future retryMediaUpload() async {
final mediaFiles = final mediaFiles =
await twonlyDatabase.mediaUploadsDao.getMediaUploadsForRetry(); await twonlyDatabase.mediaUploadsDao.getMediaUploadsForRetry();
for (final mediaFile in mediaFiles) { for (final mediaFile in mediaFiles) {
await handleSingleMediaFile(mediaFile.mediaUploadId); await handleSingleMediaFile(mediaFile.mediaUploadId, null);
} }
} }
final lockingHandleMediaFile = Mutex(); final lockingHandleMediaFile = Mutex();
Future handleSingleMediaFile(int mediaUploadId) async { Future handleSingleMediaFile(
await lockingHandleMediaFile.protect(() async { int mediaUploadId, Uint8List? tmpCurrentImageBytes) async {
// await lockingHandleMediaFile.protect(() async {
MediaUpload? media = await twonlyDatabase.mediaUploadsDao MediaUpload? media = await twonlyDatabase.mediaUploadsDao
.getMediaUploadById(mediaUploadId) .getMediaUploadById(mediaUploadId)
.getSingleOrNull(); .getSingleOrNull();
@ -76,10 +78,12 @@ Future handleSingleMediaFile(int mediaUploadId) async {
await handleAddToMessageDb(media); await handleAddToMessageDb(media);
break; break;
case UploadState.addedToMessagesDb: case UploadState.addedToMessagesDb:
await handleCompressionState(media); tmpCurrentImageBytes =
await handleCompressionState(media, tmpCurrentImageBytes);
break; break;
case UploadState.isCompressed: case UploadState.isCompressed:
await handleEncryptionState(media); tmpCurrentImageBytes =
await handleEncryptionState(media, tmpCurrentImageBytes);
break; break;
case UploadState.isEncrypted: case UploadState.isEncrypted:
if (!await handleGetUploadToken(media)) { if (!await handleGetUploadToken(media)) {
@ -87,7 +91,7 @@ Future handleSingleMediaFile(int mediaUploadId) async {
} }
break; break;
case UploadState.hasUploadToken: case UploadState.hasUploadToken:
if (!await handleUpload(media)) { if (!await handleUpload(media, tmpCurrentImageBytes)) {
return; // recoverable error. try again when connected again to the server... return; // recoverable error. try again when connected again to the server...
} }
break; break;
@ -95,6 +99,14 @@ Future handleSingleMediaFile(int mediaUploadId) async {
if (!await handleNotifyReceiver(media)) { if (!await handleNotifyReceiver(media)) {
return; // recoverable error. try again when connected again to the server... return; // recoverable error. try again when connected again to the server...
} }
try {
// delete non compressed media files
await deleteMediaFile(media, "orginal.png");
await deleteMediaFile(media, "orginal.mp4");
await deleteMediaFile(media, "encrypted");
} catch (e) {
Logger("media_send.dart").shout("$e");
}
break; break;
case UploadState.receiverNotified: case UploadState.receiverNotified:
return; return;
@ -116,10 +128,10 @@ Future handleSingleMediaFile(int mediaUploadId) async {
.shout("Non recoverable error while sending media file: $e"); .shout("Non recoverable error while sending media file: $e");
return; return;
} }
}); // });
// this will be called until there is an recoverable error OR // this will be called until there is an recoverable error OR
// the upload is ready // the upload is ready
await handleSingleMediaFile(mediaUploadId); await handleSingleMediaFile(mediaUploadId, tmpCurrentImageBytes);
} }
Future handleAddToMessageDb(MediaUpload media) async { Future handleAddToMessageDb(MediaUpload media) async {
@ -168,12 +180,17 @@ Future handleAddToMessageDb(MediaUpload media) async {
); );
} }
Future handleCompressionState(MediaUpload media) async { Future<Uint8List?> handleCompressionState(
Uint8List imageBytes = await readMediaFile(media, "orginal.png"); MediaUpload media,
Uint8List? tmpCurrentImageBytes,
) async {
Uint8List imageBytes = (tmpCurrentImageBytes != null)
? tmpCurrentImageBytes
: await readMediaFile(media, "orginal.png");
Uint8List imageBytesCompressed;
try { try {
Uint8List imageBytesCompressed = imageBytesCompressed = await FlutterImageCompress.compressWithList(
await FlutterImageCompress.compressWithList(
format: CompressFormat.png, format: CompressFormat.png,
imageBytes, imageBytes,
quality: 90, quality: 90,
@ -192,6 +209,7 @@ Future handleCompressionState(MediaUpload media) async {
Logger("media_send.dart").shout("$e"); Logger("media_send.dart").shout("$e");
// as a fall back use the orginal image // as a fall back use the orginal image
await writeMediaFile(media.mediaUploadId, "png", imageBytes); await writeMediaFile(media.mediaUploadId, "png", imageBytes);
imageBytesCompressed = imageBytes;
} }
if (media.metadata.isVideo) { if (media.metadata.isVideo) {
@ -234,10 +252,6 @@ Future handleCompressionState(MediaUpload media) async {
} }
} }
// delete non compressed media files
await deleteMediaFile(media, "orginal.png");
await deleteMediaFile(media, "orginal.mp4");
await twonlyDatabase.mediaUploadsDao.updateMediaUpload( await twonlyDatabase.mediaUploadsDao.updateMediaUpload(
media.mediaUploadId, media.mediaUploadId,
MediaUploadsCompanion( MediaUploadsCompanion(
@ -245,13 +259,16 @@ Future handleCompressionState(MediaUpload media) async {
), ),
); );
return true; return imageBytesCompressed;
} }
Future handleEncryptionState(MediaUpload media) async { Future<Uint8List> handleEncryptionState(
MediaUpload media, Uint8List? tmpCurrentImageBytes) async {
var state = MediaEncryptionData(); var state = MediaEncryptionData();
Uint8List dataToEncrypt = await readMediaFile(media, "png"); Uint8List dataToEncrypt = (tmpCurrentImageBytes != null)
? tmpCurrentImageBytes
: await readMediaFile(media, "png");
if (media.metadata.isVideo) { if (media.metadata.isVideo) {
Uint8List compressedVideo = await readMediaFile(media, "mp4"); Uint8List compressedVideo = await readMediaFile(media, "mp4");
@ -275,10 +292,11 @@ Future handleEncryptionState(MediaUpload media) async {
final algorithm = Sha256(); final algorithm = Sha256();
state.sha2Hash = (await algorithm.hash(secretBox.cipherText)).bytes; state.sha2Hash = (await algorithm.hash(secretBox.cipherText)).bytes;
final encryptedBytes = Uint8List.fromList(secretBox.cipherText);
await writeMediaFile( await writeMediaFile(
media.mediaUploadId, media.mediaUploadId,
"encrypted", "encrypted",
Uint8List.fromList(secretBox.cipherText), encryptedBytes,
); );
await twonlyDatabase.mediaUploadsDao.updateMediaUpload( await twonlyDatabase.mediaUploadsDao.updateMediaUpload(
@ -288,6 +306,7 @@ Future handleEncryptionState(MediaUpload media) async {
encryptionData: Value(state), encryptionData: Value(state),
), ),
); );
return encryptedBytes;
} }
Future<bool> handleGetUploadToken(MediaUpload media) async { Future<bool> handleGetUploadToken(MediaUpload media) async {
@ -317,8 +336,11 @@ Future<bool> handleGetUploadToken(MediaUpload media) async {
return true; return true;
} }
Future<bool> handleUpload(MediaUpload media) async { Future<bool> handleUpload(
Uint8List bytesToUpload = await readMediaFile(media, "encrypted"); MediaUpload media, Uint8List? tmpCurrentImageBytes) async {
Uint8List bytesToUpload = (tmpCurrentImageBytes != null)
? tmpCurrentImageBytes
: await readMediaFile(media, "encrypted");
int fragmentedTransportSize = 1000000; int fragmentedTransportSize = 1000000;
@ -345,6 +367,15 @@ Future<bool> handleUpload(MediaUpload media) async {
); );
if (wasSend.isError) { if (wasSend.isError) {
if (wasSend.error == ErrorCode.InvalidUpdateToken) {
await twonlyDatabase.mediaUploadsDao.updateMediaUpload(
media.mediaUploadId,
MediaUploadsCompanion(
state: Value(UploadState.isEncrypted),
),
);
return true; // this will trigger a new token request
}
Logger("media_send.dart") Logger("media_send.dart")
.shout("error while uploading media: ${wasSend.error}"); .shout("error while uploading media: ${wasSend.error}");
return false; return false;
@ -359,12 +390,6 @@ Future<bool> handleUpload(MediaUpload media) async {
), ),
); );
try {
await deleteMediaFile(media, "encrypted");
} catch (e) {
Logger("media_send.dart").shout("$e");
}
return true; return true;
} }

View file

@ -185,21 +185,21 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
sharePreviewIsShown = true; sharePreviewIsShown = true;
}); });
if (useHighQuality && !isFront) { // if (useHighQuality && !isFront) {
if (Platform.isIOS) { // if (Platform.isIOS) {
await controller?.pausePreview(); // await controller?.pausePreview();
if (!context.mounted) return; // if (!context.mounted) return;
} // }
try { // try {
// Take the picture // // Take the picture
final XFile? picture = await controller?.takePicture(); // final XFile? picture = await controller?.takePicture();
if (picture == null) return; // if (picture == null) return;
imageBytes = loadAndDeletePictureFromFile(picture); // imageBytes = loadAndDeletePictureFromFile(picture);
} catch (e) { // } catch (e) {
_showCameraException(e); // _showCameraException(e);
return; // return;
} // }
} else { // } else {
if (isFlashOn) { if (isFlashOn) {
if (isFront) { if (isFront) {
setState(() { setState(() {
@ -216,8 +216,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
controller?.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off); controller?.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off);
imageBytes = screenshotController.capture( imageBytes = screenshotController.capture(
pixelRatio: MediaQuery.of(context).devicePixelRatio); pixelRatio:
} (useHighQuality) ? MediaQuery.of(context).devicePixelRatio : 1);
// }
if (await pushMediaEditor(imageBytes, null)) { if (await pushMediaEditor(imageBytes, null)) {
return; return;
@ -235,6 +236,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
imageBytes: imageBytes, imageBytes: imageBytes,
sendTo: widget.sendTo, sendTo: widget.sendTo,
mirrorVideo: isFront && Platform.isAndroid, mirrorVideo: isFront && Platform.isAndroid,
useHighQuality: useHighQuality,
), ),
transitionsBuilder: (context, animation, secondaryAnimation, child) { transitionsBuilder: (context, animation, secondaryAnimation, child) {
return child; return child;

View file

@ -36,11 +36,13 @@ class ShareImageEditorView extends StatefulWidget {
this.sendTo, this.sendTo,
this.videoFilePath, this.videoFilePath,
required this.mirrorVideo, required this.mirrorVideo,
required this.useHighQuality,
}); });
final Future<Uint8List?>? imageBytes; final Future<Uint8List?>? imageBytes;
final XFile? videoFilePath; final XFile? videoFilePath;
final Contact? sendTo; final Contact? sendTo;
final bool mirrorVideo; final bool mirrorVideo;
final bool useHighQuality;
@override @override
State<ShareImageEditorView> createState() => _ShareImageEditorView(); State<ShareImageEditorView> createState() => _ShareImageEditorView();
} }
@ -318,7 +320,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
x.showCustomButtons = false; x.showCustomButtons = false;
} }
setState(() {}); setState(() {});
image = await screenshotController.capture(pixelRatio: pixelRatio); image = await screenshotController.capture(
pixelRatio: (widget.useHighQuality) ? pixelRatio : 1);
for (var x in layers) { for (var x in layers) {
x.showCustomButtons = true; x.showCustomButtons = true;
} }