mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 02:12:13 +00:00
fixes multiple race condition issues
This commit is contained in:
parent
919aec464e
commit
e8d8e8b160
5 changed files with 128 additions and 90 deletions
|
|
@ -15,6 +15,7 @@ import 'package:twonly/src/database/twonly.db.dart';
|
|||
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart';
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/exclusive_access.utils.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
|
|
@ -158,7 +159,8 @@ Future<void> handleDownloadStatusUpdate(TaskStatusUpdate update) async {
|
|||
}
|
||||
}
|
||||
|
||||
Mutex protectDownload = Mutex();
|
||||
Mutex _protectDownload = Mutex();
|
||||
Mutex _protectDecryption = Mutex();
|
||||
|
||||
Future<void> startDownloadMedia(MediaFile media, bool force) async {
|
||||
final mediaService = MediaFileService(media);
|
||||
|
|
@ -175,7 +177,7 @@ Future<void> startDownloadMedia(MediaFile media, bool force) async {
|
|||
return;
|
||||
}
|
||||
|
||||
final isBlocked = await protectDownload.protect<bool>(() async {
|
||||
final isBlocked = await _protectDownload.protect<bool>(() async {
|
||||
final msg = await twonlyDB.mediaFilesDao.getMediaFileById(media.mediaId);
|
||||
|
||||
if (msg == null || msg.downloadState != DownloadState.pending) {
|
||||
|
|
@ -285,66 +287,86 @@ Future<void> requestMediaReupload(String mediaId) async {
|
|||
}
|
||||
|
||||
Future<void> handleEncryptedFile(String mediaId) async {
|
||||
final mediaService = await MediaFileService.fromMediaId(mediaId);
|
||||
if (mediaService == null) {
|
||||
Log.error('Media file not found in database.');
|
||||
return;
|
||||
}
|
||||
await exclusiveAccess(
|
||||
lockName: 'decryption-$mediaId',
|
||||
mutex: _protectDecryption,
|
||||
action: () async {
|
||||
final mediaService = await MediaFileService.fromMediaId(mediaId);
|
||||
if (mediaService == null) {
|
||||
Log.error('Media file not found in database.');
|
||||
return;
|
||||
}
|
||||
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaId,
|
||||
const MediaFilesCompanion(
|
||||
downloadState: Value(DownloadState.downloaded),
|
||||
),
|
||||
if (mediaService.mediaFile.downloadState == DownloadState.ready) {
|
||||
Log.info('Decryption of $mediaId already finished.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mediaService.encryptedPath.existsSync()) {
|
||||
Log.warn(
|
||||
'Encrypted media file $mediaId does not exist anymore. Decryption probably already finished.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
late Uint8List encryptedBytes;
|
||||
try {
|
||||
encryptedBytes = await mediaService.encryptedPath.readAsBytes();
|
||||
} catch (e) {
|
||||
Log.error('Could not read encrypted media file: $e');
|
||||
await requestMediaReupload(mediaId);
|
||||
return;
|
||||
}
|
||||
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaId,
|
||||
const MediaFilesCompanion(
|
||||
downloadState: Value(DownloadState.downloaded),
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||
final secretKeyData = SecretKeyData(
|
||||
mediaService.mediaFile.encryptionKey!,
|
||||
);
|
||||
|
||||
final secretBox = SecretBox(
|
||||
encryptedBytes,
|
||||
nonce: mediaService.mediaFile.encryptionNonce!,
|
||||
mac: Mac(mediaService.mediaFile.encryptionMac!),
|
||||
);
|
||||
|
||||
final plaintextBytes = await chacha20.decrypt(
|
||||
secretBox,
|
||||
secretKey: secretKeyData,
|
||||
);
|
||||
|
||||
final rawMediaBytes = Uint8List.fromList(plaintextBytes);
|
||||
|
||||
await mediaService.tempPath.writeAsBytes(rawMediaBytes);
|
||||
} catch (e) {
|
||||
Log.error(
|
||||
'Could not decrypt the media file. Requesting a new upload.',
|
||||
);
|
||||
await requestMediaReupload(mediaId);
|
||||
return;
|
||||
}
|
||||
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaId,
|
||||
const MediaFilesCompanion(
|
||||
downloadState: Value(DownloadState.ready),
|
||||
),
|
||||
);
|
||||
|
||||
Log.info('Decryption of $mediaId was successful');
|
||||
|
||||
mediaService.encryptedPath.deleteSync();
|
||||
|
||||
unawaited(apiService.downloadDone(mediaService.mediaFile.downloadToken!));
|
||||
},
|
||||
);
|
||||
|
||||
late Uint8List encryptedBytes;
|
||||
try {
|
||||
encryptedBytes = await mediaService.encryptedPath.readAsBytes();
|
||||
} catch (e) {
|
||||
Log.error('Could not read encrypted media file: $e');
|
||||
await requestMediaReupload(mediaId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||
final secretKeyData = SecretKeyData(mediaService.mediaFile.encryptionKey!);
|
||||
|
||||
final secretBox = SecretBox(
|
||||
encryptedBytes,
|
||||
nonce: mediaService.mediaFile.encryptionNonce!,
|
||||
mac: Mac(mediaService.mediaFile.encryptionMac!),
|
||||
);
|
||||
|
||||
final plaintextBytes = await chacha20.decrypt(
|
||||
secretBox,
|
||||
secretKey: secretKeyData,
|
||||
);
|
||||
|
||||
final rawMediaBytes = Uint8List.fromList(plaintextBytes);
|
||||
|
||||
await mediaService.tempPath.writeAsBytes(rawMediaBytes);
|
||||
} catch (e) {
|
||||
Log.error(
|
||||
'Could not decrypt the media file. Requesting a new upload.',
|
||||
);
|
||||
await requestMediaReupload(mediaId);
|
||||
return;
|
||||
}
|
||||
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaId,
|
||||
const MediaFilesCompanion(
|
||||
downloadState: Value(DownloadState.ready),
|
||||
),
|
||||
);
|
||||
|
||||
Log.info('Decryption of $mediaId was successful');
|
||||
|
||||
mediaService.encryptedPath.deleteSync();
|
||||
|
||||
unawaited(apiService.downloadDone(mediaService.mediaFile.downloadToken!));
|
||||
}
|
||||
|
||||
Future<void> makeMigrationToVersion91() async {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/main.dart';
|
||||
import 'package:twonly/src/constants/keyvalue.keys.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
||||
import 'package:twonly/src/utils/exclusive_access.dart';
|
||||
import 'package:twonly/src/utils/exclusive_access.utils.dart';
|
||||
import 'package:twonly/src/utils/keyvalue.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Future<T> exclusiveAccess<T>({
|
|||
required Future<T> Function() action,
|
||||
required Mutex mutex,
|
||||
}) async {
|
||||
final lockFile = File('${AppEnvironment.supportDir}/$lockName.lock');
|
||||
final lockFile = File('${AppEnvironment.cacheDir}/$lockName.lock');
|
||||
return mutex.protect(() async {
|
||||
var lockAcquired = false;
|
||||
|
||||
|
|
@ -24,8 +24,8 @@ Future<T> exclusiveAccess<T>({
|
|||
try {
|
||||
final stat = lockFile.statSync();
|
||||
if (stat.type != FileSystemEntityType.notFound) {
|
||||
final age = DateTime.now().difference(stat.modified).inMilliseconds;
|
||||
if (age > 1000) {
|
||||
final age = DateTime.now().difference(stat.modified).inSeconds;
|
||||
if (age > 10) {
|
||||
lockFile.deleteSync();
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1,14 +1,27 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/utils/exclusive_access.utils.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
class KeyValueStore {
|
||||
static final Mutex _mutex = Mutex();
|
||||
|
||||
static Future<File> _getFilePath(String key) async {
|
||||
return File('${AppEnvironment.supportDir}/keyvalue/$key.json');
|
||||
}
|
||||
|
||||
static Future<void> delete(String key) async {
|
||||
static Future<T> _exclusive<T>(String key, Future<T> Function() action) {
|
||||
return exclusiveAccess(
|
||||
lockName: 'keyvalue-$key',
|
||||
mutex: _mutex,
|
||||
action: action,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> delete(String key) => _exclusive(key, () async {
|
||||
try {
|
||||
final file = await _getFilePath(key);
|
||||
if (file.existsSync()) {
|
||||
|
|
@ -17,30 +30,33 @@ class KeyValueStore {
|
|||
} catch (e) {
|
||||
Log.error('Error deleting file: $e');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
static Future<Map<String, dynamic>?> get(String key) async {
|
||||
try {
|
||||
final file = await _getFilePath(key);
|
||||
if (file.existsSync()) {
|
||||
final contents = await file.readAsString();
|
||||
return jsonDecode(contents) as Map<String, dynamic>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
Log.warn('Error reading file: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
static Future<Map<String, dynamic>?> get(String key) =>
|
||||
_exclusive(key, () async {
|
||||
final file = await _getFilePath(key);
|
||||
try {
|
||||
if (file.existsSync()) {
|
||||
final contents = await file.readAsString();
|
||||
return jsonDecode(contents) as Map<String, dynamic>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
Log.warn('Error reading file. Deleting it.: $e');
|
||||
file.deleteSync();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
static Future<void> put(String key, Map<String, dynamic> value) async {
|
||||
try {
|
||||
final file = await _getFilePath(key);
|
||||
await file.parent.create(recursive: true);
|
||||
await file.writeAsString(jsonEncode(value));
|
||||
} catch (e) {
|
||||
Log.error('Error writing file: $e');
|
||||
}
|
||||
}
|
||||
static Future<void> put(String key, Map<String, dynamic> value) =>
|
||||
_exclusive(key, () async {
|
||||
try {
|
||||
final file = await _getFilePath(key);
|
||||
await file.parent.create(recursive: true);
|
||||
await file.writeAsString(jsonEncode(value));
|
||||
} catch (e) {
|
||||
Log.error('Error writing file: $e');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import 'package:logging/logging.dart';
|
|||
import 'package:mutex/mutex.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/utils/exclusive_access.dart';
|
||||
import 'package:twonly/src/utils/exclusive_access.utils.dart';
|
||||
|
||||
class Log {
|
||||
static bool _isInitialized = false;
|
||||
|
|
|
|||
Loading…
Reference in a new issue