completeUpload method
completeUpload (12 3.4): checks the listed parts, joins them, checks size and SHA-256
and scans (or holds the scan, FakeFileSettings.holdScan); returns the File. A
completed session answers the file again.
Throws 404, 410 gone, 409 conflict with upload_incomplete or part_conflict.
Implementation
JsonMap completeUpload(String principalId, String fileId, JsonMap body) {
final f = _ownFile(principalId, fileId);
_expire(f);
if (f.sessionState == 'completed') return f.json;
if (f.sessionState == 'aborted' || f.sessionState == 'expired') {
throw const FakeProblem(410, 'gone');
}
for (final p in (body['parts'] as List<Object?>? ?? const [])) {
final part = p! as JsonMap;
final stored = f.parts[part['part_number']! as int];
if (stored != null && stored.etag != part['etag']) {
throw const FakeProblem(409, 'conflict', reason: 'part_conflict');
}
}
final missing = [
for (var n = 1; n <= f.partCount; n++)
if (!f.parts.containsKey(n)) n,
];
if (missing.isNotEmpty) {
throw FakeProblem(
409,
'conflict',
reason: 'upload_incomplete',
extra: {'detail': 'missing parts ${missing.join(', ')}'},
);
}
final joined = BytesBuilder(copy: false);
for (var n = 1; n <= f.partCount; n++) {
joined.add(f.parts[n]!.bytes);
}
final bytes = joined.takeBytes();
f
..sessionState = 'completed'
..parts.clear()
..bytes = bytes;
if (sha256.convert(bytes).toString() != f.declaredSha256) {
f.json['upload_state'] = 'failed';
_fileUpdated(f);
return f.json;
}
f.json['mime_type'] = _detect(f.declaredMime, bytes);
if (!fileSettings.holdScan) finishScan(fileId);
return f.json;
}