LCOV - code coverage report
Current view: top level - utils - offline.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 71 94 75.5 %
Date: 2022-06-06 11:59:57 Functions: 0 0 -

          Line data    Source code
       1             : part of flutter_data;
       2             : 
       3             : const _offlineAdapterKey = '_offline:keys';
       4             : 
       5             : /// Represents an offline request that is pending to be retried.
       6             : class OfflineOperation<T extends DataModel<T>> with EquatableMixin {
       7             :   final DataRequestLabel label;
       8             :   final String httpRequest;
       9             :   final Map<String, String>? headers;
      10             :   final String? body;
      11             :   late final String? key;
      12             :   final _OnSuccessGeneric<T>? onSuccess;
      13             :   final _OnErrorGeneric<T>? onError;
      14             :   final RemoteAdapter<T> adapter;
      15             : 
      16           1 :   OfflineOperation({
      17             :     required this.label,
      18             :     required this.httpRequest,
      19             :     this.headers,
      20             :     this.body,
      21             :     String? key,
      22             :     this.onSuccess,
      23             :     this.onError,
      24             :     required this.adapter,
      25             :   }) {
      26           4 :     this.key = key ?? label.model?._key;
      27             :   }
      28             : 
      29             :   /// Metadata format:
      30             :   /// _offline:findOne/users#3@d7bcc9
      31          12 :   static String metadataFor(DataRequestLabel label) => '_offline:$label';
      32             : 
      33           1 :   Map<String, dynamic> toJson() {
      34           1 :     return <String, dynamic>{
      35           1 :       'r': httpRequest,
      36           1 :       'k': key,
      37           1 :       'b': body,
      38           1 :       'h': headers,
      39             :     };
      40             :   }
      41             : 
      42           1 :   factory OfflineOperation.fromJson(
      43             :     DataRequestLabel label,
      44             :     Map<String, dynamic> json,
      45             :     RemoteAdapter<T> adapter,
      46             :   ) {
      47           1 :     final operation = OfflineOperation(
      48             :       label: label,
      49           1 :       httpRequest: json['r'] as String,
      50           1 :       key: json['k'] as String?,
      51           1 :       body: json['b'] as String?,
      52             :       headers:
      53           3 :           json['h'] == null ? null : Map<String, String>.from(json['h'] as Map),
      54             :       adapter: adapter,
      55             :     );
      56             : 
      57           1 :     if (operation.key != null) {
      58           3 :       final model = adapter.localAdapter.findOne(operation.key!);
      59             :       if (model != null) {
      60             :         // adapter.initializeModel(model, key: operation.key);
      61           2 :         operation.label.model = model;
      62             :       }
      63             :     }
      64             :     return operation;
      65             :   }
      66             : 
      67           1 :   Uri get uri {
      68           4 :     return httpRequest.split(' ').last.asUri;
      69             :   }
      70             : 
      71           1 :   DataRequestMethod get method {
      72             :     return DataRequestMethod.values
      73           7 :         .singleWhere((m) => m.toShortString() == httpRequest.split(' ').first);
      74             :   }
      75             : 
      76             :   /// Adds an edge from the `_offlineAdapterKey` to the `key` for save/delete
      77             :   /// and stores header/param metadata. Also stores callbacks.
      78           1 :   void add() {
      79             :     // DO NOT proceed if operation is in queue
      80           3 :     if (!adapter.offlineOperations.contains(this)) {
      81           2 :       final node = json.encode(toJson());
      82           2 :       final metadata = metadataFor(label);
      83             : 
      84           4 :       adapter.log(label, 'offline/add $metadata');
      85           3 :       adapter.graph._addEdge(_offlineAdapterKey, node, metadata: metadata);
      86             : 
      87             :       // keep callbacks in memory
      88           6 :       adapter.read(_offlineCallbackProvider)[metadata] ??= [];
      89           1 :       adapter
      90           4 :           .read(_offlineCallbackProvider)[metadata]!
      91           4 :           .add([onSuccess, onError]);
      92             :     } else {
      93             :       // trick
      94           2 :       adapter.graph
      95           2 :           ._notify([_offlineAdapterKey, ''], type: DataGraphEventType.addEdge);
      96             :     }
      97             :   }
      98             : 
      99             :   /// Removes all edges from the `_offlineAdapterKey` for
     100             :   /// current metadata, as well as callbacks from memory.
     101           6 :   static void remove(DataRequestLabel label, RemoteAdapter adapter) {
     102           6 :     final metadata = metadataFor(label);
     103          12 :     if (adapter.graph._hasEdge(_offlineAdapterKey, metadata: metadata)) {
     104           2 :       adapter.graph._removeEdges(_offlineAdapterKey, metadata: metadata);
     105           2 :       adapter.log(label, 'offline/remove $metadata');
     106           4 :       adapter.read(_offlineCallbackProvider).remove(metadata);
     107             :     }
     108             :   }
     109             : 
     110           1 :   Future<void> retry() async {
     111           2 :     final metadata = metadataFor(label);
     112             :     // look up callbacks (or provide defaults)
     113           5 :     final fns = adapter.read(_offlineCallbackProvider)[metadata] ??
     114           0 :         [
     115           0 :           [null, null]
     116             :         ];
     117             : 
     118           2 :     for (final pair in fns) {
     119           3 :       await adapter.sendRequest<T>(
     120           1 :         uri,
     121           1 :         method: method,
     122           1 :         headers: headers,
     123           1 :         label: label,
     124           1 :         body: body,
     125           1 :         onSuccess: pair.first as _OnSuccessGeneric<T>?,
     126           1 :         onError: pair.last as _OnErrorGeneric<T>?,
     127             :       );
     128             :     }
     129             :   }
     130             : 
     131           3 :   T? get model => label.model as T?;
     132             : 
     133           1 :   @override
     134           5 :   List<Object?> get props => [label, httpRequest, body, headers];
     135             : 
     136           0 :   @override
     137             :   bool get stringify => true;
     138             : }
     139             : 
     140             : extension OfflineOperationsX on Set<OfflineOperation<DataModel>> {
     141             :   /// Retries all offline operations for current type.
     142           1 :   FutureOr<void> retry() async {
     143           1 :     if (isNotEmpty) {
     144           4 :       await Future.wait(map((operation) {
     145           1 :         return operation.retry();
     146             :       }));
     147             :     }
     148             :   }
     149             : 
     150             :   /// Removes all offline operations.
     151           1 :   void reset() {
     152           1 :     if (isEmpty) {
     153             :       return;
     154             :     }
     155           2 :     final adapter = first.adapter;
     156             :     // removes node and severs edges
     157           2 :     if (adapter.graph._hasNode(_offlineAdapterKey)) {
     158           2 :       final node = adapter.graph._getNode(_offlineAdapterKey);
     159           3 :       for (final metadata in (node ?? {}).keys.toImmutableList()) {
     160           2 :         adapter.graph._removeEdges(_offlineAdapterKey, metadata: metadata);
     161             :       }
     162             :     }
     163           4 :     adapter.read(_offlineCallbackProvider).clear();
     164             :   }
     165             : 
     166             :   /// Filter by [label] kind.
     167           1 :   List<OfflineOperation> only(DataRequestLabel label) {
     168           7 :     return where((_) => _.label.kind == label.kind).toImmutableList();
     169             :   }
     170             : }
     171             : 
     172             : // stores onSuccess/onError function combos
     173           2 : final _offlineCallbackProvider =
     174           3 :     StateProvider<Map<String, List<List<Function?>>>>((_) => {});
     175             : 
     176             : /// Every time there is an offline operation added to/
     177             : /// removed from the queue, this will notify clients
     178             : /// with all pending types (could be none) such that
     179             : /// they can implement their own retry strategy.
     180           0 : final pendingOfflineTypesProvider =
     181           0 :     StateNotifierProvider<DelayedStateNotifier<Set<String>>, Set<String>?>(
     182           0 :         (ref) {
     183           0 :   final graph = ref.watch(graphNotifierProvider);
     184             : 
     185           0 :   Set<String> _pendingTypes() {
     186           0 :     final node = graph._getNode(_offlineAdapterKey, orAdd: true)!;
     187             :     // obtain types from metadata e.g. _offline:users#4:findOne
     188           0 :     return node.keys.map((m) => m.split(':')[1].split('#')[0]).toSet();
     189             :   }
     190             : 
     191           0 :   final notifier = DelayedStateNotifier<Set<String>>();
     192             :   // emit initial value
     193           0 :   Timer.run(() {
     194           0 :     if (notifier.mounted) {
     195           0 :       notifier.state = _pendingTypes();
     196             :     }
     197             :   });
     198             : 
     199           0 :   final dispose = graph.where((event) {
     200             :     // filter the right events
     201           0 :     return [DataGraphEventType.addEdge, DataGraphEventType.removeEdge]
     202           0 :             .contains(event.type) &&
     203           0 :         event.keys.length == 2 &&
     204           0 :         event.keys.containsFirst(_offlineAdapterKey);
     205           0 :   }).addListener((_) {
     206           0 :     if (notifier.mounted) {
     207             :       // recalculate all pending types
     208           0 :       notifier.state = _pendingTypes();
     209             :     }
     210             :   });
     211             : 
     212           0 :   notifier.onDispose = dispose;
     213             : 
     214             :   return notifier;
     215             : });

Generated by: LCOV version 1.15