import 'package:drift/drift.dart'; import 'package:hashlib/random.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/log.dart'; part 'mediafiles.dao.g.dart'; @DriftAccessor(tables: [MediaFiles, Messages]) class MediaFilesDao extends DatabaseAccessor with _$MediaFilesDaoMixin { // this constructor is required so that the main database can create an instance // of this object. // ignore: matching_super_parameters MediaFilesDao(super.db); Future insertOrUpdateMedia(MediaFilesCompanion mediaFile) async { try { var insertMediaFile = mediaFile; if (insertMediaFile.mediaId == const Value.absent()) { insertMediaFile = mediaFile.copyWith( mediaId: Value(uuid.v7()), ); } await into(mediaFiles).insertOnConflictUpdate(insertMediaFile); final mediaId = insertMediaFile.mediaId.value; return await (select( mediaFiles, )..where((t) => t.mediaId.equals(mediaId))).getSingle(); } catch (e) { Log.error('Could not insert media file: $e'); return null; } } Future deleteMediaFile(String mediaId) async { await (delete(mediaFiles)..where( (t) => t.mediaId.equals(mediaId), )) .go(); } Future updateMedia( String mediaId, MediaFilesCompanion updates, ) async { await (update( mediaFiles, )..where((c) => c.mediaId.equals(mediaId))).write(updates); } Future updateAllMediaFiles( MediaFilesCompanion updates, ) async { await update(mediaFiles).write(updates); } Future getMediaFileById(String mediaId) async { return (select( mediaFiles, )..where((t) => t.mediaId.equals(mediaId))).getSingleOrNull(); } Future> getMediaFilesByIds(List mediaIds) async { return (select(mediaFiles)..where((t) => t.mediaId.isIn(mediaIds))).get(); } Future getDraftMediaFile() async { final medias = await (select( mediaFiles, )..where((t) => t.isDraftMedia.equals(true))).get(); if (medias.isEmpty) { return null; } return medias.first; } Stream watchMedia(String mediaId) { return (select( mediaFiles, )..where((t) => t.mediaId.equals(mediaId))).watchSingleOrNull(); } Future resetPendingDownloadState() async { await (update(mediaFiles)..where( (c) => c.downloadState.equals( DownloadState.downloading.name, ), )) .write( const MediaFilesCompanion( downloadState: Value(DownloadState.pending), ), ); } Future> getAllMediaFilesPendingDownload() async { return (select(mediaFiles)..where( (t) => t.downloadState.equals(DownloadState.pending.name) | t.downloadState.equals(DownloadState.downloading.name), )) .get(); } Future> getAllMediaFilesReuploadRequested() async { return (select(mediaFiles)..where( (t) => t.downloadState.equals(DownloadState.reuploadRequested.name), )) .get(); } Future> getAllMediaFilesPendingMigration() async { return (select(mediaFiles)..where( (t) => t.stored.equals(true) & (t.storedFileHash.isNull() | t.hasCropAnalyzed.equals(false) | (t.hasThumbnail.equals(false) & t.type.equals(MediaType.audio.name).not()) | t.sizeInBytes.isNull()), )) .get(); } Future> getAllMediaFilesPendingUpload() async { return (select(mediaFiles)..where( (t) => (t.uploadState.equals(UploadState.initialized.name) | t.uploadState.equals(UploadState.uploadLimitReached.name) | t.uploadState.equals(UploadState.uploading.name) | t.uploadState.equals(UploadState.preprocessing.name)), )) .get(); } Stream> watchAllStoredMediaFiles() { final query = (select(mediaFiles)..where((t) => t.stored.equals(true))).join([]) ..groupBy([ const CustomExpression( 'COALESCE(stored_file_hash, media_id)', ), ]); return query.map((row) => row.readTable(mediaFiles)).watch(); } Stream> watchNewestMediaFiles() { return (select(mediaFiles) ..orderBy([(t) => OrderingTerm.desc(t.createdAt)]) ..limit(100)) .watch(); } Stream> watchMediaFilesForGroup(String groupId) { final query = select(mediaFiles).join([ innerJoin( db.messages, db.messages.mediaId.equalsExp(mediaFiles.mediaId), useColumns: false, ), ])..where(db.messages.groupId.equals(groupId)); return query.map((row) => row.readTable(mediaFiles)).watch(); } Future updateAllRetransmissionUploadingState() async { await (update(mediaFiles)..where( (t) => t.uploadState.equals(UploadState.uploading.name) & t.reuploadRequestedBy.isNotNull(), )) .write( const MediaFilesCompanion( uploadState: Value(UploadState.preprocessing), ), ); } Future> getMessageIdsByMediaHash( Uint8List hash, int senderId, ) async { final query = select(db.messages).join([ innerJoin( mediaFiles, mediaFiles.mediaId.equalsExp(db.messages.mediaId), ), ])..where( mediaFiles.storedFileHash.equals(hash) & db.messages.senderId.equals(senderId) & db.messages.openedAt.isNull(), ); final rows = await query.get(); return rows.map((row) => row.readTable(db.messages).messageId).toList(); } Future> getMediaByHash(Uint8List hash) async { final query = select(db.mediaFiles) ..where((t) => t.storedFileHash.equals(hash)); return query.get(); } Future> getStorageStats() async { final rows = await select(mediaFiles).get(); final stats = {}; for (final row in rows) { final type = row.type; final size = row.sizeInBytes ?? 0; stats[type] = (stats[type] ?? 0) + size; } return stats; } }