From 5a6afaa6d46f4a8b2ed709c7ecd5e547228b6d7a Mon Sep 17 00:00:00 2001 From: otsmr Date: Thu, 24 Apr 2025 00:28:07 +0200 Subject: [PATCH] working on #25, #65, fixes #128, #127, #122, #119, #77 --- .../twonly_database/drift_schema_v4.json | 1 + lib/main.dart | 2 +- .../database/daos/media_downloads_dao.dart | 44 + .../database/daos/media_downloads_dao.g.dart | 8 + lib/src/database/daos/media_uploads_dao.dart | 47 + .../database/daos/media_uploads_dao.g.dart | 8 + lib/src/database/daos/messages_dao.dart | 2 +- .../database/tables/media_download_table.dart | 8 + .../database/tables/media_uploads_table.dart | 216 ++ lib/src/database/tables/messages_table.dart | 3 + lib/src/database/twonly_database.dart | 15 +- lib/src/database/twonly_database.g.dart | 1122 +++++++ lib/src/database/twonly_database.steps.dart | 207 ++ lib/src/localization/app_de.arb | 1 + lib/src/localization/app_en.arb | 2 + lib/src/providers/api/media.dart | 500 ---- lib/src/providers/api/media_received.dart | 230 ++ lib/src/providers/api/media_send.dart | 472 +++ lib/src/providers/api/server_messages.dart | 130 +- lib/src/providers/api_provider.dart | 20 +- lib/src/utils/misc.dart | 2 +- .../views/camera/share_image_editor_view.dart | 2 +- lib/src/views/camera/share_image_view.dart | 2 +- .../views/chats/chat_item_details_view.dart | 7 +- lib/src/views/chats/chat_list_view.dart | 10 +- lib/src/views/chats/media_viewer_view.dart | 34 +- .../views/settings/data_and_storage_view.dart | 94 + .../views/settings/settings_main_view.dart | 10 + .../twonly_database/generated/schema.dart | 5 +- .../twonly_database/generated/schema_v4.dart | 2572 +++++++++++++++++ 30 files changed, 5113 insertions(+), 663 deletions(-) create mode 100644 drift_schemas/twonly_database/drift_schema_v4.json create mode 100644 lib/src/database/daos/media_downloads_dao.dart create mode 100644 lib/src/database/daos/media_downloads_dao.g.dart create mode 100644 lib/src/database/daos/media_uploads_dao.dart create mode 100644 lib/src/database/daos/media_uploads_dao.g.dart create mode 100644 lib/src/database/tables/media_download_table.dart create mode 100644 lib/src/database/tables/media_uploads_table.dart delete mode 100644 lib/src/providers/api/media.dart create mode 100644 lib/src/providers/api/media_received.dart create mode 100644 lib/src/providers/api/media_send.dart create mode 100644 lib/src/views/settings/data_and_storage_view.dart create mode 100644 test/drift/twonly_database/generated/schema_v4.dart diff --git a/drift_schemas/twonly_database/drift_schema_v4.json b/drift_schemas/twonly_database/drift_schema_v4.json new file mode 100644 index 0000000..846a93c --- /dev/null +++ b/drift_schemas/twonly_database/drift_schema_v4.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"contacts","was_declared_in_moor":false,"columns":[{"name":"user_id","getter_name":"userId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"defaultConstraints":"UNIQUE","dialectAwareDefaultConstraints":{"sqlite":"UNIQUE"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"display_name","getter_name":"displayName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"nick_name","getter_name":"nickName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"avatar_svg","getter_name":"avatarSvg","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"my_avatar_counter","getter_name":"myAvatarCounter","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"accepted","getter_name":"accepted","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"accepted\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"accepted\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"requested","getter_name":"requested","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"requested\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"requested\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"blocked","getter_name":"blocked","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"blocked\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"blocked\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"verified","getter_name":"verified","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"verified\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"verified\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"archived","getter_name":"archived","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"archived\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"archived\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"delete_messages_after_x_minutes","getter_name":"deleteMessagesAfterXMinutes","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('1440')","default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]},{"name":"total_media_counter","getter_name":"totalMediaCounter","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"last_message_send","getter_name":"lastMessageSend","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_message_received","getter_name":"lastMessageReceived","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_flame_counter_change","getter_name":"lastFlameCounterChange","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"last_message_exchange","getter_name":"lastMessageExchange","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]},{"name":"flame_counter","getter_name":"flameCounter","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["user_id"]}},{"id":1,"references":[0],"type":"table","data":{"name":"messages","was_declared_in_moor":false,"columns":[{"name":"contact_id","getter_name":"contactId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES contacts (user_id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES contacts (user_id)"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"message_id","getter_name":"messageId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"message_other_id","getter_name":"messageOtherId","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"response_to_message_id","getter_name":"responseToMessageId","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"response_to_other_message_id","getter_name":"responseToOtherMessageId","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"acknowledge_by_user","getter_name":"acknowledgeByUser","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"acknowledge_by_user\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"acknowledge_by_user\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"download_state","getter_name":"downloadState","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('2')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(DownloadState.values)","dart_type_name":"DownloadState"}},{"name":"acknowledge_by_server","getter_name":"acknowledgeByServer","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"acknowledge_by_server\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"acknowledge_by_server\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"error_while_sending","getter_name":"errorWhileSending","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"error_while_sending\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"error_while_sending\" IN (0, 1))"},"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[]},{"name":"kind","getter_name":"kind","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(MessageKind.values)","dart_type_name":"MessageKind"}},{"name":"content_json","getter_name":"contentJson","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"opened_at","getter_name":"openedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"send_at","getter_name":"sendAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]},{"name":"updated_at","getter_name":"updatedAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[],"type":"table","data":{"name":"media_uploads","was_declared_in_moor":false,"columns":[{"name":"media_upload_id","getter_name":"mediaUploadId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"state","getter_name":"state","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('\\'pending\\'')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(UploadState.values)","dart_type_name":"UploadState"}},{"name":"metadata","getter_name":"metadata","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"MediaUploadMetadataConverter()","dart_type_name":"MediaUploadMetadata"}},{"name":"message_ids","getter_name":"messageIds","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"IntListTypeConverter()","dart_type_name":"List"}},{"name":"encryption_data","getter_name":"encryptionData","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"MediaEncryptionDataConverter()","dart_type_name":"MediaEncryptionData"}},{"name":"upload_tokens","getter_name":"uploadTokens","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"MediaUploadTokensConverter()","dart_type_name":"MediaUploadTokens"}},{"name":"already_notified","getter_name":"alreadyNotified","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('\\'[]\\'')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"IntListTypeConverter()","dart_type_name":"List"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":3,"references":[],"type":"table","data":{"name":"signal_identity_key_stores","was_declared_in_moor":false,"columns":[{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"identity_key","getter_name":"identityKey","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["device_id","name"]}},{"id":4,"references":[],"type":"table","data":{"name":"signal_pre_key_stores","was_declared_in_moor":false,"columns":[{"name":"pre_key_id","getter_name":"preKeyId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pre_key","getter_name":"preKey","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["pre_key_id"]}},{"id":5,"references":[],"type":"table","data":{"name":"signal_sender_key_stores","was_declared_in_moor":false,"columns":[{"name":"sender_key_name","getter_name":"senderKeyName","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"sender_key","getter_name":"senderKey","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["sender_key_name"]}},{"id":6,"references":[],"type":"table","data":{"name":"signal_session_stores","was_declared_in_moor":false,"columns":[{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"session_record","getter_name":"sessionRecord","moor_type":"blob","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('CAST(strftime(\\'%s\\', CURRENT_TIMESTAMP) AS INTEGER)')","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["device_id","name"]}}]} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 08f2dd7..abc5b4d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -31,7 +31,7 @@ void main() async { apiProvider = ApiProvider(); twonlyDatabase = TwonlyDatabase(); - await twonlyDatabase.messagesDao.appRestarted(); + await twonlyDatabase.messagesDao.resetPendingDownloadState(); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); diff --git a/lib/src/database/daos/media_downloads_dao.dart b/lib/src/database/daos/media_downloads_dao.dart new file mode 100644 index 0000000..a078b16 --- /dev/null +++ b/lib/src/database/daos/media_downloads_dao.dart @@ -0,0 +1,44 @@ +import 'dart:convert'; +import 'package:drift/drift.dart'; +import 'package:logging/logging.dart'; +import 'package:twonly/src/database/tables/media_download_table.dart'; +import 'package:twonly/src/database/twonly_database.dart'; + +part 'media_downloads_dao.g.dart'; + +@DriftAccessor(tables: [MediaDownloads]) +class MediaDownloadsDao extends DatabaseAccessor + with _$MediaDownloadsDaoMixin { + MediaDownloadsDao(super.db); + + Future updateMediaDownload( + int messageId, MediaDownloadsCompanion updatedValues) { + return (update(mediaDownloads)..where((c) => c.messageId.equals(messageId))) + .write(updatedValues); + } + + Future insertMediaDownload(MediaDownloadsCompanion values) async { + try { + return await into(mediaDownloads).insert(values); + } catch (e) { + Logger("media_downloads_dao.dart") + .shout("Error while inserting media upload: $e"); + return null; + } + } + + Future deleteMediaDownload(int messageId) { + return (delete(mediaDownloads)..where((t) => t.messageId.equals(messageId))) + .go(); + } + + SingleOrNullSelectable getMediaDownloadById(int messageId) { + return select(mediaDownloads)..where((t) => t.messageId.equals(messageId)); + } + + SingleOrNullSelectable getMediaDownloadByDownloadToken( + List downloadToken) { + return select(mediaDownloads) + ..where((t) => t.downloadToken.equals(json.encode(downloadToken))); + } +} diff --git a/lib/src/database/daos/media_downloads_dao.g.dart b/lib/src/database/daos/media_downloads_dao.g.dart new file mode 100644 index 0000000..da1cdba --- /dev/null +++ b/lib/src/database/daos/media_downloads_dao.g.dart @@ -0,0 +1,8 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'media_downloads_dao.dart'; + +// ignore_for_file: type=lint +mixin _$MediaDownloadsDaoMixin on DatabaseAccessor { + $MediaDownloadsTable get mediaDownloads => attachedDatabase.mediaDownloads; +} diff --git a/lib/src/database/daos/media_uploads_dao.dart b/lib/src/database/daos/media_uploads_dao.dart new file mode 100644 index 0000000..6e6fb11 --- /dev/null +++ b/lib/src/database/daos/media_uploads_dao.dart @@ -0,0 +1,47 @@ +import 'package:drift/drift.dart'; +import 'package:logging/logging.dart'; +import 'package:twonly/src/database/tables/media_uploads_table.dart'; +import 'package:twonly/src/database/twonly_database.dart'; + +part 'media_uploads_dao.g.dart'; + +@DriftAccessor(tables: [MediaUploads]) +class MediaUploadsDao extends DatabaseAccessor + with _$MediaUploadsDaoMixin { + MediaUploadsDao(super.db); + + Future> getMediaUploadsForRetry() { + return (select(mediaUploads) + ..where( + (t) => t.state.equals(UploadState.receiverNotified.name).not())) + .get(); + } + + Future updateMediaUpload( + int mediaUploadId, MediaUploadsCompanion updatedValues) { + return (update(mediaUploads) + ..where((c) => c.mediaUploadId.equals(mediaUploadId))) + .write(updatedValues); + } + + Future insertMediaUpload(MediaUploadsCompanion values) async { + try { + return await into(mediaUploads).insert(values); + } catch (e) { + Logger("media_uploads_dao.dart") + .shout("Error while inserting media upload: $e"); + return null; + } + } + + Future deleteMediaUpload(int mediaUploadId) { + return (delete(mediaUploads) + ..where((t) => t.mediaUploadId.equals(mediaUploadId))) + .go(); + } + + SingleOrNullSelectable getMediaUploadById(int mediaUploadId) { + return select(mediaUploads) + ..where((t) => t.mediaUploadId.equals(mediaUploadId)); + } +} diff --git a/lib/src/database/daos/media_uploads_dao.g.dart b/lib/src/database/daos/media_uploads_dao.g.dart new file mode 100644 index 0000000..c2aa995 --- /dev/null +++ b/lib/src/database/daos/media_uploads_dao.g.dart @@ -0,0 +1,8 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'media_uploads_dao.dart'; + +// ignore_for_file: type=lint +mixin _$MediaUploadsDaoMixin on DatabaseAccessor { + $MediaUploadsTable get mediaUploads => attachedDatabase.mediaUploads; +} diff --git a/lib/src/database/daos/messages_dao.dart b/lib/src/database/daos/messages_dao.dart index ce48e51..7cc84c5 100644 --- a/lib/src/database/daos/messages_dao.dart +++ b/lib/src/database/daos/messages_dao.dart @@ -104,7 +104,7 @@ class MessagesDao extends DatabaseAccessor .write(updates); } - Future appRestarted() { + Future resetPendingDownloadState() { // All media files in the downloading state are reseteded to the pending state // When the app is used in mobile network, they will not be downloaded at the start // if they are not yet downloaded... diff --git a/lib/src/database/tables/media_download_table.dart b/lib/src/database/tables/media_download_table.dart new file mode 100644 index 0000000..e54ff6c --- /dev/null +++ b/lib/src/database/tables/media_download_table.dart @@ -0,0 +1,8 @@ +import 'package:drift/drift.dart'; +import 'package:twonly/src/database/tables/media_uploads_table.dart'; + +@DataClassName('MediaDownload') +class MediaDownloads extends Table { + IntColumn get messageId => integer()(); + TextColumn get downloadToken => text().map(IntListTypeConverter())(); +} diff --git a/lib/src/database/tables/media_uploads_table.dart b/lib/src/database/tables/media_uploads_table.dart new file mode 100644 index 0000000..3310b66 --- /dev/null +++ b/lib/src/database/tables/media_uploads_table.dart @@ -0,0 +1,216 @@ +import 'dart:convert'; +import 'package:drift/drift.dart'; + +enum UploadState { + pending, + addedToMessagesDb, + // added .compressed to filename + isCompressed, + // added .encypted to filename + isEncrypted, + hasUploadToken, + isUploaded, + receiverNotified, + // after all users notified all media files that are not storeable by the other person will be deleted +} + +@DataClassName('MediaUpload') +class MediaUploads extends Table { + IntColumn get mediaUploadId => integer().autoIncrement()(); + TextColumn get state => + textEnum().withDefault(Constant(UploadState.pending.name))(); + + TextColumn get metadata => text().map(MediaUploadMetadataConverter())(); + + /// exists in UploadState.addedToMessagesDb + TextColumn get messageIds => text().map(IntListTypeConverter()).nullable()(); + + /// exsists in UploadState.isEncrypted + TextColumn get encryptionData => + text().map(MediaEncryptionDataConverter()).nullable()(); + + /// exsists in UploadState.hasUploadToken + TextColumn get uploadTokens => + text().map(MediaUploadTokensConverter()).nullable()(); + + /// exists in UploadState.addedToMessagesDb + TextColumn get alreadyNotified => + text().map(IntListTypeConverter()).withDefault(Constant("[]"))(); +} + +// --- state ---- + +class MediaUploadMetadata { + late List contactIds; + late bool isRealTwonly; + late int maxShowTime; + late DateTime messageSendAt; + late bool isVideo; + late bool videoWithAudio; + + MediaUploadMetadata(); + + Map toJson() { + return { + 'contactIds': contactIds, + 'isRealTwonly': isRealTwonly, + 'maxShowTime': maxShowTime, + 'isVideo': isVideo, + 'videoWithAudio': videoWithAudio, + 'messageSendAt': messageSendAt.toIso8601String(), + }; + } + + factory MediaUploadMetadata.fromJson(Map json) { + MediaUploadMetadata state = MediaUploadMetadata(); + state.contactIds = List.from(json['contactIds']); + state.isRealTwonly = json['isRealTwonly']; + state.isVideo = json['isVideo']; + state.maxShowTime = json['maxShowTime']; + state.maxShowTime = json['maxShowTime']; + state.messageSendAt = DateTime.parse(json['messageSendAt']); + return state; + } +} + +class MediaEncryptionData { + late List sha2Hash; + late List encryptionKey; + late List encryptionMac; + late List encryptionNonce; + + MediaEncryptionData(); + + Map toJson() { + return { + 'sha2Hash': sha2Hash, + 'encryptionKey': encryptionKey, + 'encryptionMac': encryptionMac, + 'encryptionNonce': encryptionNonce, + }; + } + + factory MediaEncryptionData.fromJson(Map json) { + MediaEncryptionData state = MediaEncryptionData(); + state.sha2Hash = List.from(json['sha2Hash']); + state.encryptionKey = List.from(json['encryptionKey']); + state.encryptionMac = List.from(json['encryptionMac']); + state.encryptionNonce = List.from(json['encryptionNonce']); + return state; + } +} + +class MediaUploadTokens { + late List uploadToken; + late List> downloadTokens; + + MediaUploadTokens(); + + Map toJson() { + return { + 'uploadToken': uploadToken, + 'downloadTokens': downloadTokens, + }; + } + + factory MediaUploadTokens.fromJson(Map json) { + MediaUploadTokens state = MediaUploadTokens(); + state.uploadToken = List.from(json['uploadToken']); + state.downloadTokens = List>.from( + json['downloadTokens'].map((token) => List.from(token)), + ); + return state; + } +} + +// --- converters ---- + +class IntListTypeConverter extends TypeConverter, String> { + @override + List fromSql(String fromDb) { + return List.from(jsonDecode(fromDb)); + } + + @override + String toSql(List value) { + return json.encode(value); + } +} + +class MediaUploadMetadataConverter + extends TypeConverter + with JsonTypeConverter2> { + const MediaUploadMetadataConverter(); + + @override + MediaUploadMetadata fromSql(String fromDb) { + return fromJson(json.decode(fromDb) as Map); + } + + @override + String toSql(MediaUploadMetadata value) { + return json.encode(toJson(value)); + } + + @override + MediaUploadMetadata fromJson(Map json) { + return MediaUploadMetadata.fromJson(json); + } + + @override + Map toJson(MediaUploadMetadata value) { + return value.toJson(); + } +} + +class MediaEncryptionDataConverter + extends TypeConverter + with JsonTypeConverter2> { + const MediaEncryptionDataConverter(); + + @override + MediaEncryptionData fromSql(String fromDb) { + return fromJson(json.decode(fromDb) as Map); + } + + @override + String toSql(MediaEncryptionData value) { + return json.encode(toJson(value)); + } + + @override + MediaEncryptionData fromJson(Map json) { + return MediaEncryptionData.fromJson(json); + } + + @override + Map toJson(MediaEncryptionData value) { + return value.toJson(); + } +} + +class MediaUploadTokensConverter + extends TypeConverter + with JsonTypeConverter2> { + const MediaUploadTokensConverter(); + + @override + MediaUploadTokens fromSql(String fromDb) { + return fromJson(json.decode(fromDb) as Map); + } + + @override + String toSql(MediaUploadTokens value) { + return json.encode(toJson(value)); + } + + @override + MediaUploadTokens fromJson(Map json) { + return MediaUploadTokens.fromJson(json); + } + + @override + Map toJson(MediaUploadTokens value) { + return value.toJson(); + } +} diff --git a/lib/src/database/tables/messages_table.dart b/lib/src/database/tables/messages_table.dart index 0595746..01d2801 100644 --- a/lib/src/database/tables/messages_table.dart +++ b/lib/src/database/tables/messages_table.dart @@ -27,6 +27,9 @@ class Messages extends Table { IntColumn get messageId => integer().autoIncrement()(); IntColumn get messageOtherId => integer().nullable()(); + IntColumn get mediaUploadId => integer().nullable()(); + IntColumn get mediaDownloadId => integer().nullable()(); + IntColumn get responseToMessageId => integer().nullable()(); IntColumn get responseToOtherMessageId => integer().nullable()(); diff --git a/lib/src/database/twonly_database.dart b/lib/src/database/twonly_database.dart index 7045a29..b94cf76 100644 --- a/lib/src/database/twonly_database.dart +++ b/lib/src/database/twonly_database.dart @@ -3,8 +3,12 @@ import 'package:drift_flutter/drift_flutter.dart' show driftDatabase, DriftNativeOptions; import 'package:path_provider/path_provider.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart'; +import 'package:twonly/src/database/daos/media_downloads_dao.dart'; +import 'package:twonly/src/database/daos/media_uploads_dao.dart'; import 'package:twonly/src/database/daos/messages_dao.dart'; import 'package:twonly/src/database/tables/contacts_table.dart'; +import 'package:twonly/src/database/tables/media_download_table.dart'; +import 'package:twonly/src/database/tables/media_uploads_table.dart'; import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/database/tables/signal_identity_key_store_table.dart'; import 'package:twonly/src/database/tables/signal_pre_key_store_table.dart'; @@ -18,13 +22,17 @@ part 'twonly_database.g.dart'; @DriftDatabase(tables: [ Contacts, Messages, + MediaUploads, + MediaDownloads, SignalIdentityKeyStores, SignalPreKeyStores, SignalSenderKeyStores, SignalSessionStores ], daos: [ MessagesDao, - ContactsDao + ContactsDao, + MediaUploadsDao, + MediaDownloadsDao, ]) class TwonlyDatabase extends _$TwonlyDatabase { TwonlyDatabase([QueryExecutor? e]) @@ -35,7 +43,7 @@ class TwonlyDatabase extends _$TwonlyDatabase { TwonlyDatabase.forTesting(DatabaseConnection super.connection); @override - int get schemaVersion => 3; + int get schemaVersion => 4; static QueryExecutor _openConnection() { return driftDatabase( @@ -58,6 +66,9 @@ class TwonlyDatabase extends _$TwonlyDatabase { m.addColumn( schema.contacts, schema.contacts.deleteMessagesAfterXMinutes); }, + from3To4: (m, schema) async { + m.createTable(mediaUploads); + }, ), ); } diff --git a/lib/src/database/twonly_database.g.dart b/lib/src/database/twonly_database.g.dart index ecacf18..a7fd38b 100644 --- a/lib/src/database/twonly_database.g.dart +++ b/lib/src/database/twonly_database.g.dart @@ -958,6 +958,18 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { late final GeneratedColumn messageOtherId = GeneratedColumn( 'message_other_id', aliasedName, true, type: DriftSqlType.int, requiredDuringInsert: false); + static const VerificationMeta _mediaUploadIdMeta = + const VerificationMeta('mediaUploadId'); + @override + late final GeneratedColumn mediaUploadId = GeneratedColumn( + 'media_upload_id', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + static const VerificationMeta _mediaDownloadIdMeta = + const VerificationMeta('mediaDownloadId'); + @override + late final GeneratedColumn mediaDownloadId = GeneratedColumn( + 'media_download_id', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); static const VerificationMeta _responseToMessageIdMeta = const VerificationMeta('responseToMessageId'); @override @@ -1044,6 +1056,8 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { contactId, messageId, messageOtherId, + mediaUploadId, + mediaDownloadId, responseToMessageId, responseToOtherMessageId, acknowledgeByUser, @@ -1082,6 +1096,18 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { messageOtherId.isAcceptableOrUnknown( data['message_other_id']!, _messageOtherIdMeta)); } + if (data.containsKey('media_upload_id')) { + context.handle( + _mediaUploadIdMeta, + mediaUploadId.isAcceptableOrUnknown( + data['media_upload_id']!, _mediaUploadIdMeta)); + } + if (data.containsKey('media_download_id')) { + context.handle( + _mediaDownloadIdMeta, + mediaDownloadId.isAcceptableOrUnknown( + data['media_download_id']!, _mediaDownloadIdMeta)); + } if (data.containsKey('response_to_message_id')) { context.handle( _responseToMessageIdMeta, @@ -1146,6 +1172,10 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> { .read(DriftSqlType.int, data['${effectivePrefix}message_id'])!, messageOtherId: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}message_other_id']), + mediaUploadId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}media_upload_id']), + mediaDownloadId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}media_download_id']), responseToMessageId: attachedDatabase.typeMapping.read( DriftSqlType.int, data['${effectivePrefix}response_to_message_id']), responseToOtherMessageId: attachedDatabase.typeMapping.read( @@ -1188,6 +1218,8 @@ class Message extends DataClass implements Insertable { final int contactId; final int messageId; final int? messageOtherId; + final int? mediaUploadId; + final int? mediaDownloadId; final int? responseToMessageId; final int? responseToOtherMessageId; final bool acknowledgeByUser; @@ -1203,6 +1235,8 @@ class Message extends DataClass implements Insertable { {required this.contactId, required this.messageId, this.messageOtherId, + this.mediaUploadId, + this.mediaDownloadId, this.responseToMessageId, this.responseToOtherMessageId, required this.acknowledgeByUser, @@ -1222,6 +1256,12 @@ class Message extends DataClass implements Insertable { if (!nullToAbsent || messageOtherId != null) { map['message_other_id'] = Variable(messageOtherId); } + if (!nullToAbsent || mediaUploadId != null) { + map['media_upload_id'] = Variable(mediaUploadId); + } + if (!nullToAbsent || mediaDownloadId != null) { + map['media_download_id'] = Variable(mediaDownloadId); + } if (!nullToAbsent || responseToMessageId != null) { map['response_to_message_id'] = Variable(responseToMessageId); } @@ -1257,6 +1297,12 @@ class Message extends DataClass implements Insertable { messageOtherId: messageOtherId == null && nullToAbsent ? const Value.absent() : Value(messageOtherId), + mediaUploadId: mediaUploadId == null && nullToAbsent + ? const Value.absent() + : Value(mediaUploadId), + mediaDownloadId: mediaDownloadId == null && nullToAbsent + ? const Value.absent() + : Value(mediaDownloadId), responseToMessageId: responseToMessageId == null && nullToAbsent ? const Value.absent() : Value(responseToMessageId), @@ -1286,6 +1332,8 @@ class Message extends DataClass implements Insertable { contactId: serializer.fromJson(json['contactId']), messageId: serializer.fromJson(json['messageId']), messageOtherId: serializer.fromJson(json['messageOtherId']), + mediaUploadId: serializer.fromJson(json['mediaUploadId']), + mediaDownloadId: serializer.fromJson(json['mediaDownloadId']), responseToMessageId: serializer.fromJson(json['responseToMessageId']), responseToOtherMessageId: @@ -1311,6 +1359,8 @@ class Message extends DataClass implements Insertable { 'contactId': serializer.toJson(contactId), 'messageId': serializer.toJson(messageId), 'messageOtherId': serializer.toJson(messageOtherId), + 'mediaUploadId': serializer.toJson(mediaUploadId), + 'mediaDownloadId': serializer.toJson(mediaDownloadId), 'responseToMessageId': serializer.toJson(responseToMessageId), 'responseToOtherMessageId': serializer.toJson(responseToOtherMessageId), @@ -1332,6 +1382,8 @@ class Message extends DataClass implements Insertable { {int? contactId, int? messageId, Value messageOtherId = const Value.absent(), + Value mediaUploadId = const Value.absent(), + Value mediaDownloadId = const Value.absent(), Value responseToMessageId = const Value.absent(), Value responseToOtherMessageId = const Value.absent(), bool? acknowledgeByUser, @@ -1348,6 +1400,11 @@ class Message extends DataClass implements Insertable { messageId: messageId ?? this.messageId, messageOtherId: messageOtherId.present ? messageOtherId.value : this.messageOtherId, + mediaUploadId: + mediaUploadId.present ? mediaUploadId.value : this.mediaUploadId, + mediaDownloadId: mediaDownloadId.present + ? mediaDownloadId.value + : this.mediaDownloadId, responseToMessageId: responseToMessageId.present ? responseToMessageId.value : this.responseToMessageId, @@ -1371,6 +1428,12 @@ class Message extends DataClass implements Insertable { messageOtherId: data.messageOtherId.present ? data.messageOtherId.value : this.messageOtherId, + mediaUploadId: data.mediaUploadId.present + ? data.mediaUploadId.value + : this.mediaUploadId, + mediaDownloadId: data.mediaDownloadId.present + ? data.mediaDownloadId.value + : this.mediaDownloadId, responseToMessageId: data.responseToMessageId.present ? data.responseToMessageId.value : this.responseToMessageId, @@ -1404,6 +1467,8 @@ class Message extends DataClass implements Insertable { ..write('contactId: $contactId, ') ..write('messageId: $messageId, ') ..write('messageOtherId: $messageOtherId, ') + ..write('mediaUploadId: $mediaUploadId, ') + ..write('mediaDownloadId: $mediaDownloadId, ') ..write('responseToMessageId: $responseToMessageId, ') ..write('responseToOtherMessageId: $responseToOtherMessageId, ') ..write('acknowledgeByUser: $acknowledgeByUser, ') @@ -1424,6 +1489,8 @@ class Message extends DataClass implements Insertable { contactId, messageId, messageOtherId, + mediaUploadId, + mediaDownloadId, responseToMessageId, responseToOtherMessageId, acknowledgeByUser, @@ -1442,6 +1509,8 @@ class Message extends DataClass implements Insertable { other.contactId == this.contactId && other.messageId == this.messageId && other.messageOtherId == this.messageOtherId && + other.mediaUploadId == this.mediaUploadId && + other.mediaDownloadId == this.mediaDownloadId && other.responseToMessageId == this.responseToMessageId && other.responseToOtherMessageId == this.responseToOtherMessageId && other.acknowledgeByUser == this.acknowledgeByUser && @@ -1459,6 +1528,8 @@ class MessagesCompanion extends UpdateCompanion { final Value contactId; final Value messageId; final Value messageOtherId; + final Value mediaUploadId; + final Value mediaDownloadId; final Value responseToMessageId; final Value responseToOtherMessageId; final Value acknowledgeByUser; @@ -1474,6 +1545,8 @@ class MessagesCompanion extends UpdateCompanion { this.contactId = const Value.absent(), this.messageId = const Value.absent(), this.messageOtherId = const Value.absent(), + this.mediaUploadId = const Value.absent(), + this.mediaDownloadId = const Value.absent(), this.responseToMessageId = const Value.absent(), this.responseToOtherMessageId = const Value.absent(), this.acknowledgeByUser = const Value.absent(), @@ -1490,6 +1563,8 @@ class MessagesCompanion extends UpdateCompanion { required int contactId, this.messageId = const Value.absent(), this.messageOtherId = const Value.absent(), + this.mediaUploadId = const Value.absent(), + this.mediaDownloadId = const Value.absent(), this.responseToMessageId = const Value.absent(), this.responseToOtherMessageId = const Value.absent(), this.acknowledgeByUser = const Value.absent(), @@ -1507,6 +1582,8 @@ class MessagesCompanion extends UpdateCompanion { Expression? contactId, Expression? messageId, Expression? messageOtherId, + Expression? mediaUploadId, + Expression? mediaDownloadId, Expression? responseToMessageId, Expression? responseToOtherMessageId, Expression? acknowledgeByUser, @@ -1523,6 +1600,8 @@ class MessagesCompanion extends UpdateCompanion { if (contactId != null) 'contact_id': contactId, if (messageId != null) 'message_id': messageId, if (messageOtherId != null) 'message_other_id': messageOtherId, + if (mediaUploadId != null) 'media_upload_id': mediaUploadId, + if (mediaDownloadId != null) 'media_download_id': mediaDownloadId, if (responseToMessageId != null) 'response_to_message_id': responseToMessageId, if (responseToOtherMessageId != null) @@ -1544,6 +1623,8 @@ class MessagesCompanion extends UpdateCompanion { {Value? contactId, Value? messageId, Value? messageOtherId, + Value? mediaUploadId, + Value? mediaDownloadId, Value? responseToMessageId, Value? responseToOtherMessageId, Value? acknowledgeByUser, @@ -1559,6 +1640,8 @@ class MessagesCompanion extends UpdateCompanion { contactId: contactId ?? this.contactId, messageId: messageId ?? this.messageId, messageOtherId: messageOtherId ?? this.messageOtherId, + mediaUploadId: mediaUploadId ?? this.mediaUploadId, + mediaDownloadId: mediaDownloadId ?? this.mediaDownloadId, responseToMessageId: responseToMessageId ?? this.responseToMessageId, responseToOtherMessageId: responseToOtherMessageId ?? this.responseToOtherMessageId, @@ -1586,6 +1669,12 @@ class MessagesCompanion extends UpdateCompanion { if (messageOtherId.present) { map['message_other_id'] = Variable(messageOtherId.value); } + if (mediaUploadId.present) { + map['media_upload_id'] = Variable(mediaUploadId.value); + } + if (mediaDownloadId.present) { + map['media_download_id'] = Variable(mediaDownloadId.value); + } if (responseToMessageId.present) { map['response_to_message_id'] = Variable(responseToMessageId.value); } @@ -1631,6 +1720,8 @@ class MessagesCompanion extends UpdateCompanion { ..write('contactId: $contactId, ') ..write('messageId: $messageId, ') ..write('messageOtherId: $messageOtherId, ') + ..write('mediaUploadId: $mediaUploadId, ') + ..write('mediaDownloadId: $mediaDownloadId, ') ..write('responseToMessageId: $responseToMessageId, ') ..write('responseToOtherMessageId: $responseToOtherMessageId, ') ..write('acknowledgeByUser: $acknowledgeByUser, ') @@ -1647,6 +1738,640 @@ class MessagesCompanion extends UpdateCompanion { } } +class $MediaUploadsTable extends MediaUploads + with TableInfo<$MediaUploadsTable, MediaUpload> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $MediaUploadsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _mediaUploadIdMeta = + const VerificationMeta('mediaUploadId'); + @override + late final GeneratedColumn mediaUploadId = GeneratedColumn( + 'media_upload_id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + @override + late final GeneratedColumnWithTypeConverter state = + GeneratedColumn('state', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(UploadState.pending.name)) + .withConverter($MediaUploadsTable.$converterstate); + @override + late final GeneratedColumnWithTypeConverter + metadata = GeneratedColumn('metadata', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true) + .withConverter( + $MediaUploadsTable.$convertermetadata); + @override + late final GeneratedColumnWithTypeConverter?, String> messageIds = + GeneratedColumn('message_ids', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter?>($MediaUploadsTable.$convertermessageIdsn); + @override + late final GeneratedColumnWithTypeConverter + encryptionData = GeneratedColumn( + 'encryption_data', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter( + $MediaUploadsTable.$converterencryptionDatan); + @override + late final GeneratedColumnWithTypeConverter + uploadTokens = GeneratedColumn('upload_tokens', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter( + $MediaUploadsTable.$converteruploadTokensn); + @override + late final GeneratedColumnWithTypeConverter, String> + alreadyNotified = GeneratedColumn( + 'already_notified', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant("[]")) + .withConverter>( + $MediaUploadsTable.$converteralreadyNotified); + @override + List get $columns => [ + mediaUploadId, + state, + metadata, + messageIds, + encryptionData, + uploadTokens, + alreadyNotified + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'media_uploads'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('media_upload_id')) { + context.handle( + _mediaUploadIdMeta, + mediaUploadId.isAcceptableOrUnknown( + data['media_upload_id']!, _mediaUploadIdMeta)); + } + return context; + } + + @override + Set get $primaryKey => {mediaUploadId}; + @override + MediaUpload map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MediaUpload( + mediaUploadId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}media_upload_id'])!, + state: $MediaUploadsTable.$converterstate.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}state'])!), + metadata: $MediaUploadsTable.$convertermetadata.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}metadata'])!), + messageIds: $MediaUploadsTable.$convertermessageIdsn.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}message_ids'])), + encryptionData: $MediaUploadsTable.$converterencryptionDatan.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}encryption_data'])), + uploadTokens: $MediaUploadsTable.$converteruploadTokensn.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}upload_tokens'])), + alreadyNotified: $MediaUploadsTable.$converteralreadyNotified.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, + data['${effectivePrefix}already_notified'])!), + ); + } + + @override + $MediaUploadsTable createAlias(String alias) { + return $MediaUploadsTable(attachedDatabase, alias); + } + + static JsonTypeConverter2 $converterstate = + const EnumNameConverter(UploadState.values); + static JsonTypeConverter2> + $convertermetadata = MediaUploadMetadataConverter(); + static TypeConverter, String> $convertermessageIds = + IntListTypeConverter(); + static TypeConverter?, String?> $convertermessageIdsn = + NullAwareTypeConverter.wrap($convertermessageIds); + static JsonTypeConverter2> + $converterencryptionData = MediaEncryptionDataConverter(); + static JsonTypeConverter2?> $converterencryptionDatan = + JsonTypeConverter2.asNullable($converterencryptionData); + static JsonTypeConverter2> + $converteruploadTokens = MediaUploadTokensConverter(); + static JsonTypeConverter2?> + $converteruploadTokensn = + JsonTypeConverter2.asNullable($converteruploadTokens); + static TypeConverter, String> $converteralreadyNotified = + IntListTypeConverter(); +} + +class MediaUpload extends DataClass implements Insertable { + final int mediaUploadId; + final UploadState state; + final MediaUploadMetadata metadata; + + /// exists in UploadState.addedToMessagesDb + final List? messageIds; + + /// exsists in UploadState.isEncrypted + final MediaEncryptionData? encryptionData; + + /// exsists in UploadState.hasUploadToken + final MediaUploadTokens? uploadTokens; + + /// exists in UploadState.addedToMessagesDb + final List alreadyNotified; + const MediaUpload( + {required this.mediaUploadId, + required this.state, + required this.metadata, + this.messageIds, + this.encryptionData, + this.uploadTokens, + required this.alreadyNotified}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['media_upload_id'] = Variable(mediaUploadId); + { + map['state'] = + Variable($MediaUploadsTable.$converterstate.toSql(state)); + } + { + map['metadata'] = Variable( + $MediaUploadsTable.$convertermetadata.toSql(metadata)); + } + if (!nullToAbsent || messageIds != null) { + map['message_ids'] = Variable( + $MediaUploadsTable.$convertermessageIdsn.toSql(messageIds)); + } + if (!nullToAbsent || encryptionData != null) { + map['encryption_data'] = Variable( + $MediaUploadsTable.$converterencryptionDatan.toSql(encryptionData)); + } + if (!nullToAbsent || uploadTokens != null) { + map['upload_tokens'] = Variable( + $MediaUploadsTable.$converteruploadTokensn.toSql(uploadTokens)); + } + { + map['already_notified'] = Variable( + $MediaUploadsTable.$converteralreadyNotified.toSql(alreadyNotified)); + } + return map; + } + + MediaUploadsCompanion toCompanion(bool nullToAbsent) { + return MediaUploadsCompanion( + mediaUploadId: Value(mediaUploadId), + state: Value(state), + metadata: Value(metadata), + messageIds: messageIds == null && nullToAbsent + ? const Value.absent() + : Value(messageIds), + encryptionData: encryptionData == null && nullToAbsent + ? const Value.absent() + : Value(encryptionData), + uploadTokens: uploadTokens == null && nullToAbsent + ? const Value.absent() + : Value(uploadTokens), + alreadyNotified: Value(alreadyNotified), + ); + } + + factory MediaUpload.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MediaUpload( + mediaUploadId: serializer.fromJson(json['mediaUploadId']), + state: $MediaUploadsTable.$converterstate + .fromJson(serializer.fromJson(json['state'])), + metadata: $MediaUploadsTable.$convertermetadata.fromJson( + serializer.fromJson>(json['metadata'])), + messageIds: serializer.fromJson?>(json['messageIds']), + encryptionData: $MediaUploadsTable.$converterencryptionDatan.fromJson( + serializer.fromJson?>(json['encryptionData'])), + uploadTokens: $MediaUploadsTable.$converteruploadTokensn.fromJson( + serializer.fromJson?>(json['uploadTokens'])), + alreadyNotified: serializer.fromJson>(json['alreadyNotified']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'mediaUploadId': serializer.toJson(mediaUploadId), + 'state': serializer + .toJson($MediaUploadsTable.$converterstate.toJson(state)), + 'metadata': serializer.toJson>( + $MediaUploadsTable.$convertermetadata.toJson(metadata)), + 'messageIds': serializer.toJson?>(messageIds), + 'encryptionData': serializer.toJson?>( + $MediaUploadsTable.$converterencryptionDatan.toJson(encryptionData)), + 'uploadTokens': serializer.toJson?>( + $MediaUploadsTable.$converteruploadTokensn.toJson(uploadTokens)), + 'alreadyNotified': serializer.toJson>(alreadyNotified), + }; + } + + MediaUpload copyWith( + {int? mediaUploadId, + UploadState? state, + MediaUploadMetadata? metadata, + Value?> messageIds = const Value.absent(), + Value encryptionData = const Value.absent(), + Value uploadTokens = const Value.absent(), + List? alreadyNotified}) => + MediaUpload( + mediaUploadId: mediaUploadId ?? this.mediaUploadId, + state: state ?? this.state, + metadata: metadata ?? this.metadata, + messageIds: messageIds.present ? messageIds.value : this.messageIds, + encryptionData: + encryptionData.present ? encryptionData.value : this.encryptionData, + uploadTokens: + uploadTokens.present ? uploadTokens.value : this.uploadTokens, + alreadyNotified: alreadyNotified ?? this.alreadyNotified, + ); + MediaUpload copyWithCompanion(MediaUploadsCompanion data) { + return MediaUpload( + mediaUploadId: data.mediaUploadId.present + ? data.mediaUploadId.value + : this.mediaUploadId, + state: data.state.present ? data.state.value : this.state, + metadata: data.metadata.present ? data.metadata.value : this.metadata, + messageIds: + data.messageIds.present ? data.messageIds.value : this.messageIds, + encryptionData: data.encryptionData.present + ? data.encryptionData.value + : this.encryptionData, + uploadTokens: data.uploadTokens.present + ? data.uploadTokens.value + : this.uploadTokens, + alreadyNotified: data.alreadyNotified.present + ? data.alreadyNotified.value + : this.alreadyNotified, + ); + } + + @override + String toString() { + return (StringBuffer('MediaUpload(') + ..write('mediaUploadId: $mediaUploadId, ') + ..write('state: $state, ') + ..write('metadata: $metadata, ') + ..write('messageIds: $messageIds, ') + ..write('encryptionData: $encryptionData, ') + ..write('uploadTokens: $uploadTokens, ') + ..write('alreadyNotified: $alreadyNotified') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(mediaUploadId, state, metadata, messageIds, + encryptionData, uploadTokens, alreadyNotified); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MediaUpload && + other.mediaUploadId == this.mediaUploadId && + other.state == this.state && + other.metadata == this.metadata && + other.messageIds == this.messageIds && + other.encryptionData == this.encryptionData && + other.uploadTokens == this.uploadTokens && + other.alreadyNotified == this.alreadyNotified); +} + +class MediaUploadsCompanion extends UpdateCompanion { + final Value mediaUploadId; + final Value state; + final Value metadata; + final Value?> messageIds; + final Value encryptionData; + final Value uploadTokens; + final Value> alreadyNotified; + const MediaUploadsCompanion({ + this.mediaUploadId = const Value.absent(), + this.state = const Value.absent(), + this.metadata = const Value.absent(), + this.messageIds = const Value.absent(), + this.encryptionData = const Value.absent(), + this.uploadTokens = const Value.absent(), + this.alreadyNotified = const Value.absent(), + }); + MediaUploadsCompanion.insert({ + this.mediaUploadId = const Value.absent(), + this.state = const Value.absent(), + required MediaUploadMetadata metadata, + this.messageIds = const Value.absent(), + this.encryptionData = const Value.absent(), + this.uploadTokens = const Value.absent(), + this.alreadyNotified = const Value.absent(), + }) : metadata = Value(metadata); + static Insertable custom({ + Expression? mediaUploadId, + Expression? state, + Expression? metadata, + Expression? messageIds, + Expression? encryptionData, + Expression? uploadTokens, + Expression? alreadyNotified, + }) { + return RawValuesInsertable({ + if (mediaUploadId != null) 'media_upload_id': mediaUploadId, + if (state != null) 'state': state, + if (metadata != null) 'metadata': metadata, + if (messageIds != null) 'message_ids': messageIds, + if (encryptionData != null) 'encryption_data': encryptionData, + if (uploadTokens != null) 'upload_tokens': uploadTokens, + if (alreadyNotified != null) 'already_notified': alreadyNotified, + }); + } + + MediaUploadsCompanion copyWith( + {Value? mediaUploadId, + Value? state, + Value? metadata, + Value?>? messageIds, + Value? encryptionData, + Value? uploadTokens, + Value>? alreadyNotified}) { + return MediaUploadsCompanion( + mediaUploadId: mediaUploadId ?? this.mediaUploadId, + state: state ?? this.state, + metadata: metadata ?? this.metadata, + messageIds: messageIds ?? this.messageIds, + encryptionData: encryptionData ?? this.encryptionData, + uploadTokens: uploadTokens ?? this.uploadTokens, + alreadyNotified: alreadyNotified ?? this.alreadyNotified, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (mediaUploadId.present) { + map['media_upload_id'] = Variable(mediaUploadId.value); + } + if (state.present) { + map['state'] = Variable( + $MediaUploadsTable.$converterstate.toSql(state.value)); + } + if (metadata.present) { + map['metadata'] = Variable( + $MediaUploadsTable.$convertermetadata.toSql(metadata.value)); + } + if (messageIds.present) { + map['message_ids'] = Variable( + $MediaUploadsTable.$convertermessageIdsn.toSql(messageIds.value)); + } + if (encryptionData.present) { + map['encryption_data'] = Variable($MediaUploadsTable + .$converterencryptionDatan + .toSql(encryptionData.value)); + } + if (uploadTokens.present) { + map['upload_tokens'] = Variable( + $MediaUploadsTable.$converteruploadTokensn.toSql(uploadTokens.value)); + } + if (alreadyNotified.present) { + map['already_notified'] = Variable($MediaUploadsTable + .$converteralreadyNotified + .toSql(alreadyNotified.value)); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MediaUploadsCompanion(') + ..write('mediaUploadId: $mediaUploadId, ') + ..write('state: $state, ') + ..write('metadata: $metadata, ') + ..write('messageIds: $messageIds, ') + ..write('encryptionData: $encryptionData, ') + ..write('uploadTokens: $uploadTokens, ') + ..write('alreadyNotified: $alreadyNotified') + ..write(')')) + .toString(); + } +} + +class $MediaDownloadsTable extends MediaDownloads + with TableInfo<$MediaDownloadsTable, MediaDownload> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $MediaDownloadsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _messageIdMeta = + const VerificationMeta('messageId'); + @override + late final GeneratedColumn messageId = GeneratedColumn( + 'message_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + @override + late final GeneratedColumnWithTypeConverter, String> downloadToken = + GeneratedColumn('download_token', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true) + .withConverter>( + $MediaDownloadsTable.$converterdownloadToken); + @override + List get $columns => [messageId, downloadToken]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'media_downloads'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('message_id')) { + context.handle(_messageIdMeta, + messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta)); + } else if (isInserting) { + context.missing(_messageIdMeta); + } + return context; + } + + @override + Set get $primaryKey => const {}; + @override + MediaDownload map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MediaDownload( + messageId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}message_id'])!, + downloadToken: $MediaDownloadsTable.$converterdownloadToken.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}download_token'])!), + ); + } + + @override + $MediaDownloadsTable createAlias(String alias) { + return $MediaDownloadsTable(attachedDatabase, alias); + } + + static TypeConverter, String> $converterdownloadToken = + IntListTypeConverter(); +} + +class MediaDownload extends DataClass implements Insertable { + final int messageId; + final List downloadToken; + const MediaDownload({required this.messageId, required this.downloadToken}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['message_id'] = Variable(messageId); + { + map['download_token'] = Variable( + $MediaDownloadsTable.$converterdownloadToken.toSql(downloadToken)); + } + return map; + } + + MediaDownloadsCompanion toCompanion(bool nullToAbsent) { + return MediaDownloadsCompanion( + messageId: Value(messageId), + downloadToken: Value(downloadToken), + ); + } + + factory MediaDownload.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MediaDownload( + messageId: serializer.fromJson(json['messageId']), + downloadToken: serializer.fromJson>(json['downloadToken']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'messageId': serializer.toJson(messageId), + 'downloadToken': serializer.toJson>(downloadToken), + }; + } + + MediaDownload copyWith({int? messageId, List? downloadToken}) => + MediaDownload( + messageId: messageId ?? this.messageId, + downloadToken: downloadToken ?? this.downloadToken, + ); + MediaDownload copyWithCompanion(MediaDownloadsCompanion data) { + return MediaDownload( + messageId: data.messageId.present ? data.messageId.value : this.messageId, + downloadToken: data.downloadToken.present + ? data.downloadToken.value + : this.downloadToken, + ); + } + + @override + String toString() { + return (StringBuffer('MediaDownload(') + ..write('messageId: $messageId, ') + ..write('downloadToken: $downloadToken') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(messageId, downloadToken); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MediaDownload && + other.messageId == this.messageId && + other.downloadToken == this.downloadToken); +} + +class MediaDownloadsCompanion extends UpdateCompanion { + final Value messageId; + final Value> downloadToken; + final Value rowid; + const MediaDownloadsCompanion({ + this.messageId = const Value.absent(), + this.downloadToken = const Value.absent(), + this.rowid = const Value.absent(), + }); + MediaDownloadsCompanion.insert({ + required int messageId, + required List downloadToken, + this.rowid = const Value.absent(), + }) : messageId = Value(messageId), + downloadToken = Value(downloadToken); + static Insertable custom({ + Expression? messageId, + Expression? downloadToken, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (messageId != null) 'message_id': messageId, + if (downloadToken != null) 'download_token': downloadToken, + if (rowid != null) 'rowid': rowid, + }); + } + + MediaDownloadsCompanion copyWith( + {Value? messageId, + Value>? downloadToken, + Value? rowid}) { + return MediaDownloadsCompanion( + messageId: messageId ?? this.messageId, + downloadToken: downloadToken ?? this.downloadToken, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (messageId.present) { + map['message_id'] = Variable(messageId.value); + } + if (downloadToken.present) { + map['download_token'] = Variable($MediaDownloadsTable + .$converterdownloadToken + .toSql(downloadToken.value)); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MediaDownloadsCompanion(') + ..write('messageId: $messageId, ') + ..write('downloadToken: $downloadToken, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + class $SignalIdentityKeyStoresTable extends SignalIdentityKeyStores with TableInfo<$SignalIdentityKeyStoresTable, SignalIdentityKeyStore> { @override @@ -2633,6 +3358,8 @@ abstract class _$TwonlyDatabase extends GeneratedDatabase { $TwonlyDatabaseManager get managers => $TwonlyDatabaseManager(this); late final $ContactsTable contacts = $ContactsTable(this); late final $MessagesTable messages = $MessagesTable(this); + late final $MediaUploadsTable mediaUploads = $MediaUploadsTable(this); + late final $MediaDownloadsTable mediaDownloads = $MediaDownloadsTable(this); late final $SignalIdentityKeyStoresTable signalIdentityKeyStores = $SignalIdentityKeyStoresTable(this); late final $SignalPreKeyStoresTable signalPreKeyStores = @@ -2643,6 +3370,10 @@ abstract class _$TwonlyDatabase extends GeneratedDatabase { $SignalSessionStoresTable(this); late final MessagesDao messagesDao = MessagesDao(this as TwonlyDatabase); late final ContactsDao contactsDao = ContactsDao(this as TwonlyDatabase); + late final MediaUploadsDao mediaUploadsDao = + MediaUploadsDao(this as TwonlyDatabase); + late final MediaDownloadsDao mediaDownloadsDao = + MediaDownloadsDao(this as TwonlyDatabase); @override Iterable> get allTables => allSchemaEntities.whereType>(); @@ -2650,6 +3381,8 @@ abstract class _$TwonlyDatabase extends GeneratedDatabase { List get allSchemaEntities => [ contacts, messages, + mediaUploads, + mediaDownloads, signalIdentityKeyStores, signalPreKeyStores, signalSenderKeyStores, @@ -3130,6 +3863,8 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({ required int contactId, Value messageId, Value messageOtherId, + Value mediaUploadId, + Value mediaDownloadId, Value responseToMessageId, Value responseToOtherMessageId, Value acknowledgeByUser, @@ -3146,6 +3881,8 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({ Value contactId, Value messageId, Value messageOtherId, + Value mediaUploadId, + Value mediaDownloadId, Value responseToMessageId, Value responseToOtherMessageId, Value acknowledgeByUser, @@ -3195,6 +3932,13 @@ class $$MessagesTableFilterComposer column: $table.messageOtherId, builder: (column) => ColumnFilters(column)); + ColumnFilters get mediaUploadId => $composableBuilder( + column: $table.mediaUploadId, builder: (column) => ColumnFilters(column)); + + ColumnFilters get mediaDownloadId => $composableBuilder( + column: $table.mediaDownloadId, + builder: (column) => ColumnFilters(column)); + ColumnFilters get responseToMessageId => $composableBuilder( column: $table.responseToMessageId, builder: (column) => ColumnFilters(column)); @@ -3274,6 +4018,14 @@ class $$MessagesTableOrderingComposer column: $table.messageOtherId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get mediaUploadId => $composableBuilder( + column: $table.mediaUploadId, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get mediaDownloadId => $composableBuilder( + column: $table.mediaDownloadId, + builder: (column) => ColumnOrderings(column)); + ColumnOrderings get responseToMessageId => $composableBuilder( column: $table.responseToMessageId, builder: (column) => ColumnOrderings(column)); @@ -3349,6 +4101,12 @@ class $$MessagesTableAnnotationComposer GeneratedColumn get messageOtherId => $composableBuilder( column: $table.messageOtherId, builder: (column) => column); + GeneratedColumn get mediaUploadId => $composableBuilder( + column: $table.mediaUploadId, builder: (column) => column); + + GeneratedColumn get mediaDownloadId => $composableBuilder( + column: $table.mediaDownloadId, builder: (column) => column); + GeneratedColumn get responseToMessageId => $composableBuilder( column: $table.responseToMessageId, builder: (column) => column); @@ -3430,6 +4188,8 @@ class $$MessagesTableTableManager extends RootTableManager< Value contactId = const Value.absent(), Value messageId = const Value.absent(), Value messageOtherId = const Value.absent(), + Value mediaUploadId = const Value.absent(), + Value mediaDownloadId = const Value.absent(), Value responseToMessageId = const Value.absent(), Value responseToOtherMessageId = const Value.absent(), Value acknowledgeByUser = const Value.absent(), @@ -3446,6 +4206,8 @@ class $$MessagesTableTableManager extends RootTableManager< contactId: contactId, messageId: messageId, messageOtherId: messageOtherId, + mediaUploadId: mediaUploadId, + mediaDownloadId: mediaDownloadId, responseToMessageId: responseToMessageId, responseToOtherMessageId: responseToOtherMessageId, acknowledgeByUser: acknowledgeByUser, @@ -3462,6 +4224,8 @@ class $$MessagesTableTableManager extends RootTableManager< required int contactId, Value messageId = const Value.absent(), Value messageOtherId = const Value.absent(), + Value mediaUploadId = const Value.absent(), + Value mediaDownloadId = const Value.absent(), Value responseToMessageId = const Value.absent(), Value responseToOtherMessageId = const Value.absent(), Value acknowledgeByUser = const Value.absent(), @@ -3478,6 +4242,8 @@ class $$MessagesTableTableManager extends RootTableManager< contactId: contactId, messageId: messageId, messageOtherId: messageOtherId, + mediaUploadId: mediaUploadId, + mediaDownloadId: mediaDownloadId, responseToMessageId: responseToMessageId, responseToOtherMessageId: responseToOtherMessageId, acknowledgeByUser: acknowledgeByUser, @@ -3544,6 +4310,358 @@ typedef $$MessagesTableProcessedTableManager = ProcessedTableManager< (Message, $$MessagesTableReferences), Message, PrefetchHooks Function({bool contactId})>; +typedef $$MediaUploadsTableCreateCompanionBuilder = MediaUploadsCompanion + Function({ + Value mediaUploadId, + Value state, + required MediaUploadMetadata metadata, + Value?> messageIds, + Value encryptionData, + Value uploadTokens, + Value> alreadyNotified, +}); +typedef $$MediaUploadsTableUpdateCompanionBuilder = MediaUploadsCompanion + Function({ + Value mediaUploadId, + Value state, + Value metadata, + Value?> messageIds, + Value encryptionData, + Value uploadTokens, + Value> alreadyNotified, +}); + +class $$MediaUploadsTableFilterComposer + extends Composer<_$TwonlyDatabase, $MediaUploadsTable> { + $$MediaUploadsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get mediaUploadId => $composableBuilder( + column: $table.mediaUploadId, builder: (column) => ColumnFilters(column)); + + ColumnWithTypeConverterFilters get state => + $composableBuilder( + column: $table.state, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnWithTypeConverterFilters + get metadata => $composableBuilder( + column: $table.metadata, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnWithTypeConverterFilters?, List, String> + get messageIds => $composableBuilder( + column: $table.messageIds, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnWithTypeConverterFilters + get encryptionData => $composableBuilder( + column: $table.encryptionData, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnWithTypeConverterFilters + get uploadTokens => $composableBuilder( + column: $table.uploadTokens, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnWithTypeConverterFilters, List, String> + get alreadyNotified => $composableBuilder( + column: $table.alreadyNotified, + builder: (column) => ColumnWithTypeConverterFilters(column)); +} + +class $$MediaUploadsTableOrderingComposer + extends Composer<_$TwonlyDatabase, $MediaUploadsTable> { + $$MediaUploadsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get mediaUploadId => $composableBuilder( + column: $table.mediaUploadId, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get state => $composableBuilder( + column: $table.state, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get metadata => $composableBuilder( + column: $table.metadata, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get messageIds => $composableBuilder( + column: $table.messageIds, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get encryptionData => $composableBuilder( + column: $table.encryptionData, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get uploadTokens => $composableBuilder( + column: $table.uploadTokens, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get alreadyNotified => $composableBuilder( + column: $table.alreadyNotified, + builder: (column) => ColumnOrderings(column)); +} + +class $$MediaUploadsTableAnnotationComposer + extends Composer<_$TwonlyDatabase, $MediaUploadsTable> { + $$MediaUploadsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get mediaUploadId => $composableBuilder( + column: $table.mediaUploadId, builder: (column) => column); + + GeneratedColumnWithTypeConverter get state => + $composableBuilder(column: $table.state, builder: (column) => column); + + GeneratedColumnWithTypeConverter get metadata => + $composableBuilder(column: $table.metadata, builder: (column) => column); + + GeneratedColumnWithTypeConverter?, String> get messageIds => + $composableBuilder( + column: $table.messageIds, builder: (column) => column); + + GeneratedColumnWithTypeConverter + get encryptionData => $composableBuilder( + column: $table.encryptionData, builder: (column) => column); + + GeneratedColumnWithTypeConverter + get uploadTokens => $composableBuilder( + column: $table.uploadTokens, builder: (column) => column); + + GeneratedColumnWithTypeConverter, String> get alreadyNotified => + $composableBuilder( + column: $table.alreadyNotified, builder: (column) => column); +} + +class $$MediaUploadsTableTableManager extends RootTableManager< + _$TwonlyDatabase, + $MediaUploadsTable, + MediaUpload, + $$MediaUploadsTableFilterComposer, + $$MediaUploadsTableOrderingComposer, + $$MediaUploadsTableAnnotationComposer, + $$MediaUploadsTableCreateCompanionBuilder, + $$MediaUploadsTableUpdateCompanionBuilder, + ( + MediaUpload, + BaseReferences<_$TwonlyDatabase, $MediaUploadsTable, MediaUpload> + ), + MediaUpload, + PrefetchHooks Function()> { + $$MediaUploadsTableTableManager(_$TwonlyDatabase db, $MediaUploadsTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$MediaUploadsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$MediaUploadsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$MediaUploadsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value mediaUploadId = const Value.absent(), + Value state = const Value.absent(), + Value metadata = const Value.absent(), + Value?> messageIds = const Value.absent(), + Value encryptionData = const Value.absent(), + Value uploadTokens = const Value.absent(), + Value> alreadyNotified = const Value.absent(), + }) => + MediaUploadsCompanion( + mediaUploadId: mediaUploadId, + state: state, + metadata: metadata, + messageIds: messageIds, + encryptionData: encryptionData, + uploadTokens: uploadTokens, + alreadyNotified: alreadyNotified, + ), + createCompanionCallback: ({ + Value mediaUploadId = const Value.absent(), + Value state = const Value.absent(), + required MediaUploadMetadata metadata, + Value?> messageIds = const Value.absent(), + Value encryptionData = const Value.absent(), + Value uploadTokens = const Value.absent(), + Value> alreadyNotified = const Value.absent(), + }) => + MediaUploadsCompanion.insert( + mediaUploadId: mediaUploadId, + state: state, + metadata: metadata, + messageIds: messageIds, + encryptionData: encryptionData, + uploadTokens: uploadTokens, + alreadyNotified: alreadyNotified, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$MediaUploadsTableProcessedTableManager = ProcessedTableManager< + _$TwonlyDatabase, + $MediaUploadsTable, + MediaUpload, + $$MediaUploadsTableFilterComposer, + $$MediaUploadsTableOrderingComposer, + $$MediaUploadsTableAnnotationComposer, + $$MediaUploadsTableCreateCompanionBuilder, + $$MediaUploadsTableUpdateCompanionBuilder, + ( + MediaUpload, + BaseReferences<_$TwonlyDatabase, $MediaUploadsTable, MediaUpload> + ), + MediaUpload, + PrefetchHooks Function()>; +typedef $$MediaDownloadsTableCreateCompanionBuilder = MediaDownloadsCompanion + Function({ + required int messageId, + required List downloadToken, + Value rowid, +}); +typedef $$MediaDownloadsTableUpdateCompanionBuilder = MediaDownloadsCompanion + Function({ + Value messageId, + Value> downloadToken, + Value rowid, +}); + +class $$MediaDownloadsTableFilterComposer + extends Composer<_$TwonlyDatabase, $MediaDownloadsTable> { + $$MediaDownloadsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get messageId => $composableBuilder( + column: $table.messageId, builder: (column) => ColumnFilters(column)); + + ColumnWithTypeConverterFilters, List, String> + get downloadToken => $composableBuilder( + column: $table.downloadToken, + builder: (column) => ColumnWithTypeConverterFilters(column)); +} + +class $$MediaDownloadsTableOrderingComposer + extends Composer<_$TwonlyDatabase, $MediaDownloadsTable> { + $$MediaDownloadsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get messageId => $composableBuilder( + column: $table.messageId, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get downloadToken => $composableBuilder( + column: $table.downloadToken, + builder: (column) => ColumnOrderings(column)); +} + +class $$MediaDownloadsTableAnnotationComposer + extends Composer<_$TwonlyDatabase, $MediaDownloadsTable> { + $$MediaDownloadsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get messageId => + $composableBuilder(column: $table.messageId, builder: (column) => column); + + GeneratedColumnWithTypeConverter, String> get downloadToken => + $composableBuilder( + column: $table.downloadToken, builder: (column) => column); +} + +class $$MediaDownloadsTableTableManager extends RootTableManager< + _$TwonlyDatabase, + $MediaDownloadsTable, + MediaDownload, + $$MediaDownloadsTableFilterComposer, + $$MediaDownloadsTableOrderingComposer, + $$MediaDownloadsTableAnnotationComposer, + $$MediaDownloadsTableCreateCompanionBuilder, + $$MediaDownloadsTableUpdateCompanionBuilder, + ( + MediaDownload, + BaseReferences<_$TwonlyDatabase, $MediaDownloadsTable, MediaDownload> + ), + MediaDownload, + PrefetchHooks Function()> { + $$MediaDownloadsTableTableManager( + _$TwonlyDatabase db, $MediaDownloadsTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$MediaDownloadsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$MediaDownloadsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$MediaDownloadsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value messageId = const Value.absent(), + Value> downloadToken = const Value.absent(), + Value rowid = const Value.absent(), + }) => + MediaDownloadsCompanion( + messageId: messageId, + downloadToken: downloadToken, + rowid: rowid, + ), + createCompanionCallback: ({ + required int messageId, + required List downloadToken, + Value rowid = const Value.absent(), + }) => + MediaDownloadsCompanion.insert( + messageId: messageId, + downloadToken: downloadToken, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$MediaDownloadsTableProcessedTableManager = ProcessedTableManager< + _$TwonlyDatabase, + $MediaDownloadsTable, + MediaDownload, + $$MediaDownloadsTableFilterComposer, + $$MediaDownloadsTableOrderingComposer, + $$MediaDownloadsTableAnnotationComposer, + $$MediaDownloadsTableCreateCompanionBuilder, + $$MediaDownloadsTableUpdateCompanionBuilder, + ( + MediaDownload, + BaseReferences<_$TwonlyDatabase, $MediaDownloadsTable, MediaDownload> + ), + MediaDownload, + PrefetchHooks Function()>; typedef $$SignalIdentityKeyStoresTableCreateCompanionBuilder = SignalIdentityKeyStoresCompanion Function({ required int deviceId, @@ -4158,6 +5276,10 @@ class $TwonlyDatabaseManager { $$ContactsTableTableManager(_db, _db.contacts); $$MessagesTableTableManager get messages => $$MessagesTableTableManager(_db, _db.messages); + $$MediaUploadsTableTableManager get mediaUploads => + $$MediaUploadsTableTableManager(_db, _db.mediaUploads); + $$MediaDownloadsTableTableManager get mediaDownloads => + $$MediaDownloadsTableTableManager(_db, _db.mediaDownloads); $$SignalIdentityKeyStoresTableTableManager get signalIdentityKeyStores => $$SignalIdentityKeyStoresTableTableManager( _db, _db.signalIdentityKeyStores); diff --git a/lib/src/database/twonly_database.steps.dart b/lib/src/database/twonly_database.steps.dart index 53c8fa4..1919e72 100644 --- a/lib/src/database/twonly_database.steps.dart +++ b/lib/src/database/twonly_database.steps.dart @@ -599,9 +599,209 @@ i1.GeneratedColumn _column_40(String aliasedName) => 'delete_messages_after_x_minutes', aliasedName, false, type: i1.DriftSqlType.int, defaultValue: const CustomExpression('1440')); + +final class Schema4 extends i0.VersionedSchema { + Schema4({required super.database}) : super(version: 4); + @override + late final List entities = [ + contacts, + messages, + mediaUploads, + signalIdentityKeyStores, + signalPreKeyStores, + signalSenderKeyStores, + signalSessionStores, + ]; + late final Shape6 contacts = Shape6( + 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_40, + _column_10, + _column_11, + _column_12, + _column_13, + _column_14, + _column_15, + _column_16, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape1 messages = Shape1( + source: i0.VersionedTable( + entityName: 'messages', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_22, + _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_43, + _column_44, + _column_45, + _column_46, + _column_47, + ], + 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); +} + +class Shape7 extends i0.VersionedTable { + Shape7({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get mediaUploadId => + columnsByName['media_upload_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get state => + columnsByName['state']! as i1.GeneratedColumn; + i1.GeneratedColumn get metadata => + columnsByName['metadata']! as i1.GeneratedColumn; + i1.GeneratedColumn get messageIds => + columnsByName['message_ids']! as i1.GeneratedColumn; + i1.GeneratedColumn get encryptionData => + columnsByName['encryption_data']! as i1.GeneratedColumn; + i1.GeneratedColumn get uploadTokens => + columnsByName['upload_tokens']! as i1.GeneratedColumn; + i1.GeneratedColumn get alreadyNotified => + columnsByName['already_notified']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_41(String aliasedName) => + i1.GeneratedColumn('media_upload_id', aliasedName, false, + hasAutoIncrement: true, + type: i1.DriftSqlType.int, + defaultConstraints: + i1.GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); +i1.GeneratedColumn _column_42(String aliasedName) => + i1.GeneratedColumn('state', aliasedName, false, + type: i1.DriftSqlType.string, + defaultValue: const CustomExpression('\'pending\'')); +i1.GeneratedColumn _column_43(String aliasedName) => + i1.GeneratedColumn('metadata', aliasedName, false, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_44(String aliasedName) => + i1.GeneratedColumn('message_ids', aliasedName, true, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_45(String aliasedName) => + i1.GeneratedColumn('encryption_data', aliasedName, true, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_46(String aliasedName) => + i1.GeneratedColumn('upload_tokens', aliasedName, true, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_47(String aliasedName) => + i1.GeneratedColumn('already_notified', aliasedName, false, + type: i1.DriftSqlType.string, + defaultValue: const CustomExpression('\'[]\'')); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, + required Future Function(i1.Migrator m, Schema4 schema) from3To4, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -615,6 +815,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from2To3(migrator, schema); return 3; + case 3: + final schema = Schema4(database: database); + final migrator = i1.Migrator(database, schema); + await from3To4(migrator, schema); + return 4; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -624,9 +829,11 @@ i0.MigrationStepWithVersion migrationSteps({ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, + required Future Function(i1.Migrator m, Schema4 schema) from3To4, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( from1To2: from1To2, from2To3: from2To3, + from3To4: from3To4, )); diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index b492bd1..ffc4dc1 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -80,6 +80,7 @@ "imageEditorDrawOk": "Zeichnung machen", "settingsTitle": "Einstellungen", "settingsChats": "Chats", + "settingsStorageData": "Daten und Speicher", "settingsPreSelectedReactions": "Vorgewählte Reaktions-Emojis", "settingsPreSelectedReactionsError": "Es können maximal 12 Reaktionen ausgewählt werden.", "settingsProfile": "Profil", diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 12cb8cf..54e58aa 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -140,6 +140,8 @@ "@settingsPreSelectedReactionsError": {}, "settingsProfile": "Profile", "@settingsProfile": {}, + "settingsStorageData": "Data and storage", + "@settingsStorageData": {}, "settingsProfileCustomizeAvatar": "Customize your avatar", "@settingsProfileCustomizeAvatar": {}, "settingsProfileEditDisplayName": "Displayname", diff --git a/lib/src/providers/api/media.dart b/lib/src/providers/api/media.dart deleted file mode 100644 index 2ba9b5a..0000000 --- a/lib/src/providers/api/media.dart +++ /dev/null @@ -1,500 +0,0 @@ -import 'dart:convert'; -import 'package:camera/camera.dart'; -import 'package:cryptography_plus/cryptography_plus.dart'; -import 'package:drift/drift.dart'; -import 'package:hive/hive.dart'; -import 'package:logging/logging.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/app.dart'; -import 'package:twonly/src/database/twonly_database.dart'; -import 'package:twonly/src/database/tables/messages_table.dart'; -import 'package:twonly/src/model/json/message.dart'; -import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart'; -import 'package:twonly/src/providers/api/api.dart'; -import 'package:twonly/src/providers/api/api_utils.dart'; -import 'package:twonly/src/providers/hive.dart'; -import 'package:twonly/src/services/notification_service.dart'; -import 'package:twonly/src/utils/misc.dart'; -import 'package:video_compress/video_compress.dart'; - -Future tryDownloadAllMediaFiles() async { - if (!await isAllowedToDownload()) { - return; - } - List messages = - await twonlyDatabase.messagesDao.getAllMessagesPendingDownloading(); - - for (Message message in messages) { - MessageContent? content = - MessageContent.fromJson(message.kind, jsonDecode(message.contentJson!)); - - if (content is MediaMessageContent) { - await tryDownloadMedia( - message.messageId, - message.contactId, - content, - ); - } - } -} - -class Metadata { - late List userIds; - late Map messageIds; - late bool isRealTwonly; - late int maxShowTime; - late DateTime messageSendAt; - - Metadata(); - - Map toJson() { - // Convert Map to Map for JSON encoding - Map stringKeyMessageIds = - messageIds.map((key, value) => MapEntry(key.toString(), value)); - - return { - 'userIds': userIds, - 'messageIds': stringKeyMessageIds, - 'isRealTwonly': isRealTwonly, - 'maxShowTime': maxShowTime, - 'messageSendAt': messageSendAt.toIso8601String(), - }; - } - - factory Metadata.fromJson(Map json) { - Metadata state = Metadata(); - state.userIds = List.from(json['userIds']); - - // Convert Map to Map - state.messageIds = (json['messageIds'] as Map) - .map((key, value) => MapEntry(int.parse(key), value as int)); - - state.isRealTwonly = json['isRealTwonly']; - state.maxShowTime = json['maxShowTime']; - state.messageSendAt = DateTime.parse(json['messageSendAt']); - return state; - } -} - -class PrepareState { - late List sha2Hash; - late List encryptionKey; - late List encryptionMac; - late List encryptedBytes; - late List encryptionNonce; - - PrepareState(); - - Map toJson() { - return { - 'sha2Hash': sha2Hash, - 'encryptionKey': encryptionKey, - 'encryptionMac': encryptionMac, - 'encryptedBytes': encryptedBytes, - 'encryptionNonce': encryptionNonce, - }; - } - - factory PrepareState.fromJson(Map json) { - PrepareState state = PrepareState(); - state.sha2Hash = List.from(json['sha2Hash']); - state.encryptionKey = List.from(json['encryptionKey']); - state.encryptionMac = List.from(json['encryptionMac']); - state.encryptedBytes = List.from(json['encryptedBytes']); - state.encryptionNonce = List.from(json['encryptionNonce']); - return state; - } -} - -class UploadState { - late List uploadToken; - late List> downloadTokens; - - UploadState(); - - Map toJson() { - return { - 'uploadToken': uploadToken, - 'downloadTokens': downloadTokens, - }; - } - - factory UploadState.fromJson(Map json) { - UploadState state = UploadState(); - state.uploadToken = List.from(json['uploadToken']); - state.downloadTokens = List>.from( - json['downloadTokens'].map((token) => List.from(token)), - ); - return state; - } -} - -class States { - late Metadata metadata; - late PrepareState prepareState; - - States({ - required this.metadata, - required this.prepareState, - }); - - Map toJson() { - return { - 'metadata': metadata.toJson(), - 'prepareState': prepareState.toJson(), - }; - } - - factory States.fromJson(Map json) { - return States( - metadata: Metadata.fromJson(json['metadata']), - prepareState: PrepareState.fromJson(json['prepareState']), - ); - } -} - -class Uploader { - static Future prepareState(Uint8List rawBytes) async { - var state = PrepareState(); - - try { - final xchacha20 = Xchacha20.poly1305Aead(); - SecretKeyData secretKey = - await (await xchacha20.newSecretKey()).extract(); - - state.encryptionKey = secretKey.bytes; - state.encryptionNonce = xchacha20.newNonce(); - - final secretBox = await xchacha20.encrypt( - rawBytes, - secretKey: secretKey, - nonce: state.encryptionNonce, - ); - - state.encryptionMac = secretBox.mac.bytes; - - state.encryptedBytes = secretBox.cipherText; - final algorithm = Sha256(); - state.sha2Hash = (await algorithm.hash(state.encryptedBytes)).bytes; - - return state; - } catch (e) { - Logger("media.dart").shout("Error encrypting image: $e"); - // non recoverable state - return null; - } - } - - static Future uploadState( - PrepareState prepareState, int recipientsCount) async { - int fragmentedTransportSize = 1000000; // per upload transfer - - final res = await apiProvider.getUploadToken(recipientsCount); - - if (res.isError || !res.value.hasUploadtoken()) { - Logger("media.dart").shout("Error getting upload token!"); - return null; // will be retried on next app start - } - - Response_UploadToken tokens = res.value.uploadtoken; - - var state = UploadState(); - state.uploadToken = tokens.uploadToken; - state.downloadTokens = tokens.downloadTokens; - - // box.get("retransmit-$messageId-offset", offset) - int offset = 0; - - while (offset < prepareState.encryptedBytes.length) { - Logger("api.dart").info( - "Uploading image ${prepareState.encryptionMac} with offset: $offset"); - - int end; - List? checksum; - if (offset + fragmentedTransportSize < - prepareState.encryptedBytes.length) { - end = offset + fragmentedTransportSize; - } else { - end = prepareState.encryptedBytes.length; - checksum = prepareState.sha2Hash; - } - - Result wasSend = await apiProvider.uploadData( - state.uploadToken, - Uint8List.fromList(prepareState.encryptedBytes.sublist(offset, end)), - offset, - checksum, - ); - - if (wasSend.isError) { - Logger("api.dart").shout("error while uploading media"); - return null; - } - offset = end; - } - return state; - } - - static Future notifyState(PrepareState prepareState, UploadState uploadState, - Metadata metadata) async { - for (int targetUserId in metadata.userIds) { - // should never happen - if (uploadState.downloadTokens.isEmpty) return; - if (!metadata.messageIds.containsKey(targetUserId)) continue; - - final downloadToken = uploadState.downloadTokens.removeLast(); - - twonlyDatabase.contactsDao.incFlameCounter( - targetUserId, - false, - metadata.messageSendAt, - ); - - // Ensures the retransmit of the message - encryptAndSendMessage( - metadata.messageIds[targetUserId], - targetUserId, - MessageJson( - kind: MessageKind.media, - messageId: metadata.messageIds[targetUserId], - content: MediaMessageContent( - downloadToken: downloadToken, - maxShowTime: metadata.maxShowTime, - isRealTwonly: metadata.isRealTwonly, - isVideo: false, - encryptionKey: prepareState.encryptionKey, - encryptionMac: prepareState.encryptionMac, - encryptionNonce: prepareState.encryptionNonce, - ), - timestamp: metadata.messageSendAt, - ), - pushKind: PushKind.image, - ); - } - } -} - -Future sendMediaFile( - List userIds, - Uint8List imageBytes, - bool isRealTwonly, - int maxShowTime, - XFile? videoFilePath, - bool? enableVideoAudio, -) async { - // First: Compress the image. - Uint8List? imageBytesCompressed = await getCompressedImage(imageBytes); - if (imageBytesCompressed == null) { - // non recoverable state - Logger("media.dart").shout("Error compressing image!"); - return null; - } - - if (imageBytesCompressed.length >= 2000000) { - // non recoverable state - Logger("media.dart").shout("Image to big aborting!"); - return null; - } - - if (videoFilePath != null) { - print(videoFilePath.path); - // Second: If existand compress video - MediaInfo? mediaInfo = await VideoCompress.compressVideo( - videoFilePath.path, - quality: VideoQuality.MediumQuality, - includeAudio: enableVideoAudio, - deleteOrigin: false, - ); - - if (mediaInfo == null) { - Logger("send.media.file").shout("Error while compressing the video!"); - return; - } - print(mediaInfo.file); - } - - final prepareState = await Uploader.prepareState(imageBytes); - if (prepareState == null) { - // non recoverable state - return; - } - - var metadata = Metadata(); - metadata.userIds = userIds; - metadata.isRealTwonly = isRealTwonly; - metadata.maxShowTime = maxShowTime; - metadata.messageIds = {}; - metadata.messageSendAt = DateTime.now(); - - // at this point it is safe inform the user about the process of sending the image.. - for (final userId in metadata.userIds) { - int? messageId = await twonlyDatabase.messagesDao.insertMessage( - MessagesCompanion( - contactId: Value(userId), - kind: Value(MessageKind.media), - sendAt: Value(metadata.messageSendAt), - downloadState: Value(DownloadState.pending), - contentJson: Value( - jsonEncode( - MediaMessageContent( - maxShowTime: metadata.maxShowTime, - isRealTwonly: metadata.isRealTwonly, - isVideo: false, - ).toJson(), - ), - ), - ), - ); // dearchive contact when sending a new message - twonlyDatabase.contactsDao.updateContact( - userId, - ContactsCompanion( - archived: Value(false), - ), - ); - if (messageId != null) { - metadata.messageIds[userId] = messageId; - } else { - Logger("media.dart") - .shout("Error inserting message in messages database..."); - } - } - - String stateId = prepareState.sha2Hash.toString(); - - { - Map allMediaFiles = await getStoredMediaUploads(); - allMediaFiles[stateId] = jsonEncode( - States(metadata: metadata, prepareState: prepareState).toJson(), - ); - (await getMediaStorage()).put("mediaUploads", jsonEncode(allMediaFiles)); - } - - uploadMediaState(stateId, prepareState, metadata); -} - -Future> getStoredMediaUploads() async { - Box storage = await getMediaStorage(); - String? mediaFilesJson = storage.get("mediaUploads"); - Map allMediaFiles = {}; - - if (mediaFilesJson != null) { - allMediaFiles = jsonDecode(mediaFilesJson); - } - return allMediaFiles; -} - -Future retransmitMediaFiles() async { - Map allMediaFiles = await getStoredMediaUploads(); - if (allMediaFiles.isEmpty) return; - - bool allSuccess = true; - - for (final entry in allMediaFiles.entries) { - try { - String stateId = entry.key; - States states = States.fromJson(jsonDecode(entry.value)); - // upload one by one - allSuccess = allSuccess & - await uploadMediaState(stateId, states.prepareState, states.metadata); - } catch (e) { - Logger("media.dart").shout(e); - } - } - - if (allSuccess) { - // when all retransmittions where sucessfull tag the errors - final pendings = await twonlyDatabase.messagesDao - .getAllMessagesPendingUploadOlderThanAMinute(); - for (final pending in pendings) { - twonlyDatabase.messagesDao.updateMessageByMessageId( - pending.messageId, - MessagesCompanion(errorWhileSending: Value(true)), - ); - } - } - // return allSuccess; -} - -// if the upload failes this function is called again from the retransmitMediaFiles function which is -// called when the WebSocket is reconnected again. -Future uploadMediaState( - String stateId, PrepareState prepareState, Metadata metadata) async { - final uploadState = - await Uploader.uploadState(prepareState, metadata.userIds.length); - if (uploadState == null) { - return false; - } - - { - Map allMediaFiles = await getStoredMediaUploads(); - if (allMediaFiles.isNotEmpty) { - allMediaFiles.remove(stateId); - (await getMediaStorage()).put("mediaUploads", jsonEncode(allMediaFiles)); - } - } - - await Uploader.notifyState(prepareState, uploadState, metadata); - return true; -} - -Future tryDownloadMedia( - int messageId, int fromUserId, MediaMessageContent content, - {bool force = false}) async { - if (globalIsAppInBackground) return; - if (content.downloadToken == null) return; - - if (!force) { - if (!await isAllowedToDownload()) { - Logger("tryDownloadMedia").info("abort download over mobile connection"); - return; - } - } - - final box = await getMediaStorage(); - if (box.containsKey("${messageId}_downloaded")) { - Logger("tryDownloadMedia").shout("mediaToken already downloaded"); - return; - } - - Logger("tryDownloadMedia").info("Downloading: $messageId"); - - int offset = 0; - Uint8List? media = box.get("${content.downloadToken!}"); - if (media != null && media.isNotEmpty) { - offset = media.length; - } - - box.put("${content.downloadToken!}_messageId", messageId); - - await twonlyDatabase.messagesDao.updateMessageByOtherUser( - fromUserId, - messageId, - MessagesCompanion( - downloadState: Value(DownloadState.downloading), - ), - ); - apiProvider.triggerDownload(content.downloadToken!, offset); -} - -Future getDownloadedMedia( - Message message, List downloadToken) async { - final box = await getMediaStorage(); - Uint8List? media; - try { - media = box.get("${downloadToken}_downloaded"); - } catch (e) { - return null; - } - if (media == null) return null; - - // await userOpenedOtherMessage(otherUserId, messageOtherId); - notifyContactAboutOpeningMessage( - message.contactId, [message.messageOtherId!]); - twonlyDatabase.messagesDao.updateMessageByMessageId( - message.messageId, MessagesCompanion(openedAt: Value(DateTime.now()))); - - box.delete(downloadToken.toString()); - box.put("${downloadToken}_downloaded", "deleted"); - box.delete("${downloadToken}_messageId"); - return media; -} diff --git a/lib/src/providers/api/media_received.dart b/lib/src/providers/api/media_received.dart new file mode 100644 index 0000000..6342c76 --- /dev/null +++ b/lib/src/providers/api/media_received.dart @@ -0,0 +1,230 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:drift/drift.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/twonly_database.dart'; +import 'package:twonly/src/database/tables/messages_table.dart'; +import 'package:twonly/src/model/json/message.dart'; +import 'package:twonly/src/providers/api/media_send.dart'; +import 'package:twonly/src/providers/hive.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'dart:typed_data'; +import 'package:cryptography_plus/cryptography_plus.dart'; +import 'package:logging/logging.dart'; +import 'package:twonly/app.dart'; +import 'package:twonly/src/model/protobuf/api/client_to_server.pb.dart' + as client; +import 'package:twonly/src/model/protobuf/api/error.pb.dart'; +import 'package:twonly/src/model/protobuf/api/server_to_client.pbserver.dart'; + +Future tryDownloadAllMediaFiles() async { + List messages = + await twonlyDatabase.messagesDao.getAllMessagesPendingDownloading(); + + for (Message message in messages) { + await startDownloadMedia(message, false); + } +} + +Future startDownloadMedia(Message message, bool force) async { + if (message.contentJson == null) return; + + final content = + MessageContent.fromJson(message.kind, jsonDecode(message.contentJson!)); + + if (content is! MediaMessageContent) return; + if (content.downloadToken == null) return; + + var media = await twonlyDatabase.mediaDownloadsDao + .getMediaDownloadById(message.messageId) + .getSingleOrNull(); + if (media == null) { + await twonlyDatabase.mediaDownloadsDao.insertMediaDownload( + MediaDownloadsCompanion( + messageId: Value(message.messageId), + downloadToken: Value(content.downloadToken!), + ), + ); + media = await twonlyDatabase.mediaDownloadsDao + .getMediaDownloadById(message.messageId) + .getSingleOrNull(); + } + + if (media == null) return; + + if (!force && !await isAllowedToDownload(content.isVideo)) { + return; + } + + if (message.downloadState != DownloadState.downloaded) { + await twonlyDatabase.messagesDao.updateMessageByMessageId( + message.messageId, + MessagesCompanion( + downloadState: Value(DownloadState.downloading), + ), + ); + + int offset = 0; + Uint8List? bytes = await readMediaFile(media.messageId, "encrypted"); + if (bytes != null && bytes.isNotEmpty) { + offset = bytes.length; + } + apiProvider.triggerDownload(content.downloadToken!, offset); + } +} + +Future handleDownloadData(DownloadData data) async { + if (globalIsAppInBackground) { + // download should only be done when the app is open + return client.Response()..error = ErrorCode.InternalError; + } + + Logger("server_messages") + .info("downloading: ${data.downloadToken} ${data.fin}"); + + final box = await getMediaStorage(); + + String boxId = data.downloadToken.toString(); + + final media = await twonlyDatabase.mediaDownloadsDao + .getMediaDownloadByDownloadToken(data.downloadToken) + .getSingleOrNull(); + + if (media == null) { + Logger("server_messages") + .shout("download data received, but unknown messageID"); + // answers with ok, so the server will delete the message + var ok = client.Response_Ok()..none = true; + return client.Response()..ok = ok; + } + + if (data.fin && data.offset == 3_980_938_213 && data.data.isEmpty) { + Logger("media_received.dart").shout("Image already deleted by the server!"); + // media file was deleted by the server. remove the media from device + await twonlyDatabase.messagesDao.updateMessageByMessageId( + media.messageId, + MessagesCompanion( + errorWhileSending: Value(true), + ), + ); + await deleteMediaFile(media.messageId, "encrypted"); + var ok = client.Response_Ok()..none = true; + return client.Response()..ok = ok; + } + + Uint8List? buffered = await readMediaFile(media.messageId, "encrypted"); + Uint8List downloadedBytes; + if (buffered != null) { + if (data.offset != buffered.length) { + Logger("media_received.dart") + .shout("server send wrong offset: ${data.offset} ${buffered.length}"); + return client.Response()..error = ErrorCode.InvalidOffset; + } + var b = BytesBuilder(); + b.add(buffered); + b.add(data.data); + downloadedBytes = b.takeBytes(); + } else { + downloadedBytes = Uint8List.fromList(data.data); + } + + if (!data.fin) { + // download not finished, so waiting for more data... + await box.put(boxId, downloadedBytes); + var ok = client.Response_Ok()..none = true; + return client.Response()..ok = ok; + } + + Message? msg = await twonlyDatabase.messagesDao + .getMessageByMessageId(media.messageId) + .getSingleOrNull(); + + if (msg == null) { + Logger("media_received.dart") + .info("messageId not found in database. Ignoring download"); + // answers with ok, so the server will delete the message + var ok = client.Response_Ok()..none = true; + return client.Response()..ok = ok; + } + + MediaMessageContent content = + MediaMessageContent.fromJson(jsonDecode(msg.contentJson!)); + + final xchacha20 = Xchacha20.poly1305Aead(); + SecretKeyData secretKeyData = SecretKeyData(content.encryptionKey!); + + SecretBox secretBox = SecretBox( + downloadedBytes, + nonce: content.encryptionNonce!, + mac: Mac(content.encryptionMac!), + ); + + try { + final plaintextBytes = + await xchacha20.decrypt(secretBox, secretKey: secretKeyData); + var imageBytes = Uint8List.fromList(plaintextBytes); + + if (content.isVideo) { + final splited = extractUint8Lists(imageBytes); + imageBytes = splited[0]; + await writeMediaFile(media.messageId, "video", splited[1]); + } + + await writeMediaFile(media.messageId, "image", imageBytes); + } catch (e) { + Logger("media_received.dart").info("Decryption error: $e"); + await twonlyDatabase.messagesDao.updateMessageByMessageId( + media.messageId, + MessagesCompanion( + errorWhileSending: Value(true), + ), + ); + // answers with ok, so the server will delete the message + var ok = client.Response_Ok()..none = true; + return client.Response()..ok = ok; + } + + await twonlyDatabase.messagesDao.updateMessageByMessageId( + media.messageId, + MessagesCompanion(downloadState: Value(DownloadState.downloaded)), + ); + + await deleteMediaFile(media.messageId, "encrypted"); + + var ok = client.Response_Ok()..none = true; + return client.Response()..ok = ok; +} + +Future getImageBytes(int mediaId) async { + return await readMediaFile(mediaId, "image"); +} + +Future getVideoPath(int mediaId) async { + String basePath = await getMediaFilePath(mediaId, "received"); + return File("$basePath.video"); +} + +/// --- helper functions --- + +Future readMediaFile(int mediaId, String type) async { + String basePath = await getMediaFilePath(mediaId, "received"); + File file = File("$basePath.$type"); + if (!await file.exists()) { + return null; + } + return await file.readAsBytes(); +} + +Future writeMediaFile(int mediaId, String type, Uint8List data) async { + String basePath = await getMediaFilePath(mediaId, "received"); + File file = File("$basePath.$type"); + await file.writeAsBytes(data); +} + +Future deleteMediaFile(int mediaId, String type) async { + String basePath = await getMediaFilePath(mediaId, "received"); + File file = File("$basePath.$type"); + if (await file.exists()) { + await file.delete(); + } +} diff --git a/lib/src/providers/api/media_send.dart b/lib/src/providers/api/media_send.dart new file mode 100644 index 0000000..8ba1ab2 --- /dev/null +++ b/lib/src/providers/api/media_send.dart @@ -0,0 +1,472 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:cryptography_plus/cryptography_plus.dart'; +import 'package:drift/drift.dart'; +import 'package:flutter_image_compress/flutter_image_compress.dart'; +import 'package:logging/logging.dart'; +import 'package:mutex/mutex.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/tables/media_uploads_table.dart'; +import 'package:twonly/src/database/tables/messages_table.dart'; +import 'package:twonly/src/database/twonly_database.dart'; +import 'package:twonly/src/model/json/message.dart'; +import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart'; +import 'package:twonly/src/providers/api/api.dart'; +import 'package:twonly/src/providers/api/api_utils.dart'; +import 'package:twonly/src/services/notification_service.dart'; +import 'package:video_compress/video_compress.dart'; + +Future sendMediaFile( + List userIds, + Uint8List imageBytes, + bool isRealTwonly, + int maxShowTime, + XFile? videoFilePath, + bool? enableVideoAudio, +) async { + MediaUploadMetadata metadata = MediaUploadMetadata(); + metadata.contactIds = userIds; + metadata.isRealTwonly = isRealTwonly; + metadata.messageSendAt = DateTime.now(); + metadata.isVideo = videoFilePath != null; + metadata.videoWithAudio = enableVideoAudio != null && enableVideoAudio; + + int? mediaUploadId = await twonlyDatabase.mediaUploadsDao.insertMediaUpload( + MediaUploadsCompanion( + metadata: Value(metadata), + ), + ); + + if (mediaUploadId != null) { + await handleSingleMediaFile(mediaUploadId); + } +} + +Future retryMediaUpload() async { + final mediaFiles = + await twonlyDatabase.mediaUploadsDao.getMediaUploadsForRetry(); + for (final mediaFile in mediaFiles) { + await handleSingleMediaFile(mediaFile.mediaUploadId); + } +} + +final lockingHandleMediaFile = Mutex(); + +Future handleSingleMediaFile(int mediaUploadId) async { + await lockingHandleMediaFile.protect(() async { + MediaUpload? media = await twonlyDatabase.mediaUploadsDao + .getMediaUploadById(mediaUploadId) + .getSingleOrNull(); + if (media == null) return; + + try { + switch (media.state) { + case UploadState.pending: + await handleAddToMessageDb(media); + break; + case UploadState.addedToMessagesDb: + await handleCompressionState(media); + break; + case UploadState.isCompressed: + await handleEncryptionState(media); + break; + case UploadState.isEncrypted: + if (!await handleGetUploadToken(media)) { + return; // recoverable error. try again when connected again to the server... + } + break; + case UploadState.hasUploadToken: + if (!await handleUpload(media)) { + return; // recoverable error. try again when connected again to the server... + } + break; + case UploadState.isUploaded: + if (!await handleNotifyReceiver(media)) { + return; // recoverable error. try again when connected again to the server... + } + break; + case UploadState.receiverNotified: + return; + } + + // this will be called until there is an recoverable error OR + // the upload is ready + await handleSingleMediaFile(mediaUploadId); + } catch (e) { + // if the messageIds are already there notify the user about this error... + if (media.messageIds != null) { + for (int messageId in media.messageIds!) { + await twonlyDatabase.messagesDao.updateMessageByMessageId( + messageId, + MessagesCompanion( + errorWhileSending: Value(true), + ), + ); + } + } + await twonlyDatabase.mediaUploadsDao.deleteMediaUpload(mediaUploadId); + Logger("media_send.dart") + .shout("Non recoverable error while sending media file: $e"); + } + }); +} + +Future handleAddToMessageDb(MediaUpload media) async { + List messageIds = []; + + for (final contactId in media.metadata.contactIds) { + int? messageId = await twonlyDatabase.messagesDao.insertMessage( + MessagesCompanion( + contactId: Value(contactId), + kind: Value(MessageKind.media), + sendAt: Value(media.metadata.messageSendAt), + downloadState: Value(DownloadState.pending), + mediaUploadId: Value(media.mediaUploadId), + contentJson: Value( + jsonEncode( + MediaMessageContent( + maxShowTime: media.metadata.maxShowTime, + isRealTwonly: media.metadata.isRealTwonly, + isVideo: media.metadata.isVideo, + ).toJson(), + ), + ), + ), + ); // dearchive contact when sending a new message + await twonlyDatabase.contactsDao.updateContact( + contactId, + ContactsCompanion( + archived: Value(false), + ), + ); + if (messageId != null) { + messageIds.add(messageId); + } else { + Logger("media_send.dart") + .shout("Error inserting media upload message in database."); + } + } + + await twonlyDatabase.mediaUploadsDao.updateMediaUpload( + media.mediaUploadId, + MediaUploadsCompanion( + state: Value(UploadState.addedToMessagesDb), + messageIds: Value(messageIds), + ), + ); +} + +Future handleCompressionState(MediaUpload media) async { + Uint8List imageBytes = await readMediaFile(media, "image"); + + try { + Uint8List imageBytesCompressed = + await FlutterImageCompress.compressWithList( + imageBytes, + quality: 90, + ); + + if (imageBytesCompressed.length >= 1 * 1000 * 1000) { + // if the media file is over 1MB compress it with 60% + imageBytesCompressed = await FlutterImageCompress.compressWithList( + imageBytes, + quality: 60, + ); + } + await writeMediaFile(media, "image.compressed", imageBytesCompressed); + } catch (e) { + Logger("media_send.dart").shout("$e"); + // as a fall back use the original image + await writeMediaFile(media, "image.compressed", imageBytes); + } + + if (media.metadata.isVideo) { + String basePath = await getMediaFilePath(media.mediaUploadId, "send"); + File videoOriginalFile = File("$basePath.video"); + File videoCompressedFile = File("$basePath.video.compressed"); + + MediaInfo? mediaInfo; + + try { + mediaInfo = await VideoCompress.compressVideo( + videoOriginalFile.path, + quality: VideoQuality.Res1280x720Quality, + deleteOrigin: false, + includeAudio: media.metadata.videoWithAudio, + ); + + if (mediaInfo!.filesize! >= 20 * 1000 * 1000) { + // if the media file is over 20MB compress it with low quality + mediaInfo = await VideoCompress.compressVideo( + videoOriginalFile.path, + quality: VideoQuality.Res960x540Quality, + deleteOrigin: false, + includeAudio: media.metadata.videoWithAudio, + ); + } + } catch (e) { + Logger("media_send.dart").shout("$e"); + } + + if (mediaInfo == null) { + Logger("media_send.dart").shout("Error compressing video."); + mediaInfo!.file!.rename(videoCompressedFile.path); + } else { + // as a fall back use the non compressed version + videoOriginalFile.rename(videoCompressedFile.path); + } + } + + // delete non compressed media files + await deleteMediaFile(media, "image"); + await deleteMediaFile(media, "video"); + + await twonlyDatabase.mediaUploadsDao.updateMediaUpload( + media.mediaUploadId, + MediaUploadsCompanion( + state: Value(UploadState.isCompressed), + ), + ); + + return true; +} + +Future handleEncryptionState(MediaUpload media) async { + var state = MediaEncryptionData(); + + Uint8List dataToEncrypt = await readMediaFile(media, "image.compressed"); + + if (media.metadata.isVideo) { + Uint8List compressedVideo = await readMediaFile(media, "video.compressed"); + dataToEncrypt = combineUint8Lists(dataToEncrypt, compressedVideo); + } + + final xchacha20 = Xchacha20.poly1305Aead(); + SecretKeyData secretKey = await (await xchacha20.newSecretKey()).extract(); + + state.encryptionKey = secretKey.bytes; + state.encryptionNonce = xchacha20.newNonce(); + + final secretBox = await xchacha20.encrypt( + dataToEncrypt, + secretKey: secretKey, + nonce: state.encryptionNonce, + ); + + state.encryptionMac = secretBox.mac.bytes; + + final algorithm = Sha256(); + state.sha2Hash = (await algorithm.hash(secretBox.cipherText)).bytes; + + await writeMediaFile( + media, + "encrypted", + Uint8List.fromList(secretBox.cipherText), + ); + + await twonlyDatabase.mediaUploadsDao.updateMediaUpload( + media.mediaUploadId, + MediaUploadsCompanion( + state: Value(UploadState.isEncrypted), + encryptionData: Value(state), + ), + ); +} + +Future handleGetUploadToken(MediaUpload media) async { + final res = + await apiProvider.getUploadToken(media.metadata.contactIds.length); + + if (res.isError || !res.value.hasUploadtoken()) { + Logger("media_send.dart") + .shout("Will be tried again when reconnected to server!"); + return false; + } + + Response_UploadToken tokens = res.value.uploadtoken; + + var token = MediaUploadTokens(); + token.uploadToken = tokens.uploadToken; + token.downloadTokens = tokens.downloadTokens; + + await twonlyDatabase.mediaUploadsDao.updateMediaUpload( + media.mediaUploadId, + MediaUploadsCompanion( + state: Value(UploadState.hasUploadToken), + uploadTokens: Value(token), + ), + ); + + return true; +} + +Future handleUpload(MediaUpload media) async { + Uint8List bytesToUpload = await readMediaFile(media, "encrypted"); + + int fragmentedTransportSize = 1000000; + + int offset = 0; + + while (offset < bytesToUpload.length) { + Logger("media_send.dart").fine( + "Uploading media file ${media.mediaUploadId} with offset: $offset"); + + int end; + List? checksum; + if (offset + fragmentedTransportSize < bytesToUpload.length) { + end = offset + fragmentedTransportSize; + } else { + end = bytesToUpload.length; + checksum = media.encryptionData!.sha2Hash; + } + + Result wasSend = await apiProvider.uploadData( + media.uploadTokens!.uploadToken, + Uint8List.fromList(bytesToUpload.sublist(offset, end)), + offset, + checksum, + ); + + if (wasSend.isError) { + Logger("media_send.dart") + .shout("error while uploading media: ${wasSend.error}"); + return false; + } + offset = end; + } + + await twonlyDatabase.mediaUploadsDao.updateMediaUpload( + media.mediaUploadId, + MediaUploadsCompanion( + state: Value(UploadState.isUploaded), + ), + ); + + await deleteMediaFile(media, "encrypted"); + + return true; +} + +Future handleNotifyReceiver(MediaUpload media) async { + List alreadyNotified = media.alreadyNotified; + + for (var i = 0; i < media.messageIds!.length; i++) { + int messageId = media.messageIds![i]; + + if (alreadyNotified.contains(messageId)) { + continue; + } + + Message? message = await twonlyDatabase.messagesDao + .getMessageByMessageId(messageId) + .getSingleOrNull(); + if (message == null) continue; + + await twonlyDatabase.contactsDao.incFlameCounter( + message.contactId, + false, + message.sendAt, + ); + + // Ensures the retransmit of the message + await encryptAndSendMessage( + messageId, + message.contactId, + MessageJson( + kind: MessageKind.media, + messageId: messageId, + content: MediaMessageContent( + downloadToken: media.uploadTokens!.downloadTokens[i], + maxShowTime: media.metadata.maxShowTime, + isRealTwonly: media.metadata.isRealTwonly, + isVideo: media.metadata.isVideo, + encryptionKey: media.encryptionData!.encryptionKey, + encryptionMac: media.encryptionData!.encryptionMac, + encryptionNonce: media.encryptionData!.encryptionNonce, + ), + timestamp: media.metadata.messageSendAt, + ), + pushKind: (media.metadata.isRealTwonly) + ? PushKind.twonly + : (media.metadata.isVideo) + ? PushKind.video + : PushKind.image, + ); + + alreadyNotified.add(messageId); + await twonlyDatabase.mediaUploadsDao.updateMediaUpload( + media.mediaUploadId, + MediaUploadsCompanion( + alreadyNotified: Value(alreadyNotified), + ), + ); + } + + await twonlyDatabase.mediaUploadsDao.updateMediaUpload( + media.mediaUploadId, + MediaUploadsCompanion( + state: Value(UploadState.receiverNotified), + ), + ); + return true; +} + +/// --- helper functions --- + +Future readMediaFile(MediaUpload media, String type) async { + String basePath = await getMediaFilePath(media.mediaUploadId, "send"); + File file = File("$basePath.$type"); + if (!await file.exists()) { + throw Exception("$file not found"); + } + return await file.readAsBytes(); +} + +Future writeMediaFile( + MediaUpload media, String type, Uint8List data) async { + String basePath = await getMediaFilePath(media.mediaUploadId, "send"); + File file = File("$basePath.$type"); + await file.writeAsBytes(data); +} + +Future deleteMediaFile(MediaUpload media, String type) async { + String basePath = await getMediaFilePath(media.mediaUploadId, "send"); + File file = File("$basePath.$type"); + if (await file.exists()) { + await file.delete(); + } +} + +Future getMediaFilePath(int mediaId, String type) async { + final basedir = await getApplicationSupportDirectory(); + final mediaSendDir = Directory(join(basedir.path, 'media', type)); + if (!await mediaSendDir.exists()) { + await mediaSendDir.create(recursive: true); + } + return join(mediaSendDir.path, '$mediaId'); +} + +/// combines two utf8 list +Uint8List combineUint8Lists(Uint8List list1, Uint8List list2) { + final combinedLength = 4 + list1.length + list2.length; + final combinedList = Uint8List(combinedLength); + final byteData = ByteData.sublistView(combinedList); + byteData.setInt32( + 0, list1.length, Endian.big); // Store size in big-endian format + combinedList.setRange(4, 4 + list1.length, list1); + combinedList.setRange(4 + list1.length, combinedLength, list2); + return combinedList; +} + +List extractUint8Lists(Uint8List combinedList) { + final byteData = ByteData.sublistView(combinedList); + final sizeOfList1 = byteData.getInt32(0, Endian.big); + final list1 = Uint8List.view(combinedList.buffer, 4, sizeOfList1); + final list2 = Uint8List.view(combinedList.buffer, 4 + sizeOfList1, + combinedList.lengthInBytes - 4 - sizeOfList1); + return [list1, list2]; +} diff --git a/lib/src/providers/api/server_messages.dart b/lib/src/providers/api/server_messages.dart index dab0b67..0d7b81c 100644 --- a/lib/src/providers/api/server_messages.dart +++ b/lib/src/providers/api/server_messages.dart @@ -1,13 +1,10 @@ import 'dart:convert'; -import 'dart:typed_data'; -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'; import 'package:logging/logging.dart'; import 'package:mutex/mutex.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/app.dart'; import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/model/json/message.dart'; @@ -17,11 +14,9 @@ import 'package:twonly/src/model/protobuf/api/client_to_server.pbserver.dart'; import 'package:twonly/src/model/protobuf/api/error.pb.dart'; import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart' as server; -import 'package:twonly/src/model/protobuf/api/server_to_client.pbserver.dart'; import 'package:twonly/src/providers/api/api.dart'; import 'package:twonly/src/providers/api/api_utils.dart'; -import 'package:twonly/src/providers/api/media.dart'; -import 'package:twonly/src/providers/hive.dart'; +import 'package:twonly/src/providers/api/media_received.dart'; import 'package:twonly/src/services/notification_service.dart'; // ignore: library_prefixes import 'package:twonly/src/utils/signal.dart' as SignalHelper; @@ -58,115 +53,6 @@ Future handleServerMessage(server.ServerToClient msg) async { }); } -Future handleDownloadData(DownloadData data) async { - if (globalIsAppInBackground) { - // download should only be done when the app is open - return client.Response()..error = ErrorCode.InternalError; - } - - Logger("server_messages") - .info("downloading: ${data.downloadToken} ${data.fin}"); - - final box = await getMediaStorage(); - - String boxId = data.downloadToken.toString(); - - int? messageId = box.get("${data.downloadToken}_messageId"); - - if (messageId == null) { - Logger("server_messages") - .shout("download data received, but unknown messageID"); - // answers with ok, so the server will delete the message - var ok = client.Response_Ok()..none = true; - return client.Response()..ok = ok; - } - - if (data.fin && data.data.isEmpty) { - Logger("server_messages") - .shout("Got an image message, but was already deleted by the server!"); - // media file was deleted by the server. remove the media from device - await twonlyDatabase.messagesDao.deleteMessageById(messageId); - await box.delete(boxId); - await box.delete("${data.downloadToken}_downloaded"); - var ok = client.Response_Ok()..none = true; - return client.Response()..ok = ok; - } - - Uint8List? buffered = box.get(boxId); - Uint8List downloadedBytes; - if (buffered != null) { - if (data.offset != buffered.length) { - Logger("server_messages") - .info("server send wrong offset: ${data.offset} ${buffered.length}"); - // Logger("handleDownloadData").error(object) - return client.Response()..error = ErrorCode.InvalidOffset; - } - var b = BytesBuilder(); - b.add(buffered); - b.add(data.data); - - downloadedBytes = b.takeBytes(); - } else { - downloadedBytes = Uint8List.fromList(data.data); - } - - if (!data.fin) { - // download not finished, so waiting for more data... - await box.put(boxId, downloadedBytes); - var ok = client.Response_Ok()..none = true; - return client.Response()..ok = ok; - } - - Message? msg = await twonlyDatabase.messagesDao - .getMessageByMessageId(messageId) - .getSingleOrNull(); - if (msg == null) { - Logger("server_messages") - .info("messageId not found in database. Ignoring download"); - // answers with ok, so the server will delete the message - var ok = client.Response_Ok()..none = true; - return client.Response()..ok = ok; - } - - MediaMessageContent content = - MediaMessageContent.fromJson(jsonDecode(msg.contentJson!)); - - final xchacha20 = Xchacha20.poly1305Aead(); - SecretKeyData secretKeyData = SecretKeyData(content.encryptionKey!); - - SecretBox secretBox = SecretBox( - downloadedBytes, - nonce: content.encryptionNonce!, - mac: Mac(content.encryptionMac!), - ); - - try { - final rawBytes = - await xchacha20.decrypt(secretBox, secretKey: secretKeyData); - - await box.put("${data.downloadToken}_downloaded", rawBytes); - } catch (e) { - Logger("server_messages").info("Decryption error: $e"); - // deleting message as this is an invalid image - await twonlyDatabase.messagesDao.deleteMessageById(messageId); - // answers with ok, so the server will delete the message - var ok = client.Response_Ok()..none = true; - return client.Response()..ok = ok; - } - - Logger("server_messages").info("Downloaded: $messageId"); - await twonlyDatabase.messagesDao.updateMessageByOtherUser( - msg.contactId, - messageId, - MessagesCompanion(downloadState: Value(DownloadState.downloaded)), - ); - - await box.delete(boxId); - - var ok = client.Response_Ok()..none = true; - return client.Response()..ok = ok; -} - Future handleNewMessage(int fromUserId, Uint8List body) async { MessageJson? message = await SignalHelper.getDecryptedText(fromUserId, body); if (message == null) { @@ -315,15 +201,11 @@ Future handleNewMessage(int fromUserId, Uint8List body) async { message.timestamp, ); - if (!globalIsAppInBackground) { - final content = message.content; - if (content is MediaMessageContent) { - tryDownloadMedia( - messageId, - fromUserId, - content, - ); - } + final msg = await twonlyDatabase.messagesDao + .getMessageByMessageId(messageId) + .getSingleOrNull(); + if (msg != null) { + startDownloadMedia(msg, false); } } // dearchive contact when receiving a new message diff --git a/lib/src/providers/api_provider.dart b/lib/src/providers/api_provider.dart index b002166..4e3945a 100644 --- a/lib/src/providers/api_provider.dart +++ b/lib/src/providers/api_provider.dart @@ -17,7 +17,8 @@ import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart' as server; import 'package:twonly/src/providers/api/api.dart'; import 'package:twonly/src/providers/api/api_utils.dart'; -import 'package:twonly/src/providers/api/media.dart'; +import 'package:twonly/src/providers/api/media_received.dart'; +import 'package:twonly/src/providers/api/media_send.dart'; import 'package:twonly/src/providers/api/server_messages.dart'; import 'package:twonly/src/services/fcm_service.dart'; import 'package:twonly/src/utils/misc.dart'; @@ -82,13 +83,20 @@ class ApiProvider { if (!globalIsAppInBackground) { tryTransmitMessages(); - retransmitMediaFiles(); + retryMediaUpload(); tryDownloadAllMediaFiles(); notifyContactsAboutProfileChange(); twonlyDatabase.markUpdated(); } } + Future onClosed() async { + _channel = null; + isAuthenticated = false; + globalCallbackConnectionState(false); + await twonlyDatabase.messagesDao.resetPendingDownloadState(); + } + Future close(Function callback) async { log.info("Closing the websocket connection!"); if (_channel != null) { @@ -131,16 +139,12 @@ class ApiProvider { void _onDone() { log.info("WebSocket Closed"); - globalCallbackConnectionState(false); - _channel = null; - isAuthenticated = false; + onClosed(); } void _onError(dynamic e) { log.info("WebSocket Error: $e"); - globalCallbackConnectionState(false); - _channel = null; - isAuthenticated = false; + onClosed(); } void _onData(dynamic msgBuffer) async { diff --git a/lib/src/utils/misc.dart b/lib/src/utils/misc.dart index 21d7e82..681996c 100644 --- a/lib/src/utils/misc.dart +++ b/lib/src/utils/misc.dart @@ -160,7 +160,7 @@ Future authenticateUser(String localizedReason, return false; } -Future isAllowedToDownload() async { +Future isAllowedToDownload(bool isVideo) async { final List connectivityResult = await (Connectivity().checkConnectivity()); if (connectivityResult.contains(ConnectivityResult.mobile)) { diff --git a/lib/src/views/camera/share_image_editor_view.dart b/lib/src/views/camera/share_image_editor_view.dart index 8eeb034..82da961 100644 --- a/lib/src/views/camera/share_image_editor_view.dart +++ b/lib/src/views/camera/share_image_editor_view.dart @@ -5,13 +5,13 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:logging/logging.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/providers/api/media_send.dart'; import 'package:twonly/src/views/camera/components/save_to_gallery.dart'; import 'package:twonly/src/views/camera/image_editor/action_button.dart'; import 'package:twonly/src/views/components/media_view_sizing.dart'; import 'package:twonly/src/views/components/notification_badge.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart'; import 'package:twonly/src/database/twonly_database.dart'; -import 'package:twonly/src/providers/api/media.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/views/camera/share_image_view.dart'; diff --git a/lib/src/views/camera/share_image_view.dart b/lib/src/views/camera/share_image_view.dart index 8dc35f5..924c770 100644 --- a/lib/src/views/camera/share_image_view.dart +++ b/lib/src/views/camera/share_image_view.dart @@ -5,6 +5,7 @@ import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/providers/api/media_send.dart'; import 'package:twonly/src/views/camera/components/best_friends_selector.dart'; import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/headline.dart'; @@ -12,7 +13,6 @@ import 'package:twonly/src/views/components/initialsavatar.dart'; import 'package:twonly/src/views/components/verified_shield.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart'; import 'package:twonly/src/database/twonly_database.dart'; -import 'package:twonly/src/providers/api/media.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/home_view.dart'; diff --git a/lib/src/views/chats/chat_item_details_view.dart b/lib/src/views/chats/chat_item_details_view.dart index b1455ad..f1bbdaa 100644 --- a/lib/src/views/chats/chat_item_details_view.dart +++ b/lib/src/views/chats/chat_item_details_view.dart @@ -15,7 +15,7 @@ import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/providers/api/api.dart'; -import 'package:twonly/src/providers/api/media.dart'; +import 'package:twonly/src/providers/api/media_received.dart'; import 'package:twonly/src/services/notification_service.dart'; import 'package:twonly/src/views/camera/camera_send_to_view.dart'; import 'package:twonly/src/views/chats/media_viewer_view.dart'; @@ -229,9 +229,8 @@ class ChatListEntry extends StatelessWidget { return MediaViewerView(contact); }), ); - } else { - tryDownloadMedia(message.messageId, message.contactId, content, - force: true); + } else if (message.downloadState == DownloadState.pending) { + startDownloadMedia(message, true); } } }, diff --git a/lib/src/views/chats/chat_list_view.dart b/lib/src/views/chats/chat_list_view.dart index 3bb4e08..7249025 100644 --- a/lib/src/views/chats/chat_list_view.dart +++ b/lib/src/views/chats/chat_list_view.dart @@ -1,8 +1,8 @@ import 'dart:async'; -import 'dart:convert'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/providers/api/media_received.dart'; import 'package:twonly/src/views/components/connection_state.dart'; import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/initialsavatar.dart'; @@ -12,8 +12,6 @@ import 'package:twonly/src/views/components/user_context_menu.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart'; import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/tables/messages_table.dart'; -import 'package:twonly/src/model/json/message.dart'; -import 'package:twonly/src/providers/api/media.dart'; import 'package:twonly/src/providers/connection_provider.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/camera/camera_send_to_view.dart'; @@ -325,11 +323,7 @@ class _UserListItem extends State { msgs.first.openedAt == null) { switch (msgs.first.downloadState) { case DownloadState.pending: - MediaMessageContent content = MediaMessageContent.fromJson( - jsonDecode(msgs.first.contentJson!)); - tryDownloadMedia( - msgs.first.messageId, msgs.first.contactId, content, - force: true); + startDownloadMedia(msgs.first, true); case DownloadState.downloaded: Navigator.push( context, diff --git a/lib/src/views/chats/media_viewer_view.dart b/lib/src/views/chats/media_viewer_view.dart index 6369973..66ae2b4 100644 --- a/lib/src/views/chats/media_viewer_view.dart +++ b/lib/src/views/chats/media_viewer_view.dart @@ -12,7 +12,7 @@ import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/model/json/message.dart'; import 'package:twonly/src/providers/api/api.dart'; -import 'package:twonly/src/providers/api/media.dart'; +import 'package:twonly/src/providers/api/media_received.dart'; import 'package:twonly/src/services/notification_service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/storage.dart'; @@ -145,17 +145,29 @@ class _MediaViewerViewState extends State { setState(() { isDownloading = true; }); - await tryDownloadMedia(current.messageId, current.contactId, content, - force: true); + await startDownloadMedia(current, true); } - do { - if (isDownloading) { - await Future.delayed(Duration(milliseconds: 10)); - if (!apiProvider.isConnected) break; - } - if (content.downloadToken == null) break; - imageBytes = await getDownloadedMedia(current, content.downloadToken!); - } while (isDownloading && imageBytes == null); + + + load downloaded status from database + + notifyContactAboutOpeningMessage( + message.contactId, [message.messageOtherId!]); + twonlyDatabase.messagesDao.updateMessageByMessageId( + message.messageId, MessagesCompanion(openedAt: Value(DateTime.now()))); + // do { + // if (isDownloading) { + // await Future.delayed(Duration(milliseconds: 10)); + // if (!apiProvider.isConnected) break; + // } + // if (content.downloadToken == null) break; + // imageBytes = await getDownloadedMedia(current, content.downloadToken!); + // } while (isDownloading && imageBytes == null); + + if twonly deleteMediaFile() + + + isDownloading = false; if (imageBytes == null) { diff --git a/lib/src/views/settings/data_and_storage_view.dart b/lib/src/views/settings/data_and_storage_view.dart new file mode 100644 index 0000000..892643a --- /dev/null +++ b/lib/src/views/settings/data_and_storage_view.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:twonly/src/views/components/better_list_title.dart'; +import 'package:twonly/src/views/components/better_text.dart'; +import 'package:twonly/src/views/components/radio_button.dart'; +import 'package:twonly/src/providers/settings_change_provider.dart'; +import 'package:twonly/src/utils/misc.dart'; + +class DataAndStorageView extends StatelessWidget { + const DataAndStorageView({super.key}); + + void _showSelectThemeMode(BuildContext context) async { + ThemeMode? selectedValue = context.read().themeMode; + + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(context.lang.settingsAppearanceTheme), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + RadioButton( + value: ThemeMode.system, + groupValue: selectedValue, + label: 'System default', + onChanged: (ThemeMode? value) { + selectedValue = value; + Navigator.of(context).pop(); + }, + ), + RadioButton( + value: ThemeMode.light, + groupValue: selectedValue, + label: 'Light', + onChanged: (ThemeMode? value) { + selectedValue = value; + Navigator.of(context).pop(); + }, + ), + RadioButton( + value: ThemeMode.dark, + groupValue: selectedValue, + label: 'Dark', + onChanged: (ThemeMode? value) { + selectedValue = value; + Navigator.of(context).pop(); + }, + ), + ], + ), + ); + }, + ); + if (selectedValue != null && context.mounted) { + context.read().updateThemeMode(selectedValue); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.lang.settingsStorageData), + ), + body: ListView( + children: [ + BetterText(text: "Media auto-download"), + ListTile( + title: Text("When using mobile data"), + subtitle: Text("Images", style: TextStyle(color: Colors.grey)), + onTap: () { + _showSelectThemeMode(context); + }, + ), + ListTile( + title: Text("When using Wi-Fi"), + subtitle: Text("Images", style: TextStyle(color: Colors.grey)), + onTap: () { + _showSelectThemeMode(context); + }, + ), + ListTile( + title: Text("When using roaming"), + subtitle: Text("Images", style: TextStyle(color: Colors.grey)), + onTap: () { + _showSelectThemeMode(context); + }, + ), + ], + ), + ); + } +} diff --git a/lib/src/views/settings/settings_main_view.dart b/lib/src/views/settings/settings_main_view.dart index 6daab5b..af3e31b 100644 --- a/lib/src/views/settings/settings_main_view.dart +++ b/lib/src/views/settings/settings_main_view.dart @@ -151,6 +151,16 @@ class _SettingsMainViewState extends State { })); }, ), + BetterListTile( + icon: FontAwesomeIcons.chartPie, + text: context.lang.settingsStorageData, + onTap: () async { + Navigator.push(context, + MaterialPageRoute(builder: (context) { + return NotificationView(); + })); + }, + ), const Divider(), BetterListTile( icon: FontAwesomeIcons.circleQuestion, diff --git a/test/drift/twonly_database/generated/schema.dart b/test/drift/twonly_database/generated/schema.dart index 209e70d..22131b1 100644 --- a/test/drift/twonly_database/generated/schema.dart +++ b/test/drift/twonly_database/generated/schema.dart @@ -6,6 +6,7 @@ import 'package:drift/internal/migrations.dart'; import 'schema_v1.dart' as v1; import 'schema_v2.dart' as v2; import 'schema_v3.dart' as v3; +import 'schema_v4.dart' as v4; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -17,10 +18,12 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v2.DatabaseAtV2(db); case 3: return v3.DatabaseAtV3(db); + case 4: + return v4.DatabaseAtV4(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2, 3]; + static const versions = const [1, 2, 3, 4]; } diff --git a/test/drift/twonly_database/generated/schema_v4.dart b/test/drift/twonly_database/generated/schema_v4.dart new file mode 100644 index 0000000..240b075 --- /dev/null +++ b/test/drift/twonly_database/generated/schema_v4.dart @@ -0,0 +1,2572 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class Contacts extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Contacts(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn username = GeneratedColumn( + 'username', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + late final GeneratedColumn displayName = GeneratedColumn( + 'display_name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn nickName = GeneratedColumn( + 'nick_name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn avatarSvg = GeneratedColumn( + 'avatar_svg', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn myAvatarCounter = GeneratedColumn( + 'my_avatar_counter', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0')); + late final GeneratedColumn accepted = GeneratedColumn( + 'accepted', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("accepted" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn requested = GeneratedColumn( + 'requested', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("requested" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn blocked = GeneratedColumn( + 'blocked', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("blocked" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn verified = GeneratedColumn( + 'verified', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("verified" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn archived = GeneratedColumn( + 'archived', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("archived" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn deleteMessagesAfterXMinutes = + GeneratedColumn( + 'delete_messages_after_x_minutes', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('1440')); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + late final GeneratedColumn totalMediaCounter = GeneratedColumn( + 'total_media_counter', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0')); + late final GeneratedColumn lastMessageSend = + GeneratedColumn('last_message_send', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn lastMessageReceived = + GeneratedColumn('last_message_received', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn lastFlameCounterChange = + GeneratedColumn('last_flame_counter_change', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn lastMessageExchange = + GeneratedColumn('last_message_exchange', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + late final GeneratedColumn flameCounter = GeneratedColumn( + 'flame_counter', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0')); + @override + List get $columns => [ + userId, + username, + displayName, + nickName, + avatarSvg, + myAvatarCounter, + accepted, + requested, + blocked, + verified, + archived, + deleteMessagesAfterXMinutes, + createdAt, + totalMediaCounter, + lastMessageSend, + lastMessageReceived, + lastFlameCounterChange, + lastMessageExchange, + flameCounter + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'contacts'; + @override + Set get $primaryKey => {userId}; + @override + ContactsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ContactsData( + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + username: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}username'])!, + displayName: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}display_name']), + nickName: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}nick_name']), + avatarSvg: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}avatar_svg']), + myAvatarCounter: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}my_avatar_counter'])!, + accepted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}accepted'])!, + requested: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}requested'])!, + blocked: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}blocked'])!, + verified: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}verified'])!, + archived: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}archived'])!, + deleteMessagesAfterXMinutes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}delete_messages_after_x_minutes'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + totalMediaCounter: attachedDatabase.typeMapping.read( + DriftSqlType.int, data['${effectivePrefix}total_media_counter'])!, + lastMessageSend: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}last_message_send']), + lastMessageReceived: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}last_message_received']), + lastFlameCounterChange: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}last_flame_counter_change']), + lastMessageExchange: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}last_message_exchange'])!, + flameCounter: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}flame_counter'])!, + ); + } + + @override + Contacts createAlias(String alias) { + return Contacts(attachedDatabase, alias); + } +} + +class ContactsData extends DataClass implements Insertable { + final int userId; + final String username; + final String? displayName; + final String? nickName; + final String? avatarSvg; + final int myAvatarCounter; + final bool accepted; + final bool requested; + final bool blocked; + final bool verified; + final bool archived; + final int deleteMessagesAfterXMinutes; + final DateTime createdAt; + final int totalMediaCounter; + final DateTime? lastMessageSend; + final DateTime? lastMessageReceived; + final DateTime? lastFlameCounterChange; + final DateTime lastMessageExchange; + final int flameCounter; + const ContactsData( + {required this.userId, + required this.username, + this.displayName, + this.nickName, + this.avatarSvg, + required this.myAvatarCounter, + required this.accepted, + required this.requested, + required this.blocked, + required this.verified, + required this.archived, + required this.deleteMessagesAfterXMinutes, + required this.createdAt, + required this.totalMediaCounter, + this.lastMessageSend, + this.lastMessageReceived, + this.lastFlameCounterChange, + required this.lastMessageExchange, + required this.flameCounter}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['username'] = Variable(username); + if (!nullToAbsent || displayName != null) { + map['display_name'] = Variable(displayName); + } + if (!nullToAbsent || nickName != null) { + map['nick_name'] = Variable(nickName); + } + if (!nullToAbsent || avatarSvg != null) { + map['avatar_svg'] = Variable(avatarSvg); + } + map['my_avatar_counter'] = Variable(myAvatarCounter); + map['accepted'] = Variable(accepted); + map['requested'] = Variable(requested); + map['blocked'] = Variable(blocked); + map['verified'] = Variable(verified); + map['archived'] = Variable(archived); + map['delete_messages_after_x_minutes'] = + Variable(deleteMessagesAfterXMinutes); + map['created_at'] = Variable(createdAt); + map['total_media_counter'] = Variable(totalMediaCounter); + if (!nullToAbsent || lastMessageSend != null) { + map['last_message_send'] = Variable(lastMessageSend); + } + if (!nullToAbsent || lastMessageReceived != null) { + map['last_message_received'] = Variable(lastMessageReceived); + } + if (!nullToAbsent || lastFlameCounterChange != null) { + map['last_flame_counter_change'] = + Variable(lastFlameCounterChange); + } + map['last_message_exchange'] = Variable(lastMessageExchange); + map['flame_counter'] = Variable(flameCounter); + return map; + } + + ContactsCompanion toCompanion(bool nullToAbsent) { + return ContactsCompanion( + userId: Value(userId), + username: Value(username), + displayName: displayName == null && nullToAbsent + ? const Value.absent() + : Value(displayName), + nickName: nickName == null && nullToAbsent + ? const Value.absent() + : Value(nickName), + avatarSvg: avatarSvg == null && nullToAbsent + ? const Value.absent() + : Value(avatarSvg), + myAvatarCounter: Value(myAvatarCounter), + accepted: Value(accepted), + requested: Value(requested), + blocked: Value(blocked), + verified: Value(verified), + archived: Value(archived), + deleteMessagesAfterXMinutes: Value(deleteMessagesAfterXMinutes), + createdAt: Value(createdAt), + totalMediaCounter: Value(totalMediaCounter), + lastMessageSend: lastMessageSend == null && nullToAbsent + ? const Value.absent() + : Value(lastMessageSend), + lastMessageReceived: lastMessageReceived == null && nullToAbsent + ? const Value.absent() + : Value(lastMessageReceived), + lastFlameCounterChange: lastFlameCounterChange == null && nullToAbsent + ? const Value.absent() + : Value(lastFlameCounterChange), + lastMessageExchange: Value(lastMessageExchange), + flameCounter: Value(flameCounter), + ); + } + + factory ContactsData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ContactsData( + userId: serializer.fromJson(json['userId']), + username: serializer.fromJson(json['username']), + displayName: serializer.fromJson(json['displayName']), + nickName: serializer.fromJson(json['nickName']), + avatarSvg: serializer.fromJson(json['avatarSvg']), + myAvatarCounter: serializer.fromJson(json['myAvatarCounter']), + accepted: serializer.fromJson(json['accepted']), + requested: serializer.fromJson(json['requested']), + blocked: serializer.fromJson(json['blocked']), + verified: serializer.fromJson(json['verified']), + archived: serializer.fromJson(json['archived']), + deleteMessagesAfterXMinutes: + serializer.fromJson(json['deleteMessagesAfterXMinutes']), + createdAt: serializer.fromJson(json['createdAt']), + totalMediaCounter: serializer.fromJson(json['totalMediaCounter']), + lastMessageSend: serializer.fromJson(json['lastMessageSend']), + lastMessageReceived: + serializer.fromJson(json['lastMessageReceived']), + lastFlameCounterChange: + serializer.fromJson(json['lastFlameCounterChange']), + lastMessageExchange: + serializer.fromJson(json['lastMessageExchange']), + flameCounter: serializer.fromJson(json['flameCounter']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'username': serializer.toJson(username), + 'displayName': serializer.toJson(displayName), + 'nickName': serializer.toJson(nickName), + 'avatarSvg': serializer.toJson(avatarSvg), + 'myAvatarCounter': serializer.toJson(myAvatarCounter), + 'accepted': serializer.toJson(accepted), + 'requested': serializer.toJson(requested), + 'blocked': serializer.toJson(blocked), + 'verified': serializer.toJson(verified), + 'archived': serializer.toJson(archived), + 'deleteMessagesAfterXMinutes': + serializer.toJson(deleteMessagesAfterXMinutes), + 'createdAt': serializer.toJson(createdAt), + 'totalMediaCounter': serializer.toJson(totalMediaCounter), + 'lastMessageSend': serializer.toJson(lastMessageSend), + 'lastMessageReceived': serializer.toJson(lastMessageReceived), + 'lastFlameCounterChange': + serializer.toJson(lastFlameCounterChange), + 'lastMessageExchange': serializer.toJson(lastMessageExchange), + 'flameCounter': serializer.toJson(flameCounter), + }; + } + + ContactsData copyWith( + {int? userId, + String? username, + Value displayName = const Value.absent(), + Value nickName = const Value.absent(), + Value avatarSvg = const Value.absent(), + int? myAvatarCounter, + bool? accepted, + bool? requested, + bool? blocked, + bool? verified, + bool? archived, + int? deleteMessagesAfterXMinutes, + DateTime? createdAt, + int? totalMediaCounter, + Value lastMessageSend = const Value.absent(), + Value lastMessageReceived = const Value.absent(), + Value lastFlameCounterChange = const Value.absent(), + DateTime? lastMessageExchange, + int? flameCounter}) => + ContactsData( + userId: userId ?? this.userId, + username: username ?? this.username, + displayName: displayName.present ? displayName.value : this.displayName, + nickName: nickName.present ? nickName.value : this.nickName, + avatarSvg: avatarSvg.present ? avatarSvg.value : this.avatarSvg, + myAvatarCounter: myAvatarCounter ?? this.myAvatarCounter, + accepted: accepted ?? this.accepted, + requested: requested ?? this.requested, + blocked: blocked ?? this.blocked, + verified: verified ?? this.verified, + archived: archived ?? this.archived, + deleteMessagesAfterXMinutes: + deleteMessagesAfterXMinutes ?? this.deleteMessagesAfterXMinutes, + createdAt: createdAt ?? this.createdAt, + totalMediaCounter: totalMediaCounter ?? this.totalMediaCounter, + lastMessageSend: lastMessageSend.present + ? lastMessageSend.value + : this.lastMessageSend, + lastMessageReceived: lastMessageReceived.present + ? lastMessageReceived.value + : this.lastMessageReceived, + lastFlameCounterChange: lastFlameCounterChange.present + ? lastFlameCounterChange.value + : this.lastFlameCounterChange, + lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange, + flameCounter: flameCounter ?? this.flameCounter, + ); + ContactsData copyWithCompanion(ContactsCompanion data) { + return ContactsData( + userId: data.userId.present ? data.userId.value : this.userId, + username: data.username.present ? data.username.value : this.username, + displayName: + data.displayName.present ? data.displayName.value : this.displayName, + nickName: data.nickName.present ? data.nickName.value : this.nickName, + avatarSvg: data.avatarSvg.present ? data.avatarSvg.value : this.avatarSvg, + myAvatarCounter: data.myAvatarCounter.present + ? data.myAvatarCounter.value + : this.myAvatarCounter, + accepted: data.accepted.present ? data.accepted.value : this.accepted, + requested: data.requested.present ? data.requested.value : this.requested, + blocked: data.blocked.present ? data.blocked.value : this.blocked, + verified: data.verified.present ? data.verified.value : this.verified, + archived: data.archived.present ? data.archived.value : this.archived, + deleteMessagesAfterXMinutes: data.deleteMessagesAfterXMinutes.present + ? data.deleteMessagesAfterXMinutes.value + : this.deleteMessagesAfterXMinutes, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + totalMediaCounter: data.totalMediaCounter.present + ? data.totalMediaCounter.value + : this.totalMediaCounter, + lastMessageSend: data.lastMessageSend.present + ? data.lastMessageSend.value + : this.lastMessageSend, + lastMessageReceived: data.lastMessageReceived.present + ? data.lastMessageReceived.value + : this.lastMessageReceived, + lastFlameCounterChange: data.lastFlameCounterChange.present + ? data.lastFlameCounterChange.value + : this.lastFlameCounterChange, + lastMessageExchange: data.lastMessageExchange.present + ? data.lastMessageExchange.value + : this.lastMessageExchange, + flameCounter: data.flameCounter.present + ? data.flameCounter.value + : this.flameCounter, + ); + } + + @override + String toString() { + return (StringBuffer('ContactsData(') + ..write('userId: $userId, ') + ..write('username: $username, ') + ..write('displayName: $displayName, ') + ..write('nickName: $nickName, ') + ..write('avatarSvg: $avatarSvg, ') + ..write('myAvatarCounter: $myAvatarCounter, ') + ..write('accepted: $accepted, ') + ..write('requested: $requested, ') + ..write('blocked: $blocked, ') + ..write('verified: $verified, ') + ..write('archived: $archived, ') + ..write('deleteMessagesAfterXMinutes: $deleteMessagesAfterXMinutes, ') + ..write('createdAt: $createdAt, ') + ..write('totalMediaCounter: $totalMediaCounter, ') + ..write('lastMessageSend: $lastMessageSend, ') + ..write('lastMessageReceived: $lastMessageReceived, ') + ..write('lastFlameCounterChange: $lastFlameCounterChange, ') + ..write('lastMessageExchange: $lastMessageExchange, ') + ..write('flameCounter: $flameCounter') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + userId, + username, + displayName, + nickName, + avatarSvg, + myAvatarCounter, + accepted, + requested, + blocked, + verified, + archived, + deleteMessagesAfterXMinutes, + createdAt, + totalMediaCounter, + lastMessageSend, + lastMessageReceived, + lastFlameCounterChange, + lastMessageExchange, + flameCounter); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ContactsData && + other.userId == this.userId && + other.username == this.username && + other.displayName == this.displayName && + other.nickName == this.nickName && + other.avatarSvg == this.avatarSvg && + other.myAvatarCounter == this.myAvatarCounter && + other.accepted == this.accepted && + other.requested == this.requested && + other.blocked == this.blocked && + other.verified == this.verified && + other.archived == this.archived && + other.deleteMessagesAfterXMinutes == + this.deleteMessagesAfterXMinutes && + other.createdAt == this.createdAt && + other.totalMediaCounter == this.totalMediaCounter && + other.lastMessageSend == this.lastMessageSend && + other.lastMessageReceived == this.lastMessageReceived && + other.lastFlameCounterChange == this.lastFlameCounterChange && + other.lastMessageExchange == this.lastMessageExchange && + other.flameCounter == this.flameCounter); +} + +class ContactsCompanion extends UpdateCompanion { + final Value userId; + final Value username; + final Value displayName; + final Value nickName; + final Value avatarSvg; + final Value myAvatarCounter; + final Value accepted; + final Value requested; + final Value blocked; + final Value verified; + final Value archived; + final Value deleteMessagesAfterXMinutes; + final Value createdAt; + final Value totalMediaCounter; + final Value lastMessageSend; + final Value lastMessageReceived; + final Value lastFlameCounterChange; + final Value lastMessageExchange; + final Value flameCounter; + const ContactsCompanion({ + this.userId = const Value.absent(), + this.username = const Value.absent(), + this.displayName = const Value.absent(), + this.nickName = const Value.absent(), + this.avatarSvg = const Value.absent(), + this.myAvatarCounter = const Value.absent(), + this.accepted = const Value.absent(), + this.requested = const Value.absent(), + this.blocked = const Value.absent(), + this.verified = const Value.absent(), + this.archived = const Value.absent(), + this.deleteMessagesAfterXMinutes = const Value.absent(), + this.createdAt = const Value.absent(), + this.totalMediaCounter = const Value.absent(), + this.lastMessageSend = const Value.absent(), + this.lastMessageReceived = const Value.absent(), + this.lastFlameCounterChange = const Value.absent(), + this.lastMessageExchange = const Value.absent(), + this.flameCounter = const Value.absent(), + }); + ContactsCompanion.insert({ + this.userId = const Value.absent(), + required String username, + this.displayName = const Value.absent(), + this.nickName = const Value.absent(), + this.avatarSvg = const Value.absent(), + this.myAvatarCounter = const Value.absent(), + this.accepted = const Value.absent(), + this.requested = const Value.absent(), + this.blocked = const Value.absent(), + this.verified = const Value.absent(), + this.archived = const Value.absent(), + this.deleteMessagesAfterXMinutes = const Value.absent(), + this.createdAt = const Value.absent(), + this.totalMediaCounter = const Value.absent(), + this.lastMessageSend = const Value.absent(), + this.lastMessageReceived = const Value.absent(), + this.lastFlameCounterChange = const Value.absent(), + this.lastMessageExchange = const Value.absent(), + this.flameCounter = const Value.absent(), + }) : username = Value(username); + static Insertable custom({ + Expression? userId, + Expression? username, + Expression? displayName, + Expression? nickName, + Expression? avatarSvg, + Expression? myAvatarCounter, + Expression? accepted, + Expression? requested, + Expression? blocked, + Expression? verified, + Expression? archived, + Expression? deleteMessagesAfterXMinutes, + Expression? createdAt, + Expression? totalMediaCounter, + Expression? lastMessageSend, + Expression? lastMessageReceived, + Expression? lastFlameCounterChange, + Expression? lastMessageExchange, + Expression? flameCounter, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (username != null) 'username': username, + if (displayName != null) 'display_name': displayName, + if (nickName != null) 'nick_name': nickName, + if (avatarSvg != null) 'avatar_svg': avatarSvg, + if (myAvatarCounter != null) 'my_avatar_counter': myAvatarCounter, + if (accepted != null) 'accepted': accepted, + if (requested != null) 'requested': requested, + if (blocked != null) 'blocked': blocked, + if (verified != null) 'verified': verified, + if (archived != null) 'archived': archived, + if (deleteMessagesAfterXMinutes != null) + 'delete_messages_after_x_minutes': deleteMessagesAfterXMinutes, + if (createdAt != null) 'created_at': createdAt, + if (totalMediaCounter != null) 'total_media_counter': totalMediaCounter, + if (lastMessageSend != null) 'last_message_send': lastMessageSend, + if (lastMessageReceived != null) + 'last_message_received': lastMessageReceived, + if (lastFlameCounterChange != null) + 'last_flame_counter_change': lastFlameCounterChange, + if (lastMessageExchange != null) + 'last_message_exchange': lastMessageExchange, + if (flameCounter != null) 'flame_counter': flameCounter, + }); + } + + ContactsCompanion copyWith( + {Value? userId, + Value? username, + Value? displayName, + Value? nickName, + Value? avatarSvg, + Value? myAvatarCounter, + Value? accepted, + Value? requested, + Value? blocked, + Value? verified, + Value? archived, + Value? deleteMessagesAfterXMinutes, + Value? createdAt, + Value? totalMediaCounter, + Value? lastMessageSend, + Value? lastMessageReceived, + Value? lastFlameCounterChange, + Value? lastMessageExchange, + Value? flameCounter}) { + return ContactsCompanion( + userId: userId ?? this.userId, + username: username ?? this.username, + displayName: displayName ?? this.displayName, + nickName: nickName ?? this.nickName, + avatarSvg: avatarSvg ?? this.avatarSvg, + myAvatarCounter: myAvatarCounter ?? this.myAvatarCounter, + accepted: accepted ?? this.accepted, + requested: requested ?? this.requested, + blocked: blocked ?? this.blocked, + verified: verified ?? this.verified, + archived: archived ?? this.archived, + deleteMessagesAfterXMinutes: + deleteMessagesAfterXMinutes ?? this.deleteMessagesAfterXMinutes, + createdAt: createdAt ?? this.createdAt, + totalMediaCounter: totalMediaCounter ?? this.totalMediaCounter, + lastMessageSend: lastMessageSend ?? this.lastMessageSend, + lastMessageReceived: lastMessageReceived ?? this.lastMessageReceived, + lastFlameCounterChange: + lastFlameCounterChange ?? this.lastFlameCounterChange, + lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange, + flameCounter: flameCounter ?? this.flameCounter, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (displayName.present) { + map['display_name'] = Variable(displayName.value); + } + if (nickName.present) { + map['nick_name'] = Variable(nickName.value); + } + if (avatarSvg.present) { + map['avatar_svg'] = Variable(avatarSvg.value); + } + if (myAvatarCounter.present) { + map['my_avatar_counter'] = Variable(myAvatarCounter.value); + } + if (accepted.present) { + map['accepted'] = Variable(accepted.value); + } + if (requested.present) { + map['requested'] = Variable(requested.value); + } + if (blocked.present) { + map['blocked'] = Variable(blocked.value); + } + if (verified.present) { + map['verified'] = Variable(verified.value); + } + if (archived.present) { + map['archived'] = Variable(archived.value); + } + if (deleteMessagesAfterXMinutes.present) { + map['delete_messages_after_x_minutes'] = + Variable(deleteMessagesAfterXMinutes.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (totalMediaCounter.present) { + map['total_media_counter'] = Variable(totalMediaCounter.value); + } + if (lastMessageSend.present) { + map['last_message_send'] = Variable(lastMessageSend.value); + } + if (lastMessageReceived.present) { + map['last_message_received'] = + Variable(lastMessageReceived.value); + } + if (lastFlameCounterChange.present) { + map['last_flame_counter_change'] = + Variable(lastFlameCounterChange.value); + } + if (lastMessageExchange.present) { + map['last_message_exchange'] = + Variable(lastMessageExchange.value); + } + if (flameCounter.present) { + map['flame_counter'] = Variable(flameCounter.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ContactsCompanion(') + ..write('userId: $userId, ') + ..write('username: $username, ') + ..write('displayName: $displayName, ') + ..write('nickName: $nickName, ') + ..write('avatarSvg: $avatarSvg, ') + ..write('myAvatarCounter: $myAvatarCounter, ') + ..write('accepted: $accepted, ') + ..write('requested: $requested, ') + ..write('blocked: $blocked, ') + ..write('verified: $verified, ') + ..write('archived: $archived, ') + ..write('deleteMessagesAfterXMinutes: $deleteMessagesAfterXMinutes, ') + ..write('createdAt: $createdAt, ') + ..write('totalMediaCounter: $totalMediaCounter, ') + ..write('lastMessageSend: $lastMessageSend, ') + ..write('lastMessageReceived: $lastMessageReceived, ') + ..write('lastFlameCounterChange: $lastFlameCounterChange, ') + ..write('lastMessageExchange: $lastMessageExchange, ') + ..write('flameCounter: $flameCounter') + ..write(')')) + .toString(); + } +} + +class Messages extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Messages(this.attachedDatabase, [this._alias]); + late final GeneratedColumn contactId = GeneratedColumn( + 'contact_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES contacts (user_id)')); + late final GeneratedColumn messageId = GeneratedColumn( + 'message_id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn messageOtherId = GeneratedColumn( + 'message_other_id', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn responseToMessageId = GeneratedColumn( + 'response_to_message_id', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn responseToOtherMessageId = + GeneratedColumn('response_to_other_message_id', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn acknowledgeByUser = GeneratedColumn( + 'acknowledge_by_user', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("acknowledge_by_user" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn downloadState = GeneratedColumn( + 'download_state', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('2')); + late final GeneratedColumn acknowledgeByServer = GeneratedColumn( + 'acknowledge_by_server', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("acknowledge_by_server" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn errorWhileSending = GeneratedColumn( + 'error_while_sending', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("error_while_sending" IN (0, 1))'), + defaultValue: const CustomExpression('0')); + late final GeneratedColumn kind = GeneratedColumn( + 'kind', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn contentJson = GeneratedColumn( + 'content_json', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn openedAt = GeneratedColumn( + 'opened_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + late final GeneratedColumn sendAt = GeneratedColumn( + 'send_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List get $columns => [ + contactId, + messageId, + messageOtherId, + responseToMessageId, + responseToOtherMessageId, + acknowledgeByUser, + downloadState, + acknowledgeByServer, + errorWhileSending, + kind, + contentJson, + openedAt, + sendAt, + updatedAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'messages'; + @override + Set get $primaryKey => {messageId}; + @override + MessagesData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MessagesData( + contactId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}contact_id'])!, + messageId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}message_id'])!, + messageOtherId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}message_other_id']), + responseToMessageId: attachedDatabase.typeMapping.read( + DriftSqlType.int, data['${effectivePrefix}response_to_message_id']), + responseToOtherMessageId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}response_to_other_message_id']), + acknowledgeByUser: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}acknowledge_by_user'])!, + downloadState: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}download_state'])!, + acknowledgeByServer: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}acknowledge_by_server'])!, + errorWhileSending: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}error_while_sending'])!, + kind: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}kind'])!, + contentJson: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}content_json']), + openedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}opened_at']), + sendAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}send_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + Messages createAlias(String alias) { + return Messages(attachedDatabase, alias); + } +} + +class MessagesData extends DataClass implements Insertable { + final int contactId; + final int messageId; + final int? messageOtherId; + final int? responseToMessageId; + final int? responseToOtherMessageId; + final bool acknowledgeByUser; + final int downloadState; + final bool acknowledgeByServer; + final bool errorWhileSending; + final String kind; + final String? contentJson; + final DateTime? openedAt; + final DateTime sendAt; + final DateTime updatedAt; + const MessagesData( + {required this.contactId, + required this.messageId, + this.messageOtherId, + this.responseToMessageId, + this.responseToOtherMessageId, + required this.acknowledgeByUser, + required this.downloadState, + required this.acknowledgeByServer, + required this.errorWhileSending, + required this.kind, + this.contentJson, + this.openedAt, + required this.sendAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['contact_id'] = Variable(contactId); + map['message_id'] = Variable(messageId); + if (!nullToAbsent || messageOtherId != null) { + map['message_other_id'] = Variable(messageOtherId); + } + if (!nullToAbsent || responseToMessageId != null) { + map['response_to_message_id'] = Variable(responseToMessageId); + } + if (!nullToAbsent || responseToOtherMessageId != null) { + map['response_to_other_message_id'] = + Variable(responseToOtherMessageId); + } + map['acknowledge_by_user'] = Variable(acknowledgeByUser); + map['download_state'] = Variable(downloadState); + map['acknowledge_by_server'] = Variable(acknowledgeByServer); + map['error_while_sending'] = Variable(errorWhileSending); + map['kind'] = Variable(kind); + if (!nullToAbsent || contentJson != null) { + map['content_json'] = Variable(contentJson); + } + if (!nullToAbsent || openedAt != null) { + map['opened_at'] = Variable(openedAt); + } + map['send_at'] = Variable(sendAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + MessagesCompanion toCompanion(bool nullToAbsent) { + return MessagesCompanion( + contactId: Value(contactId), + messageId: Value(messageId), + messageOtherId: messageOtherId == null && nullToAbsent + ? const Value.absent() + : Value(messageOtherId), + responseToMessageId: responseToMessageId == null && nullToAbsent + ? const Value.absent() + : Value(responseToMessageId), + responseToOtherMessageId: responseToOtherMessageId == null && nullToAbsent + ? const Value.absent() + : Value(responseToOtherMessageId), + acknowledgeByUser: Value(acknowledgeByUser), + downloadState: Value(downloadState), + acknowledgeByServer: Value(acknowledgeByServer), + errorWhileSending: Value(errorWhileSending), + kind: Value(kind), + contentJson: contentJson == null && nullToAbsent + ? const Value.absent() + : Value(contentJson), + openedAt: openedAt == null && nullToAbsent + ? const Value.absent() + : Value(openedAt), + sendAt: Value(sendAt), + updatedAt: Value(updatedAt), + ); + } + + factory MessagesData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MessagesData( + contactId: serializer.fromJson(json['contactId']), + messageId: serializer.fromJson(json['messageId']), + messageOtherId: serializer.fromJson(json['messageOtherId']), + responseToMessageId: + serializer.fromJson(json['responseToMessageId']), + responseToOtherMessageId: + serializer.fromJson(json['responseToOtherMessageId']), + acknowledgeByUser: serializer.fromJson(json['acknowledgeByUser']), + downloadState: serializer.fromJson(json['downloadState']), + acknowledgeByServer: + serializer.fromJson(json['acknowledgeByServer']), + errorWhileSending: serializer.fromJson(json['errorWhileSending']), + kind: serializer.fromJson(json['kind']), + contentJson: serializer.fromJson(json['contentJson']), + openedAt: serializer.fromJson(json['openedAt']), + sendAt: serializer.fromJson(json['sendAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'contactId': serializer.toJson(contactId), + 'messageId': serializer.toJson(messageId), + 'messageOtherId': serializer.toJson(messageOtherId), + 'responseToMessageId': serializer.toJson(responseToMessageId), + 'responseToOtherMessageId': + serializer.toJson(responseToOtherMessageId), + 'acknowledgeByUser': serializer.toJson(acknowledgeByUser), + 'downloadState': serializer.toJson(downloadState), + 'acknowledgeByServer': serializer.toJson(acknowledgeByServer), + 'errorWhileSending': serializer.toJson(errorWhileSending), + 'kind': serializer.toJson(kind), + 'contentJson': serializer.toJson(contentJson), + 'openedAt': serializer.toJson(openedAt), + 'sendAt': serializer.toJson(sendAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + MessagesData copyWith( + {int? contactId, + int? messageId, + Value messageOtherId = const Value.absent(), + Value responseToMessageId = const Value.absent(), + Value responseToOtherMessageId = const Value.absent(), + bool? acknowledgeByUser, + int? downloadState, + bool? acknowledgeByServer, + bool? errorWhileSending, + String? kind, + Value contentJson = const Value.absent(), + Value openedAt = const Value.absent(), + DateTime? sendAt, + DateTime? updatedAt}) => + MessagesData( + contactId: contactId ?? this.contactId, + messageId: messageId ?? this.messageId, + messageOtherId: + messageOtherId.present ? messageOtherId.value : this.messageOtherId, + responseToMessageId: responseToMessageId.present + ? responseToMessageId.value + : this.responseToMessageId, + responseToOtherMessageId: responseToOtherMessageId.present + ? responseToOtherMessageId.value + : this.responseToOtherMessageId, + acknowledgeByUser: acknowledgeByUser ?? this.acknowledgeByUser, + downloadState: downloadState ?? this.downloadState, + acknowledgeByServer: acknowledgeByServer ?? this.acknowledgeByServer, + errorWhileSending: errorWhileSending ?? this.errorWhileSending, + kind: kind ?? this.kind, + contentJson: contentJson.present ? contentJson.value : this.contentJson, + openedAt: openedAt.present ? openedAt.value : this.openedAt, + sendAt: sendAt ?? this.sendAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + MessagesData copyWithCompanion(MessagesCompanion data) { + return MessagesData( + contactId: data.contactId.present ? data.contactId.value : this.contactId, + messageId: data.messageId.present ? data.messageId.value : this.messageId, + messageOtherId: data.messageOtherId.present + ? data.messageOtherId.value + : this.messageOtherId, + responseToMessageId: data.responseToMessageId.present + ? data.responseToMessageId.value + : this.responseToMessageId, + responseToOtherMessageId: data.responseToOtherMessageId.present + ? data.responseToOtherMessageId.value + : this.responseToOtherMessageId, + acknowledgeByUser: data.acknowledgeByUser.present + ? data.acknowledgeByUser.value + : this.acknowledgeByUser, + downloadState: data.downloadState.present + ? data.downloadState.value + : this.downloadState, + acknowledgeByServer: data.acknowledgeByServer.present + ? data.acknowledgeByServer.value + : this.acknowledgeByServer, + errorWhileSending: data.errorWhileSending.present + ? data.errorWhileSending.value + : this.errorWhileSending, + kind: data.kind.present ? data.kind.value : this.kind, + contentJson: + data.contentJson.present ? data.contentJson.value : this.contentJson, + openedAt: data.openedAt.present ? data.openedAt.value : this.openedAt, + sendAt: data.sendAt.present ? data.sendAt.value : this.sendAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('MessagesData(') + ..write('contactId: $contactId, ') + ..write('messageId: $messageId, ') + ..write('messageOtherId: $messageOtherId, ') + ..write('responseToMessageId: $responseToMessageId, ') + ..write('responseToOtherMessageId: $responseToOtherMessageId, ') + ..write('acknowledgeByUser: $acknowledgeByUser, ') + ..write('downloadState: $downloadState, ') + ..write('acknowledgeByServer: $acknowledgeByServer, ') + ..write('errorWhileSending: $errorWhileSending, ') + ..write('kind: $kind, ') + ..write('contentJson: $contentJson, ') + ..write('openedAt: $openedAt, ') + ..write('sendAt: $sendAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + contactId, + messageId, + messageOtherId, + responseToMessageId, + responseToOtherMessageId, + acknowledgeByUser, + downloadState, + acknowledgeByServer, + errorWhileSending, + kind, + contentJson, + openedAt, + sendAt, + updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MessagesData && + other.contactId == this.contactId && + other.messageId == this.messageId && + other.messageOtherId == this.messageOtherId && + other.responseToMessageId == this.responseToMessageId && + other.responseToOtherMessageId == this.responseToOtherMessageId && + other.acknowledgeByUser == this.acknowledgeByUser && + other.downloadState == this.downloadState && + other.acknowledgeByServer == this.acknowledgeByServer && + other.errorWhileSending == this.errorWhileSending && + other.kind == this.kind && + other.contentJson == this.contentJson && + other.openedAt == this.openedAt && + other.sendAt == this.sendAt && + other.updatedAt == this.updatedAt); +} + +class MessagesCompanion extends UpdateCompanion { + final Value contactId; + final Value messageId; + final Value messageOtherId; + final Value responseToMessageId; + final Value responseToOtherMessageId; + final Value acknowledgeByUser; + final Value downloadState; + final Value acknowledgeByServer; + final Value errorWhileSending; + final Value kind; + final Value contentJson; + final Value openedAt; + final Value sendAt; + final Value updatedAt; + const MessagesCompanion({ + this.contactId = const Value.absent(), + this.messageId = const Value.absent(), + this.messageOtherId = const Value.absent(), + this.responseToMessageId = const Value.absent(), + this.responseToOtherMessageId = const Value.absent(), + this.acknowledgeByUser = const Value.absent(), + this.downloadState = const Value.absent(), + this.acknowledgeByServer = const Value.absent(), + this.errorWhileSending = const Value.absent(), + this.kind = const Value.absent(), + this.contentJson = const Value.absent(), + this.openedAt = const Value.absent(), + this.sendAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + MessagesCompanion.insert({ + required int contactId, + this.messageId = const Value.absent(), + this.messageOtherId = const Value.absent(), + this.responseToMessageId = const Value.absent(), + this.responseToOtherMessageId = const Value.absent(), + this.acknowledgeByUser = const Value.absent(), + this.downloadState = const Value.absent(), + this.acknowledgeByServer = const Value.absent(), + this.errorWhileSending = const Value.absent(), + required String kind, + this.contentJson = const Value.absent(), + this.openedAt = const Value.absent(), + this.sendAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : contactId = Value(contactId), + kind = Value(kind); + static Insertable custom({ + Expression? contactId, + Expression? messageId, + Expression? messageOtherId, + Expression? responseToMessageId, + Expression? responseToOtherMessageId, + Expression? acknowledgeByUser, + Expression? downloadState, + Expression? acknowledgeByServer, + Expression? errorWhileSending, + Expression? kind, + Expression? contentJson, + Expression? openedAt, + Expression? sendAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (contactId != null) 'contact_id': contactId, + if (messageId != null) 'message_id': messageId, + if (messageOtherId != null) 'message_other_id': messageOtherId, + if (responseToMessageId != null) + 'response_to_message_id': responseToMessageId, + if (responseToOtherMessageId != null) + 'response_to_other_message_id': responseToOtherMessageId, + if (acknowledgeByUser != null) 'acknowledge_by_user': acknowledgeByUser, + if (downloadState != null) 'download_state': downloadState, + if (acknowledgeByServer != null) + 'acknowledge_by_server': acknowledgeByServer, + if (errorWhileSending != null) 'error_while_sending': errorWhileSending, + if (kind != null) 'kind': kind, + if (contentJson != null) 'content_json': contentJson, + if (openedAt != null) 'opened_at': openedAt, + if (sendAt != null) 'send_at': sendAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + MessagesCompanion copyWith( + {Value? contactId, + Value? messageId, + Value? messageOtherId, + Value? responseToMessageId, + Value? responseToOtherMessageId, + Value? acknowledgeByUser, + Value? downloadState, + Value? acknowledgeByServer, + Value? errorWhileSending, + Value? kind, + Value? contentJson, + Value? openedAt, + Value? sendAt, + Value? updatedAt}) { + return MessagesCompanion( + contactId: contactId ?? this.contactId, + messageId: messageId ?? this.messageId, + messageOtherId: messageOtherId ?? this.messageOtherId, + responseToMessageId: responseToMessageId ?? this.responseToMessageId, + responseToOtherMessageId: + responseToOtherMessageId ?? this.responseToOtherMessageId, + acknowledgeByUser: acknowledgeByUser ?? this.acknowledgeByUser, + downloadState: downloadState ?? this.downloadState, + acknowledgeByServer: acknowledgeByServer ?? this.acknowledgeByServer, + errorWhileSending: errorWhileSending ?? this.errorWhileSending, + kind: kind ?? this.kind, + contentJson: contentJson ?? this.contentJson, + openedAt: openedAt ?? this.openedAt, + sendAt: sendAt ?? this.sendAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (contactId.present) { + map['contact_id'] = Variable(contactId.value); + } + if (messageId.present) { + map['message_id'] = Variable(messageId.value); + } + if (messageOtherId.present) { + map['message_other_id'] = Variable(messageOtherId.value); + } + if (responseToMessageId.present) { + map['response_to_message_id'] = Variable(responseToMessageId.value); + } + if (responseToOtherMessageId.present) { + map['response_to_other_message_id'] = + Variable(responseToOtherMessageId.value); + } + if (acknowledgeByUser.present) { + map['acknowledge_by_user'] = Variable(acknowledgeByUser.value); + } + if (downloadState.present) { + map['download_state'] = Variable(downloadState.value); + } + if (acknowledgeByServer.present) { + map['acknowledge_by_server'] = Variable(acknowledgeByServer.value); + } + if (errorWhileSending.present) { + map['error_while_sending'] = Variable(errorWhileSending.value); + } + if (kind.present) { + map['kind'] = Variable(kind.value); + } + if (contentJson.present) { + map['content_json'] = Variable(contentJson.value); + } + if (openedAt.present) { + map['opened_at'] = Variable(openedAt.value); + } + if (sendAt.present) { + map['send_at'] = Variable(sendAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MessagesCompanion(') + ..write('contactId: $contactId, ') + ..write('messageId: $messageId, ') + ..write('messageOtherId: $messageOtherId, ') + ..write('responseToMessageId: $responseToMessageId, ') + ..write('responseToOtherMessageId: $responseToOtherMessageId, ') + ..write('acknowledgeByUser: $acknowledgeByUser, ') + ..write('downloadState: $downloadState, ') + ..write('acknowledgeByServer: $acknowledgeByServer, ') + ..write('errorWhileSending: $errorWhileSending, ') + ..write('kind: $kind, ') + ..write('contentJson: $contentJson, ') + ..write('openedAt: $openedAt, ') + ..write('sendAt: $sendAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class MediaUploads extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MediaUploads(this.attachedDatabase, [this._alias]); + late final GeneratedColumn mediaUploadId = GeneratedColumn( + 'media_upload_id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn state = GeneratedColumn( + 'state', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'pending\'')); + late final GeneratedColumn metadata = GeneratedColumn( + 'metadata', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn messageIds = GeneratedColumn( + 'message_ids', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn encryptionData = GeneratedColumn( + 'encryption_data', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn uploadTokens = GeneratedColumn( + 'upload_tokens', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn alreadyNotified = GeneratedColumn( + 'already_notified', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'[]\'')); + @override + List get $columns => [ + mediaUploadId, + state, + metadata, + messageIds, + encryptionData, + uploadTokens, + alreadyNotified + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'media_uploads'; + @override + Set get $primaryKey => {mediaUploadId}; + @override + MediaUploadsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MediaUploadsData( + mediaUploadId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}media_upload_id'])!, + state: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}state'])!, + metadata: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}metadata'])!, + messageIds: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}message_ids']), + encryptionData: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}encryption_data']), + uploadTokens: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}upload_tokens']), + alreadyNotified: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}already_notified'])!, + ); + } + + @override + MediaUploads createAlias(String alias) { + return MediaUploads(attachedDatabase, alias); + } +} + +class MediaUploadsData extends DataClass + implements Insertable { + final int mediaUploadId; + final String state; + final String metadata; + final String? messageIds; + final String? encryptionData; + final String? uploadTokens; + final String alreadyNotified; + const MediaUploadsData( + {required this.mediaUploadId, + required this.state, + required this.metadata, + this.messageIds, + this.encryptionData, + this.uploadTokens, + required this.alreadyNotified}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['media_upload_id'] = Variable(mediaUploadId); + map['state'] = Variable(state); + map['metadata'] = Variable(metadata); + if (!nullToAbsent || messageIds != null) { + map['message_ids'] = Variable(messageIds); + } + if (!nullToAbsent || encryptionData != null) { + map['encryption_data'] = Variable(encryptionData); + } + if (!nullToAbsent || uploadTokens != null) { + map['upload_tokens'] = Variable(uploadTokens); + } + map['already_notified'] = Variable(alreadyNotified); + return map; + } + + MediaUploadsCompanion toCompanion(bool nullToAbsent) { + return MediaUploadsCompanion( + mediaUploadId: Value(mediaUploadId), + state: Value(state), + metadata: Value(metadata), + messageIds: messageIds == null && nullToAbsent + ? const Value.absent() + : Value(messageIds), + encryptionData: encryptionData == null && nullToAbsent + ? const Value.absent() + : Value(encryptionData), + uploadTokens: uploadTokens == null && nullToAbsent + ? const Value.absent() + : Value(uploadTokens), + alreadyNotified: Value(alreadyNotified), + ); + } + + factory MediaUploadsData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MediaUploadsData( + mediaUploadId: serializer.fromJson(json['mediaUploadId']), + state: serializer.fromJson(json['state']), + metadata: serializer.fromJson(json['metadata']), + messageIds: serializer.fromJson(json['messageIds']), + encryptionData: serializer.fromJson(json['encryptionData']), + uploadTokens: serializer.fromJson(json['uploadTokens']), + alreadyNotified: serializer.fromJson(json['alreadyNotified']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'mediaUploadId': serializer.toJson(mediaUploadId), + 'state': serializer.toJson(state), + 'metadata': serializer.toJson(metadata), + 'messageIds': serializer.toJson(messageIds), + 'encryptionData': serializer.toJson(encryptionData), + 'uploadTokens': serializer.toJson(uploadTokens), + 'alreadyNotified': serializer.toJson(alreadyNotified), + }; + } + + MediaUploadsData copyWith( + {int? mediaUploadId, + String? state, + String? metadata, + Value messageIds = const Value.absent(), + Value encryptionData = const Value.absent(), + Value uploadTokens = const Value.absent(), + String? alreadyNotified}) => + MediaUploadsData( + mediaUploadId: mediaUploadId ?? this.mediaUploadId, + state: state ?? this.state, + metadata: metadata ?? this.metadata, + messageIds: messageIds.present ? messageIds.value : this.messageIds, + encryptionData: + encryptionData.present ? encryptionData.value : this.encryptionData, + uploadTokens: + uploadTokens.present ? uploadTokens.value : this.uploadTokens, + alreadyNotified: alreadyNotified ?? this.alreadyNotified, + ); + MediaUploadsData copyWithCompanion(MediaUploadsCompanion data) { + return MediaUploadsData( + mediaUploadId: data.mediaUploadId.present + ? data.mediaUploadId.value + : this.mediaUploadId, + state: data.state.present ? data.state.value : this.state, + metadata: data.metadata.present ? data.metadata.value : this.metadata, + messageIds: + data.messageIds.present ? data.messageIds.value : this.messageIds, + encryptionData: data.encryptionData.present + ? data.encryptionData.value + : this.encryptionData, + uploadTokens: data.uploadTokens.present + ? data.uploadTokens.value + : this.uploadTokens, + alreadyNotified: data.alreadyNotified.present + ? data.alreadyNotified.value + : this.alreadyNotified, + ); + } + + @override + String toString() { + return (StringBuffer('MediaUploadsData(') + ..write('mediaUploadId: $mediaUploadId, ') + ..write('state: $state, ') + ..write('metadata: $metadata, ') + ..write('messageIds: $messageIds, ') + ..write('encryptionData: $encryptionData, ') + ..write('uploadTokens: $uploadTokens, ') + ..write('alreadyNotified: $alreadyNotified') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(mediaUploadId, state, metadata, messageIds, + encryptionData, uploadTokens, alreadyNotified); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MediaUploadsData && + other.mediaUploadId == this.mediaUploadId && + other.state == this.state && + other.metadata == this.metadata && + other.messageIds == this.messageIds && + other.encryptionData == this.encryptionData && + other.uploadTokens == this.uploadTokens && + other.alreadyNotified == this.alreadyNotified); +} + +class MediaUploadsCompanion extends UpdateCompanion { + final Value mediaUploadId; + final Value state; + final Value metadata; + final Value messageIds; + final Value encryptionData; + final Value uploadTokens; + final Value alreadyNotified; + const MediaUploadsCompanion({ + this.mediaUploadId = const Value.absent(), + this.state = const Value.absent(), + this.metadata = const Value.absent(), + this.messageIds = const Value.absent(), + this.encryptionData = const Value.absent(), + this.uploadTokens = const Value.absent(), + this.alreadyNotified = const Value.absent(), + }); + MediaUploadsCompanion.insert({ + this.mediaUploadId = const Value.absent(), + this.state = const Value.absent(), + required String metadata, + this.messageIds = const Value.absent(), + this.encryptionData = const Value.absent(), + this.uploadTokens = const Value.absent(), + this.alreadyNotified = const Value.absent(), + }) : metadata = Value(metadata); + static Insertable custom({ + Expression? mediaUploadId, + Expression? state, + Expression? metadata, + Expression? messageIds, + Expression? encryptionData, + Expression? uploadTokens, + Expression? alreadyNotified, + }) { + return RawValuesInsertable({ + if (mediaUploadId != null) 'media_upload_id': mediaUploadId, + if (state != null) 'state': state, + if (metadata != null) 'metadata': metadata, + if (messageIds != null) 'message_ids': messageIds, + if (encryptionData != null) 'encryption_data': encryptionData, + if (uploadTokens != null) 'upload_tokens': uploadTokens, + if (alreadyNotified != null) 'already_notified': alreadyNotified, + }); + } + + MediaUploadsCompanion copyWith( + {Value? mediaUploadId, + Value? state, + Value? metadata, + Value? messageIds, + Value? encryptionData, + Value? uploadTokens, + Value? alreadyNotified}) { + return MediaUploadsCompanion( + mediaUploadId: mediaUploadId ?? this.mediaUploadId, + state: state ?? this.state, + metadata: metadata ?? this.metadata, + messageIds: messageIds ?? this.messageIds, + encryptionData: encryptionData ?? this.encryptionData, + uploadTokens: uploadTokens ?? this.uploadTokens, + alreadyNotified: alreadyNotified ?? this.alreadyNotified, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (mediaUploadId.present) { + map['media_upload_id'] = Variable(mediaUploadId.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (metadata.present) { + map['metadata'] = Variable(metadata.value); + } + if (messageIds.present) { + map['message_ids'] = Variable(messageIds.value); + } + if (encryptionData.present) { + map['encryption_data'] = Variable(encryptionData.value); + } + if (uploadTokens.present) { + map['upload_tokens'] = Variable(uploadTokens.value); + } + if (alreadyNotified.present) { + map['already_notified'] = Variable(alreadyNotified.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MediaUploadsCompanion(') + ..write('mediaUploadId: $mediaUploadId, ') + ..write('state: $state, ') + ..write('metadata: $metadata, ') + ..write('messageIds: $messageIds, ') + ..write('encryptionData: $encryptionData, ') + ..write('uploadTokens: $uploadTokens, ') + ..write('alreadyNotified: $alreadyNotified') + ..write(')')) + .toString(); + } +} + +class SignalIdentityKeyStores extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SignalIdentityKeyStores(this.attachedDatabase, [this._alias]); + late final GeneratedColumn deviceId = GeneratedColumn( + 'device_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn identityKey = + GeneratedColumn('identity_key', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List get $columns => + [deviceId, name, identityKey, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'signal_identity_key_stores'; + @override + Set get $primaryKey => {deviceId, name}; + @override + SignalIdentityKeyStoresData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SignalIdentityKeyStoresData( + deviceId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}device_id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + identityKey: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}identity_key'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SignalIdentityKeyStores createAlias(String alias) { + return SignalIdentityKeyStores(attachedDatabase, alias); + } +} + +class SignalIdentityKeyStoresData extends DataClass + implements Insertable { + final int deviceId; + final String name; + final Uint8List identityKey; + final DateTime createdAt; + const SignalIdentityKeyStoresData( + {required this.deviceId, + required this.name, + required this.identityKey, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['device_id'] = Variable(deviceId); + map['name'] = Variable(name); + map['identity_key'] = Variable(identityKey); + map['created_at'] = Variable(createdAt); + return map; + } + + SignalIdentityKeyStoresCompanion toCompanion(bool nullToAbsent) { + return SignalIdentityKeyStoresCompanion( + deviceId: Value(deviceId), + name: Value(name), + identityKey: Value(identityKey), + createdAt: Value(createdAt), + ); + } + + factory SignalIdentityKeyStoresData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SignalIdentityKeyStoresData( + deviceId: serializer.fromJson(json['deviceId']), + name: serializer.fromJson(json['name']), + identityKey: serializer.fromJson(json['identityKey']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'deviceId': serializer.toJson(deviceId), + 'name': serializer.toJson(name), + 'identityKey': serializer.toJson(identityKey), + 'createdAt': serializer.toJson(createdAt), + }; + } + + SignalIdentityKeyStoresData copyWith( + {int? deviceId, + String? name, + Uint8List? identityKey, + DateTime? createdAt}) => + SignalIdentityKeyStoresData( + deviceId: deviceId ?? this.deviceId, + name: name ?? this.name, + identityKey: identityKey ?? this.identityKey, + createdAt: createdAt ?? this.createdAt, + ); + SignalIdentityKeyStoresData copyWithCompanion( + SignalIdentityKeyStoresCompanion data) { + return SignalIdentityKeyStoresData( + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + name: data.name.present ? data.name.value : this.name, + identityKey: + data.identityKey.present ? data.identityKey.value : this.identityKey, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SignalIdentityKeyStoresData(') + ..write('deviceId: $deviceId, ') + ..write('name: $name, ') + ..write('identityKey: $identityKey, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + deviceId, name, $driftBlobEquality.hash(identityKey), createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SignalIdentityKeyStoresData && + other.deviceId == this.deviceId && + other.name == this.name && + $driftBlobEquality.equals(other.identityKey, this.identityKey) && + other.createdAt == this.createdAt); +} + +class SignalIdentityKeyStoresCompanion + extends UpdateCompanion { + final Value deviceId; + final Value name; + final Value identityKey; + final Value createdAt; + final Value rowid; + const SignalIdentityKeyStoresCompanion({ + this.deviceId = const Value.absent(), + this.name = const Value.absent(), + this.identityKey = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + SignalIdentityKeyStoresCompanion.insert({ + required int deviceId, + required String name, + required Uint8List identityKey, + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : deviceId = Value(deviceId), + name = Value(name), + identityKey = Value(identityKey); + static Insertable custom({ + Expression? deviceId, + Expression? name, + Expression? identityKey, + Expression? createdAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (deviceId != null) 'device_id': deviceId, + if (name != null) 'name': name, + if (identityKey != null) 'identity_key': identityKey, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + SignalIdentityKeyStoresCompanion copyWith( + {Value? deviceId, + Value? name, + Value? identityKey, + Value? createdAt, + Value? rowid}) { + return SignalIdentityKeyStoresCompanion( + deviceId: deviceId ?? this.deviceId, + name: name ?? this.name, + identityKey: identityKey ?? this.identityKey, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (deviceId.present) { + map['device_id'] = Variable(deviceId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (identityKey.present) { + map['identity_key'] = Variable(identityKey.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SignalIdentityKeyStoresCompanion(') + ..write('deviceId: $deviceId, ') + ..write('name: $name, ') + ..write('identityKey: $identityKey, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class SignalPreKeyStores extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SignalPreKeyStores(this.attachedDatabase, [this._alias]); + late final GeneratedColumn preKeyId = GeneratedColumn( + 'pre_key_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: false); + late final GeneratedColumn preKey = GeneratedColumn( + 'pre_key', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List get $columns => [preKeyId, preKey, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'signal_pre_key_stores'; + @override + Set get $primaryKey => {preKeyId}; + @override + SignalPreKeyStoresData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SignalPreKeyStoresData( + preKeyId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}pre_key_id'])!, + preKey: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}pre_key'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SignalPreKeyStores createAlias(String alias) { + return SignalPreKeyStores(attachedDatabase, alias); + } +} + +class SignalPreKeyStoresData extends DataClass + implements Insertable { + final int preKeyId; + final Uint8List preKey; + final DateTime createdAt; + const SignalPreKeyStoresData( + {required this.preKeyId, required this.preKey, required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['pre_key_id'] = Variable(preKeyId); + map['pre_key'] = Variable(preKey); + map['created_at'] = Variable(createdAt); + return map; + } + + SignalPreKeyStoresCompanion toCompanion(bool nullToAbsent) { + return SignalPreKeyStoresCompanion( + preKeyId: Value(preKeyId), + preKey: Value(preKey), + createdAt: Value(createdAt), + ); + } + + factory SignalPreKeyStoresData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SignalPreKeyStoresData( + preKeyId: serializer.fromJson(json['preKeyId']), + preKey: serializer.fromJson(json['preKey']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'preKeyId': serializer.toJson(preKeyId), + 'preKey': serializer.toJson(preKey), + 'createdAt': serializer.toJson(createdAt), + }; + } + + SignalPreKeyStoresData copyWith( + {int? preKeyId, Uint8List? preKey, DateTime? createdAt}) => + SignalPreKeyStoresData( + preKeyId: preKeyId ?? this.preKeyId, + preKey: preKey ?? this.preKey, + createdAt: createdAt ?? this.createdAt, + ); + SignalPreKeyStoresData copyWithCompanion(SignalPreKeyStoresCompanion data) { + return SignalPreKeyStoresData( + preKeyId: data.preKeyId.present ? data.preKeyId.value : this.preKeyId, + preKey: data.preKey.present ? data.preKey.value : this.preKey, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SignalPreKeyStoresData(') + ..write('preKeyId: $preKeyId, ') + ..write('preKey: $preKey, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(preKeyId, $driftBlobEquality.hash(preKey), createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SignalPreKeyStoresData && + other.preKeyId == this.preKeyId && + $driftBlobEquality.equals(other.preKey, this.preKey) && + other.createdAt == this.createdAt); +} + +class SignalPreKeyStoresCompanion + extends UpdateCompanion { + final Value preKeyId; + final Value preKey; + final Value createdAt; + const SignalPreKeyStoresCompanion({ + this.preKeyId = const Value.absent(), + this.preKey = const Value.absent(), + this.createdAt = const Value.absent(), + }); + SignalPreKeyStoresCompanion.insert({ + this.preKeyId = const Value.absent(), + required Uint8List preKey, + this.createdAt = const Value.absent(), + }) : preKey = Value(preKey); + static Insertable custom({ + Expression? preKeyId, + Expression? preKey, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (preKeyId != null) 'pre_key_id': preKeyId, + if (preKey != null) 'pre_key': preKey, + if (createdAt != null) 'created_at': createdAt, + }); + } + + SignalPreKeyStoresCompanion copyWith( + {Value? preKeyId, + Value? preKey, + Value? createdAt}) { + return SignalPreKeyStoresCompanion( + preKeyId: preKeyId ?? this.preKeyId, + preKey: preKey ?? this.preKey, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (preKeyId.present) { + map['pre_key_id'] = Variable(preKeyId.value); + } + if (preKey.present) { + map['pre_key'] = Variable(preKey.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SignalPreKeyStoresCompanion(') + ..write('preKeyId: $preKeyId, ') + ..write('preKey: $preKey, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class SignalSenderKeyStores extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SignalSenderKeyStores(this.attachedDatabase, [this._alias]); + late final GeneratedColumn senderKeyName = GeneratedColumn( + 'sender_key_name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn senderKey = GeneratedColumn( + 'sender_key', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + @override + List get $columns => [senderKeyName, senderKey]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'signal_sender_key_stores'; + @override + Set get $primaryKey => {senderKeyName}; + @override + SignalSenderKeyStoresData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SignalSenderKeyStoresData( + senderKeyName: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}sender_key_name'])!, + senderKey: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}sender_key'])!, + ); + } + + @override + SignalSenderKeyStores createAlias(String alias) { + return SignalSenderKeyStores(attachedDatabase, alias); + } +} + +class SignalSenderKeyStoresData extends DataClass + implements Insertable { + final String senderKeyName; + final Uint8List senderKey; + const SignalSenderKeyStoresData( + {required this.senderKeyName, required this.senderKey}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['sender_key_name'] = Variable(senderKeyName); + map['sender_key'] = Variable(senderKey); + return map; + } + + SignalSenderKeyStoresCompanion toCompanion(bool nullToAbsent) { + return SignalSenderKeyStoresCompanion( + senderKeyName: Value(senderKeyName), + senderKey: Value(senderKey), + ); + } + + factory SignalSenderKeyStoresData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SignalSenderKeyStoresData( + senderKeyName: serializer.fromJson(json['senderKeyName']), + senderKey: serializer.fromJson(json['senderKey']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'senderKeyName': serializer.toJson(senderKeyName), + 'senderKey': serializer.toJson(senderKey), + }; + } + + SignalSenderKeyStoresData copyWith( + {String? senderKeyName, Uint8List? senderKey}) => + SignalSenderKeyStoresData( + senderKeyName: senderKeyName ?? this.senderKeyName, + senderKey: senderKey ?? this.senderKey, + ); + SignalSenderKeyStoresData copyWithCompanion( + SignalSenderKeyStoresCompanion data) { + return SignalSenderKeyStoresData( + senderKeyName: data.senderKeyName.present + ? data.senderKeyName.value + : this.senderKeyName, + senderKey: data.senderKey.present ? data.senderKey.value : this.senderKey, + ); + } + + @override + String toString() { + return (StringBuffer('SignalSenderKeyStoresData(') + ..write('senderKeyName: $senderKeyName, ') + ..write('senderKey: $senderKey') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(senderKeyName, $driftBlobEquality.hash(senderKey)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SignalSenderKeyStoresData && + other.senderKeyName == this.senderKeyName && + $driftBlobEquality.equals(other.senderKey, this.senderKey)); +} + +class SignalSenderKeyStoresCompanion + extends UpdateCompanion { + final Value senderKeyName; + final Value senderKey; + final Value rowid; + const SignalSenderKeyStoresCompanion({ + this.senderKeyName = const Value.absent(), + this.senderKey = const Value.absent(), + this.rowid = const Value.absent(), + }); + SignalSenderKeyStoresCompanion.insert({ + required String senderKeyName, + required Uint8List senderKey, + this.rowid = const Value.absent(), + }) : senderKeyName = Value(senderKeyName), + senderKey = Value(senderKey); + static Insertable custom({ + Expression? senderKeyName, + Expression? senderKey, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (senderKeyName != null) 'sender_key_name': senderKeyName, + if (senderKey != null) 'sender_key': senderKey, + if (rowid != null) 'rowid': rowid, + }); + } + + SignalSenderKeyStoresCompanion copyWith( + {Value? senderKeyName, + Value? senderKey, + Value? rowid}) { + return SignalSenderKeyStoresCompanion( + senderKeyName: senderKeyName ?? this.senderKeyName, + senderKey: senderKey ?? this.senderKey, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (senderKeyName.present) { + map['sender_key_name'] = Variable(senderKeyName.value); + } + if (senderKey.present) { + map['sender_key'] = Variable(senderKey.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SignalSenderKeyStoresCompanion(') + ..write('senderKeyName: $senderKeyName, ') + ..write('senderKey: $senderKey, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class SignalSessionStores extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SignalSessionStores(this.attachedDatabase, [this._alias]); + late final GeneratedColumn deviceId = GeneratedColumn( + 'device_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn sessionRecord = + GeneratedColumn('session_record', aliasedName, false, + type: DriftSqlType.blob, requiredDuringInsert: true); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression( + 'CAST(strftime(\'%s\', CURRENT_TIMESTAMP) AS INTEGER)')); + @override + List get $columns => + [deviceId, name, sessionRecord, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'signal_session_stores'; + @override + Set get $primaryKey => {deviceId, name}; + @override + SignalSessionStoresData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SignalSessionStoresData( + deviceId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}device_id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + sessionRecord: attachedDatabase.typeMapping + .read(DriftSqlType.blob, data['${effectivePrefix}session_record'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SignalSessionStores createAlias(String alias) { + return SignalSessionStores(attachedDatabase, alias); + } +} + +class SignalSessionStoresData extends DataClass + implements Insertable { + final int deviceId; + final String name; + final Uint8List sessionRecord; + final DateTime createdAt; + const SignalSessionStoresData( + {required this.deviceId, + required this.name, + required this.sessionRecord, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['device_id'] = Variable(deviceId); + map['name'] = Variable(name); + map['session_record'] = Variable(sessionRecord); + map['created_at'] = Variable(createdAt); + return map; + } + + SignalSessionStoresCompanion toCompanion(bool nullToAbsent) { + return SignalSessionStoresCompanion( + deviceId: Value(deviceId), + name: Value(name), + sessionRecord: Value(sessionRecord), + createdAt: Value(createdAt), + ); + } + + factory SignalSessionStoresData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SignalSessionStoresData( + deviceId: serializer.fromJson(json['deviceId']), + name: serializer.fromJson(json['name']), + sessionRecord: serializer.fromJson(json['sessionRecord']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'deviceId': serializer.toJson(deviceId), + 'name': serializer.toJson(name), + 'sessionRecord': serializer.toJson(sessionRecord), + 'createdAt': serializer.toJson(createdAt), + }; + } + + SignalSessionStoresData copyWith( + {int? deviceId, + String? name, + Uint8List? sessionRecord, + DateTime? createdAt}) => + SignalSessionStoresData( + deviceId: deviceId ?? this.deviceId, + name: name ?? this.name, + sessionRecord: sessionRecord ?? this.sessionRecord, + createdAt: createdAt ?? this.createdAt, + ); + SignalSessionStoresData copyWithCompanion(SignalSessionStoresCompanion data) { + return SignalSessionStoresData( + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + name: data.name.present ? data.name.value : this.name, + sessionRecord: data.sessionRecord.present + ? data.sessionRecord.value + : this.sessionRecord, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SignalSessionStoresData(') + ..write('deviceId: $deviceId, ') + ..write('name: $name, ') + ..write('sessionRecord: $sessionRecord, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + deviceId, name, $driftBlobEquality.hash(sessionRecord), createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SignalSessionStoresData && + other.deviceId == this.deviceId && + other.name == this.name && + $driftBlobEquality.equals(other.sessionRecord, this.sessionRecord) && + other.createdAt == this.createdAt); +} + +class SignalSessionStoresCompanion + extends UpdateCompanion { + final Value deviceId; + final Value name; + final Value sessionRecord; + final Value createdAt; + final Value rowid; + const SignalSessionStoresCompanion({ + this.deviceId = const Value.absent(), + this.name = const Value.absent(), + this.sessionRecord = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + SignalSessionStoresCompanion.insert({ + required int deviceId, + required String name, + required Uint8List sessionRecord, + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : deviceId = Value(deviceId), + name = Value(name), + sessionRecord = Value(sessionRecord); + static Insertable custom({ + Expression? deviceId, + Expression? name, + Expression? sessionRecord, + Expression? createdAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (deviceId != null) 'device_id': deviceId, + if (name != null) 'name': name, + if (sessionRecord != null) 'session_record': sessionRecord, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + SignalSessionStoresCompanion copyWith( + {Value? deviceId, + Value? name, + Value? sessionRecord, + Value? createdAt, + Value? rowid}) { + return SignalSessionStoresCompanion( + deviceId: deviceId ?? this.deviceId, + name: name ?? this.name, + sessionRecord: sessionRecord ?? this.sessionRecord, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (deviceId.present) { + map['device_id'] = Variable(deviceId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (sessionRecord.present) { + map['session_record'] = Variable(sessionRecord.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SignalSessionStoresCompanion(') + ..write('deviceId: $deviceId, ') + ..write('name: $name, ') + ..write('sessionRecord: $sessionRecord, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV4 extends GeneratedDatabase { + DatabaseAtV4(QueryExecutor e) : super(e); + late final Contacts contacts = Contacts(this); + late final Messages messages = Messages(this); + late final MediaUploads mediaUploads = MediaUploads(this); + late final SignalIdentityKeyStores signalIdentityKeyStores = + SignalIdentityKeyStores(this); + late final SignalPreKeyStores signalPreKeyStores = SignalPreKeyStores(this); + late final SignalSenderKeyStores signalSenderKeyStores = + SignalSenderKeyStores(this); + late final SignalSessionStores signalSessionStores = + SignalSessionStores(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + contacts, + messages, + mediaUploads, + signalIdentityKeyStores, + signalPreKeyStores, + signalSenderKeyStores, + signalSessionStores + ]; + @override + int get schemaVersion => 4; +}