diff --git a/example/pubspec.yaml b/example/pubspec.yaml index b86bdabb..a59c9599 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -25,7 +25,7 @@ dependency_overrides: solidui: git: url: https://github.com/anusii/solidui.git - ref: tony/127_browserpod + ref: tony/204_pod_initialisation dev_dependencies: flutter_lints: ^6.0.0 diff --git a/lib/solidpod.dart b/lib/solidpod.dart index d26ebf69..d0650306 100644 --- a/lib/solidpod.dart +++ b/lib/solidpod.dart @@ -136,10 +136,13 @@ export 'src/solid/utils/get_url_helper.dart' export 'src/solid/utils/init_helper.dart' show + clearPodStructureInitialised, generateDefaultFolders, generateCustomFolders, generateDefaultFiles, - initPod; + initPod, + isPodStructureInitialised, + markPodStructureInitialised; /// Read encrypted/non-encrypted files stored in a POD diff --git a/lib/src/solid/api/rest_api.dart b/lib/src/solid/api/rest_api.dart index 743cadd4..8fe7e1cf 100644 --- a/lib/src/solid/api/rest_api.dart +++ b/lib/src/solid/api/rest_api.dart @@ -142,30 +142,48 @@ Future> initialStructureTest( 'fileNames': [], }; - for (final containerName in folders) { - // NB: the trailing separator in path is essential for this check - final resourceUrl = await getDirUrl(containerName); - if (await checkResourceStatus(resourceUrl, isFile: false) == - ResourceStatus.notExist) { - allExists = false; + // Resolve URLs and check existence of all folders in parallel. + + final folderResults = await Future.wait( + folders.map((containerName) async { + final resourceUrl = await getDirUrl(containerName); + final status = await checkResourceStatus(resourceUrl, isFile: false); + return (name: containerName, url: resourceUrl, status: status); + }), + ); - resNotExist['folders'].add(resourceUrl); - resNotExist['folderNames'].add(containerName); + for (final result in folderResults) { + if (result.status == ResourceStatus.notExist) { + allExists = false; + resNotExist['folders'].add(result.url); + resNotExist['folderNames'].add(result.name); } } + // Resolve URLs and check existence of all files in parallel. + + final fileFutures = + >[]; for (final containerName in files.keys) { final fileNameList = files[containerName] as List; for (final fileName in fileNameList) { - final resourceUrl = await getFileUrl( - [containerName as String, fileName].join('/'), - ); - if (await checkResourceStatus(resourceUrl, isFile: true) == - ResourceStatus.notExist) { - allExists = false; - resNotExist['files'].add(resourceUrl); - resNotExist['fileNames'].add(fileName); - } + fileFutures.add(() async { + final resourceUrl = await getFileUrl( + [containerName as String, fileName].join('/'), + ); + final status = await checkResourceStatus(resourceUrl, isFile: true); + return (fileName: fileName, url: resourceUrl, status: status); + }()); + } + } + + final fileResults = await Future.wait(fileFutures); + + for (final result in fileResults) { + if (result.status == ResourceStatus.notExist) { + allExists = false; + resNotExist['files'].add(result.url); + resNotExist['fileNames'].add(result.fileName); } } diff --git a/lib/src/solid/utils/init_helper.dart b/lib/src/solid/utils/init_helper.dart index a9756aef..4e03b605 100644 --- a/lib/src/solid/utils/init_helper.dart +++ b/lib/src/solid/utils/init_helper.dart @@ -29,9 +29,12 @@ library; +import 'package:flutter/foundation.dart' show debugPrint; + import 'package:solidpod/src/solid/api/rest_api.dart'; import 'package:solidpod/src/solid/constants/common.dart'; import 'package:solidpod/src/solid/constants/web_acl.dart'; +import 'package:solidpod/src/solid/utils/authdata_manager.dart'; import 'package:solidpod/src/solid/utils/exceptions.dart'; import 'package:solidpod/src/solid/utils/get_url_helper.dart'; import 'package:solidpod/src/solid/utils/key_manager.dart'; @@ -41,14 +44,70 @@ import 'package:solidpod/src/solid/utils/misc.dart' getEncKeyPath, getIndKeyPath, getPubKeyPath, - isUserLoggedIn; + isUserLoggedIn, + writeToSecureStorage; import 'package:solidpod/src/solid/utils/permission.dart' show genAclTurtle; import 'package:solidpod/src/solid/utils/rdf.dart' show genPermLogTTLStr; +/// Key prefix for the POD initialisation flag in secure storage. + +const String _podInitFlagPrefix = '_pod_init_done_'; + +/// Builds a unique storage key for the current user and app. + +Future _getPodInitFlagKey() async { + final webId = await AuthDataManager.getWebId(); + if (webId == null || webId.isEmpty) { + throw NotLoggedInException( + 'Cannot check initialisation flag without logging in', + ); + } + return '$_podInitFlagPrefix${appDirName}_$webId'; +} + +/// Returns `true` if the POD structure has already been initialised for the +/// current user and app, allowing subsequent logins to skip the full +/// folder/file existence check. + +Future isPodStructureInitialised() async { + try { + final key = await _getPodInitFlagKey(); + final value = await secureStorage.read(key: key); + return value == 'true'; + } on Object catch (e) { + debugPrint('isPodStructureInitialised() check failed: $e'); + return false; + } +} + +/// Persists a flag indicating the POD structure has been successfully +/// initialised for the current user and app. + +Future markPodStructureInitialised() async { + final key = await _getPodInitFlagKey(); + await writeToSecureStorage(key, 'true'); +} + +/// Clears the initialisation flag, forcing a full structure check on the +/// next login. Useful when the expected folder/file layout changes between +/// app versions. + +Future clearPodStructureInitialised() async { + try { + final key = await _getPodInitFlagKey(); + if (await secureStorage.containsKey(key: key)) { + await secureStorage.delete(key: key); + } + } on Object catch (e) { + debugPrint('clearPodStructureInitialised() failed: $e'); + } +} + /// Generates a list of default folder paths for a given application. /// -/// This function takes the name of an application as input and returns a list of strings. -/// Each string in the list represents a path to a default folder for the application. +/// This function takes the name of an application as input and returns a list +/// of strings. Each string in the list represents a path to a default folder +/// for the application. Future> generateDefaultFolders() async { final dataDirLoc = [appDirName, dataDir].join('/'); @@ -91,8 +150,9 @@ List generateCustomFolders(List customFolderPaths) { /// Generates a list of default folder paths for a given application. /// -/// This function takes the name of an application as input and returns a list of strings. -/// Each string in the list represents a path to a default folder for the application. +/// This function takes the name of an application as input and returns a list +/// of strings. Each string in the list represents a path to a default folder +/// for the application. Future> generateDefaultFiles() async { final sharingDirLoc = [appDirName, sharingDir].join('/'); @@ -204,4 +264,6 @@ Future initPod( await createResource(f, content: fileContent, replaceIfExist: aclFlag); } + + await markPodStructureInitialised(); } diff --git a/pubspec.yaml b/pubspec.yaml index 9047d360..d1627618 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,7 @@ dependency_overrides: solidui: git: url: https://github.com/anusii/solidui.git - ref: tony/127_browserpod + ref: tony/204_pod_initialisation dev_dependencies: build_runner: ^2.10.5