126 lines
3.5 KiB
Dart
126 lines
3.5 KiB
Dart
|
|
import 'package:equatable/equatable.dart';
|
|
import 'package:http_parser/http_parser.dart';
|
|
import 'package:jmap_dart_client/http/converter/id_nullable_converter.dart';
|
|
import 'package:jmap_dart_client/http/converter/media_type_nullable_converter.dart';
|
|
import 'package:jmap_dart_client/http/converter/part_id_nullable_converter.dart';
|
|
import 'package:jmap_dart_client/http/converter/unsigned_int_nullable_converter.dart';
|
|
import 'package:jmap_dart_client/jmap/account_id.dart';
|
|
import 'package:jmap_dart_client/jmap/core/id.dart';
|
|
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
|
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
|
|
import 'package:json_annotation/json_annotation.dart';
|
|
import 'package:uri/uri.dart';
|
|
|
|
part 'attachment.g.dart';
|
|
|
|
@JsonSerializable(
|
|
includeIfNull: false,
|
|
explicitToJson: true,
|
|
converters: [
|
|
PartIdNullableConverter(),
|
|
IdNullableConverter(),
|
|
UnsignedIntNullableConverter(),
|
|
MediaTypeNullableConverter(),
|
|
],
|
|
)
|
|
class Attachment with EquatableMixin {
|
|
|
|
static const String eventICSSubtype = 'ics';
|
|
static const String eventCalendarSubtype = 'calendar';
|
|
static const String applicationRTFType = 'application/rtf';
|
|
static const String _defaultName = 'unknown-attachment';
|
|
|
|
final PartId? partId;
|
|
final Id? blobId;
|
|
final UnsignedInt? size;
|
|
final String? name;
|
|
final MediaType? type;
|
|
final String? cid;
|
|
final ContentDisposition? disposition;
|
|
final String? charset;
|
|
|
|
Attachment({
|
|
this.partId,
|
|
this.blobId,
|
|
this.size,
|
|
this.name,
|
|
this.type,
|
|
this.cid,
|
|
this.disposition,
|
|
this.charset,
|
|
});
|
|
|
|
bool noCid() => cid == null || cid?.isEmpty == true;
|
|
|
|
bool hasCid() => cid != null && cid?.isNotEmpty == true;
|
|
|
|
bool isDispositionInlined() => disposition == ContentDisposition.inline;
|
|
|
|
bool isDispositionUndefined() => disposition == null;
|
|
|
|
bool isApplicationRTFInlined() => type?.mimeType == applicationRTFType && isDispositionInlined();
|
|
|
|
String getDownloadUrl(String baseDownloadUrl, AccountId accountId) {
|
|
final downloadUriTemplate = UriTemplate(baseDownloadUrl);
|
|
final downloadUri = downloadUriTemplate.expand({
|
|
'accountId' : accountId.id.value,
|
|
'blobId' : '${blobId?.value}',
|
|
'name' : name ?? '',
|
|
'type' : type?.mimeType ?? '',
|
|
});
|
|
return downloadUri;
|
|
}
|
|
|
|
String generateFileName() {
|
|
final rawName = (name?.trim().isNotEmpty == true)
|
|
? name!.trim()
|
|
: (blobId != null
|
|
? (type?.subtype != null
|
|
? '${blobId!.value}.${type!.subtype}'
|
|
: blobId!.value)
|
|
: _defaultName);
|
|
|
|
final sanitized = rawName.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_').trim();
|
|
// Fall back if sanitized name has no meaningful content (e.g. '???' → '___')
|
|
return sanitized.contains(RegExp(r'[^_\s]')) ? sanitized : _defaultName;
|
|
}
|
|
|
|
factory Attachment.fromJson(Map<String, dynamic> json) => _$AttachmentFromJson(json);
|
|
|
|
Map<String, dynamic> toJson() => _$AttachmentToJson(this);
|
|
|
|
@override
|
|
List<Object?> get props => [
|
|
partId,
|
|
blobId,
|
|
size,
|
|
name,
|
|
type,
|
|
cid,
|
|
disposition,
|
|
charset
|
|
];
|
|
}
|
|
|
|
enum ContentDisposition {
|
|
inline,
|
|
attachment,
|
|
other
|
|
}
|
|
|
|
extension DispositionStringExtension on String? {
|
|
ContentDisposition? toContentDisposition() {
|
|
if (this != null) {
|
|
switch(this) {
|
|
case 'inline':
|
|
return ContentDisposition.inline;
|
|
case 'attachment':
|
|
return ContentDisposition.attachment;
|
|
default:
|
|
return ContentDisposition.other;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
} |