hopefully final fix for the retransmission problem

This commit is contained in:
otsmr 2025-06-24 14:00:55 +02:00
parent 79caf95afe
commit 674e42722d
16 changed files with 8456 additions and 203 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -23,6 +23,15 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
}
Future<List<int>> getRetransmitAbleMessages() async {
final countDeleted = await (delete(messageRetransmissions)
..where((t) =>
t.encryptedHash.isNull() & t.acknowledgeByServerAt.isNotNull()))
.go();
if (countDeleted > 0) {
Log.info("Deleted $countDeleted faulty retransmissions");
}
return (await (select(messageRetransmissions)
..where((t) => t.acknowledgeByServerAt.isNull()))
.get())
@ -45,19 +54,11 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
.write(updatedValues);
}
Future resetAckStatusForAllMessages(int fromUserId) async {
final deletedCount = await (delete(messageRetransmissions)
..where((m) =>
m.willNotGetACKByUser.equals(true) &
m.acknowledgeByServerAt.isNotNull()))
.go();
if (deletedCount > 0) {
Log.info('$deletedCount faulty retransmission messages where deleted.');
}
Future resetAckStatusFor(int fromUserId, Uint8List encryptedHash) async {
return ((update(messageRetransmissions))
..where((m) =>
m.willNotGetACKByUser.equals(false) &
m.contactId.equals(fromUserId)))
m.contactId.equals(fromUserId) &
m.encryptedHash.equals(encryptedHash)))
.write(
MessageRetransmissionsCompanion(
acknowledgeByServerAt: Value(null),

View file

@ -14,9 +14,7 @@ class MessageRetransmissions extends Table {
BlobColumn get plaintextContent => blob()();
BlobColumn get pushData => blob().nullable()();
BoolColumn get willNotGetACKByUser =>
boolean().withDefault(Constant(false))();
BlobColumn get encryptedHash => blob().nullable()();
DateTimeColumn get acknowledgeByServerAt => dateTime().nullable()();
}

View file

@ -53,7 +53,7 @@ class TwonlyDatabase extends _$TwonlyDatabase {
TwonlyDatabase.forTesting(DatabaseConnection super.connection);
@override
int get schemaVersion => 12;
int get schemaVersion => 14;
static QueryExecutor _openConnection() {
return driftDatabase(
@ -125,6 +125,14 @@ class TwonlyDatabase extends _$TwonlyDatabase {
m.addColumn(schema.messageRetransmissions,
schema.messageRetransmissions.willNotGetACKByUser);
},
from12To13: (m, schema) async {
m.dropColumn(
schema.messageRetransmissions, "will_not_get_a_c_k_by_user");
},
from13To14: (m, schema) async {
m.addColumn(schema.messageRetransmissions,
schema.messageRetransmissions.encryptedHash);
},
),
);
}

View file

@ -4225,16 +4225,12 @@ 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');
static const VerificationMeta _encryptedHashMeta =
const VerificationMeta('encryptedHash');
@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));
late final GeneratedColumn<Uint8List> encryptedHash =
GeneratedColumn<Uint8List>('encrypted_hash', aliasedName, true,
type: DriftSqlType.blob, requiredDuringInsert: false);
static const VerificationMeta _acknowledgeByServerAtMeta =
const VerificationMeta('acknowledgeByServerAt');
@override
@ -4248,7 +4244,7 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
messageId,
plaintextContent,
pushData,
willNotGetACKByUser,
encryptedHash,
acknowledgeByServerAt
];
@override
@ -4290,11 +4286,11 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
context.handle(_pushDataMeta,
pushData.isAcceptableOrUnknown(data['push_data']!, _pushDataMeta));
}
if (data.containsKey('will_not_get_a_c_k_by_user')) {
if (data.containsKey('encrypted_hash')) {
context.handle(
_willNotGetACKByUserMeta,
willNotGetACKByUser.isAcceptableOrUnknown(
data['will_not_get_a_c_k_by_user']!, _willNotGetACKByUserMeta));
_encryptedHashMeta,
encryptedHash.isAcceptableOrUnknown(
data['encrypted_hash']!, _encryptedHashMeta));
}
if (data.containsKey('acknowledge_by_server_at')) {
context.handle(
@ -4321,8 +4317,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'])!,
encryptedHash: attachedDatabase.typeMapping
.read(DriftSqlType.blob, data['${effectivePrefix}encrypted_hash']),
acknowledgeByServerAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}acknowledge_by_server_at']),
@ -4342,7 +4338,7 @@ class MessageRetransmission extends DataClass
final int? messageId;
final Uint8List plaintextContent;
final Uint8List? pushData;
final bool willNotGetACKByUser;
final Uint8List? encryptedHash;
final DateTime? acknowledgeByServerAt;
const MessageRetransmission(
{required this.retransmissionId,
@ -4350,7 +4346,7 @@ class MessageRetransmission extends DataClass
this.messageId,
required this.plaintextContent,
this.pushData,
required this.willNotGetACKByUser,
this.encryptedHash,
this.acknowledgeByServerAt});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
@ -4364,7 +4360,9 @@ 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 || encryptedHash != null) {
map['encrypted_hash'] = Variable<Uint8List>(encryptedHash);
}
if (!nullToAbsent || acknowledgeByServerAt != null) {
map['acknowledge_by_server_at'] =
Variable<DateTime>(acknowledgeByServerAt);
@ -4383,7 +4381,9 @@ class MessageRetransmission extends DataClass
pushData: pushData == null && nullToAbsent
? const Value.absent()
: Value(pushData),
willNotGetACKByUser: Value(willNotGetACKByUser),
encryptedHash: encryptedHash == null && nullToAbsent
? const Value.absent()
: Value(encryptedHash),
acknowledgeByServerAt: acknowledgeByServerAt == null && nullToAbsent
? const Value.absent()
: Value(acknowledgeByServerAt),
@ -4400,8 +4400,7 @@ class MessageRetransmission extends DataClass
plaintextContent:
serializer.fromJson<Uint8List>(json['plaintextContent']),
pushData: serializer.fromJson<Uint8List?>(json['pushData']),
willNotGetACKByUser:
serializer.fromJson<bool>(json['willNotGetACKByUser']),
encryptedHash: serializer.fromJson<Uint8List?>(json['encryptedHash']),
acknowledgeByServerAt:
serializer.fromJson<DateTime?>(json['acknowledgeByServerAt']),
);
@ -4415,7 +4414,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),
'encryptedHash': serializer.toJson<Uint8List?>(encryptedHash),
'acknowledgeByServerAt':
serializer.toJson<DateTime?>(acknowledgeByServerAt),
};
@ -4427,7 +4426,7 @@ class MessageRetransmission extends DataClass
Value<int?> messageId = const Value.absent(),
Uint8List? plaintextContent,
Value<Uint8List?> pushData = const Value.absent(),
bool? willNotGetACKByUser,
Value<Uint8List?> encryptedHash = const Value.absent(),
Value<DateTime?> acknowledgeByServerAt = const Value.absent()}) =>
MessageRetransmission(
retransmissionId: retransmissionId ?? this.retransmissionId,
@ -4435,7 +4434,8 @@ 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,
encryptedHash:
encryptedHash.present ? encryptedHash.value : this.encryptedHash,
acknowledgeByServerAt: acknowledgeByServerAt.present
? acknowledgeByServerAt.value
: this.acknowledgeByServerAt,
@ -4452,9 +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,
encryptedHash: data.encryptedHash.present
? data.encryptedHash.value
: this.encryptedHash,
acknowledgeByServerAt: data.acknowledgeByServerAt.present
? data.acknowledgeByServerAt.value
: this.acknowledgeByServerAt,
@ -4469,7 +4469,7 @@ class MessageRetransmission extends DataClass
..write('messageId: $messageId, ')
..write('plaintextContent: $plaintextContent, ')
..write('pushData: $pushData, ')
..write('willNotGetACKByUser: $willNotGetACKByUser, ')
..write('encryptedHash: $encryptedHash, ')
..write('acknowledgeByServerAt: $acknowledgeByServerAt')
..write(')'))
.toString();
@ -4482,7 +4482,7 @@ class MessageRetransmission extends DataClass
messageId,
$driftBlobEquality.hash(plaintextContent),
$driftBlobEquality.hash(pushData),
willNotGetACKByUser,
$driftBlobEquality.hash(encryptedHash),
acknowledgeByServerAt);
@override
bool operator ==(Object other) =>
@ -4494,7 +4494,7 @@ class MessageRetransmission extends DataClass
$driftBlobEquality.equals(
other.plaintextContent, this.plaintextContent) &&
$driftBlobEquality.equals(other.pushData, this.pushData) &&
other.willNotGetACKByUser == this.willNotGetACKByUser &&
$driftBlobEquality.equals(other.encryptedHash, this.encryptedHash) &&
other.acknowledgeByServerAt == this.acknowledgeByServerAt);
}
@ -4505,7 +4505,7 @@ class MessageRetransmissionsCompanion
final Value<int?> messageId;
final Value<Uint8List> plaintextContent;
final Value<Uint8List?> pushData;
final Value<bool> willNotGetACKByUser;
final Value<Uint8List?> encryptedHash;
final Value<DateTime?> acknowledgeByServerAt;
const MessageRetransmissionsCompanion({
this.retransmissionId = const Value.absent(),
@ -4513,7 +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.encryptedHash = const Value.absent(),
this.acknowledgeByServerAt = const Value.absent(),
});
MessageRetransmissionsCompanion.insert({
@ -4522,7 +4522,7 @@ class MessageRetransmissionsCompanion
this.messageId = const Value.absent(),
required Uint8List plaintextContent,
this.pushData = const Value.absent(),
this.willNotGetACKByUser = const Value.absent(),
this.encryptedHash = const Value.absent(),
this.acknowledgeByServerAt = const Value.absent(),
}) : contactId = Value(contactId),
plaintextContent = Value(plaintextContent);
@ -4532,7 +4532,7 @@ class MessageRetransmissionsCompanion
Expression<int>? messageId,
Expression<Uint8List>? plaintextContent,
Expression<Uint8List>? pushData,
Expression<bool>? willNotGetACKByUser,
Expression<Uint8List>? encryptedHash,
Expression<DateTime>? acknowledgeByServerAt,
}) {
return RawValuesInsertable({
@ -4541,8 +4541,7 @@ 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 (encryptedHash != null) 'encrypted_hash': encryptedHash,
if (acknowledgeByServerAt != null)
'acknowledge_by_server_at': acknowledgeByServerAt,
});
@ -4554,7 +4553,7 @@ class MessageRetransmissionsCompanion
Value<int?>? messageId,
Value<Uint8List>? plaintextContent,
Value<Uint8List?>? pushData,
Value<bool>? willNotGetACKByUser,
Value<Uint8List?>? encryptedHash,
Value<DateTime?>? acknowledgeByServerAt}) {
return MessageRetransmissionsCompanion(
retransmissionId: retransmissionId ?? this.retransmissionId,
@ -4562,7 +4561,7 @@ class MessageRetransmissionsCompanion
messageId: messageId ?? this.messageId,
plaintextContent: plaintextContent ?? this.plaintextContent,
pushData: pushData ?? this.pushData,
willNotGetACKByUser: willNotGetACKByUser ?? this.willNotGetACKByUser,
encryptedHash: encryptedHash ?? this.encryptedHash,
acknowledgeByServerAt:
acknowledgeByServerAt ?? this.acknowledgeByServerAt,
);
@ -4586,9 +4585,8 @@ 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 (encryptedHash.present) {
map['encrypted_hash'] = Variable<Uint8List>(encryptedHash.value);
}
if (acknowledgeByServerAt.present) {
map['acknowledge_by_server_at'] =
@ -4605,7 +4603,7 @@ class MessageRetransmissionsCompanion
..write('messageId: $messageId, ')
..write('plaintextContent: $plaintextContent, ')
..write('pushData: $pushData, ')
..write('willNotGetACKByUser: $willNotGetACKByUser, ')
..write('encryptedHash: $encryptedHash, ')
..write('acknowledgeByServerAt: $acknowledgeByServerAt')
..write(')'))
.toString();
@ -7154,7 +7152,7 @@ typedef $$MessageRetransmissionsTableCreateCompanionBuilder
Value<int?> messageId,
required Uint8List plaintextContent,
Value<Uint8List?> pushData,
Value<bool> willNotGetACKByUser,
Value<Uint8List?> encryptedHash,
Value<DateTime?> acknowledgeByServerAt,
});
typedef $$MessageRetransmissionsTableUpdateCompanionBuilder
@ -7164,7 +7162,7 @@ typedef $$MessageRetransmissionsTableUpdateCompanionBuilder
Value<int?> messageId,
Value<Uint8List> plaintextContent,
Value<Uint8List?> pushData,
Value<bool> willNotGetACKByUser,
Value<Uint8List?> encryptedHash,
Value<DateTime?> acknowledgeByServerAt,
});
@ -7224,9 +7222,8 @@ 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<Uint8List> get encryptedHash => $composableBuilder(
column: $table.encryptedHash, builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get acknowledgeByServerAt => $composableBuilder(
column: $table.acknowledgeByServerAt,
@ -7293,8 +7290,8 @@ class $$MessageRetransmissionsTableOrderingComposer
ColumnOrderings<Uint8List> get pushData => $composableBuilder(
column: $table.pushData, builder: (column) => ColumnOrderings(column));
ColumnOrderings<bool> get willNotGetACKByUser => $composableBuilder(
column: $table.willNotGetACKByUser,
ColumnOrderings<Uint8List> get encryptedHash => $composableBuilder(
column: $table.encryptedHash,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get acknowledgeByServerAt => $composableBuilder(
@ -7360,8 +7357,8 @@ 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<Uint8List> get encryptedHash => $composableBuilder(
column: $table.encryptedHash, builder: (column) => column);
GeneratedColumn<DateTime> get acknowledgeByServerAt => $composableBuilder(
column: $table.acknowledgeByServerAt, builder: (column) => column);
@ -7439,7 +7436,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<Uint8List?> encryptedHash = const Value.absent(),
Value<DateTime?> acknowledgeByServerAt = const Value.absent(),
}) =>
MessageRetransmissionsCompanion(
@ -7448,7 +7445,7 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
messageId: messageId,
plaintextContent: plaintextContent,
pushData: pushData,
willNotGetACKByUser: willNotGetACKByUser,
encryptedHash: encryptedHash,
acknowledgeByServerAt: acknowledgeByServerAt,
),
createCompanionCallback: ({
@ -7457,7 +7454,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<Uint8List?> encryptedHash = const Value.absent(),
Value<DateTime?> acknowledgeByServerAt = const Value.absent(),
}) =>
MessageRetransmissionsCompanion.insert(
@ -7466,7 +7463,7 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
messageId: messageId,
plaintextContent: plaintextContent,
pushData: pushData,
willNotGetACKByUser: willNotGetACKByUser,
encryptedHash: encryptedHash,
acknowledgeByServerAt: acknowledgeByServerAt,
),
withReferenceMapper: (p0) => p0

View file

@ -2722,6 +2722,490 @@ i1.GeneratedColumn<bool> _column_68(String aliasedName) =>
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
'CHECK ("will_not_get_a_c_k_by_user" IN (0, 1))'),
defaultValue: const CustomExpression('0'));
final class Schema13 extends i0.VersionedSchema {
Schema13({required super.database}) : super(version: 13);
@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 Shape16 messageRetransmissions = Shape16(
source: i0.VersionedTable(
entityName: 'message_retransmissions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_62,
_column_63,
_column_64,
_column_65,
_column_66,
_column_67,
],
attachedDatabase: database,
),
alias: null);
}
final class Schema14 extends i0.VersionedSchema {
Schema14({required super.database}) : super(version: 14);
@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 Shape18 messageRetransmissions = Shape18(
source: i0.VersionedTable(
entityName: 'message_retransmissions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_62,
_column_63,
_column_64,
_column_65,
_column_66,
_column_69,
_column_67,
],
attachedDatabase: database,
),
alias: null);
}
class Shape18 extends i0.VersionedTable {
Shape18({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<i2.Uint8List> get encryptedHash =>
columnsByName['encrypted_hash']! as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<DateTime> get acknowledgeByServerAt =>
columnsByName['acknowledge_by_server_at']!
as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<i2.Uint8List> _column_69(String aliasedName) =>
i1.GeneratedColumn<i2.Uint8List>('encrypted_hash', aliasedName, true,
type: i1.DriftSqlType.blob);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@ -2734,6 +3218,8 @@ i0.MigrationStepWithVersion migrationSteps({
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,
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@ -2792,6 +3278,16 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from11To12(migrator, schema);
return 12;
case 12:
final schema = Schema13(database: database);
final migrator = i1.Migrator(database, schema);
await from12To13(migrator, schema);
return 13;
case 13:
final schema = Schema14(database: database);
final migrator = i1.Migrator(database, schema);
await from13To14(migrator, schema);
return 14;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@ -2810,6 +3306,8 @@ i1.OnUpgrade stepByStep({
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,
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
}) =>
i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
@ -2824,4 +3322,6 @@ i1.OnUpgrade stepByStep({
from9To10: from9To10,
from10To11: from10To11,
from11To12: from11To12,
from12To13: from12To13,
from13To14: from13To14,
));

View file

@ -96,6 +96,8 @@ class MessageContent {
return FlameSyncContent.fromJson(json);
case MessageKind.ack:
return AckContent.fromJson(json);
case MessageKind.signalDecryptError:
return SignalDecryptErrorContent.fromJson(json);
default:
return null;
}
@ -222,6 +224,24 @@ class ReopenedMediaFileContent extends MessageContent {
}
}
class SignalDecryptErrorContent extends MessageContent {
List<int> encryptedHash;
SignalDecryptErrorContent({required this.encryptedHash});
static SignalDecryptErrorContent fromJson(Map json) {
return SignalDecryptErrorContent(
encryptedHash: List<int>.from(json['encryptedHash']),
);
}
@override
Map toJson() {
return {
'encryptedHash': encryptedHash,
};
}
}
class AckContent extends MessageContent {
int? messageIdToAck;
int retransIdToAck;

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:cryptography_plus/cryptography_plus.dart';
import 'package:drift/drift.dart';
import 'package:fixnum/fixnum.dart';
import 'package:twonly/globals.dart';
@ -10,6 +11,8 @@ 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/model/protobuf/push_notification/push_notification.pb.dart';
import 'package:twonly/src/services/api/server_messages.dart'
show messageGetsAck;
import 'package:twonly/src/services/api/utils.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/services/signal/encryption.signal.dart';
@ -24,20 +27,15 @@ Future tryTransmitMessages() async {
if (retransIds.isEmpty) return;
bool filterPreKeys = false;
if (retransIds.length > 100) {
filterPreKeys = true; // just a workaround until I can fix the real issue :/
}
for (final retransId in retransIds) {
sendRetransmitMessage(retransId, filterPreKeys: filterPreKeys);
sendRetransmitMessage(retransId, fromRetransmissionDb: true);
//twonlyDB.messageRetransmissionDao.deleteRetransmissionById(retransId);
}
}
Future sendRetransmitMessage(int retransId,
{bool filterPreKeys = false}) async {
{bool fromRetransmissionDb = false}) async {
try {
MessageRetransmission? retrans = await twonlyDB.messageRetransmissionDao
.getRetransmissionById(retransId)
.getSingleOrNull();
@ -54,20 +52,15 @@ Future sendRetransmitMessage(int retransId,
),
),
);
if (filterPreKeys && json.kind == MessageKind.pushKey) {
if (!retrans.willNotGetACKByUser) {
Log.error("Why is willNotGetACKByUser false????");
}
Log.info("Filtering preKeys: ${json.kind} to ${retrans.contactId}");
await twonlyDB.messageRetransmissionDao.deleteRetransmissionById(retransId);
DateTime timestampToCheck = DateTime.parse("2025-06-24T12:00:00");
if (json.timestamp.isBefore(timestampToCheck)) {
Log.info("Deleting retransmission because it is before the update...");
await twonlyDB.messageRetransmissionDao
.deleteRetransmissionById(retransId);
return;
}
Log.info("Retransmitting: ${json.kind} to ${retrans.contactId}");
// if (json.kind
// .contains(MessageKind.pushKey.name)) {
// await twonlyDB.messageRetransmissionDao.deleteRetransmissionById(retransId);
// return;
// }
Contact? contact = await twonlyDB.contactsDao
.getContactByUserId(retrans.contactId)
@ -93,6 +86,15 @@ Future sendRetransmitMessage(int retransId,
return;
}
final encryptedHash = (await Sha256().hash(encryptedBytes)).bytes;
await twonlyDB.messageRetransmissionDao.updateRetransmission(
retransId,
MessageRetransmissionsCompanion(
encryptedHash: Value(Uint8List.fromList(encryptedHash)),
),
);
Result resp = await apiService.sendTextMessage(
retrans.contactId,
encryptedBytes,
@ -132,12 +134,7 @@ Future sendRetransmitMessage(int retransId,
}
if (!retry) {
if (!retrans.willNotGetACKByUser && json.kind == MessageKind.pushKey) {
Log.error("Why is willNotGetACKByUser false????");
}
if (retrans.willNotGetACKByUser ||
json.kind == MessageKind.pushKey ||
json.kind == MessageKind.ack) {
if (!messageGetsAck(json.kind)) {
await twonlyDB.messageRetransmissionDao
.deleteRetransmissionById(retransId);
} else {
@ -149,12 +146,19 @@ Future sendRetransmitMessage(int retransId,
);
}
}
} catch (e) {
Log.error("error resending message: $e");
await twonlyDB.messageRetransmissionDao.deleteRetransmissionById(retransId);
}
}
// encrypts and stores the message and then sends it in the background
Future encryptAndSendMessageAsync(int? messageId, int userId, MessageJson msg,
{PushNotification? pushNotification,
bool willNotGetACKByUser = false}) async {
Future encryptAndSendMessageAsync(
int? messageId,
int userId,
MessageJson msg, {
PushNotification? pushNotification,
}) async {
if (gIsDemoUser) {
return;
}
@ -170,7 +174,6 @@ Future encryptAndSendMessageAsync(int? messageId, int userId, MessageJson msg,
messageId: Value(messageId),
plaintextContent: Value(Uint8List(0)),
pushData: Value(pushData),
willNotGetACKByUser: Value(willNotGetACKByUser),
),
);

View file

@ -1,5 +1,6 @@
import 'dart:convert';
import 'dart:io';
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';
@ -22,7 +23,6 @@ import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/services/notifications/setup.notifications.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/services/thumbnail.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
@ -60,15 +60,20 @@ Future handleServerMessage(server.ServerToClient msg) async {
DateTime lastSignalDecryptMessage = DateTime.now().subtract(Duration(hours: 1));
DateTime lastPushKeyRequest = DateTime.now().subtract(Duration(hours: 1));
bool messageGetsAck(MessageKind kind) {
return kind != MessageKind.pushKey && kind != MessageKind.ack;
}
Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
MessageJson? message = await signalDecryptMessage(fromUserId, body);
if (message == null) {
final encryptedHash = (await Sha256().hash(body)).bytes;
await encryptAndSendMessageAsync(
null,
fromUserId,
MessageJson(
kind: MessageKind.signalDecryptError,
content: MessageContent(),
content: SignalDecryptErrorContent(encryptedHash: encryptedHash),
timestamp: DateTime.now(),
),
);
@ -82,9 +87,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
Log.info("Got: ${message.kind} from $fromUserId");
if (message.kind != MessageKind.ack &&
message.kind != MessageKind.pushKey &&
message.retransId != null) {
if (messageGetsAck(message.kind) && message.retransId != null) {
Log.info("Sending ACK for ${message.kind}");
/// ACK every message
@ -99,7 +102,6 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
retransIdToAck: message.retransId!),
timestamp: DateTime.now(),
),
willNotGetACKByUser: true,
);
}
@ -124,15 +126,15 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
}
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(fromUserId);
final content = message.content;
if (content is SignalDecryptErrorContent) {
await twonlyDB.messageRetransmissionDao.resetAckStatusFor(
fromUserId,
Uint8List.fromList(content.encryptedHash),
);
tryTransmitMessages();
}

View file

@ -110,7 +110,6 @@ Future sendNewPushKey(int userId, PushKey pushKey) async {
pushKey.createdAtUnixTimestamp.toInt(),
),
),
willNotGetACKByUser: true, // hot fix, this can be removed later...
);
}

View file

@ -1,5 +1,4 @@
import 'dart:io';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -8,7 +7,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/constants/secure_storage_keys.dart';
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/services/fcm.service.dart';
@ -29,13 +27,6 @@ class NotificationView extends StatelessWidget {
ListTile(
title: Text(context.lang.settingsNotifyTroubleshooting),
subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc),
onLongPress: (kDebugMode)
? () async {
await twonlyDB.messageRetransmissionDao
.resetAckStatusForAllMessages(537506372);
tryTransmitMessages();
}
: null,
onTap: () async {
await initFCMAfterAuthenticated();
String? storedToken = await FlutterSecureStorage()

View file

@ -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.43+43
version: 0.0.45+45
environment:
sdk: ^3.6.0

View file

@ -15,6 +15,8 @@ import 'schema_v9.dart' as v9;
import 'schema_v10.dart' as v10;
import 'schema_v11.dart' as v11;
import 'schema_v12.dart' as v12;
import 'schema_v13.dart' as v13;
import 'schema_v14.dart' as v14;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
@ -44,10 +46,14 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v11.DatabaseAtV11(db);
case 12:
return v12.DatabaseAtV12(db);
case 13:
return v13.DatabaseAtV13(db);
case 14:
return v14.DatabaseAtV14(db);
default:
throw MissingSchemaException(version, versions);
}
}
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff