mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 10:58:40 +00:00
add fast upload and download
This commit is contained in:
parent
341c7a36da
commit
f1dc6dff54
5 changed files with 137 additions and 59 deletions
|
|
@ -45,7 +45,7 @@ final lockRetransStore = Mutex();
|
|||
/// It handles errors and does automatically tries to reconnect on
|
||||
/// errors or network changes.
|
||||
class ApiService {
|
||||
final String apiHost = (kDebugMode) ? "10.99.0.140:3030" : "api.twonly.eu";
|
||||
final String apiHost = (kDebugMode) ? "192.168.178.89:3030" : "api.twonly.eu";
|
||||
final String apiSecure = (kDebugMode) ? "" : "s";
|
||||
|
||||
bool appIsOutdated = false;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
|
|
@ -75,30 +76,39 @@ Future<bool> isAllowedToDownload(bool isVideo) async {
|
|||
}
|
||||
|
||||
Future handleDownloadStatusUpdate(TaskStatusUpdate update) async {
|
||||
bool failed = false;
|
||||
int messageId = int.parse(update.task.taskId.replaceAll("download_", ""));
|
||||
bool failed = false;
|
||||
|
||||
if (update.status == TaskStatus.failed ||
|
||||
update.status == TaskStatus.canceled) {
|
||||
Log.error("Download failed: ${update.status}");
|
||||
failed = true;
|
||||
} else if (update.status == TaskStatus.complete) {
|
||||
if (update.responseStatusCode == 200) {
|
||||
Log.info("Download was successfully for $messageId");
|
||||
await handleEncryptedFile(messageId);
|
||||
failed = false;
|
||||
} else {
|
||||
failed = true;
|
||||
Log.error(
|
||||
"Got invalid response status code: ${update.responseStatusCode}");
|
||||
}
|
||||
} else {
|
||||
Log.info("Got $update for $messageId");
|
||||
return;
|
||||
}
|
||||
await handleDownloadStatusUpdateInternal(messageId, failed);
|
||||
}
|
||||
|
||||
Future handleDownloadStatusUpdateInternal(int messageId, bool failed) async {
|
||||
if (failed) {
|
||||
Log.error("Download failed for $messageId");
|
||||
Message? message = await twonlyDB.messagesDao
|
||||
.getMessageByMessageId(messageId)
|
||||
.getSingleOrNull();
|
||||
if (message != null) {
|
||||
await handleMediaError(message);
|
||||
}
|
||||
} else {
|
||||
Log.info("Download was successfully for $messageId");
|
||||
await handleEncryptedFile(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,11 +161,6 @@ Future startDownloadMedia(Message message, bool force,
|
|||
);
|
||||
}
|
||||
|
||||
// int offset = 0;
|
||||
// Uint8List? bytes = await readMediaFile(media.messageId, "encrypted");
|
||||
// if (bytes != null && bytes.isNotEmpty) {
|
||||
// offset = bytes.length;
|
||||
|
||||
downloadStartedForMediaReceived[message.messageId] = DateTime.now();
|
||||
|
||||
String downloadToken = uint8ListToHex(content.downloadToken!);
|
||||
|
|
@ -175,13 +180,51 @@ Future startDownloadMedia(Message message, bool force,
|
|||
);
|
||||
|
||||
Log.info(
|
||||
"Got media file. Starting download: ${downloadToken.substring(0, 10)}");
|
||||
"Got media file. Starting download: ${downloadToken.substring(0, 10)}",
|
||||
);
|
||||
|
||||
final result = await FileDownloader().enqueue(task);
|
||||
|
||||
return result;
|
||||
try {
|
||||
await downloadFileFast(media.messageId, apiUrl);
|
||||
return;
|
||||
} catch (e) {
|
||||
Log.error("Exception during upload: $e");
|
||||
Log.error("Fast download failed: $e");
|
||||
final result = await FileDownloader().enqueue(task);
|
||||
return result;
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error("Exception during download: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> downloadFileFast(
|
||||
int messageId,
|
||||
String apiUrl,
|
||||
) async {
|
||||
final String directoryPath =
|
||||
"${(await getApplicationSupportDirectory()).path}/media/received/";
|
||||
final String filename = "$messageId.encrypted";
|
||||
|
||||
final Directory directory = Directory(directoryPath);
|
||||
if (!await directory.exists()) {
|
||||
await directory.create(recursive: true);
|
||||
}
|
||||
|
||||
final String filePath = "${directory.path}/$filename";
|
||||
|
||||
final response =
|
||||
await http.get(Uri.parse(apiUrl)).timeout(Duration(seconds: 6));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
await File(filePath).writeAsBytes(response.bodyBytes);
|
||||
Log.info('Download successful: $filePath');
|
||||
await handleDownloadStatusUpdateInternal(messageId, false);
|
||||
return;
|
||||
} else {
|
||||
if (response.statusCode == 404) {
|
||||
await handleDownloadStatusUpdateInternal(messageId, true);
|
||||
return;
|
||||
}
|
||||
throw Exception("Fast upload failed with status: ${response.statusCode}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'dart:io';
|
|||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
|
@ -410,31 +411,13 @@ Future handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
|||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (update.status == TaskStatus.failed ||
|
||||
update.status == TaskStatus.canceled) {
|
||||
Log.error("Upload failed: ${update.status}");
|
||||
failed = true;
|
||||
} else if (update.status == TaskStatus.complete) {
|
||||
if (update.responseStatusCode == 200) {
|
||||
Log.info("Upload of $mediaUploadId success!");
|
||||
|
||||
await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
||||
mediaUploadId,
|
||||
MediaUploadsCompanion(
|
||||
state: Value(UploadState.receiverNotified),
|
||||
),
|
||||
);
|
||||
|
||||
for (final messageId in media.messageIds!) {
|
||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||
messageId,
|
||||
MessagesCompanion(
|
||||
acknowledgeByServer: Value(true),
|
||||
errorWhileSending: Value(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
await handleUploadSuccess(media);
|
||||
return;
|
||||
} else if (update.responseStatusCode != null) {
|
||||
if (update.responseStatusCode! >= 400 &&
|
||||
|
|
@ -462,6 +445,26 @@ Future handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
|||
'Status update for ${update.task.taskId} with status ${update.status}');
|
||||
}
|
||||
|
||||
Future handleUploadSuccess(MediaUpload media) async {
|
||||
Log.info("Upload of ${media.mediaUploadId} success!");
|
||||
await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
||||
media.mediaUploadId,
|
||||
MediaUploadsCompanion(
|
||||
state: Value(UploadState.receiverNotified),
|
||||
),
|
||||
);
|
||||
|
||||
for (final messageId in media.messageIds!) {
|
||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||
messageId,
|
||||
MessagesCompanion(
|
||||
acknowledgeByServer: Value(true),
|
||||
errorWhileSending: Value(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future handleUploadError(MediaUpload mediaUpload) async {
|
||||
// if the messageIds are already there notify the user about this error...
|
||||
if (mediaUpload.messageIds != null) {
|
||||
|
|
@ -573,12 +576,13 @@ Future handleMediaUpload(MediaUpload media) async {
|
|||
|
||||
final uploadRequestBytes = uploadRequest.writeToBuffer();
|
||||
|
||||
String? apiAuthToken =
|
||||
String? apiAuthTokenRaw =
|
||||
await FlutterSecureStorage().read(key: SecureStorageKeys.apiAuthToken);
|
||||
if (apiAuthToken == null) {
|
||||
if (apiAuthTokenRaw == null) {
|
||||
Log.error("api auth token not defined.");
|
||||
return;
|
||||
}
|
||||
String apiAuthToken = uint8ListToHex(base64Decode(apiAuthTokenRaw));
|
||||
|
||||
File uploadRequestFile = await writeSendMediaFile(
|
||||
media.mediaUploadId,
|
||||
|
|
@ -590,6 +594,12 @@ Future handleMediaUpload(MediaUpload media) async {
|
|||
"http${apiService.apiSecure}://${apiService.apiHost}/api/upload";
|
||||
|
||||
try {
|
||||
Log.info("Starting upload from ${media.mediaUploadId}");
|
||||
|
||||
try {
|
||||
await uploadFileFast(media, uploadRequestBytes, apiUrl, apiAuthToken);
|
||||
} catch (e) {
|
||||
Log.error("Fast upload failed: $e. Using slow method.");
|
||||
final task = UploadTask.fromFile(
|
||||
taskId: "upload_${media.mediaUploadId}",
|
||||
displayName: (media.metadata?.isVideo ?? false) ? "image" : "video",
|
||||
|
|
@ -598,27 +608,51 @@ Future handleMediaUpload(MediaUpload media) async {
|
|||
priority: 0,
|
||||
retries: 10,
|
||||
headers: {
|
||||
'x-twonly-auth-token': uint8ListToHex(base64Decode(apiAuthToken))
|
||||
'x-twonly-auth-token': apiAuthToken,
|
||||
},
|
||||
);
|
||||
await FileDownloader().enqueue(task);
|
||||
}
|
||||
|
||||
Log.info("Starting upload from ${media.mediaUploadId}");
|
||||
|
||||
final result = await FileDownloader().enqueue(task);
|
||||
|
||||
if (result) {
|
||||
await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
||||
media.mediaUploadId,
|
||||
MediaUploadsCompanion(
|
||||
state: Value(UploadState.uploadTaskStarted),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error("Exception during upload: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future uploadFileFast(
|
||||
MediaUpload media,
|
||||
Uint8List uploadRequestFile,
|
||||
String apiUrl,
|
||||
String apiAuthToken,
|
||||
) async {
|
||||
var requestMultipart = http.MultipartRequest(
|
||||
"POST",
|
||||
Uri.parse(apiUrl),
|
||||
);
|
||||
requestMultipart.headers['x-twonly-auth-token'] = apiAuthToken;
|
||||
|
||||
requestMultipart.files.add(http.MultipartFile.fromBytes(
|
||||
"file",
|
||||
uploadRequestFile,
|
||||
filename: "upload",
|
||||
));
|
||||
|
||||
final response = await requestMultipart.send().timeout(Duration(seconds: 3));
|
||||
if (response.statusCode == 200) {
|
||||
Log.info('Upload successful!');
|
||||
await handleUploadSuccess(media);
|
||||
return;
|
||||
} else {
|
||||
Log.info('Upload failed with status: ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> compressVideoIfExists(int mediaUploadId) async {
|
||||
String basePath = await getMediaFilePath(mediaUploadId, "send");
|
||||
File videoOriginalFile = File("$basePath.original.mp4");
|
||||
|
|
|
|||
|
|
@ -97,7 +97,8 @@ Future performTwonlySafeBackup({bool force = false}) async {
|
|||
await backupDatabaseFile.delete();
|
||||
await backupDatabaseFileCleaned.delete();
|
||||
|
||||
print("twonlyDatabaseBytes = ${twonlyDatabaseBytes.lengthInBytes}");
|
||||
Log.info("twonlyDatabaseBytes = ${twonlyDatabaseBytes.lengthInBytes}");
|
||||
Log.info("secureStorageBytes = ${jsonEncode(secureStorageBackup)}");
|
||||
|
||||
final backupProto = TwonlySafeBackupContent(
|
||||
secureStorageJson: jsonEncode(secureStorageBackup),
|
||||
|
|
@ -171,7 +172,7 @@ Future performTwonlySafeBackup({bool force = false}) async {
|
|||
file: encryptedBackupBytesFile,
|
||||
httpRequestMethod: "PUT",
|
||||
url: (await getTwonlySafeBackupUrl())!,
|
||||
requiresWiFi: true,
|
||||
// requiresWiFi: true,
|
||||
priority: 5,
|
||||
post: 'binary',
|
||||
retries: 2,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
|
|||
# Prevent accidental publishing to pub.dev.
|
||||
publish_to: 'none'
|
||||
|
||||
version: 0.0.47+47
|
||||
version: 0.0.48+48
|
||||
|
||||
environment:
|
||||
sdk: ^3.6.0
|
||||
|
|
|
|||
Loading…
Reference in a new issue