LCOV - code coverage report
Current view: top level - client - local_secondary.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 93 142 65.5 %
Date: 2022-01-19 17:54:05 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:convert';
       2             : 
       3             : import 'package:at_client/at_client.dart';
       4             : import 'package:at_client/src/client/secondary.dart';
       5             : import 'package:at_commons/at_builders.dart';
       6             : import 'package:at_commons/at_commons.dart';
       7             : import 'package:at_lookup/at_lookup.dart';
       8             : import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart';
       9             : import 'package:at_utils/at_utils.dart';
      10             : 
      11             : /// Contains methods to execute verb on local secondary storage using [executeVerb]
      12             : /// Set [AtClientPreference.isLocalStoreRequired] to true and other preferences that your app needs.
      13             : /// Delete and Update commands will be synced to the server
      14             : class LocalSecondary implements Secondary {
      15             :   final AtClient _atClient;
      16             : 
      17             :   final _logger = AtSignLogger('LocalSecondary');
      18             : 
      19             :   /// Local keystore used to store data for the current atSign.
      20             :   SecondaryKeyStore? keyStore;
      21             : 
      22           1 :   LocalSecondary(this._atClient) {
      23           2 :     keyStore = SecondaryPersistenceStoreFactory.getInstance()
      24           3 :         .getSecondaryPersistenceStore(_atClient.getCurrentAtSign())!
      25           1 :         .getSecondaryKeyStore();
      26             :   }
      27             : 
      28             :   /// Executes a verb builder on the local secondary. For update and delete operation, if [sync] is
      29             :   /// set to true then data is synced from local to remote.
      30             :   /// if [sync] is set to false, no sync operation is done.
      31             :   @override
      32           1 :   Future<String?> executeVerb(VerbBuilder builder, {sync}) async {
      33             :     String? verbResult;
      34             : 
      35             :     try {
      36           2 :       if (builder is UpdateVerbBuilder || builder is DeleteVerbBuilder) {
      37             :         //1. if local and server are out of sync, first sync before updating current key-value
      38             : 
      39             :         //2 . update/delete to local store
      40           1 :         if (builder is UpdateVerbBuilder) {
      41           2 :           verbResult = await _update(builder);
      42           1 :         } else if (builder is DeleteVerbBuilder) {
      43           2 :           verbResult = await _delete(builder);
      44             :         }
      45             :         // 3. sync latest update/delete if strategy is immediate
      46             :         if (sync != null && sync) {
      47           0 :           _logger.finer('calling sync immediate from local secondary');
      48           0 :           AtClientManager.getInstance().syncService.sync();
      49             :         }
      50           1 :       } else if (builder is LLookupVerbBuilder) {
      51           2 :         verbResult = await _llookup(builder);
      52           1 :       } else if (builder is ScanVerbBuilder) {
      53           2 :         verbResult = await _scan(builder);
      54             :       }
      55           1 :     } on Exception catch (e) {
      56           1 :       if (e is AtLookUpException) {
      57           0 :         return Future.error(AtClientException(e.errorCode, e.errorMessage));
      58             :       } else {
      59             :         rethrow;
      60             :       }
      61             :     }
      62             :     return verbResult;
      63             :   }
      64             : 
      65           1 :   Future<String> _update(UpdateVerbBuilder builder) async {
      66             :     try {
      67             :       dynamic updateResult;
      68           1 :       var updateKey = AtClientUtil.buildKey(builder);
      69           1 :       switch (builder.operation) {
      70           1 :         case UPDATE_META:
      71           0 :           var metadata = Metadata();
      72             :           metadata
      73           0 :             ..ttl = builder.ttl
      74           0 :             ..ttb = builder.ttb
      75           0 :             ..ttr = builder.ttr
      76           0 :             ..ccd = builder.ccd
      77           0 :             ..isBinary = builder.isBinary
      78           0 :             ..isEncrypted = builder.isEncrypted;
      79           0 :           var atMetadata = AtMetadataAdapter(metadata);
      80           0 :           updateResult = await keyStore!.putMeta(updateKey, atMetadata);
      81             :           break;
      82             :         default:
      83           1 :           var atData = AtData();
      84           2 :           atData.data = builder.value;
      85           1 :           if (builder.dataSignature != null) {
      86           0 :             var metadata = Metadata();
      87             :             metadata
      88           0 :               ..ttl = builder.ttl
      89           0 :               ..ttb = builder.ttb
      90           0 :               ..ttr = builder.ttr
      91           0 :               ..ccd = builder.ccd
      92           0 :               ..isBinary = builder.isBinary
      93           0 :               ..isEncrypted = builder.isEncrypted
      94           0 :               ..dataSignature = builder.dataSignature;
      95           0 :             var atMetadata = AtMetadataAdapter(metadata);
      96             :             updateResult =
      97           0 :                 await keyStore!.putAll(updateKey, atData, atMetadata);
      98             :             break;
      99             :           }
     100             :           // #TODO replace below call with putAll.
     101           3 :           updateResult = await keyStore!.put(updateKey, atData,
     102           1 :               time_to_live: builder.ttl,
     103           1 :               time_to_born: builder.ttb,
     104           1 :               time_to_refresh: builder.ttr,
     105           1 :               isCascade: builder.ccd,
     106           1 :               isBinary: builder.isBinary,
     107           1 :               isEncrypted: builder.isEncrypted);
     108             :           break;
     109             :       }
     110           1 :       return 'data:$updateResult';
     111           0 :     } on DataStoreException catch (e) {
     112           0 :       _logger.severe('exception in local update:${e.toString()}');
     113             :       rethrow;
     114             :     }
     115             :   }
     116             : 
     117           1 :   Future<String> _llookup(LLookupVerbBuilder builder) async {
     118             :     try {
     119             :       var llookupKey = '';
     120           1 :       if (builder.isCached) {
     121           0 :         llookupKey += 'cached:';
     122             :       }
     123           1 :       if (builder.sharedWith != null) {
     124           0 :         llookupKey += '${AtUtils.formatAtSign(builder.sharedWith!)}:';
     125             :       }
     126           1 :       if (builder.atKey != null) {
     127           2 :         llookupKey += builder.atKey!;
     128             :       }
     129           1 :       if (builder.sharedBy != null) {
     130           3 :         llookupKey += AtUtils.formatAtSign(builder.sharedBy)!;
     131             :       }
     132           3 :       var llookupMeta = await keyStore!.getMeta(llookupKey);
     133           1 :       var isActive = _isActiveKey(llookupMeta);
     134             :       String? result;
     135             :       if (isActive) {
     136           3 :         var llookupResult = await keyStore!.get(llookupKey);
     137           2 :         result = _prepareResponseData(builder.operation, llookupResult);
     138             :       }
     139           1 :       return 'data:$result';
     140           1 :     } on DataStoreException catch (e) {
     141           0 :       _logger.severe('exception in llookup:${e.toString()}');
     142             :       rethrow;
     143             :     }
     144             :   }
     145             : 
     146           1 :   Future<String> _delete(DeleteVerbBuilder builder) async {
     147             :     try {
     148             :       var deleteKey = '';
     149           1 :       if (builder.isCached) {
     150           0 :         deleteKey += 'cached:';
     151             :       }
     152           1 :       if (builder.isPublic) {
     153           0 :         deleteKey += 'public:';
     154             :       }
     155           1 :       if (builder.sharedWith != null && builder.sharedWith!.isNotEmpty) {
     156           0 :         deleteKey += '${AtUtils.formatAtSign(builder.sharedWith!)}:';
     157             :       }
     158           3 :       if (builder.sharedBy != null && builder.sharedBy!.isNotEmpty) {
     159           1 :         deleteKey +=
     160           4 :             '${builder.atKey}${AtUtils.formatAtSign(builder.sharedBy!)}';
     161             :       } else {
     162           0 :         deleteKey += builder.atKey!;
     163             :       }
     164           3 :       var deleteResult = await keyStore!.remove(deleteKey);
     165           1 :       return 'data:$deleteResult';
     166           0 :     } on DataStoreException catch (e) {
     167           0 :       _logger.severe('exception in delete:${e.toString()}');
     168             :       rethrow;
     169             :     }
     170             :   }
     171             : 
     172           1 :   Future<String?> _scan(ScanVerbBuilder builder) async {
     173             :     try {
     174             :       // Call to remote secondary sever and performs an outbound scan to retrieve values from sharedBy secondary
     175             :       // shared with current atSign
     176           1 :       if (builder.sharedBy != null) {
     177           0 :         var command = builder.buildCommand();
     178           0 :         return await RemoteSecondary(
     179           0 :                 _atClient.getCurrentAtSign()!, _atClient.getPreferences()!,
     180           0 :                 privateKey: _atClient.getPreferences()!.privateKey)
     181           0 :             .executeCommand(command, auth: true);
     182             :       }
     183             :       List<String?> keys;
     184           3 :       keys = keyStore!.getKeys(regex: builder.regex) as List<String?>;
     185             :       // Gets keys shared to sharedWith atSign.
     186           1 :       if (builder.sharedWith != null) {
     187           0 :         keys.retainWhere(
     188           0 :             (element) => element!.startsWith(builder.sharedWith!) == true);
     189             :       }
     190           2 :       keys.removeWhere((key) =>
     191           2 :           key.toString().startsWith('privatekey:') ||
     192           2 :           key.toString().startsWith('private:') ||
     193           2 :           key.toString().startsWith('public:_'));
     194           1 :       var keyString = keys.toString();
     195             :       // Apply regex on keyString to remove unnecessary characters and spaces
     196           2 :       keyString = keyString.replaceFirst(RegExp(r'^\['), '');
     197           2 :       keyString = keyString.replaceFirst(RegExp(r'\]$'), '');
     198           1 :       keyString = keyString.replaceAll(', ', ',');
     199           2 :       var keysArray = keyString.isNotEmpty ? (keyString.split(',')) : [];
     200           1 :       return json.encode(keysArray);
     201           0 :     } on DataStoreException catch (e) {
     202           0 :       _logger.severe('exception in scan:${e.toString()}');
     203             :       rethrow;
     204             :     }
     205             :   }
     206             : 
     207           1 :   bool _isActiveKey(AtMetaData? atMetaData) {
     208             :     if (atMetaData == null) return true;
     209           1 :     var ttb = atMetaData.availableAt;
     210           1 :     var ttl = atMetaData.expiresAt;
     211             :     if (ttb == null && ttl == null) return true;
     212           0 :     var now = DateTime.now().toUtc().millisecondsSinceEpoch;
     213             :     if (ttb != null) {
     214           0 :       var ttbMs = ttb.toUtc().millisecondsSinceEpoch;
     215           0 :       if (ttbMs > now) return false;
     216             :     }
     217             :     if (ttl != null) {
     218           0 :       var ttlMs = ttl.toUtc().millisecondsSinceEpoch;
     219           0 :       if (ttlMs < now) return false;
     220             :     }
     221             :     //If TTB or TTL populated but not met, return true.
     222             :     return true;
     223             :   }
     224             : 
     225           1 :   String? _prepareResponseData(String? operation, AtData? atData) {
     226             :     String? result;
     227             :     if (atData == null) {
     228             :       return result;
     229             :     }
     230             :     switch (operation) {
     231           1 :       case 'meta':
     232           0 :         result = json.encode(atData.metaData!.toJson());
     233             :         break;
     234           1 :       case 'all':
     235           0 :         result = json.encode(atData.toJson());
     236             :         break;
     237             :       default:
     238           1 :         result = atData.data;
     239             :         break;
     240             :     }
     241             :     return result;
     242             :   }
     243             : 
     244           1 :   Future<String?> getPrivateKey() async {
     245           3 :     var privateKeyData = await keyStore!.get(AT_PKAM_PRIVATE_KEY);
     246           1 :     return privateKeyData?.data;
     247             :   }
     248             : 
     249           1 :   Future<String?> getEncryptionPrivateKey() async {
     250           3 :     var privateKeyData = await keyStore!.get(AT_ENCRYPTION_PRIVATE_KEY);
     251           1 :     return privateKeyData?.data;
     252             :   }
     253             : 
     254           1 :   Future<String?> getPublicKey() async {
     255           3 :     var publicKeyData = await keyStore!.get(AT_PKAM_PUBLIC_KEY);
     256           1 :     return publicKeyData?.data;
     257             :   }
     258             : 
     259           1 :   Future<String?> getEncryptionPublicKey(String atSign) async {
     260           1 :     atSign = AtUtils.formatAtSign(atSign)!;
     261             :     var privateKeyData =
     262           4 :         await keyStore!.get('$AT_ENCRYPTION_PUBLIC_KEY$atSign');
     263           1 :     return privateKeyData?.data;
     264             :   }
     265             : 
     266           1 :   Future<String?> getEncryptionSelfKey() async {
     267           3 :     var selfKeyData = await keyStore!.get(AT_ENCRYPTION_SELF_KEY);
     268           1 :     return selfKeyData?.data;
     269             :   }
     270             : 
     271             :   ///Returns `true` on successfully storing the values into local secondary.
     272           1 :   Future<bool> putValue(String key, String value) async {
     273             :     dynamic isStored;
     274           2 :     var atData = AtData()..data = value;
     275           3 :     isStored = await keyStore!.put(key, atData);
     276             :     return isStored != null ? true : false;
     277             :   }
     278             : }

Generated by: LCOV version 1.13