mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:28:41 +00:00
fix #207
This commit is contained in:
parent
4108d9a798
commit
80746b5139
29 changed files with 4852 additions and 390 deletions
271
.vscode/launch.json
vendored
Normal file
271
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "twonly-app",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "twonly-app (profile mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "twonly-app (release mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage",
|
||||
"cwd": "dependencies/flutter_secure_storage",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "flutter_zxing",
|
||||
"cwd": "dependencies/flutter_zxing",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "flutter_zxing (profile mode)",
|
||||
"cwd": "dependencies/flutter_zxing",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "flutter_zxing (release mode)",
|
||||
"cwd": "dependencies/flutter_zxing",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage (profile mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage (release mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_darwin",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_darwin",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_darwin (profile mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_darwin",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_darwin (release mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_darwin",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_linux",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_linux",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_linux (profile mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_linux",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_linux (release mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_linux",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_platform_interface",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_platform_interface",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_platform_interface (profile mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_platform_interface",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_platform_interface (release mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_platform_interface",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_web",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_web",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_web (profile mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_web",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_web (release mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_web",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_windows",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_windows",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_windows (profile mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_windows",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_windows (release mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_windows",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "example",
|
||||
"cwd": "dependencies/flutter_zxing/example",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "example (profile mode)",
|
||||
"cwd": "dependencies/flutter_zxing/example",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "example (release mode)",
|
||||
"cwd": "dependencies/flutter_zxing/example",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "zxscanner",
|
||||
"cwd": "dependencies/flutter_zxing/zxscanner",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "zxscanner (profile mode)",
|
||||
"cwd": "dependencies/flutter_zxing/zxscanner",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "zxscanner (release mode)",
|
||||
"cwd": "dependencies/flutter_zxing/zxscanner",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_macos",
|
||||
"cwd": "dependencies/flutter_secure_storage/archived_packages/flutter_secure_storage_macos",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_macos (profile mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/archived_packages/flutter_secure_storage_macos",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "flutter_secure_storage_macos (release mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/archived_packages/flutter_secure_storage_macos",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "example",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage/example",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "example (profile mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage/example",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "example (release mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage/example",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
},
|
||||
{
|
||||
"name": "example",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_windows/example",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "example (profile mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_windows/example",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "example (release mode)",
|
||||
"cwd": "dependencies/flutter_secure_storage/flutter_secure_storage_windows/example",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
}
|
||||
]
|
||||
}
|
||||
1
drift_schemas/twonly_database/drift_schema_v12.json
Normal file
1
drift_schemas/twonly_database/drift_schema_v12.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -44,6 +44,7 @@ void main() async {
|
|||
twonlyDB = TwonlyDatabase();
|
||||
await twonlyDB.messagesDao.resetPendingDownloadState();
|
||||
await twonlyDB.messagesDao.handleMediaFilesOlderThan7Days();
|
||||
await twonlyDB.signalDao.purgeOutDatedPreKeys();
|
||||
|
||||
// purge media files in the background
|
||||
purgeReceivedMediaFiles();
|
||||
|
|
|
|||
|
|
@ -36,9 +36,34 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
|
|||
..where((t) => t.retransmissionId.equals(retransmissionId));
|
||||
}
|
||||
|
||||
Future updateRetransmission(
|
||||
int retransmissionId,
|
||||
MessageRetransmissionsCompanion updatedValues,
|
||||
) {
|
||||
return (update(messageRetransmissions)
|
||||
..where((c) => c.retransmissionId.equals(retransmissionId)))
|
||||
.write(updatedValues);
|
||||
}
|
||||
|
||||
Future resetAckStatusForAllMessages() {
|
||||
return ((update(messageRetransmissions))
|
||||
..where((m) => m.willNotGetACKByUser.equals(false)))
|
||||
.write(
|
||||
MessageRetransmissionsCompanion(
|
||||
acknowledgeByServerAt: Value(null),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future deleteRetransmissionById(int retransmissionId) {
|
||||
return (delete(messageRetransmissions)
|
||||
..where((t) => t.retransmissionId.equals(retransmissionId)))
|
||||
.go();
|
||||
}
|
||||
|
||||
Future deleteRetransmissionByMessageId(int messageId) {
|
||||
return (delete(messageRetransmissions)
|
||||
..where((t) => t.messageId.equals(messageId)))
|
||||
.go();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/tables/signal_contact_prekey_table.dart';
|
||||
import 'package:twonly/src/database/tables/signal_contact_signed_prekey_table.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
part 'signal_dao.g.dart';
|
||||
|
||||
@DriftAccessor(tables: [SignalContactPreKeys, SignalContactSignedPreKeys])
|
||||
@DriftAccessor(tables: [
|
||||
SignalContactPreKeys,
|
||||
SignalContactSignedPreKeys,
|
||||
])
|
||||
class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
||||
// this constructor is required so that the main database can create an instance
|
||||
// of this object.
|
||||
|
|
@ -19,6 +24,12 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
|||
.go();
|
||||
}
|
||||
|
||||
Future deleteAllPreKeysByContactId(int contactId) async {
|
||||
await (delete(signalContactPreKeys)
|
||||
..where((t) => t.contactId.equals(contactId)))
|
||||
.go();
|
||||
}
|
||||
|
||||
// 1: Count the number of pre-keys by contact ID
|
||||
Future<int> countPreKeysByContactId(int contactId) {
|
||||
return (select(signalContactPreKeys)
|
||||
|
|
@ -49,9 +60,13 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
|||
// 3: Insert multiple pre-keys
|
||||
Future<void> insertPreKeys(
|
||||
List<SignalContactPreKeysCompanion> preKeys) async {
|
||||
await batch((batch) {
|
||||
batch.insertAll(signalContactPreKeys, preKeys);
|
||||
});
|
||||
for (final preKey in preKeys) {
|
||||
try {
|
||||
into(signalContactPreKeys).insert(preKey);
|
||||
} catch (e) {
|
||||
Log.error("$e");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4: Get signed pre-key by contact ID
|
||||
|
|
@ -64,12 +79,28 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
|||
// 5: Insert or update signed pre-key by contact ID
|
||||
Future<void> insertOrUpdateSignedPreKeyByContactId(
|
||||
SignalContactSignedPreKeysCompanion signedPreKey) async {
|
||||
final existingKey =
|
||||
await getSignedPreKeyByContactId(signedPreKey.contactId.value);
|
||||
if (existingKey != null) {
|
||||
await update(signalContactSignedPreKeys).replace(signedPreKey);
|
||||
} else {
|
||||
await into(signalContactSignedPreKeys).insert(signedPreKey);
|
||||
}
|
||||
await (delete(signalContactSignedPreKeys)
|
||||
..where((t) => t.contactId.equals(signedPreKey.contactId.value)))
|
||||
.go();
|
||||
await into(signalContactSignedPreKeys).insert(signedPreKey);
|
||||
}
|
||||
|
||||
Future<void> purgeOutDatedPreKeys() async {
|
||||
// other pre keys are valid 25 days
|
||||
await (delete(signalContactSignedPreKeys)
|
||||
..where((t) => (t.createdAt.isSmallerThanValue(
|
||||
DateTime.now().subtract(
|
||||
Duration(days: 25),
|
||||
),
|
||||
))))
|
||||
.go();
|
||||
// own pre keys are valid for 40 days
|
||||
await (delete(twonlyDB.signalPreKeyStores)
|
||||
..where((t) => (t.createdAt.isSmallerThanValue(
|
||||
DateTime.now().subtract(
|
||||
Duration(days: 40),
|
||||
),
|
||||
))))
|
||||
.go();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,25 +32,6 @@ class ConnectPreKeyStore extends PreKeyStore {
|
|||
.go();
|
||||
}
|
||||
|
||||
static Future<int?> getNextPreKeyId() async {
|
||||
try {
|
||||
String tableName = twonlyDB.signalPreKeyStores.actualTableName;
|
||||
String columnName = twonlyDB.signalPreKeyStores.preKeyId.name;
|
||||
|
||||
final result = await twonlyDB
|
||||
.customSelect('SELECT MAX($columnName) AS max_id FROM $tableName')
|
||||
.get();
|
||||
int? count = result.first.read<int?>('max_id');
|
||||
if (count == null) {
|
||||
return 0;
|
||||
}
|
||||
return count + 1;
|
||||
} catch (e) {
|
||||
Log.error("$e");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> storePreKey(int preKeyId, PreKeyRecord record) async {
|
||||
final preKeyCompanion = SignalPreKeyStoresCompanion(
|
||||
|
|
|
|||
|
|
@ -22,24 +22,6 @@ class ConnectSignedPreKeyStore extends SignedPreKeyStore {
|
|||
return store;
|
||||
}
|
||||
|
||||
Future<int> getNextKeyId() async {
|
||||
final storage = FlutterSecureStorage();
|
||||
final storeSerialized = await storage.read(
|
||||
key: SecureStorageKeys.signalSignedPreKey,
|
||||
);
|
||||
if (storeSerialized == null) {
|
||||
return 0;
|
||||
}
|
||||
final storeHashMap = json.decode(storeSerialized);
|
||||
var maxKeyId = 0;
|
||||
for (final item in storeHashMap) {
|
||||
if (maxKeyId < item[0]) {
|
||||
maxKeyId = item[0];
|
||||
}
|
||||
}
|
||||
return maxKeyId + 1;
|
||||
}
|
||||
|
||||
Future safeStore(HashMap<int, Uint8List> store) async {
|
||||
final storage = FlutterSecureStorage();
|
||||
var storeHashMap = [];
|
||||
|
|
|
|||
|
|
@ -15,5 +15,8 @@ class MessageRetransmissions extends Table {
|
|||
BlobColumn get plaintextContent => blob()();
|
||||
BlobColumn get pushData => blob().nullable()();
|
||||
|
||||
BoolColumn get willNotGetACKByUser =>
|
||||
boolean().withDefault(Constant(false))();
|
||||
|
||||
DateTimeColumn get acknowledgeByServerAt => dateTime().nullable()();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ enum MessageKind {
|
|||
opened,
|
||||
ack,
|
||||
pushKey,
|
||||
requestPushKey,
|
||||
receiveMediaError,
|
||||
signalDecryptError
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class TwonlyDatabase extends _$TwonlyDatabase {
|
|||
TwonlyDatabase.forTesting(DatabaseConnection super.connection);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 11;
|
||||
int get schemaVersion => 12;
|
||||
|
||||
static QueryExecutor _openConnection() {
|
||||
return driftDatabase(
|
||||
|
|
@ -121,6 +121,10 @@ class TwonlyDatabase extends _$TwonlyDatabase {
|
|||
from10To11: (m, schema) async {
|
||||
m.createTable(messageRetransmissions);
|
||||
},
|
||||
from11To12: (m, schema) async {
|
||||
m.addColumn(schema.messageRetransmissions,
|
||||
schema.messageRetransmissions.willNotGetACKByUser);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -135,6 +139,12 @@ class TwonlyDatabase extends _$TwonlyDatabase {
|
|||
await delete(messageRetransmissions).go();
|
||||
await delete(mediaDownloads).go();
|
||||
await delete(mediaUploads).go();
|
||||
await update(contacts).write(
|
||||
ContactsCompanion(
|
||||
avatarSvg: Value(null),
|
||||
myAvatarCounter: Value(0),
|
||||
),
|
||||
);
|
||||
await delete(signalContactPreKeys).go();
|
||||
await delete(signalContactSignedPreKeys).go();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4225,6 +4225,16 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
|
|||
late final GeneratedColumn<Uint8List> pushData = GeneratedColumn<Uint8List>(
|
||||
'push_data', aliasedName, true,
|
||||
type: DriftSqlType.blob, requiredDuringInsert: false);
|
||||
static const VerificationMeta _willNotGetACKByUserMeta =
|
||||
const VerificationMeta('willNotGetACKByUser');
|
||||
@override
|
||||
late final GeneratedColumn<bool> willNotGetACKByUser = GeneratedColumn<bool>(
|
||||
'will_not_get_a_c_k_by_user', aliasedName, false,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("will_not_get_a_c_k_by_user" IN (0, 1))'),
|
||||
defaultValue: Constant(false));
|
||||
static const VerificationMeta _acknowledgeByServerAtMeta =
|
||||
const VerificationMeta('acknowledgeByServerAt');
|
||||
@override
|
||||
|
|
@ -4238,6 +4248,7 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
|
|||
messageId,
|
||||
plaintextContent,
|
||||
pushData,
|
||||
willNotGetACKByUser,
|
||||
acknowledgeByServerAt
|
||||
];
|
||||
@override
|
||||
|
|
@ -4279,6 +4290,12 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
|
|||
context.handle(_pushDataMeta,
|
||||
pushData.isAcceptableOrUnknown(data['push_data']!, _pushDataMeta));
|
||||
}
|
||||
if (data.containsKey('will_not_get_a_c_k_by_user')) {
|
||||
context.handle(
|
||||
_willNotGetACKByUserMeta,
|
||||
willNotGetACKByUser.isAcceptableOrUnknown(
|
||||
data['will_not_get_a_c_k_by_user']!, _willNotGetACKByUserMeta));
|
||||
}
|
||||
if (data.containsKey('acknowledge_by_server_at')) {
|
||||
context.handle(
|
||||
_acknowledgeByServerAtMeta,
|
||||
|
|
@ -4304,6 +4321,8 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
|
|||
DriftSqlType.blob, data['${effectivePrefix}plaintext_content'])!,
|
||||
pushData: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.blob, data['${effectivePrefix}push_data']),
|
||||
willNotGetACKByUser: attachedDatabase.typeMapping.read(DriftSqlType.bool,
|
||||
data['${effectivePrefix}will_not_get_a_c_k_by_user'])!,
|
||||
acknowledgeByServerAt: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}acknowledge_by_server_at']),
|
||||
|
|
@ -4323,6 +4342,7 @@ class MessageRetransmission extends DataClass
|
|||
final int? messageId;
|
||||
final Uint8List plaintextContent;
|
||||
final Uint8List? pushData;
|
||||
final bool willNotGetACKByUser;
|
||||
final DateTime? acknowledgeByServerAt;
|
||||
const MessageRetransmission(
|
||||
{required this.retransmissionId,
|
||||
|
|
@ -4330,6 +4350,7 @@ class MessageRetransmission extends DataClass
|
|||
this.messageId,
|
||||
required this.plaintextContent,
|
||||
this.pushData,
|
||||
required this.willNotGetACKByUser,
|
||||
this.acknowledgeByServerAt});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
|
|
@ -4343,6 +4364,7 @@ class MessageRetransmission extends DataClass
|
|||
if (!nullToAbsent || pushData != null) {
|
||||
map['push_data'] = Variable<Uint8List>(pushData);
|
||||
}
|
||||
map['will_not_get_a_c_k_by_user'] = Variable<bool>(willNotGetACKByUser);
|
||||
if (!nullToAbsent || acknowledgeByServerAt != null) {
|
||||
map['acknowledge_by_server_at'] =
|
||||
Variable<DateTime>(acknowledgeByServerAt);
|
||||
|
|
@ -4361,6 +4383,7 @@ class MessageRetransmission extends DataClass
|
|||
pushData: pushData == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(pushData),
|
||||
willNotGetACKByUser: Value(willNotGetACKByUser),
|
||||
acknowledgeByServerAt: acknowledgeByServerAt == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(acknowledgeByServerAt),
|
||||
|
|
@ -4377,6 +4400,8 @@ class MessageRetransmission extends DataClass
|
|||
plaintextContent:
|
||||
serializer.fromJson<Uint8List>(json['plaintextContent']),
|
||||
pushData: serializer.fromJson<Uint8List?>(json['pushData']),
|
||||
willNotGetACKByUser:
|
||||
serializer.fromJson<bool>(json['willNotGetACKByUser']),
|
||||
acknowledgeByServerAt:
|
||||
serializer.fromJson<DateTime?>(json['acknowledgeByServerAt']),
|
||||
);
|
||||
|
|
@ -4390,6 +4415,7 @@ class MessageRetransmission extends DataClass
|
|||
'messageId': serializer.toJson<int?>(messageId),
|
||||
'plaintextContent': serializer.toJson<Uint8List>(plaintextContent),
|
||||
'pushData': serializer.toJson<Uint8List?>(pushData),
|
||||
'willNotGetACKByUser': serializer.toJson<bool>(willNotGetACKByUser),
|
||||
'acknowledgeByServerAt':
|
||||
serializer.toJson<DateTime?>(acknowledgeByServerAt),
|
||||
};
|
||||
|
|
@ -4401,6 +4427,7 @@ class MessageRetransmission extends DataClass
|
|||
Value<int?> messageId = const Value.absent(),
|
||||
Uint8List? plaintextContent,
|
||||
Value<Uint8List?> pushData = const Value.absent(),
|
||||
bool? willNotGetACKByUser,
|
||||
Value<DateTime?> acknowledgeByServerAt = const Value.absent()}) =>
|
||||
MessageRetransmission(
|
||||
retransmissionId: retransmissionId ?? this.retransmissionId,
|
||||
|
|
@ -4408,6 +4435,7 @@ class MessageRetransmission extends DataClass
|
|||
messageId: messageId.present ? messageId.value : this.messageId,
|
||||
plaintextContent: plaintextContent ?? this.plaintextContent,
|
||||
pushData: pushData.present ? pushData.value : this.pushData,
|
||||
willNotGetACKByUser: willNotGetACKByUser ?? this.willNotGetACKByUser,
|
||||
acknowledgeByServerAt: acknowledgeByServerAt.present
|
||||
? acknowledgeByServerAt.value
|
||||
: this.acknowledgeByServerAt,
|
||||
|
|
@ -4424,6 +4452,9 @@ class MessageRetransmission extends DataClass
|
|||
? data.plaintextContent.value
|
||||
: this.plaintextContent,
|
||||
pushData: data.pushData.present ? data.pushData.value : this.pushData,
|
||||
willNotGetACKByUser: data.willNotGetACKByUser.present
|
||||
? data.willNotGetACKByUser.value
|
||||
: this.willNotGetACKByUser,
|
||||
acknowledgeByServerAt: data.acknowledgeByServerAt.present
|
||||
? data.acknowledgeByServerAt.value
|
||||
: this.acknowledgeByServerAt,
|
||||
|
|
@ -4438,6 +4469,7 @@ class MessageRetransmission extends DataClass
|
|||
..write('messageId: $messageId, ')
|
||||
..write('plaintextContent: $plaintextContent, ')
|
||||
..write('pushData: $pushData, ')
|
||||
..write('willNotGetACKByUser: $willNotGetACKByUser, ')
|
||||
..write('acknowledgeByServerAt: $acknowledgeByServerAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
|
|
@ -4450,6 +4482,7 @@ class MessageRetransmission extends DataClass
|
|||
messageId,
|
||||
$driftBlobEquality.hash(plaintextContent),
|
||||
$driftBlobEquality.hash(pushData),
|
||||
willNotGetACKByUser,
|
||||
acknowledgeByServerAt);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
|
|
@ -4461,6 +4494,7 @@ class MessageRetransmission extends DataClass
|
|||
$driftBlobEquality.equals(
|
||||
other.plaintextContent, this.plaintextContent) &&
|
||||
$driftBlobEquality.equals(other.pushData, this.pushData) &&
|
||||
other.willNotGetACKByUser == this.willNotGetACKByUser &&
|
||||
other.acknowledgeByServerAt == this.acknowledgeByServerAt);
|
||||
}
|
||||
|
||||
|
|
@ -4471,6 +4505,7 @@ class MessageRetransmissionsCompanion
|
|||
final Value<int?> messageId;
|
||||
final Value<Uint8List> plaintextContent;
|
||||
final Value<Uint8List?> pushData;
|
||||
final Value<bool> willNotGetACKByUser;
|
||||
final Value<DateTime?> acknowledgeByServerAt;
|
||||
const MessageRetransmissionsCompanion({
|
||||
this.retransmissionId = const Value.absent(),
|
||||
|
|
@ -4478,6 +4513,7 @@ class MessageRetransmissionsCompanion
|
|||
this.messageId = const Value.absent(),
|
||||
this.plaintextContent = const Value.absent(),
|
||||
this.pushData = const Value.absent(),
|
||||
this.willNotGetACKByUser = const Value.absent(),
|
||||
this.acknowledgeByServerAt = const Value.absent(),
|
||||
});
|
||||
MessageRetransmissionsCompanion.insert({
|
||||
|
|
@ -4486,6 +4522,7 @@ class MessageRetransmissionsCompanion
|
|||
this.messageId = const Value.absent(),
|
||||
required Uint8List plaintextContent,
|
||||
this.pushData = const Value.absent(),
|
||||
this.willNotGetACKByUser = const Value.absent(),
|
||||
this.acknowledgeByServerAt = const Value.absent(),
|
||||
}) : contactId = Value(contactId),
|
||||
plaintextContent = Value(plaintextContent);
|
||||
|
|
@ -4495,6 +4532,7 @@ class MessageRetransmissionsCompanion
|
|||
Expression<int>? messageId,
|
||||
Expression<Uint8List>? plaintextContent,
|
||||
Expression<Uint8List>? pushData,
|
||||
Expression<bool>? willNotGetACKByUser,
|
||||
Expression<DateTime>? acknowledgeByServerAt,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
|
|
@ -4503,6 +4541,8 @@ class MessageRetransmissionsCompanion
|
|||
if (messageId != null) 'message_id': messageId,
|
||||
if (plaintextContent != null) 'plaintext_content': plaintextContent,
|
||||
if (pushData != null) 'push_data': pushData,
|
||||
if (willNotGetACKByUser != null)
|
||||
'will_not_get_a_c_k_by_user': willNotGetACKByUser,
|
||||
if (acknowledgeByServerAt != null)
|
||||
'acknowledge_by_server_at': acknowledgeByServerAt,
|
||||
});
|
||||
|
|
@ -4514,6 +4554,7 @@ class MessageRetransmissionsCompanion
|
|||
Value<int?>? messageId,
|
||||
Value<Uint8List>? plaintextContent,
|
||||
Value<Uint8List?>? pushData,
|
||||
Value<bool>? willNotGetACKByUser,
|
||||
Value<DateTime?>? acknowledgeByServerAt}) {
|
||||
return MessageRetransmissionsCompanion(
|
||||
retransmissionId: retransmissionId ?? this.retransmissionId,
|
||||
|
|
@ -4521,6 +4562,7 @@ class MessageRetransmissionsCompanion
|
|||
messageId: messageId ?? this.messageId,
|
||||
plaintextContent: plaintextContent ?? this.plaintextContent,
|
||||
pushData: pushData ?? this.pushData,
|
||||
willNotGetACKByUser: willNotGetACKByUser ?? this.willNotGetACKByUser,
|
||||
acknowledgeByServerAt:
|
||||
acknowledgeByServerAt ?? this.acknowledgeByServerAt,
|
||||
);
|
||||
|
|
@ -4544,6 +4586,10 @@ class MessageRetransmissionsCompanion
|
|||
if (pushData.present) {
|
||||
map['push_data'] = Variable<Uint8List>(pushData.value);
|
||||
}
|
||||
if (willNotGetACKByUser.present) {
|
||||
map['will_not_get_a_c_k_by_user'] =
|
||||
Variable<bool>(willNotGetACKByUser.value);
|
||||
}
|
||||
if (acknowledgeByServerAt.present) {
|
||||
map['acknowledge_by_server_at'] =
|
||||
Variable<DateTime>(acknowledgeByServerAt.value);
|
||||
|
|
@ -4559,6 +4605,7 @@ class MessageRetransmissionsCompanion
|
|||
..write('messageId: $messageId, ')
|
||||
..write('plaintextContent: $plaintextContent, ')
|
||||
..write('pushData: $pushData, ')
|
||||
..write('willNotGetACKByUser: $willNotGetACKByUser, ')
|
||||
..write('acknowledgeByServerAt: $acknowledgeByServerAt')
|
||||
..write(')'))
|
||||
.toString();
|
||||
|
|
@ -7107,6 +7154,7 @@ typedef $$MessageRetransmissionsTableCreateCompanionBuilder
|
|||
Value<int?> messageId,
|
||||
required Uint8List plaintextContent,
|
||||
Value<Uint8List?> pushData,
|
||||
Value<bool> willNotGetACKByUser,
|
||||
Value<DateTime?> acknowledgeByServerAt,
|
||||
});
|
||||
typedef $$MessageRetransmissionsTableUpdateCompanionBuilder
|
||||
|
|
@ -7116,6 +7164,7 @@ typedef $$MessageRetransmissionsTableUpdateCompanionBuilder
|
|||
Value<int?> messageId,
|
||||
Value<Uint8List> plaintextContent,
|
||||
Value<Uint8List?> pushData,
|
||||
Value<bool> willNotGetACKByUser,
|
||||
Value<DateTime?> acknowledgeByServerAt,
|
||||
});
|
||||
|
||||
|
|
@ -7175,6 +7224,10 @@ class $$MessageRetransmissionsTableFilterComposer
|
|||
ColumnFilters<Uint8List> get pushData => $composableBuilder(
|
||||
column: $table.pushData, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<bool> get willNotGetACKByUser => $composableBuilder(
|
||||
column: $table.willNotGetACKByUser,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<DateTime> get acknowledgeByServerAt => $composableBuilder(
|
||||
column: $table.acknowledgeByServerAt,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
|
@ -7240,6 +7293,10 @@ class $$MessageRetransmissionsTableOrderingComposer
|
|||
ColumnOrderings<Uint8List> get pushData => $composableBuilder(
|
||||
column: $table.pushData, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<bool> get willNotGetACKByUser => $composableBuilder(
|
||||
column: $table.willNotGetACKByUser,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<DateTime> get acknowledgeByServerAt => $composableBuilder(
|
||||
column: $table.acknowledgeByServerAt,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
|
@ -7303,6 +7360,9 @@ class $$MessageRetransmissionsTableAnnotationComposer
|
|||
GeneratedColumn<Uint8List> get pushData =>
|
||||
$composableBuilder(column: $table.pushData, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<bool> get willNotGetACKByUser => $composableBuilder(
|
||||
column: $table.willNotGetACKByUser, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DateTime> get acknowledgeByServerAt => $composableBuilder(
|
||||
column: $table.acknowledgeByServerAt, builder: (column) => column);
|
||||
|
||||
|
|
@ -7379,6 +7439,7 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
|
|||
Value<int?> messageId = const Value.absent(),
|
||||
Value<Uint8List> plaintextContent = const Value.absent(),
|
||||
Value<Uint8List?> pushData = const Value.absent(),
|
||||
Value<bool> willNotGetACKByUser = const Value.absent(),
|
||||
Value<DateTime?> acknowledgeByServerAt = const Value.absent(),
|
||||
}) =>
|
||||
MessageRetransmissionsCompanion(
|
||||
|
|
@ -7387,6 +7448,7 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
|
|||
messageId: messageId,
|
||||
plaintextContent: plaintextContent,
|
||||
pushData: pushData,
|
||||
willNotGetACKByUser: willNotGetACKByUser,
|
||||
acknowledgeByServerAt: acknowledgeByServerAt,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
|
|
@ -7395,6 +7457,7 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
|
|||
Value<int?> messageId = const Value.absent(),
|
||||
required Uint8List plaintextContent,
|
||||
Value<Uint8List?> pushData = const Value.absent(),
|
||||
Value<bool> willNotGetACKByUser = const Value.absent(),
|
||||
Value<DateTime?> acknowledgeByServerAt = const Value.absent(),
|
||||
}) =>
|
||||
MessageRetransmissionsCompanion.insert(
|
||||
|
|
@ -7403,6 +7466,7 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
|
|||
messageId: messageId,
|
||||
plaintextContent: plaintextContent,
|
||||
pushData: pushData,
|
||||
willNotGetACKByUser: willNotGetACKByUser,
|
||||
acknowledgeByServerAt: acknowledgeByServerAt,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
|
|
|
|||
|
|
@ -2465,6 +2465,263 @@ i1.GeneratedColumn<i2.Uint8List> _column_66(String aliasedName) =>
|
|||
i1.GeneratedColumn<DateTime> _column_67(String aliasedName) =>
|
||||
i1.GeneratedColumn<DateTime>('acknowledge_by_server_at', aliasedName, true,
|
||||
type: i1.DriftSqlType.dateTime);
|
||||
|
||||
final class Schema12 extends i0.VersionedSchema {
|
||||
Schema12({required super.database}) : super(version: 12);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
contacts,
|
||||
messages,
|
||||
mediaUploads,
|
||||
mediaDownloads,
|
||||
signalIdentityKeyStores,
|
||||
signalPreKeyStores,
|
||||
signalSenderKeyStores,
|
||||
signalSessionStores,
|
||||
signalContactPreKeys,
|
||||
signalContactSignedPreKeys,
|
||||
messageRetransmissions,
|
||||
];
|
||||
late final Shape13 contacts = Shape13(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'contacts',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [
|
||||
'PRIMARY KEY(user_id)',
|
||||
],
|
||||
columns: [
|
||||
_column_0,
|
||||
_column_1,
|
||||
_column_2,
|
||||
_column_3,
|
||||
_column_4,
|
||||
_column_5,
|
||||
_column_6,
|
||||
_column_7,
|
||||
_column_8,
|
||||
_column_9,
|
||||
_column_39,
|
||||
_column_53,
|
||||
_column_57,
|
||||
_column_54,
|
||||
_column_40,
|
||||
_column_10,
|
||||
_column_11,
|
||||
_column_12,
|
||||
_column_13,
|
||||
_column_14,
|
||||
_column_55,
|
||||
_column_15,
|
||||
_column_16,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape10 messages = Shape10(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'messages',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [
|
||||
_column_17,
|
||||
_column_18,
|
||||
_column_19,
|
||||
_column_48,
|
||||
_column_49,
|
||||
_column_20,
|
||||
_column_21,
|
||||
_column_22,
|
||||
_column_52,
|
||||
_column_23,
|
||||
_column_24,
|
||||
_column_25,
|
||||
_column_26,
|
||||
_column_27,
|
||||
_column_28,
|
||||
_column_29,
|
||||
_column_30,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape7 mediaUploads = Shape7(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'media_uploads',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [
|
||||
_column_41,
|
||||
_column_42,
|
||||
_column_56,
|
||||
_column_44,
|
||||
_column_45,
|
||||
_column_46,
|
||||
_column_47,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape9 mediaDownloads = Shape9(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'media_downloads',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [
|
||||
_column_50,
|
||||
_column_51,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape2 signalIdentityKeyStores = Shape2(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_identity_key_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [
|
||||
'PRIMARY KEY(device_id, name)',
|
||||
],
|
||||
columns: [
|
||||
_column_31,
|
||||
_column_32,
|
||||
_column_33,
|
||||
_column_10,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape3 signalPreKeyStores = Shape3(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_pre_key_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [
|
||||
'PRIMARY KEY(pre_key_id)',
|
||||
],
|
||||
columns: [
|
||||
_column_34,
|
||||
_column_35,
|
||||
_column_10,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape4 signalSenderKeyStores = Shape4(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_sender_key_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [
|
||||
'PRIMARY KEY(sender_key_name)',
|
||||
],
|
||||
columns: [
|
||||
_column_36,
|
||||
_column_37,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape5 signalSessionStores = Shape5(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_session_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [
|
||||
'PRIMARY KEY(device_id, name)',
|
||||
],
|
||||
columns: [
|
||||
_column_31,
|
||||
_column_32,
|
||||
_column_38,
|
||||
_column_10,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape14 signalContactPreKeys = Shape14(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_contact_pre_keys',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [
|
||||
'PRIMARY KEY(contact_id, pre_key_id)',
|
||||
],
|
||||
columns: [
|
||||
_column_58,
|
||||
_column_34,
|
||||
_column_35,
|
||||
_column_10,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape15 signalContactSignedPreKeys = Shape15(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_contact_signed_pre_keys',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [
|
||||
'PRIMARY KEY(contact_id)',
|
||||
],
|
||||
columns: [
|
||||
_column_58,
|
||||
_column_59,
|
||||
_column_60,
|
||||
_column_61,
|
||||
_column_10,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
late final Shape17 messageRetransmissions = Shape17(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'message_retransmissions',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [
|
||||
_column_62,
|
||||
_column_63,
|
||||
_column_64,
|
||||
_column_65,
|
||||
_column_66,
|
||||
_column_68,
|
||||
_column_67,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null);
|
||||
}
|
||||
|
||||
class Shape17 extends i0.VersionedTable {
|
||||
Shape17({required super.source, required super.alias}) : super.aliased();
|
||||
i1.GeneratedColumn<int> get retransmissionId =>
|
||||
columnsByName['retransmission_id']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get contactId =>
|
||||
columnsByName['contact_id']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get messageId =>
|
||||
columnsByName['message_id']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<i2.Uint8List> get plaintextContent =>
|
||||
columnsByName['plaintext_content']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||
i1.GeneratedColumn<i2.Uint8List> get pushData =>
|
||||
columnsByName['push_data']! as i1.GeneratedColumn<i2.Uint8List>;
|
||||
i1.GeneratedColumn<bool> get willNotGetACKByUser =>
|
||||
columnsByName['will_not_get_a_c_k_by_user']! as i1.GeneratedColumn<bool>;
|
||||
i1.GeneratedColumn<DateTime> get acknowledgeByServerAt =>
|
||||
columnsByName['acknowledge_by_server_at']!
|
||||
as i1.GeneratedColumn<DateTime>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<bool> _column_68(String aliasedName) =>
|
||||
i1.GeneratedColumn<bool>('will_not_get_a_c_k_by_user', aliasedName, false,
|
||||
type: i1.DriftSqlType.bool,
|
||||
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("will_not_get_a_c_k_by_user" IN (0, 1))'),
|
||||
defaultValue: const CustomExpression('0'));
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
|
|
@ -2476,6 +2733,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
|
|
@ -2529,6 +2787,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
final migrator = i1.Migrator(database, schema);
|
||||
await from10To11(migrator, schema);
|
||||
return 11;
|
||||
case 11:
|
||||
final schema = Schema12(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from11To12(migrator, schema);
|
||||
return 12;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
|
|
@ -2546,6 +2809,7 @@ i1.OnUpgrade stepByStep({
|
|||
required Future<void> Function(i1.Migrator m, Schema9 schema) from8To9,
|
||||
required Future<void> Function(i1.Migrator m, Schema10 schema) from9To10,
|
||||
required Future<void> Function(i1.Migrator m, Schema11 schema) from10To11,
|
||||
required Future<void> Function(i1.Migrator m, Schema12 schema) from11To12,
|
||||
}) =>
|
||||
i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
|
|
@ -2559,4 +2823,5 @@ i1.OnUpgrade stepByStep({
|
|||
from8To9: from8To9,
|
||||
from9To10: from9To10,
|
||||
from10To11: from10To11,
|
||||
from11To12: from11To12,
|
||||
));
|
||||
|
|
|
|||
|
|
@ -37,11 +37,13 @@ class MessageJson {
|
|||
final MessageKind kind;
|
||||
final MessageContent? content;
|
||||
final int? messageId;
|
||||
int? retransId;
|
||||
DateTime timestamp;
|
||||
|
||||
MessageJson({
|
||||
required this.kind,
|
||||
this.messageId,
|
||||
this.retransId,
|
||||
required this.content,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
|
@ -57,6 +59,7 @@ class MessageJson {
|
|||
return MessageJson(
|
||||
kind: kind,
|
||||
messageId: (json['messageId'] as num?)?.toInt(),
|
||||
retransId: (json['retransId'] as num?)?.toInt(),
|
||||
content: MessageContent.fromJson(
|
||||
kind, json['content'] as Map<String, dynamic>),
|
||||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp']),
|
||||
|
|
@ -67,6 +70,7 @@ class MessageJson {
|
|||
'kind': kind.name,
|
||||
'content': content?.toJson(),
|
||||
'messageId': messageId,
|
||||
'retransId': retransId,
|
||||
'timestamp': timestamp.toUtc().millisecondsSinceEpoch,
|
||||
};
|
||||
}
|
||||
|
|
@ -90,6 +94,8 @@ class MessageContent {
|
|||
return ReopenedMediaFileContent.fromJson(json);
|
||||
case MessageKind.flameSync:
|
||||
return FlameSyncContent.fromJson(json);
|
||||
case MessageKind.ack:
|
||||
return AckContent.fromJson(json);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
@ -216,6 +222,27 @@ class ReopenedMediaFileContent extends MessageContent {
|
|||
}
|
||||
}
|
||||
|
||||
class AckContent extends MessageContent {
|
||||
int? messageIdToAck;
|
||||
int retransIdToAck;
|
||||
AckContent({required this.messageIdToAck, required this.retransIdToAck});
|
||||
|
||||
static AckContent fromJson(Map json) {
|
||||
return AckContent(
|
||||
messageIdToAck: json['messageIdToAck'],
|
||||
retransIdToAck: json['retransIdToAck'],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map toJson() {
|
||||
return {
|
||||
'messageIdToAck': messageIdToAck,
|
||||
'retransIdToAck': retransIdToAck,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileContent extends MessageContent {
|
||||
String avatarSvg;
|
||||
String displayName;
|
||||
|
|
|
|||
|
|
@ -62,6 +62,14 @@ class UserData {
|
|||
|
||||
DateTime? signalLastSignedPreKeyUpdated;
|
||||
|
||||
// -- Custom DATA --
|
||||
|
||||
@JsonKey(defaultValue: 100_000)
|
||||
int currentPreKeyIndexStart = 100_000;
|
||||
|
||||
@JsonKey(defaultValue: 100_000)
|
||||
int currentSignedPreKeyIndexStart = 100_000;
|
||||
|
||||
// --- BACKUP ---
|
||||
|
||||
DateTime? nextTimeToShowBackupNotice;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
|||
json['signalLastSignedPreKeyUpdated'] == null
|
||||
? null
|
||||
: DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String)
|
||||
..currentPreKeyIndexStart =
|
||||
(json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000
|
||||
..currentSignedPreKeyIndexStart =
|
||||
(json['currentSignedPreKeyIndexStart'] as num?)?.toInt() ?? 100000
|
||||
..nextTimeToShowBackupNotice = json['nextTimeToShowBackupNotice'] == null
|
||||
? null
|
||||
: DateTime.parse(json['nextTimeToShowBackupNotice'] as String)
|
||||
|
|
@ -83,6 +87,8 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'myBestFriendContactId': instance.myBestFriendContactId,
|
||||
'signalLastSignedPreKeyUpdated':
|
||||
instance.signalLastSignedPreKeyUpdated?.toIso8601String(),
|
||||
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
|
||||
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
||||
'nextTimeToShowBackupNotice':
|
||||
instance.nextTimeToShowBackupNotice?.toIso8601String(),
|
||||
'backupServer': instance.backupServer,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
|
|
@ -9,60 +8,23 @@ import 'package:twonly/src/database/tables/messages_table.dart';
|
|||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/model/json/userdata.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||
import 'package:twonly/src/services/api/media_upload.dart';
|
||||
import 'package:twonly/src/services/api/utils.dart';
|
||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
||||
class DirtyResendingItem {
|
||||
DirtyResendingItem({required this.gotLastAck});
|
||||
DateTime gotLastAck;
|
||||
Timer? timer;
|
||||
}
|
||||
|
||||
class DirtyResending {
|
||||
static final Map<int, DirtyResendingItem> _gotLastAck = {};
|
||||
|
||||
static Future gotAckFromUser(int contactID) async {
|
||||
_gotLastAck[contactID]?.timer?.cancel();
|
||||
|
||||
_gotLastAck[contactID] = DirtyResendingItem(gotLastAck: DateTime.now());
|
||||
_gotLastAck[contactID]?.timer = Timer(Duration(seconds: 10), () async {
|
||||
_gotLastAck.remove(contactID);
|
||||
_handleNonACKMessagesForUser(contactID);
|
||||
});
|
||||
}
|
||||
|
||||
static Future _handleNonACKMessagesForUser(int contactID) async {
|
||||
final List<Message> toResendMessages =
|
||||
await twonlyDB.messagesDao.getAllNonACKMessagesFromUser();
|
||||
|
||||
for (final Message message in toResendMessages) {
|
||||
Log.info("Got newer ACKs from user ${message.messageId}");
|
||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||
message.messageId,
|
||||
MessagesCompanion(
|
||||
errorWhileSending: Value(true),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future handleOlderNonAckMessages() async {}
|
||||
|
||||
Future tryTransmitMessages() async {
|
||||
final retransIds =
|
||||
await twonlyDB.messageRetransmissionDao.getRetransmitAbleMessages();
|
||||
|
||||
if (retransIds.isEmpty) return;
|
||||
|
||||
Log.info("Retransmitting ${retransIds.length} text messages");
|
||||
|
||||
if (retransIds.isEmpty) return;
|
||||
|
||||
for (final retransId in retransIds) {
|
||||
sendRetransmitMessage(retransId);
|
||||
//twonlyDB.messageRetransmissionDao.deleteRetransmissionById(retransId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,10 +62,6 @@ Future sendRetransmitMessage(int retransId) async {
|
|||
return;
|
||||
}
|
||||
|
||||
var hash = uint8ListToHex(
|
||||
Uint8List.fromList((await Sha256().hash(encryptedBytes)).bytes));
|
||||
Log.info("Sending message: ${hash.substring(0, 10)}");
|
||||
|
||||
Result resp = await apiService.sendTextMessage(
|
||||
retrans.contactId,
|
||||
encryptedBytes,
|
||||
|
|
@ -142,13 +100,23 @@ Future sendRetransmitMessage(int retransId) async {
|
|||
}
|
||||
|
||||
if (!retry) {
|
||||
await twonlyDB.messageRetransmissionDao.deleteRetransmissionById(retransId);
|
||||
if (retrans.willNotGetACKByUser) {
|
||||
await twonlyDB.messageRetransmissionDao
|
||||
.deleteRetransmissionById(retransId);
|
||||
} else {
|
||||
await twonlyDB.messageRetransmissionDao.updateRetransmission(
|
||||
retransId,
|
||||
MessageRetransmissionsCompanion(
|
||||
acknowledgeByServerAt: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encrypts and stores the message and then sends it in the background
|
||||
Future encryptAndSendMessageAsync(int? messageId, int userId, MessageJson msg,
|
||||
{PushKind? pushKind}) async {
|
||||
{PushKind? pushKind, bool willNotGetACKByUser = false}) async {
|
||||
if (gIsDemoUser) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -158,15 +126,13 @@ Future encryptAndSendMessageAsync(int? messageId, int userId, MessageJson msg,
|
|||
pushData = await getPushData(userId, pushKind);
|
||||
}
|
||||
|
||||
Uint8List plaintextContent =
|
||||
Uint8List.fromList(gzip.encode(utf8.encode(jsonEncode(msg.toJson()))));
|
||||
|
||||
int? retransId = await twonlyDB.messageRetransmissionDao.insertRetransmission(
|
||||
MessageRetransmissionsCompanion(
|
||||
contactId: Value(userId),
|
||||
messageId: Value(messageId),
|
||||
plaintextContent: Value(plaintextContent),
|
||||
plaintextContent: Value(Uint8List(0)),
|
||||
pushData: Value(pushData),
|
||||
willNotGetACKByUser: Value(willNotGetACKByUser),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
@ -175,6 +141,16 @@ Future encryptAndSendMessageAsync(int? messageId, int userId, MessageJson msg,
|
|||
return;
|
||||
}
|
||||
|
||||
msg.retransId = retransId;
|
||||
|
||||
Uint8List plaintextContent =
|
||||
Uint8List.fromList(gzip.encode(utf8.encode(jsonEncode(msg.toJson()))));
|
||||
|
||||
await twonlyDB.messageRetransmissionDao.updateRetransmission(
|
||||
retransId,
|
||||
MessageRetransmissionsCompanion(
|
||||
plaintextContent: Value(plaintextContent)));
|
||||
|
||||
// this can now be done in the background...
|
||||
sendRetransmitMessage(retransId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
|
|
@ -14,13 +13,13 @@ import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pbserve
|
|||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||
as server;
|
||||
import 'package:twonly/src/services/api/media_upload.dart';
|
||||
import 'package:twonly/src/services/api/messages.dart';
|
||||
import 'package:twonly/src/services/api/utils.dart';
|
||||
import 'package:twonly/src/services/api/media_download.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||
import 'package:twonly/src/services/signal/prekeys.signal.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
|
|
@ -36,9 +35,6 @@ Future handleServerMessage(server.ServerToClient msg) async {
|
|||
} else if (msg.v0.hasNewMessage()) {
|
||||
Uint8List body = Uint8List.fromList(msg.v0.newMessage.body);
|
||||
int fromUserId = msg.v0.newMessage.fromUserId.toInt();
|
||||
var hash = uint8ListToHex(Uint8List.fromList(
|
||||
(await Sha256().hash(msg.v0.newMessage.body)).bytes));
|
||||
Log.info("Got new message from server: ${hash.substring(0, 10)}");
|
||||
response = await handleNewMessage(fromUserId, body);
|
||||
} else {
|
||||
Log.error("Got a new message from the server: $msg");
|
||||
|
|
@ -56,9 +52,24 @@ Future handleServerMessage(server.ServerToClient msg) async {
|
|||
});
|
||||
}
|
||||
|
||||
DateTime lastSignalDecryptMessage = DateTime.now().subtract(Duration(hours: 1));
|
||||
DateTime lastPushKeyRequest = DateTime.now().subtract(Duration(hours: 1));
|
||||
|
||||
Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||
MessageJson? message = await signalDecryptMessage(fromUserId, body);
|
||||
if (message == null) {
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
fromUserId,
|
||||
MessageJson(
|
||||
kind: MessageKind.signalDecryptError,
|
||||
content: MessageContent(),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
Log.error("Could not decrypt others message!");
|
||||
|
||||
// Message is not valid, so server can delete it
|
||||
var ok = client.Response_Ok()..none = true;
|
||||
return client.Response()..ok = ok;
|
||||
|
|
@ -66,7 +77,58 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
|||
|
||||
Log.info("Got: ${message.kind}");
|
||||
|
||||
if (message.kind != MessageKind.ack && message.retransId != null) {
|
||||
Log.info("Sending ACK for ${message.kind}");
|
||||
|
||||
/// ACK every message
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
fromUserId,
|
||||
MessageJson(
|
||||
kind: MessageKind.ack,
|
||||
messageId: null,
|
||||
content: AckContent(
|
||||
messageIdToAck: message.messageId,
|
||||
retransIdToAck: message.retransId!),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
willNotGetACKByUser: true,
|
||||
);
|
||||
}
|
||||
|
||||
switch (message.kind) {
|
||||
case MessageKind.ack:
|
||||
final content = message.content;
|
||||
if (content is AckContent) {
|
||||
if (content.messageIdToAck != null) {
|
||||
final update = MessagesCompanion(
|
||||
acknowledgeByUser: Value(true),
|
||||
errorWhileSending: Value(false),
|
||||
);
|
||||
await twonlyDB.messagesDao.updateMessageByOtherUser(
|
||||
fromUserId,
|
||||
content.messageIdToAck!,
|
||||
update,
|
||||
);
|
||||
}
|
||||
|
||||
await twonlyDB.messageRetransmissionDao
|
||||
.deleteRetransmissionById(content.retransIdToAck);
|
||||
}
|
||||
break;
|
||||
case MessageKind.signalDecryptError:
|
||||
if (lastSignalDecryptMessage
|
||||
.isBefore(DateTime.now().subtract(Duration(seconds: 60)))) {
|
||||
Log.error(
|
||||
"Got signal decrypt error from other user! Sending all non ACK messages again.");
|
||||
lastSignalDecryptMessage = DateTime.now();
|
||||
await twonlyDB.signalDao.deleteAllPreKeysByContactId(fromUserId);
|
||||
await requestNewPrekeysForContact(fromUserId);
|
||||
await twonlyDB.messageRetransmissionDao.resetAckStatusForAllMessages();
|
||||
tryTransmitMessages();
|
||||
}
|
||||
|
||||
break;
|
||||
case MessageKind.contactRequest:
|
||||
return handleContactRequest(fromUserId, message);
|
||||
|
||||
|
|
@ -148,21 +210,12 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
|||
}
|
||||
break;
|
||||
|
||||
case MessageKind.ack:
|
||||
final update = MessagesCompanion(
|
||||
acknowledgeByUser: Value(true),
|
||||
errorWhileSending: Value(false),
|
||||
);
|
||||
await twonlyDB.messagesDao.updateMessageByOtherUser(
|
||||
fromUserId,
|
||||
message.messageId!,
|
||||
update,
|
||||
);
|
||||
|
||||
// search for older messages, that where not yet ack by the other party
|
||||
DirtyResending.gotAckFromUser(fromUserId);
|
||||
|
||||
break;
|
||||
case MessageKind.requestPushKey:
|
||||
if (lastPushKeyRequest
|
||||
.isBefore(DateTime.now().subtract(Duration(seconds: 60)))) {
|
||||
lastPushKeyRequest = DateTime.now();
|
||||
setupNotificationWithUsers(force: true);
|
||||
}
|
||||
|
||||
case MessageKind.pushKey:
|
||||
if (message.content != null) {
|
||||
|
|
@ -277,17 +330,6 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
|||
}
|
||||
}
|
||||
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
fromUserId,
|
||||
MessageJson(
|
||||
kind: MessageKind.ack,
|
||||
messageId: message.messageId!,
|
||||
content: MessageContent(),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
// unarchive contact when receiving a new message
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
fromUserId,
|
||||
|
|
|
|||
|
|
@ -254,6 +254,15 @@ Future<Uint8List?> getPushData(int toUserId, PushKind kind) async {
|
|||
// this will be enforced after every app uses this system... :/
|
||||
// return null;
|
||||
Log.error("Using insecure key as the receiver does not send a push key!");
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
toUserId,
|
||||
my.MessageJson(
|
||||
kind: MessageKind.requestPushKey,
|
||||
content: my.MessageContent(),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||
import 'package:twonly/src/database/signal/connect_pre_key_store.dart';
|
||||
import 'package:twonly/src/model/json/signal_identity.dart';
|
||||
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
|
||||
import 'package:twonly/src/model/json/userdata.dart';
|
||||
|
|
@ -59,9 +58,14 @@ Future signalHandleNewServerConnection() async {
|
|||
}
|
||||
|
||||
Future<List<PreKeyRecord>> signalGetPreKeys() async {
|
||||
int? start = await ConnectPreKeyStore.getNextPreKeyId();
|
||||
if (start == null) return [];
|
||||
print(start);
|
||||
final user = await getUser();
|
||||
if (user == null) return [];
|
||||
|
||||
int start = user.currentPreKeyIndexStart;
|
||||
await updateUserdata((user) {
|
||||
user.currentPreKeyIndexStart += 200;
|
||||
return user;
|
||||
});
|
||||
final preKeys = generatePreKeys(start, 200);
|
||||
final signalStore = await getSignalStore();
|
||||
if (signalStore == null) return [];
|
||||
|
|
@ -123,11 +127,17 @@ Future createIfNotExistsSignalIdentity() async {
|
|||
|
||||
Future<SignedPreKeyRecord?> _getNewSignalSignedPreKey() async {
|
||||
var identityKeyPair = await getSignalIdentityKeyPair();
|
||||
if (identityKeyPair == null) return null;
|
||||
final user = await getUser();
|
||||
final signalStore = await getSignalStore();
|
||||
if (signalStore == null) return null;
|
||||
if (identityKeyPair == null || signalStore == null || user == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int signedPreKeyId = await signalStore.signedPreKeyStore.getNextKeyId();
|
||||
int signedPreKeyId = user.currentSignedPreKeyIndexStart;
|
||||
await updateUserdata((user) {
|
||||
user.currentSignedPreKeyIndexStart += 1;
|
||||
return user;
|
||||
});
|
||||
|
||||
final SignedPreKeyRecord signedPreKey = generateSignedPreKey(
|
||||
identityKeyPair,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
|
@ -18,51 +19,70 @@ class OtherPreKeys {
|
|||
final List<int> signedPreKeySignature;
|
||||
}
|
||||
|
||||
Mutex requestNewKeys = Mutex();
|
||||
DateTime lastPreKeyRequest = DateTime.now().subtract(Duration(hours: 1));
|
||||
DateTime lastSignedPreKeyRequest = DateTime.now().subtract(Duration(hours: 1));
|
||||
|
||||
Future requestNewPrekeysForContact(int contactId) async {
|
||||
final otherKeys = await apiService.getPreKeysByUserId(contactId);
|
||||
if (otherKeys != null) {
|
||||
Log.info("got fresh pre keys from other $contactId!");
|
||||
final preKeys = otherKeys.preKeys
|
||||
.map(
|
||||
(preKey) => SignalContactPreKeysCompanion(
|
||||
contactId: Value(contactId),
|
||||
preKey: Value(Uint8List.fromList(preKey.prekey)),
|
||||
preKeyId: Value(preKey.id.toInt()),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
await twonlyDB.signalDao.insertPreKeys(preKeys);
|
||||
} else {
|
||||
Log.error("could not load new pre keys for user $contactId");
|
||||
if (lastPreKeyRequest
|
||||
.isAfter(DateTime.now().subtract(Duration(seconds: 60)))) {
|
||||
Log.info("last pre request was 60s before");
|
||||
return;
|
||||
}
|
||||
lastPreKeyRequest = DateTime.now();
|
||||
requestNewKeys.protect(() async {
|
||||
final otherKeys = await apiService.getPreKeysByUserId(contactId);
|
||||
if (otherKeys != null) {
|
||||
Log.info(
|
||||
"got fresh ${otherKeys.preKeys.length} pre keys from other $contactId!");
|
||||
final preKeys = otherKeys.preKeys
|
||||
.map(
|
||||
(preKey) => SignalContactPreKeysCompanion(
|
||||
contactId: Value(contactId),
|
||||
preKey: Value(Uint8List.fromList(preKey.prekey)),
|
||||
preKeyId: Value(preKey.id.toInt()),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
await twonlyDB.signalDao.insertPreKeys(preKeys);
|
||||
} else {
|
||||
Log.error("could not load new pre keys for user $contactId");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<SignalContactPreKey?> getPreKeyByContactId(int contactId) async {
|
||||
int count = await twonlyDB.signalDao.countPreKeysByContactId(contactId);
|
||||
if (count < 10) {
|
||||
Log.info(
|
||||
"There are $count < 10 prekeys for $contactId. Loading fresh once from the server.",
|
||||
);
|
||||
Log.info("Requesting new prekeys: $count < 10");
|
||||
requestNewPrekeysForContact(contactId);
|
||||
}
|
||||
return twonlyDB.signalDao.popPreKeyByContactId(contactId);
|
||||
}
|
||||
|
||||
Future requestNewSignedPreKeyForContact(int contactId) async {
|
||||
final signedPreKey = await apiService.getSignedKeyByUserId(contactId);
|
||||
if (signedPreKey != null) {
|
||||
Log.info("got fresh signed pre keys from other $contactId!");
|
||||
await twonlyDB.signalDao.insertOrUpdateSignedPreKeyByContactId(
|
||||
SignalContactSignedPreKeysCompanion(
|
||||
contactId: Value(contactId),
|
||||
signedPreKey: Value(Uint8List.fromList(signedPreKey.signedPrekey)),
|
||||
signedPreKeySignature:
|
||||
Value(Uint8List.fromList(signedPreKey.signedPrekeySignature)),
|
||||
signedPreKeyId: Value(signedPreKey.signedPrekeyId.toInt()),
|
||||
));
|
||||
} else {
|
||||
Log.error("could not load new signed pre key for user $contactId");
|
||||
if (lastSignedPreKeyRequest
|
||||
.isAfter(DateTime.now().subtract(Duration(seconds: 60)))) {
|
||||
Log.info("last signed pre request was 60s before");
|
||||
return;
|
||||
}
|
||||
lastSignedPreKeyRequest = DateTime.now();
|
||||
await requestNewKeys.protect(() async {
|
||||
final signedPreKey = await apiService.getSignedKeyByUserId(contactId);
|
||||
if (signedPreKey != null) {
|
||||
Log.info("got fresh signed pre keys from other $contactId!");
|
||||
await twonlyDB.signalDao.insertOrUpdateSignedPreKeyByContactId(
|
||||
SignalContactSignedPreKeysCompanion(
|
||||
contactId: Value(contactId),
|
||||
signedPreKey: Value(Uint8List.fromList(signedPreKey.signedPrekey)),
|
||||
signedPreKeySignature:
|
||||
Value(Uint8List.fromList(signedPreKey.signedPrekeySignature)),
|
||||
signedPreKeyId: Value(signedPreKey.signedPrekeyId.toInt()),
|
||||
));
|
||||
} else {
|
||||
Log.error("could not load new signed pre key for user $contactId");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<SignalContactSignedPreKey?> getSignedPreKeyByContactId(
|
||||
|
|
|
|||
|
|
@ -1,212 +0,0 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift_flutter/drift_flutter.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/model/json/userdata.dart';
|
||||
import 'package:twonly/src/model/protobuf/backup/backup.pb.dart';
|
||||
import 'package:twonly/src/services/api/media_upload.dart';
|
||||
import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
import 'package:twonly/src/views/settings/backup/backup.view.dart';
|
||||
|
||||
Future performTwonlySafeBackup({bool force = false}) async {
|
||||
final user = await getUser();
|
||||
|
||||
if (user == null || user.twonlySafeBackup == null || user.isDemoUser) {
|
||||
Log.warn("perform twonly safe backup was called while it is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.twonlySafeBackup!.backupUploadState ==
|
||||
LastBackupUploadState.pending) {
|
||||
Log.warn("Backup upload is already pending.");
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime? lastUpdateTime = user.twonlySafeBackup!.lastBackupDone;
|
||||
if (!force && lastUpdateTime != null) {
|
||||
if (lastUpdateTime.isAfter(DateTime.now().subtract(Duration(days: 1)))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.info("Starting new twonly Safe-Backup.");
|
||||
|
||||
final baseDir = (await getApplicationSupportDirectory()).path;
|
||||
|
||||
final backupDir = Directory(join(baseDir, "backup_twonly_safe/"));
|
||||
await backupDir.create(recursive: true);
|
||||
|
||||
final backupDatabaseFile =
|
||||
File(join(backupDir.path, "twonly_database.backup.sqlite"));
|
||||
|
||||
// copy database
|
||||
final originalDatabase = File(join(baseDir, "twonly_database.sqlite"));
|
||||
await originalDatabase.copy(backupDatabaseFile.path);
|
||||
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
final backupDB = TwonlyDatabase(
|
||||
driftDatabase(
|
||||
name: "twonly_database.backup",
|
||||
native: DriftNativeOptions(
|
||||
databaseDirectory: () async {
|
||||
return backupDir;
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await backupDB.deleteDataForTwonlySafe();
|
||||
|
||||
var secureStorageBackup = {};
|
||||
final storage = FlutterSecureStorage();
|
||||
secureStorageBackup[SecureStorageKeys.signalIdentity] =
|
||||
await storage.read(key: SecureStorageKeys.signalIdentity);
|
||||
secureStorageBackup[SecureStorageKeys.signalSignedPreKey] =
|
||||
await storage.read(key: SecureStorageKeys.signalSignedPreKey);
|
||||
|
||||
var userBackup = await getUser();
|
||||
if (userBackup == null) return;
|
||||
// FILTER settings which should not be in the backup
|
||||
userBackup.twonlySafeBackup = null;
|
||||
userBackup.lastImageSend = null;
|
||||
userBackup.todaysImageCounter = null;
|
||||
userBackup.lastPlanBallance = "";
|
||||
userBackup.additionalUserInvites = "";
|
||||
userBackup.signalLastSignedPreKeyUpdated = null;
|
||||
|
||||
secureStorageBackup[SecureStorageKeys.userData] = jsonEncode(userBackup);
|
||||
|
||||
// Compress and convert backup data
|
||||
|
||||
final twonlyDatabaseBytes = await backupDatabaseFile.readAsBytes();
|
||||
await backupDatabaseFile.delete();
|
||||
|
||||
final backupProto = TwonlySafeBackupContent(
|
||||
secureStorageJson: jsonEncode(secureStorageBackup),
|
||||
twonlyDatabase: twonlyDatabaseBytes,
|
||||
);
|
||||
|
||||
final backupBytes = gzip.encode(backupProto.writeToBuffer());
|
||||
|
||||
final backupHash = uint8ListToHex((await Sha256().hash(backupBytes)).bytes);
|
||||
|
||||
if (user.twonlySafeBackup!.lastBackupDone == null ||
|
||||
user.twonlySafeBackup!.lastBackupDone!
|
||||
.isAfter(DateTime.now().subtract(Duration(days: 90)))) {
|
||||
force = true;
|
||||
}
|
||||
|
||||
final lastHash =
|
||||
await storage.read(key: SecureStorageKeys.twonlySafeLastBackupHash);
|
||||
|
||||
if (lastHash != null && !force) {
|
||||
if (backupHash == lastHash) {
|
||||
Log.info("Since last backup nothing has changed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
await storage.write(
|
||||
key: SecureStorageKeys.twonlySafeLastBackupHash,
|
||||
value: backupHash,
|
||||
);
|
||||
|
||||
// Encrypt backup data
|
||||
|
||||
final xchacha20 = Xchacha20.poly1305Aead();
|
||||
final nonce = xchacha20.newNonce();
|
||||
|
||||
final secretBox = await xchacha20.encrypt(
|
||||
backupBytes,
|
||||
secretKey: SecretKey(user.twonlySafeBackup!.encryptionKey),
|
||||
nonce: nonce,
|
||||
);
|
||||
|
||||
final encryptedBackupBytes = (TwonlySafeBackupEncrypted(
|
||||
mac: secretBox.mac.bytes,
|
||||
nonce: nonce,
|
||||
cipherText: secretBox.cipherText,
|
||||
)).writeToBuffer();
|
||||
|
||||
Log.info("Backup files created.");
|
||||
|
||||
var encryptedBackupBytesFile =
|
||||
File(join(backupDir.path, "twonly_safe.backup"));
|
||||
|
||||
await encryptedBackupBytesFile.writeAsBytes(encryptedBackupBytes);
|
||||
|
||||
Log.info(
|
||||
"Create twonly Safe backup with a size of ${encryptedBackupBytes.length} bytes.");
|
||||
|
||||
if (user.backupServer != null) {
|
||||
if (encryptedBackupBytes.length > user.backupServer!.maxBackupBytes) {
|
||||
Log.error("Backup is to big for the alternative backup server.");
|
||||
await updateUserdata((user) {
|
||||
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed;
|
||||
return user;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final task = UploadTask.fromFile(
|
||||
taskId: "backup",
|
||||
file: encryptedBackupBytesFile,
|
||||
httpRequestMethod: "PUT",
|
||||
url: (await getTwonlySafeBackupUrl())!,
|
||||
requiresWiFi: true,
|
||||
priority: 5,
|
||||
retries: 2,
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
},
|
||||
);
|
||||
if (await FileDownloader().enqueue(task)) {
|
||||
Log.info("Starting upload from twonly Safe backup.");
|
||||
await updateUserdata((user) {
|
||||
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.pending;
|
||||
user.twonlySafeBackup!.lastBackupDone = DateTime.now();
|
||||
user.twonlySafeBackup!.lastBackupSize = encryptedBackupBytes.length;
|
||||
return user;
|
||||
});
|
||||
gUpdateBackupView();
|
||||
} else {
|
||||
Log.error("Error starting UploadTask for twonly Safe.");
|
||||
}
|
||||
}
|
||||
|
||||
Future handleBackupStatusUpdate(TaskStatusUpdate update) async {
|
||||
if (update.status == TaskStatus.failed ||
|
||||
update.status == TaskStatus.canceled) {
|
||||
Log.error(
|
||||
"twonly Safe upload failed. ${update.responseStatusCode} ${update.responseBody} ${update.responseHeaders} ${update.exception}");
|
||||
await updateUserdata((user) {
|
||||
if (user.twonlySafeBackup != null) {
|
||||
user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed;
|
||||
}
|
||||
return user;
|
||||
});
|
||||
} else if (update.status == TaskStatus.complete) {
|
||||
Log.error(
|
||||
"twonly Safe uploaded with status code ${update.responseStatusCode}");
|
||||
await updateUserdata((user) {
|
||||
if (user.twonlySafeBackup != null) {
|
||||
user.twonlySafeBackup!.backupUploadState =
|
||||
LastBackupUploadState.success;
|
||||
}
|
||||
return user;
|
||||
});
|
||||
} else {
|
||||
Log.info("Backup is in state: ${update.status}");
|
||||
return;
|
||||
}
|
||||
gUpdateBackupView();
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ Future performTwonlySafeBackup({bool force = false}) async {
|
|||
final user = await getUser();
|
||||
|
||||
if (user == null || user.twonlySafeBackup == null || user.isDemoUser) {
|
||||
// Log.warn("perform twonly safe backup was called while it is disabled");
|
||||
Log.warn("perform twonly safe backup was called while it is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ Future performTwonlySafeBackup({bool force = false}) async {
|
|||
}
|
||||
}
|
||||
|
||||
Log.info("Starting new twonly Safe-Backup.");
|
||||
Log.info("Starting new twonly Safe-Backup!");
|
||||
|
||||
final baseDir = (await getApplicationSupportDirectory()).path;
|
||||
|
||||
|
|
@ -48,6 +48,9 @@ Future performTwonlySafeBackup({bool force = false}) async {
|
|||
final backupDatabaseFile =
|
||||
File(join(backupDir.path, "twonly_database.backup.sqlite"));
|
||||
|
||||
final backupDatabaseFileCleaned =
|
||||
File(join(backupDir.path, "twonly_database.backup.cleaned.sqlite"));
|
||||
|
||||
// copy database
|
||||
final originalDatabase = File(join(baseDir, "twonly_database.sqlite"));
|
||||
await originalDatabase.copy(backupDatabaseFile.path);
|
||||
|
|
@ -66,6 +69,10 @@ Future performTwonlySafeBackup({bool force = false}) async {
|
|||
|
||||
await backupDB.deleteDataForTwonlySafe();
|
||||
|
||||
await backupDB
|
||||
.customStatement('VACUUM INTO ?', [backupDatabaseFileCleaned.path]);
|
||||
backupDB.close();
|
||||
|
||||
var secureStorageBackup = {};
|
||||
final storage = FlutterSecureStorage();
|
||||
secureStorageBackup[SecureStorageKeys.signalIdentity] =
|
||||
|
|
@ -87,8 +94,11 @@ Future performTwonlySafeBackup({bool force = false}) async {
|
|||
|
||||
// Compress and convert backup data
|
||||
|
||||
final twonlyDatabaseBytes = await backupDatabaseFile.readAsBytes();
|
||||
final twonlyDatabaseBytes = await backupDatabaseFileCleaned.readAsBytes();
|
||||
await backupDatabaseFile.delete();
|
||||
await backupDatabaseFileCleaned.delete();
|
||||
|
||||
print("twonlyDatabaseBytes = ${twonlyDatabaseBytes.lengthInBytes}");
|
||||
|
||||
final backupProto = TwonlySafeBackupContent(
|
||||
secureStorageJson: jsonEncode(secureStorageBackup),
|
||||
|
|
@ -163,8 +173,8 @@ Future performTwonlySafeBackup({bool force = false}) async {
|
|||
httpRequestMethod: "PUT",
|
||||
url: (await getTwonlySafeBackupUrl())!,
|
||||
requiresWiFi: true,
|
||||
post: 'binary',
|
||||
priority: 5,
|
||||
post: 'binary',
|
||||
retries: 2,
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import 'package:http/http.dart' as http;
|
|||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/model/json/userdata.dart';
|
||||
import 'package:twonly/src/model/protobuf/backup/backup.pb.dart';
|
||||
import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart';
|
||||
|
|
@ -85,6 +87,40 @@ Future handleBackupData(
|
|||
final originalDatabase = File(join(baseDir, "twonly_database.sqlite"));
|
||||
await originalDatabase.writeAsBytes(backupContent.twonlyDatabase);
|
||||
|
||||
/// When restoring the last message ID must be increased otherwise
|
||||
/// receivers would mark them as duplicates as they where already
|
||||
/// send.
|
||||
final database = TwonlyDatabase();
|
||||
var lastMessageSend = 0;
|
||||
int? randomUserId;
|
||||
|
||||
final contacts = await database.contactsDao.getAllNotBlockedContacts();
|
||||
for (final contact in contacts) {
|
||||
randomUserId = contact.userId;
|
||||
final days = DateTime.now().difference(contact.lastMessageExchange).inDays;
|
||||
if (days < lastMessageSend) {
|
||||
lastMessageSend = days;
|
||||
}
|
||||
}
|
||||
|
||||
if (randomUserId != null) {
|
||||
// for each day add 400 message ids
|
||||
var dummyMessagesCounter = (lastMessageSend + 1) * 400;
|
||||
Log.info(
|
||||
"Creating $dummyMessagesCounter dummy messages to increase message counter as last message was $lastMessageSend days ago.");
|
||||
for (var i = 0; i < dummyMessagesCounter; i++) {
|
||||
await database.messagesDao.insertMessage(
|
||||
MessagesCompanion(
|
||||
contactId: Value(randomUserId),
|
||||
kind: Value(MessageKind.ack),
|
||||
acknowledgeByServer: Value(true),
|
||||
errorWhileSending: Value(true),
|
||||
),
|
||||
);
|
||||
}
|
||||
await database.messagesDao.deleteAllMessagesByContactId(randomUserId);
|
||||
}
|
||||
|
||||
final storage = FlutterSecureStorage();
|
||||
|
||||
final secureStorage = jsonDecode(backupContent.secureStorageJson);
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
TextMessageContent? content = TextMessageContent.fromJson(
|
||||
jsonDecode(messages[index].contentJson!));
|
||||
if (EmojiAnimation.supported(content.text)) {
|
||||
size = 95;
|
||||
size = 99;
|
||||
} else {
|
||||
size = 11 +
|
||||
calculateNumberOfLines(content.text,
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
},
|
||||
));
|
||||
},
|
||||
label: Text("Restore identity"),
|
||||
label: Text(context.lang.twonlySafeRecoverBtn),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class _BackupViewState extends State<BackupView> {
|
|||
String backupStatus(LastBackupUploadState status) {
|
||||
switch (status) {
|
||||
case LastBackupUploadState.none:
|
||||
return '';
|
||||
return context.lang.backupPending;
|
||||
case LastBackupUploadState.pending:
|
||||
return context.lang.backupPending;
|
||||
case LastBackupUploadState.failed:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ class _TwonlyIdentityBackupViewState extends State<TwonlyIdentityBackupView> {
|
|||
final TextEditingController repeatedPasswordCtrl = TextEditingController();
|
||||
|
||||
Future onPressedEnableTwonlySafe() async {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
if (!await isSecurePassword(passwordCtrl.text)) {
|
||||
if (!mounted) return;
|
||||
bool ignore = await showAlertDialog(
|
||||
|
|
@ -33,6 +35,11 @@ class _TwonlyIdentityBackupViewState extends State<TwonlyIdentityBackupView> {
|
|||
customOk: context.lang.backupInsecurePasswordCancel,
|
||||
);
|
||||
if (ignore) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -119,6 +126,7 @@ class _TwonlyIdentityBackupViewState extends State<TwonlyIdentityBackupView> {
|
|||
child: Text(
|
||||
context.lang.backupPasswordRequirement,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: ((passwordCtrl.text.length < 8 &&
|
||||
passwordCtrl.text.isNotEmpty))
|
||||
? Colors.red
|
||||
|
|
@ -143,6 +151,7 @@ class _TwonlyIdentityBackupViewState extends State<TwonlyIdentityBackupView> {
|
|||
child: Text(
|
||||
context.lang.passwordRepeatedNotEqual,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: (passwordCtrl.text != repeatedPasswordCtrl.text &&
|
||||
repeatedPasswordCtrl.text.isNotEmpty)
|
||||
? Colors.red
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'schema_v8.dart' as v8;
|
|||
import 'schema_v9.dart' as v9;
|
||||
import 'schema_v10.dart' as v10;
|
||||
import 'schema_v11.dart' as v11;
|
||||
import 'schema_v12.dart' as v12;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
|
|
@ -41,10 +42,12 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||
return v10.DatabaseAtV10(db);
|
||||
case 11:
|
||||
return v11.DatabaseAtV11(db);
|
||||
case 12:
|
||||
return v12.DatabaseAtV12(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
}
|
||||
|
||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
|
||||
}
|
||||
|
|
|
|||
3883
test/drift/twonly_database/generated/schema_v12.dart
Normal file
3883
test/drift/twonly_database/generated/schema_v12.dart
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue