initPod function
Initialise the directory and file structure in a POD.
Implementation
Future<void> initPod(
String securityKey, {
List<String>? dirUrls,
List<String>? fileUrls,
}) async {
// Check if the user has logged in.
if (!await isUserLoggedIn()) {
throw NotLoggedInException('Can not initialise POD without logging in');
}
// Check (and generate) the directory URLs.
//
// 20260618 miduo666/gjw Only regenerate when the caller did not provide any
// list (null) rather than also checking if the directory is empty. It may be
// partially created yet we still may want to proceed. An empty list means "no
// directories to create" and should be respected. We used to guard with `||
// dirUrls.isEmpty` here as well. This would then skip to defaultDirs that do
// also need to be created, incase they have not been.
if (dirUrls == null) {
final defaultDirs = await generateDefaultFolders();
dirUrls = [for (final d in defaultDirs) await getDirUrl(d)];
}
// Determine whether the encryption infrastructure already exists on the
// server. If the encryption directory and its key files are already in
// place, the POD has been initialised before and we must NOT regenerate
// the keyset — doing so would overwrite the existing RSA pair on the
// server and orphan every previously encrypted resource. This case is
// hit when the wizard is re-run to add a newly required folder (such as
// the notification or profile directory) on a previously initialised POD.
final encDirUrl = await getDirUrl(await getEncDirPath());
final encKeyUrl = await getFileUrl(await getEncKeyPath());
final indKeyUrl = await getFileUrl(await getIndKeyPath());
final pubKeyUrl = await getFileUrl(await getPubKeyPath());
final encDirExists = await checkResourceStatus(encDirUrl, isFile: false) ==
ResourceStatus.exist;
final encKeyExists = encDirExists &&
await checkResourceStatus(encKeyUrl) == ResourceStatus.exist;
// Only require the encryption directory in the missing-folder list when
// it does not already exist on the server. Otherwise it is legitimate to
// call initPod() with a partial set of folders (e.g. just the notification
// or profile directory) and we should simply top up whatever is missing.
if (!encDirExists && !dirUrls.contains(encDirUrl)) {
throw Exception('Can not initialise POD without creating $encDirUrl');
}
// Create the required directories.
for (final d in dirUrls) {
await createResource(
d,
isFile: false,
contentType: ResourceContentType.directory,
);
}
// Check (and generate) the file URLs.
if (fileUrls == null || fileUrls.isEmpty) {
final defaultFiles = await generateDefaultFiles();
fileUrls = <String>[];
for (final entry in defaultFiles.entries) {
final d = entry.key;
for (final f in entry.value as List) {
fileUrls.add([d, f].join('/'));
}
}
}
if (encKeyExists) {
// The POD already has an encryption keyset on the server. Verify the
// user-supplied security key against the existing verification key and
// cache it locally so subsequent operations do not need to prompt the
// user again. setSecurityKey() throws if verification fails, which the
// wizard surfaces back to the user.
await KeyManager.setSecurityKey(securityKey);
} else {
// First-time initialisation: create the encKeyFile, indKeyFile and
// pubKeyFile on the server, and cache the security key locally.
await KeyManager.initPodKeys(securityKey);
}
// The key files are managed by KeyManager — never recreate them as part
// of the generic file-creation loop below.
fileUrls.remove(encKeyUrl);
fileUrls.remove(indKeyUrl);
fileUrls.remove(pubKeyUrl);
for (final f in fileUrls) {
final fileName = f.split('/').last;
late String fileContent;
late bool aclFlag;
if (f.split('.').last == 'acl') {
final items = f.split('.');
final resourceUrl = items.getRange(0, items.length - 1).join('.');
Set<AccessMode>? publicAccess;
Set<AccessMode>? authUserAccess;
var isFile = true;
switch (fileName) {
case '$pubKeyFile.acl':
publicAccess = {AccessMode.read};
case '$permLogFile.acl':
publicAccess = {AccessMode.append};
default:
assert(fileName == '.acl');
isFile = false;
// The notifications directory ACL grants public Append so that
// any user (including cross-pod senders) can POST encrypted
// per-notification files into a recipient's folder; Read/Write/
// Control remain with the owner only. Files inherit the
// container's default ACL so the owner can read everything
// landing in there, while third parties cannot enumerate or
// read each other's deliveries. The shared directory ACL
// grants public read/write. The profile directory ACL is
// owner-only (empty publicAccess).
if (f.contains('/$notificationDir/')) {
publicAccess = {AccessMode.append};
} else if (f.contains('/$sharedDir/')) {
publicAccess = {AccessMode.read, AccessMode.write};
} else {
publicAccess = {};
}
}
fileContent = await genAclTurtle(
resourceUrl,
isFile: isFile,
publicAccess: publicAccess,
authUserAccess: authUserAccess,
);
aclFlag = true;
} else {
assert(fileName == permLogFile);
fileContent = genPermLogTTLStr(f);
aclFlag = false;
}
await createResource(f, content: fileContent, replaceIfExist: aclFlag);
}
await markPodStructureInitialised();
}