👽 Fix attachment uploading

This commit is contained in:
LittleSheep 2024-12-28 17:19:20 +08:00
parent 91c85e8a58
commit cf0df91d8c
6 changed files with 785 additions and 152 deletions

View File

@ -21,7 +21,7 @@ class SnAttachmentProvider {
void putCache(Iterable<SnAttachment> items, {bool noCheck = false}) {
for (final item in items) {
if ((item.isAnalyzed && item.isUploaded) || noCheck) {
if (item.isAnalyzed || noCheck) {
_cache[item.rid] = item;
}
}
@ -34,7 +34,7 @@ class SnAttachmentProvider {
final resp = await _sn.client.get('/cgi/uc/attachments/$rid/meta');
final out = SnAttachment.fromJson(resp.data);
if (out.isAnalyzed && out.isUploaded) {
if (out.isAnalyzed) {
_cache[rid] = out;
}
@ -62,11 +62,12 @@ class SnAttachmentProvider {
'id': pendingFetch.join(','),
},
);
final out = resp.data['data'].map((e) => e['id'] == 0 ? null : SnAttachment.fromJson(e)).toList();
final List<SnAttachment?> out =
resp.data['data'].map((e) => e['id'] == 0 ? null : SnAttachment.fromJson(e)).cast<SnAttachment?>().toList();
for (final item in out) {
if (item == null) continue;
if (item.isAnalyzed && item.isUploaded) {
if (item.isAnalyzed) {
_cache[item.rid] = item;
}
result[randomMapping[item.rid]!] = item;
@ -117,7 +118,7 @@ class SnAttachmentProvider {
return SnAttachment.fromJson(resp.data);
}
Future<(SnAttachment, int)> chunkedUploadInitialize(
Future<(SnAttachmentFragment, int)> chunkedUploadInitialize(
int size,
String filename,
String pool,
@ -134,7 +135,7 @@ class SnAttachmentProvider {
mimetypeOverride = mimetype;
}
final resp = await _sn.client.post('/cgi/uc/attachments/multipart', data: {
final resp = await _sn.client.post('/cgi/uc/fragments', data: {
'alt': fileAlt,
'name': filename,
'pool': pool,
@ -143,17 +144,17 @@ class SnAttachmentProvider {
if (mimetypeOverride != null) 'mimetype': mimetypeOverride,
});
return (SnAttachment.fromJson(resp.data['meta']), resp.data['chunk_size'] as int);
return (SnAttachmentFragment.fromJson(resp.data['meta']), resp.data['chunk_size'] as int);
}
Future<SnAttachment> _chunkedUploadOnePart(
Future<dynamic> _chunkedUploadOnePart(
Uint8List data,
String rid,
String cid, {
Function(double progress)? onProgress,
}) async {
final resp = await _sn.client.post(
'/cgi/uc/attachments/multipart/$rid/$cid',
'/cgi/uc/fragments/$rid/$cid',
data: data,
options: Options(headers: {'Content-Type': 'application/octet-stream'}),
onSendProgress: (count, total) {
@ -163,21 +164,27 @@ class SnAttachmentProvider {
},
);
return SnAttachment.fromJson(resp.data);
if (resp.data['attachment'] != null) {
return SnAttachment.fromJson(resp.data['attachment']);
} else {
return SnAttachmentFragment.fromJson(resp.data['fragment']);
}
}
Future<SnAttachment> chunkedUploadParts(
XFile file,
SnAttachment place,
SnAttachmentFragment place,
int chunkSize, {
Function(double progress)? onProgress,
}) async {
final Map<String, dynamic> chunks = place.fileChunks ?? {};
final Map<String, dynamic> chunks = place.fileChunks;
var currentTask = 0;
final queue = Queue<Future<void>>();
final activeTasks = <Future<void>>[];
late SnAttachment out;
for (final entry in chunks.entries) {
queue.add(() async {
final beginCursor = entry.value * chunkSize;
@ -187,7 +194,7 @@ class SnAttachmentProvider {
);
final data = Uint8List.fromList(await file.openRead(beginCursor, endCursor).expand((chunk) => chunk).toList());
place = await _chunkedUploadOnePart(
final result = await _chunkedUploadOnePart(
data,
place.rid,
entry.key,
@ -197,6 +204,12 @@ class SnAttachmentProvider {
onProgress?.call(overallProgress);
currentTask++;
if (result is SnAttachmentFragment) {
place = result;
} else {
out = result as SnAttachment;
}
}());
}
@ -213,7 +226,7 @@ class SnAttachmentProvider {
}
}
return place;
return out;
}
Future<SnAttachment> updateOne(

View File

@ -19,7 +19,7 @@ class SnAttachment with _$SnAttachment {
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required dynamic deletedAt,
required DateTime? deletedAt,
required String rid,
required String uuid,
required int size,
@ -31,19 +31,20 @@ class SnAttachment with _$SnAttachment {
required int refCount,
@Default(0) int contentRating,
@Default(0) int qualityRating,
required dynamic fileChunks,
required dynamic cleanedAt,
required DateTime? cleanedAt,
required bool isAnalyzed,
required bool isUploaded,
required bool isSelfRef,
required dynamic ref,
required dynamic refId,
required SnAttachment? ref,
required int? refId,
required SnAttachmentPool? pool,
required int poolId,
required int accountId,
int? thumbnailId,
SnAttachment? thumbnail,
int? compressedId,
SnAttachment? compressed,
@Default({}) Map<String, dynamic> usermeta,
@Default({}) Map<String, dynamic> metadata,
String? thumbnail,
}) = _SnAttachment;
factory SnAttachment.fromJson(Map<String, Object?> json) => _$SnAttachmentFromJson(json);
@ -61,6 +62,37 @@ class SnAttachment with _$SnAttachment {
};
}
@freezed
class SnAttachmentFragment with _$SnAttachmentFragment {
const SnAttachmentFragment._();
const factory SnAttachmentFragment({
required int id,
required DateTime createdAt,
required DateTime updatedAt,
required DateTime? deletedAt,
required String rid,
required String uuid,
required int size,
required String name,
required String alt,
required String mimetype,
required String hash,
String? fingerprint,
@Default({}) Map<String, int> fileChunks,
@Default([]) List<String> fileChunksMissing,
}) = _SnAttachmentFragment;
factory SnAttachmentFragment.fromJson(Map<String, Object?> json) => _$SnAttachmentFragmentFromJson(json);
SnMediaType get mediaType => switch (mimetype.split('/').firstOrNull) {
'image' => SnMediaType.image,
'video' => SnMediaType.video,
'audio' => SnMediaType.audio,
_ => SnMediaType.file,
};
}
@freezed
class SnAttachmentPool with _$SnAttachmentPool {
const factory SnAttachmentPool({

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,9 @@ _$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) =>
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'],
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
rid: json['rid'] as String,
uuid: json['uuid'] as String,
size: (json['size'] as num).toInt(),
@ -23,21 +25,30 @@ _$SnAttachmentImpl _$$SnAttachmentImplFromJson(Map<String, dynamic> json) =>
refCount: (json['ref_count'] as num).toInt(),
contentRating: (json['content_rating'] as num?)?.toInt() ?? 0,
qualityRating: (json['quality_rating'] as num?)?.toInt() ?? 0,
fileChunks: json['file_chunks'],
cleanedAt: json['cleaned_at'],
cleanedAt: json['cleaned_at'] == null
? null
: DateTime.parse(json['cleaned_at'] as String),
isAnalyzed: json['is_analyzed'] as bool,
isUploaded: json['is_uploaded'] as bool,
isSelfRef: json['is_self_ref'] as bool,
ref: json['ref'],
refId: json['ref_id'],
ref: json['ref'] == null
? null
: SnAttachment.fromJson(json['ref'] as Map<String, dynamic>),
refId: (json['ref_id'] as num?)?.toInt(),
pool: json['pool'] == null
? null
: SnAttachmentPool.fromJson(json['pool'] as Map<String, dynamic>),
poolId: (json['pool_id'] as num).toInt(),
accountId: (json['account_id'] as num).toInt(),
thumbnailId: (json['thumbnail_id'] as num?)?.toInt(),
thumbnail: json['thumbnail'] == null
? null
: SnAttachment.fromJson(json['thumbnail'] as Map<String, dynamic>),
compressedId: (json['compressed_id'] as num?)?.toInt(),
compressed: json['compressed'] == null
? null
: SnAttachment.fromJson(json['compressed'] as Map<String, dynamic>),
usermeta: json['usermeta'] as Map<String, dynamic>? ?? const {},
metadata: json['metadata'] as Map<String, dynamic>? ?? const {},
thumbnail: json['thumbnail'] as String?,
);
Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
@ -45,7 +56,7 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt,
'deleted_at': instance.deletedAt?.toIso8601String(),
'rid': instance.rid,
'uuid': instance.uuid,
'size': instance.size,
@ -57,19 +68,66 @@ Map<String, dynamic> _$$SnAttachmentImplToJson(_$SnAttachmentImpl instance) =>
'ref_count': instance.refCount,
'content_rating': instance.contentRating,
'quality_rating': instance.qualityRating,
'file_chunks': instance.fileChunks,
'cleaned_at': instance.cleanedAt,
'cleaned_at': instance.cleanedAt?.toIso8601String(),
'is_analyzed': instance.isAnalyzed,
'is_uploaded': instance.isUploaded,
'is_self_ref': instance.isSelfRef,
'ref': instance.ref,
'ref': instance.ref?.toJson(),
'ref_id': instance.refId,
'pool': instance.pool?.toJson(),
'pool_id': instance.poolId,
'account_id': instance.accountId,
'thumbnail_id': instance.thumbnailId,
'thumbnail': instance.thumbnail?.toJson(),
'compressed_id': instance.compressedId,
'compressed': instance.compressed?.toJson(),
'usermeta': instance.usermeta,
'metadata': instance.metadata,
'thumbnail': instance.thumbnail,
};
_$SnAttachmentFragmentImpl _$$SnAttachmentFragmentImplFromJson(
Map<String, dynamic> json) =>
_$SnAttachmentFragmentImpl(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
rid: json['rid'] as String,
uuid: json['uuid'] as String,
size: (json['size'] as num).toInt(),
name: json['name'] as String,
alt: json['alt'] as String,
mimetype: json['mimetype'] as String,
hash: json['hash'] as String,
fingerprint: json['fingerprint'] as String?,
fileChunks: (json['file_chunks'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, (e as num).toInt()),
) ??
const {},
fileChunksMissing: (json['file_chunks_missing'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
);
Map<String, dynamic> _$$SnAttachmentFragmentImplToJson(
_$SnAttachmentFragmentImpl instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'rid': instance.rid,
'uuid': instance.uuid,
'size': instance.size,
'name': instance.name,
'alt': instance.alt,
'mimetype': instance.mimetype,
'hash': instance.hash,
'fingerprint': instance.fingerprint,
'file_chunks': instance.fileChunks,
'file_chunks_missing': instance.fileChunksMissing,
};
_$SnAttachmentPoolImpl _$$SnAttachmentPoolImplFromJson(

View File

@ -215,9 +215,9 @@ class _AttachmentItemContentVideoState extends State<_AttachmentItemContentVideo
behavior: HitTestBehavior.opaque,
child: Stack(
children: [
if (widget.data.thumbnail?.isNotEmpty ?? false)
if (widget.data.thumbnail != null)
AutoResizeUniversalImage(
sn.getAttachmentUrl(widget.data.thumbnail!),
sn.getAttachmentUrl(widget.data.thumbnail!.rid),
fit: BoxFit.cover,
)
else

View File

@ -315,7 +315,7 @@ class _PostMediaPendingItem extends StatelessWidget {
fit: StackFit.expand,
children: [
if (media.attachment?.thumbnail != null)
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!)),
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!.rid)),
const Icon(Symbols.videocam, color: Colors.white, shadows: [
Shadow(
offset: Offset(1, 1),
@ -332,7 +332,7 @@ class _PostMediaPendingItem extends StatelessWidget {
fit: StackFit.expand,
children: [
if (media.attachment?.thumbnail != null)
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!)),
AutoResizeUniversalImage(sn.getAttachmentUrl(media.attachment!.thumbnail!.rid)),
const Icon(Symbols.audio_file, color: Colors.white, shadows: [
Shadow(
offset: Offset(1, 1),