initPod function

Future<void> initPod(
  1. String securityKey, {
  2. List<String>? dirUrls,
  3. List<String>? fileUrls,
})

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();
}