Conversation
There was a problem hiding this comment.
Pull request overview
This PR targets issue #224 by reducing perceived login/initialization latency in the Solid POD client codepaths, primarily by introducing in-memory caching and parallelizing URL resolution/resource creation.
Changes:
- Speed up
initPod()by parallelizing URL resolution and concurrent file creation, and by avoiding unnecessary regeneration when lists are explicitly provided. - Add in-memory caches for token responses and profile card data to reduce repeated deserialization/JWT checks and repeated profile fetches.
- Minor import-order/formatting cleanups in the example app.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/src/solid/utils/init_helper.dart | Parallel URL resolution + concurrent file creation; new encryption setup flow. |
| lib/src/solid/utils/authdata_manager.dart | Adds in-memory token and profile caches; clears caches on logout. |
| lib/src/solid/authenticate.dart | Adds optional wasAlreadyLoggedIn; reuses cached issuer/profile data to avoid redundant GETs. |
| example/lib/utils/rdf.dart | Import/whitespace cleanup. |
| example/lib/main.dart | Import ordering cleanup. |
| example/lib/features/view_keys.dart | Import ordering cleanup. |
| example/lib/features/file_service.dart | Import ordering cleanup. |
| example/lib/features/edit_keyvalue.dart | Import ordering cleanup. |
| example/lib/features/create_acl_inherited_file.dart | Import ordering cleanup. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -208,62 +237,92 @@ Future<void> initPod( | |||
| } | |||
|
|
|||
| // Check (and generate) the file URLs. | |||
| // Only regenerate when the caller did not provide any list (null). | |||
| // An empty list means "no files to create" and should be respected. | |||
|
|
|||
| if (fileUrls == null || fileUrls.isEmpty) { | |||
| if (fileUrls == null) { | |||
| final defaultFiles = await generateDefaultFiles(); | |||
| fileUrls = <String>[]; | |||
| // Resolve all file URLs in parallel — each call reads the cached webId. | |||
| final urlFutures = <Future<String>>[]; | |||
| for (final entry in defaultFiles.entries) { | |||
| final d = entry.key; | |||
| final d = entry.key as String; | |||
| for (final f in entry.value as List) { | |||
| fileUrls.add([d, f].join('/')); | |||
| urlFutures.add(getFileUrl([d, f as String].join('/'))); | |||
| } | |||
| } | |||
| fileUrls = await Future.wait(urlFutures); | |||
| } | |||
|
|
|||
| // Create the encKeyFile, indKeyFile and pubKeyFile | |||
| // and remove them from the fileUrls list. | |||
|
|
|||
| await KeyManager.initPodKeys(securityKey); | |||
| fileUrls.remove(await getFileUrl(await getEncKeyPath())); | |||
| fileUrls.remove(await getFileUrl(await getIndKeyPath())); | |||
| fileUrls.remove(await getFileUrl(await getPubKeyPath())); | |||
|
|
|||
| 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('.'); | |||
| late Set<AccessMode> publicAccess; | |||
| var isFile = true; | |||
| switch (fileName) { | |||
| case '$pubKeyFile.acl': | |||
| publicAccess = {AccessMode.read}; | |||
| case '$permLogFile.acl': | |||
| publicAccess = {AccessMode.append}; | |||
| default: | |||
| assert(fileName == '.acl'); | |||
| publicAccess = {AccessMode.read, AccessMode.write}; | |||
| isFile = false; | |||
| } | |||
| // Handle encryption key setup. | |||
|
|
|||
| fileContent = await genAclTurtle( | |||
| resourceUrl, | |||
| isFile: isFile, | |||
| publicAccess: publicAccess, | |||
| ); | |||
| if (needsEncSetup) { | |||
| // First-time setup: create encryption key files from scratch. | |||
|
|
|||
| aclFlag = true; | |||
| } else { | |||
| assert(fileName == permLogFile); | |||
| fileContent = genPermLogTTLStr(f); | |||
| aclFlag = false; | |||
| } | |||
| await KeyManager.initPodKeys(securityKey); | |||
| } else { | |||
| // Encryption already initialised: just verify and set the security key | |||
| // so that encrypted operations work in this session without overwriting | |||
| // existing keys. | |||
|
|
|||
| await createResource(f, content: fileContent, replaceIfExist: aclFlag); | |||
| await KeyManager.setSecurityKey(securityKey); | |||
| } | |||
|
|
|||
| // Remove encryption key file URLs from the list (already handled above). | |||
| // Resolve all three key paths in parallel, then remove them from the list. | |||
|
|
|||
| final encKeyUrls = await Future.wait([ | |||
| getEncKeyPath().then(getFileUrl), | |||
| getIndKeyPath().then(getFileUrl), | |||
| getPubKeyPath().then(getFileUrl), | |||
| ]); | |||
| for (final url in encKeyUrls) { | |||
| fileUrls.remove(url); | |||
| } | |||
There was a problem hiding this comment.
needsEncSetup is decided based on whether the encryption directory is in dirUrls or exists on the server, but key-file existence isn’t checked. When needsEncSetup is false you still remove the enc/ind/pub key URLs from fileUrls, so if any of those key files are missing/corrupt while the directory exists, initPod will neither recreate them nor include them in the subsequent createResource loop, and KeyManager.setSecurityKey() may fail when it tries to load/verify keys. Consider determining needsEncSetup by checking the status of the key files themselves (or falling back to initPodKeys on setSecurityKey failure) and only removing key URLs when you actually handle them via KeyManager.
| var profData = getCachedIssuerProfileBody(profCardUrl); | ||
| // If the issuer was resolved from a plain URI (not a WebID profile URL), | ||
| // the profile body was not pre-fetched. Fetch it now with the | ||
| // authenticated access token. | ||
| profData ??= utf8.decode(await getResource(profCardUrl)); |
There was a problem hiding this comment.
getCachedIssuerProfileBody() is referenced here but there is no definition in this repository. If this relies on package:solid_auth exporting that API, please ensure the minimum solid_auth version in pubspec actually includes it (otherwise consumers pinned to 0.1.28 will fail to compile), or add a local implementation/abstraction within this package.
| switch (fileName) { | ||
| case '$pubKeyFile.acl': | ||
| publicAccess = {AccessMode.read}; | ||
| case '$permLogFile.acl': | ||
| publicAccess = {AccessMode.append}; | ||
| default: | ||
| assert(fileName == '.acl'); | ||
| publicAccess = {AccessMode.read, AccessMode.write}; | ||
| isFile = false; | ||
| } |
There was a problem hiding this comment.
The switch over fileName is missing terminating statements (break/return/continue) for the first two cases. In Dart this is either a compile-time error or will behave as unintended fall-through (setting publicAccess/isFile incorrectly). Add explicit termination for each case (or rewrite as an if/else or switch expression) so each ACL file gets the intended permissions.
Pull Request Details
What issue does this PR address
Associated Issue
Type of Change
How Has This Been Tested?
Please describe the tests that you ran to verify your changes.
Checklist
Complete the check-list below to ensure your branch is ready for PR.
make preporflutter analyze lib)dart testoutput or screenshot included in issue #Finalising
Once PR discussion is complete and reviewers have approved: