sendFileEvent method

Future<String?> sendFileEvent(
  1. SDNFile file, {
  2. String? txid,
  3. Event? inReplyTo,
  4. String? editEventId,
  5. int? shrinkImageMaxDimension,
  6. SDNImageFile? thumbnail,
  7. Map<String, dynamic>? extraContent,
  8. String? threadRootEventId,
  9. String? threadLastEventId,
})

Sends a file to this room after uploading it. Returns the mxc uri of the uploaded file. If waitUntilSent is true, the future will wait until the message event has received the server. Otherwise the future will only wait until the file has been uploaded. Optionally specify extraContent to tack on to the event.

In case file is a SDNImageFile, thumbnail is automatically computed unless it is explicitly provided. Set shrinkImageMaxDimension to for example 1600 if you want to shrink your image before sending. This is ignored if the File is not a SDNImageFile.

Implementation

Future<String?> sendFileEvent(
  SDNFile file, {
  String? txid,
  Event? inReplyTo,
  String? editEventId,
  int? shrinkImageMaxDimension,
  SDNImageFile? thumbnail,
  Map<String, dynamic>? extraContent,
  String? threadRootEventId,
  String? threadLastEventId,
}) async {
  txid ??= client.generateUniqueTransactionId();
  sendingFilePlaceholders[txid] = file;
  if (thumbnail != null) {
    sendingFileThumbnails[txid] = thumbnail;
  }

  // Create a fake Event object as a placeholder for the uploading file:
  final syncUpdate = SyncUpdate(
    nextBatch: '',
    rooms: RoomsUpdate(
      join: {
        id: JoinedRoomUpdate(
          timeline: TimelineUpdate(
            events: [
              SDNEvent(
                content: {
                  'msgtype': file.msgType,
                  'body': file.name,
                  'filename': file.name,
                },
                type: EventTypes.Message,
                eventId: txid,
                senderId: client.userID!,
                originServerTs: DateTime.now(),
                unsigned: {
                  messageSendingStatusKey: EventStatus.sending.intValue,
                  'transaction_id': txid,
                  ...FileSendRequestCredentials(
                    inReplyTo: inReplyTo?.eventId,
                    editEventId: editEventId,
                    shrinkImageMaxDimension: shrinkImageMaxDimension,
                    extraContent: extraContent,
                  ).toJson(),
                },
              ),
            ],
          ),
        ),
      },
    ),
  );

  SDNFile uploadFile = file; // ignore: omit_local_variable_types
  // computing the thumbnail in case we can
  if (file is SDNImageFile &&
      (thumbnail == null || shrinkImageMaxDimension != null)) {
    syncUpdate.rooms!.join!.values.first.timeline!.events!.first
            .unsigned![fileSendingStatusKey] =
        FileSendingStatus.generatingThumbnail.name;
    await _handleFakeSync(syncUpdate);
    thumbnail ??= await file.generateThumbnail(
      nativeImplementations: client.nativeImplementations,
      customImageResizer: client.customImageResizer,
    );
    if (shrinkImageMaxDimension != null) {
      file = await SDNImageFile.shrink(
        bytes: file.bytes,
        name: file.name,
        maxDimension: shrinkImageMaxDimension,
        customImageResizer: client.customImageResizer,
        nativeImplementations: client.nativeImplementations,
      );
    }

    if (thumbnail != null && file.size < thumbnail.size) {
      thumbnail = null; // in this case, the thumbnail is not usefull
    }
  }

  // Check media config of the server before sending the file. Stop if the
  // Media config is unreachable or the file is bigger than the given maxsize.
  try {
    final mediaConfig = await client.getConfig();
    final maxMediaSize = mediaConfig.mUploadSize;
    if (maxMediaSize != null && maxMediaSize < file.bytes.lengthInBytes) {
      throw FileTooBigSDNException(file.bytes.lengthInBytes, maxMediaSize);
    }
  } catch (e) {
    Logs().d('Config error while sending file', e);
    syncUpdate.rooms!.join!.values.first.timeline!.events!.first
        .unsigned![messageSendingStatusKey] = EventStatus.error.intValue;
    await _handleFakeSync(syncUpdate);
    rethrow;
  }

  SDNFile? uploadThumbnail = thumbnail; // ignore: omit_local_variable_types
  EncryptedFile? encryptedFile;
  EncryptedFile? encryptedThumbnail;
  if (encrypted && client.fileEncryptionEnabled) {
    syncUpdate.rooms!.join!.values.first.timeline!.events!.first
        .unsigned![fileSendingStatusKey] = FileSendingStatus.encrypting.name;
    await _handleFakeSync(syncUpdate);
    encryptedFile = await file.encrypt();
    uploadFile = encryptedFile.toSDNFile();

    if (thumbnail != null) {
      encryptedThumbnail = await thumbnail.encrypt();
      uploadThumbnail = encryptedThumbnail.toSDNFile();
    }
  }
  Uri? uploadResp, thumbnailUploadResp;

  final timeoutDate = DateTime.now().add(client.sendTimelineEventTimeout);

  syncUpdate.rooms!.join!.values.first.timeline!.events!.first
      .unsigned![fileSendingStatusKey] = FileSendingStatus.uploading.name;
  while (uploadResp == null ||
      (uploadThumbnail != null && thumbnailUploadResp == null)) {
    try {
      uploadResp = await client.uploadContent(
        uploadFile.bytes,
        filename: uploadFile.name,
        contentType: uploadFile.mimeType,
      );
      thumbnailUploadResp = uploadThumbnail != null
          ? await client.uploadContent(
              uploadThumbnail.bytes,
              filename: uploadThumbnail.name,
              contentType: uploadThumbnail.mimeType,
            )
          : null;
    } on SDNException catch (_) {
      syncUpdate.rooms!.join!.values.first.timeline!.events!.first
          .unsigned![messageSendingStatusKey] = EventStatus.error.intValue;
      await _handleFakeSync(syncUpdate);
      rethrow;
    } catch (_) {
      if (DateTime.now().isAfter(timeoutDate)) {
        syncUpdate.rooms!.join!.values.first.timeline!.events!.first
            .unsigned![messageSendingStatusKey] = EventStatus.error.intValue;
        await _handleFakeSync(syncUpdate);
        rethrow;
      }
      Logs().v('Send File into room failed. Try again...');
      await Future.delayed(Duration(seconds: 1));
    }
  }

  // Send event
  final content = <String, dynamic>{
    'msgtype': file.msgType,
    'body': file.name,
    'filename': file.name,
    if (encryptedFile == null) 'url': uploadResp.toString(),
    if (encryptedFile != null)
      'file': {
        'url': uploadResp.toString(),
        'mimetype': file.mimeType,
        'v': 'v2',
        'key': {
          'alg': 'A256CTR',
          'ext': true,
          'k': encryptedFile.k,
          'key_ops': ['encrypt', 'decrypt'],
          'kty': 'oct'
        },
        'iv': encryptedFile.iv,
        'hashes': {'sha256': encryptedFile.sha256}
      },
    'info': {
      ...file.info,
      if (thumbnail != null && encryptedThumbnail == null)
        'thumbnail_url': thumbnailUploadResp.toString(),
      if (thumbnail != null && encryptedThumbnail != null)
        'thumbnail_file': {
          'url': thumbnailUploadResp.toString(),
          'mimetype': thumbnail.mimeType,
          'v': 'v2',
          'key': {
            'alg': 'A256CTR',
            'ext': true,
            'k': encryptedThumbnail.k,
            'key_ops': ['encrypt', 'decrypt'],
            'kty': 'oct'
          },
          'iv': encryptedThumbnail.iv,
          'hashes': {'sha256': encryptedThumbnail.sha256}
        },
      if (thumbnail != null) 'thumbnail_info': thumbnail.info,
      if (thumbnail?.blurhash != null &&
          file is SDNImageFile &&
          file.blurhash == null)
        'xyz.amorgan.blurhash': thumbnail!.blurhash
    },
    if (extraContent != null) ...extraContent,
  };
  final eventId = await sendEvent(
    content,
    txid: txid,
    inReplyTo: inReplyTo,
    editEventId: editEventId,
    threadRootEventId: threadRootEventId,
    threadLastEventId: threadLastEventId,
  );
  sendingFilePlaceholders.remove(txid);
  sendingFileThumbnails.remove(txid);
  return eventId;
}