deleteLargeFile function
Delete a large file previously sent using writeLargeFile with URL
remoteFilePath (relative to appname/data directory) in POD.
By default the file is deleted from the current user's own POD. To delete
a large file owned by another user, provide that owner's WebID via
ownerWebId (consistent with writeLargeFile and readLargeFile).
Implementation
Future<void> deleteLargeFile({
required String remoteFilePath,
String? ownerWebId,
bool isPodRelativePath = false,
void Function(int, int)? onProgress,
}) async {
// Check if the corresponding Turtle file and directory of chunks exist
final externWebId = await resolveExternalOwner(ownerWebId);
final filePath = isPodRelativePath
? remoteFilePath
: [await getDataDirPath(), remoteFilePath].join('/');
final chunkDirUrl = await getDirUrl(
_getChunkDirPath(filePath),
webId: externWebId,
);
final fileUrl = await getFileUrl('$filePath.ttl', webId: externWebId);
if (await checkResourceStatus(fileUrl, isFile: true) !=
ResourceStatus.exist &&
await checkResourceStatus(chunkDirUrl, isFile: false) !=
ResourceStatus.exist) {
debugPrint('The requested file does not exist.');
return;
}
// For a large file on the user's OWN POD, revoke any recipients' access to
// the file's shareable resources (the metadata file and the chunk directory)
// before deleting them, so the recipients' permission logs no longer show
// access to a file that no longer exists. This mirrors deleteFile() for
// regular files. Revocation rewrites ACLs and is therefore only possible on
// the user's own POD, so it is skipped for external deletions.
if (externWebId == null) {
await _revokeLargeFileRecipients(fileUrl, isFile: true);
await _revokeLargeFileRecipients(chunkDirUrl, isFile: false);
}
// Parse the Turtle file with metadata of the (chunked) large file
// on server to get the URLs of individual chunks
final triples = turtleToTripleMap(
externWebId == null
? await readPod(fileUrl, pathType: PathType.absoluteUrl)
: await readExternalPod(fileUrl),
);
assert(triples.length == 1);
assert(triples.containsKey(fileUrl));
final map = triples[fileUrl];
final chunkPred = SIIPredicate.dataChunk.uriRef.value;
assert(map!.containsKey(chunkPred));
// Delete the individual chunks.
//
// turtleToTripleMap() yields a bare String when a predicate has a single
// object and an Iterable when it has several, so a file small enough to fit
// in one chunk arrives here as a String. Normalise as the read path does
// (see getChunkStream below); iterating the String directly threw
// "type 'String' is not a subtype of type 'Iterable<dynamic>'".
final chunkObj = map![chunkPred];
assert(chunkObj != null);
final chunkUrls = chunkObj is Iterable
? chunkObj.map((e) => e as String).toList()
: [chunkObj as String];
final chunkCount = chunkUrls.length;
var deleted = 0;
for (final chunkUrl in chunkUrls) {
await deleteResource(chunkUrl, ResourceContentType.binary);
// await deleteAclForResource(chunkUrl); // this may not be necessary
deleted += 1;
if (onProgress != null) {
onProgress(deleted, chunkCount);
}
}
// Delete the directory with individual chunks
await deleteResource(
'$chunkDirUrl${_getChunkDirInitFileName()}',
ResourceContentType.turtleText,
);
await deleteAclForResource(chunkDirUrl);
await deleteResource(chunkDirUrl, ResourceContentType.directory);
// Delete the representing turtle file
await deleteResource(fileUrl, ResourceContentType.turtleText);
// A successful delete is not announced, matching deleteFile() for ordinary
// files, which logs only warnings. Callers that replace a large file delete
// then write, so announcing it printed a "Deleted" line per file for what
// was really an overwrite.
}