LCOV - code coverage report
Current view: top level - src/graph - data_graph_notifier.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 165 175 94.3 %
Date: 2020-07-19 14:30:42 Functions: 0 0 -

          Line data    Source code
       1             : part of flutter_data;
       2             : 
       3             : class DataGraphNotifier extends StateNotifier<DataGraphEvent>
       4             :     with _Lifecycle<DataGraphNotifier> {
       5           1 :   @protected
       6           1 :   DataGraphNotifier(this._hiveLocalStorage) : super(null);
       7             : 
       8             :   final HiveLocalStorage _hiveLocalStorage;
       9             : 
      10             :   @protected
      11             :   Box<Map> box;
      12             :   bool _doAssert = true;
      13             : 
      14             :   @override
      15           1 :   Future<DataGraphNotifier> initialize() async {
      16           1 :     if (isInitialized) return this;
      17           3 :     await _hiveLocalStorage.initialize();
      18           5 :     box = await _hiveLocalStorage.hive.openBox('_graph');
      19             : 
      20           2 :     await super.initialize();
      21             :     return this;
      22             :   }
      23             : 
      24           1 :   @override
      25           1 :   bool get isInitialized => box != null;
      26             : 
      27             :   @override
      28           1 :   Future<void> dispose() async {
      29           2 :     await super.dispose();
      30             :   }
      31             : 
      32             :   // key-related methods
      33             : 
      34           1 :   String getKeyForId(String type, dynamic id, {String keyIfAbsent}) {
      35           1 :     type = DataHelpers.getType(type);
      36             :     if (id != null) {
      37           1 :       final _id = 'id:$type#$id';
      38             : 
      39           1 :       if (_getNode(_id) != null) {
      40           1 :         final tos = _getEdge(_id, metadata: 'key');
      41           1 :         if (tos != null && tos.isNotEmpty) {
      42           1 :           final key = tos.first;
      43             :           return key;
      44             :         }
      45             :       }
      46             : 
      47             :       if (keyIfAbsent != null) {
      48             :         // this means the method is instructed to
      49             :         // create nodes and edges
      50             : 
      51           1 :         if (!_hasNode(keyIfAbsent)) {
      52           1 :           _addNode(keyIfAbsent, notify: false);
      53             :         }
      54           1 :         if (!_hasNode(_id)) {
      55           1 :           _addNode(_id, notify: false);
      56             :         }
      57           1 :         _removeEdges(keyIfAbsent,
      58             :             metadata: 'id', inverseMetadata: 'key', notify: false);
      59           1 :         _addEdge(keyIfAbsent, _id,
      60             :             metadata: 'id', inverseMetadata: 'key', notify: false);
      61             :         return keyIfAbsent;
      62             :       }
      63             :     } else if (keyIfAbsent != null) {
      64             :       // if no ID is supplied but keyIfAbsent is, create node for key
      65           1 :       if (!_hasNode(keyIfAbsent)) {
      66           1 :         _addNode(keyIfAbsent, notify: false);
      67             :       }
      68             :       return keyIfAbsent;
      69             :     }
      70             :     return null;
      71             :   }
      72             : 
      73           2 :   void removeKey(String key) => _removeNode(key);
      74             : 
      75             :   // id-related methods
      76             : 
      77           1 :   String getId(String key) {
      78           1 :     final tos = _getEdge(key, metadata: 'id');
      79           1 :     return tos == null || tos.isEmpty
      80             :         ? null
      81           5 :         : (denamespace(tos.first).split('#')..removeAt(0)).join('#');
      82             :   }
      83             : 
      84           0 :   void removeId(String type, dynamic id) => _removeNode('id:$type#$id');
      85             : 
      86             :   // nodes
      87             : 
      88           1 :   void _assertKey(String key) {
      89           1 :     if (_doAssert && key != null) {
      90           4 :       assert(key.split(':').length == 2);
      91             :     }
      92             :   }
      93             : 
      94           1 :   void addNode(String key, {bool notify = true}) {
      95           1 :     _assertKey(key);
      96           1 :     _addNode(key, notify: notify);
      97             :   }
      98             : 
      99           1 :   void addNodes(Iterable<String> keys, {bool notify = true}) {
     100           2 :     for (final key in keys) {
     101           1 :       _assertKey(key);
     102             :     }
     103           1 :     _addNodes(keys, notify: notify);
     104             :   }
     105             : 
     106           1 :   Map<String, List<String>> getNode(String key) {
     107           1 :     _assertKey(key);
     108           1 :     return _getNode(key);
     109             :   }
     110             : 
     111           1 :   bool hasNode(String key) {
     112           1 :     _assertKey(key);
     113           1 :     return _hasNode(key);
     114             :   }
     115             : 
     116           1 :   void removeNode(String key) {
     117           1 :     _assertKey(key);
     118           1 :     return _removeNode(key);
     119             :   }
     120             : 
     121             :   // edges
     122             : 
     123           1 :   void addEdges(String from,
     124             :       {@required String metadata,
     125             :       @required Iterable<String> tos,
     126             :       String inverseMetadata,
     127             :       bool notify = true}) {
     128           1 :     _assertKey(from);
     129           1 :     _assertKey(metadata);
     130           1 :     _assertKey(inverseMetadata);
     131           1 :     _addEdges(from,
     132             :         metadata: metadata, tos: tos, inverseMetadata: inverseMetadata);
     133             :   }
     134             : 
     135           1 :   List<String> getEdge(String key, {@required String metadata}) {
     136           1 :     _assertKey(key);
     137           1 :     _assertKey(metadata);
     138           1 :     return _getEdge(key, metadata: metadata);
     139             :   }
     140             : 
     141           1 :   void addEdge(String from, String to,
     142             :       {@required String metadata, String inverseMetadata, bool notify = true}) {
     143           1 :     _assertKey(from);
     144           1 :     _assertKey(metadata);
     145           1 :     _assertKey(inverseMetadata);
     146           1 :     return _addEdge(from, to,
     147             :         metadata: metadata, inverseMetadata: inverseMetadata, notify: notify);
     148             :   }
     149             : 
     150           1 :   void removeEdges(String from,
     151             :       {@required String metadata,
     152             :       Iterable<String> tos,
     153             :       String inverseMetadata,
     154             :       bool notify = true}) {
     155           1 :     _assertKey(from);
     156           1 :     _assertKey(metadata);
     157           1 :     _assertKey(inverseMetadata);
     158           1 :     return _removeEdges(from,
     159             :         metadata: metadata, inverseMetadata: inverseMetadata, notify: notify);
     160             :   }
     161             : 
     162           1 :   void removeEdge(String from, String to,
     163             :       {@required String metadata, String inverseMetadata, bool notify = true}) {
     164           1 :     _assertKey(from);
     165           1 :     _assertKey(metadata);
     166           1 :     _assertKey(inverseMetadata);
     167           1 :     return _removeEdge(from, to,
     168             :         metadata: metadata, inverseMetadata: inverseMetadata, notify: notify);
     169             :   }
     170             : 
     171           1 :   bool hasEdge(String key, {@required String metadata}) {
     172           1 :     _assertKey(key);
     173           1 :     _assertKey(metadata);
     174           1 :     return _hasEdge(key, metadata: metadata);
     175             :   }
     176             : 
     177           3 :   String denamespace(String namespacedKey) => namespacedKey.split(':').last;
     178             : 
     179             :   // debug utilities
     180             : 
     181           2 :   Map<String, Map> dumpGraph() => _toMap();
     182             : 
     183           1 :   @protected
     184             :   @visibleForTesting
     185           1 :   void debugAssert(bool value) => _doAssert = value;
     186             : 
     187             :   // private API
     188             : 
     189           1 :   Map<String, List<String>> _getNode(String key) {
     190           0 :     assert(key != null, 'key cannot be null');
     191           3 :     return box.get(key)?.cast<String, List<String>>();
     192             :   }
     193             : 
     194           1 :   bool _hasNode(String key) {
     195           2 :     return box.containsKey(key);
     196             :   }
     197             : 
     198           1 :   List<String> _getEdge(String key, {@required String metadata}) {
     199           1 :     final node = _getNode(key);
     200             :     if (node != null) {
     201           1 :       return node[metadata];
     202             :     }
     203             :     return null;
     204             :   }
     205             : 
     206           1 :   bool _hasEdge(String key, {@required String metadata}) {
     207           1 :     final fromNode = _getNode(key);
     208           2 :     return fromNode?.keys?.contains(metadata) ?? false;
     209             :   }
     210             : 
     211             :   // write
     212             : 
     213           1 :   void _addNodes(Iterable<String> keys, {bool notify = true}) {
     214           2 :     for (final key in keys) {
     215           1 :       _addNode(key, notify: notify);
     216             :     }
     217             :   }
     218             : 
     219           1 :   void _addNode(String key, {bool notify = true}) {
     220           0 :     assert(key != null, 'key cannot be null');
     221           2 :     if (!box.containsKey(key)) {
     222           3 :       box.put(key, {});
     223             :       if (notify) {
     224           2 :         state = DataGraphEvent(
     225           1 :             keys: [key], type: DataGraphEventType.addNode, graph: this);
     226             :       }
     227             :     }
     228             :   }
     229             : 
     230           1 :   void _removeNode(String key, {bool notify = true}) {
     231           0 :     assert(key != null, 'key cannot be null');
     232           1 :     final fromNode = _getNode(key);
     233             : 
     234             :     if (fromNode == null) {
     235             :       return;
     236             :     }
     237             : 
     238             :     // sever all incoming edges
     239           2 :     for (final toKey in _connectedKeys(key)) {
     240           1 :       final toNode = _getNode(toKey);
     241             :       // remove deleted key from all metadatas
     242             :       if (toNode != null) {
     243           3 :         for (final entry in toNode.entries.toSet()) {
     244           3 :           _removeEdges(toKey, tos: [key], metadata: entry.key);
     245             :         }
     246             :       }
     247             :     }
     248             : 
     249           2 :     box.delete(key);
     250             : 
     251             :     if (notify) {
     252           2 :       state = DataGraphEvent(
     253           1 :           keys: [key], type: DataGraphEventType.removeNode, graph: this);
     254             :     }
     255             :   }
     256             : 
     257           1 :   void _addEdge(String from, String to,
     258             :       {@required String metadata, String inverseMetadata, bool notify = true}) {
     259           1 :     _addEdges(from,
     260           1 :         tos: [to],
     261             :         metadata: metadata,
     262             :         inverseMetadata: inverseMetadata,
     263             :         notify: notify);
     264             :   }
     265             : 
     266           1 :   void _addEdges(String from,
     267             :       {@required String metadata,
     268             :       @required Iterable<String> tos,
     269             :       String inverseMetadata,
     270             :       bool notify = true}) {
     271           1 :     final fromNode = _getNode(from);
     272           0 :     assert(fromNode != null && tos != null);
     273             : 
     274           1 :     if (tos.isEmpty) {
     275             :       return;
     276             :     }
     277             : 
     278             :     // use a set to ensure resulting list elements are unique
     279           5 :     fromNode[metadata] = {...?fromNode[metadata], ...tos}.toList();
     280             : 
     281             :     if (notify) {
     282           2 :       state = DataGraphEvent(
     283           4 :         keys: [from, ...tos],
     284             :         metadata: metadata,
     285             :         type: DataGraphEventType.addEdge,
     286             :         graph: this,
     287             :       );
     288             :     }
     289             : 
     290             :     if (inverseMetadata != null) {
     291           2 :       for (final to in tos) {
     292             :         // get or create toNode
     293             :         final toNode =
     294           2 :             _hasNode(to) ? _getNode(to) : (this.._addNode(to))._getNode(to);
     295             : 
     296             :         // use a set to ensure resulting list elements are unique
     297           6 :         toNode[inverseMetadata] = {...?toNode[inverseMetadata], from}.toList();
     298             :       }
     299             : 
     300             :       if (notify) {
     301           2 :         state = DataGraphEvent(
     302           4 :           keys: [...tos, from],
     303             :           metadata: inverseMetadata,
     304             :           type: DataGraphEventType.addEdge,
     305             :           graph: this,
     306             :         );
     307             :       }
     308             :     }
     309             :   }
     310             : 
     311           1 :   void _removeEdge(String from, String to,
     312             :       {@required String metadata, String inverseMetadata, bool notify = true}) {
     313           1 :     _removeEdges(from,
     314           1 :         tos: [to],
     315             :         metadata: metadata,
     316             :         inverseMetadata: inverseMetadata,
     317             :         notify: notify);
     318             :   }
     319             : 
     320           1 :   void _removeEdges(String from,
     321             :       {@required String metadata,
     322             :       Iterable<String> tos,
     323             :       String inverseMetadata,
     324             :       bool notify = true}) {
     325           1 :     final fromNode = _getNode(from);
     326           0 :     assert(fromNode != null);
     327             : 
     328           1 :     if (tos != null && fromNode[metadata] != null) {
     329             :       // remove all tos from fromNode[metadata]
     330           3 :       fromNode[metadata].removeWhere(tos.contains);
     331           2 :       if (fromNode[metadata].isEmpty) {
     332           1 :         fromNode.remove(metadata);
     333             :       }
     334             :     } else {
     335             :       // tos == null as argument means ALL
     336             :       // remove metadata and retrieve all tos
     337           1 :       tos = fromNode.remove(metadata);
     338             :     }
     339             : 
     340             :     if (notify) {
     341           2 :       state = DataGraphEvent(
     342           4 :         keys: [from, ...?tos],
     343             :         metadata: metadata,
     344             :         type: DataGraphEventType.removeEdge,
     345             :         graph: this,
     346             :       );
     347             :     }
     348             : 
     349             :     if (tos != null && inverseMetadata != null) {
     350           2 :       for (final to in tos) {
     351           1 :         final toNode = _getNode(to);
     352           1 :         if (toNode != null && toNode[inverseMetadata] != null) {
     353           2 :           toNode[inverseMetadata].remove(from);
     354           2 :           if (toNode[inverseMetadata].isEmpty) {
     355           1 :             toNode.remove(inverseMetadata);
     356             :           }
     357             :         }
     358           1 :         if (toNode.isEmpty) {
     359           1 :           _removeNode(to, notify: notify);
     360             :         }
     361             :       }
     362             : 
     363             :       if (notify) {
     364           2 :         state = DataGraphEvent(
     365           4 :           keys: [...tos, from],
     366             :           metadata: inverseMetadata,
     367             :           type: DataGraphEventType.removeEdge,
     368             :           graph: this,
     369             :         );
     370             :       }
     371             :     }
     372             :   }
     373             : 
     374           1 :   void _notify(List<String> keys, DataGraphEventType type) {
     375           2 :     state = DataGraphEvent(
     376             :       type: type,
     377             :       keys: keys,
     378             :       graph: this,
     379             :     );
     380             :   }
     381             : 
     382             :   // misc
     383             : 
     384           1 :   Set<String> _connectedKeys(String key, {Iterable<String> metadatas}) {
     385           1 :     final node = _getNode(key);
     386             :     if (node == null) {
     387             :       return {};
     388             :     }
     389             : 
     390           3 :     return node.entries.fold({}, (acc, entry) {
     391           0 :       if (metadatas != null && !metadatas.contains(entry.key)) {
     392             :         return acc;
     393             :       }
     394           2 :       return acc..addAll(entry.value);
     395             :     });
     396             :   }
     397             : 
     398           4 :   Map<String, Map> _toMap() => box.toMap().cast();
     399             : }
     400             : 
     401           1 : enum DataGraphEventType {
     402           1 :   addNode,
     403           1 :   removeNode,
     404           1 :   updateNode,
     405           1 :   addEdge,
     406           1 :   removeEdge,
     407           1 :   updateEdge
     408             : }
     409             : 
     410             : extension DataGraphEventTypeX on DataGraphEventType {
     411           2 :   bool get isNode => [
     412             :         DataGraphEventType.addNode,
     413             :         DataGraphEventType.updateNode,
     414             :         DataGraphEventType.removeNode,
     415           1 :       ].contains(this);
     416           2 :   bool get isEdge => [
     417             :         DataGraphEventType.addEdge,
     418             :         DataGraphEventType.updateEdge,
     419             :         DataGraphEventType.removeEdge,
     420           1 :       ].contains(this);
     421             : }
     422             : 
     423             : class DataGraphEvent {
     424           1 :   const DataGraphEvent({
     425             :     this.keys,
     426             :     this.metadata,
     427             :     this.type,
     428             :     this.graph,
     429             :   });
     430             :   final List<String> keys;
     431             :   final String metadata;
     432             :   final DataGraphEventType type;
     433             :   final DataGraphNotifier graph;
     434             : 
     435           0 :   @override
     436             :   String toString() {
     437           0 :     return '[GraphEvent] $type: $keys';
     438             :   }
     439             : }
     440             : 
     441           3 : final graphProvider = Provider<DataGraphNotifier>((ref) {
     442           0 :   return DataGraphNotifier(ref.read(hiveLocalStorageProvider));
     443             : });

Generated by: LCOV version 1.14