Line data Source code
1 : // Copyright 2023 The terCAD team. All rights reserved.
2 : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
3 :
4 : import 'package:app_finance/_classes/storage/app_preferences.dart';
5 : import 'package:flutter/foundation.dart';
6 : import 'package:flutter/widgets.dart';
7 : import 'package:peerdart/peerdart.dart';
8 : import 'package:uuid/uuid.dart';
9 :
10 : class SyncStatus {
11 : final String id;
12 : bool? status;
13 0 : SyncStatus(this.id, [this.status]);
14 : }
15 :
16 : class SyncPeer extends SyncStatus {
17 : DataConnection connection;
18 0 : SyncPeer(super.id, this.connection);
19 : }
20 :
21 : class AppSync extends ChangeNotifier {
22 : late Peer peer;
23 : final Map<String, SyncPeer> _status = {};
24 : final Map<Type, Function> _cb = {};
25 : final Map<Type, Function> _cbBin = {};
26 :
27 0 : AppSync() : super() {
28 0 : final id = getUuid();
29 0 : if (id != null && id.isNotEmpty) {
30 0 : enable(id);
31 0 : connect();
32 : }
33 : }
34 :
35 0 : List<String> _get() {
36 0 : return AppPreferences.get(AppPreferences.prefP2P)?.split(',').where((e) => e.isNotEmpty).toList() ?? [];
37 : }
38 :
39 0 : void add(String uuid) {
40 0 : List<String> peers = _get();
41 0 : peers.add(uuid);
42 0 : AppPreferences.set(AppPreferences.prefP2P, (<String>{}..addAll(peers)).toList().join(',')).then((_) => connect());
43 : }
44 :
45 0 : void del(String uuid) {
46 0 : _status[uuid]?.connection.dispose();
47 0 : _status.remove(uuid);
48 0 : AppPreferences.set(AppPreferences.prefP2P, (_get()..remove(uuid)).join(',')).then((_) => notifyListeners());
49 : }
50 :
51 0 : void connect() {
52 0 : List<String> peers = _get();
53 0 : for (int i = 0; i < peers.length; i++) {
54 0 : trace(peers[i]);
55 : }
56 : }
57 :
58 0 : void trace(String id) {
59 0 : WidgetsBinding.instance.addPostFrameCallback((_) {
60 0 : if (peer == null) {
61 : return;
62 : }
63 0 : if (!peer.open) {
64 0 : peer.reconnect();
65 : }
66 0 : if (_status[id] == null) {
67 0 : _status[id] = SyncPeer(id, peer.connect(id));
68 0 : } else if (!_status[id]!.connection.open) {
69 0 : peer.removeConnection(_status[id]!.connection);
70 0 : _status[id]!.connection = peer.connect(id);
71 : }
72 : // TBD: when connected, verify postponed messages
73 0 : _status[id]!.connection.once('open').then((v) {
74 0 : WidgetsBinding.instance.addPostFrameCallback((_) {
75 0 : _status[id]!.status = true;
76 0 : notifyListeners();
77 : });
78 : });
79 0 : _status[id]!.connection.once('close').then((v) {
80 0 : WidgetsBinding.instance.addPostFrameCallback((_) {
81 0 : _status[id]!.status = false;
82 0 : notifyListeners();
83 : });
84 : });
85 : });
86 : }
87 :
88 0 : List<SyncStatus> getPeers() {
89 0 : return _status.values.map((e) => SyncStatus(e.id, e.status)).toList();
90 : }
91 :
92 0 : void follow(Type type, Function callback) => _cb[type] = callback;
93 :
94 0 : void followBinary(Type type, Function callback) => _cbBin[type] = callback;
95 :
96 0 : void unfollow(Type type) {
97 0 : _cb.remove(type);
98 0 : _cbBin.remove(type);
99 : }
100 :
101 0 : bool isActive() {
102 0 : return AppPreferences.get(AppPreferences.prefPeer) != null;
103 : }
104 :
105 0 : void disable() {
106 0 : WidgetsBinding.instance.addPostFrameCallback((_) {
107 0 : AppPreferences.clear(AppPreferences.prefPeer);
108 0 : AppPreferences.clear(AppPreferences.prefP2P);
109 0 : peer.dispose();
110 : });
111 : }
112 :
113 0 : void enable([String? id]) {
114 0 : WidgetsBinding.instance.addPostFrameCallback((_) {
115 0 : id ??= getUuid(true);
116 0 : peer = Peer(id: id);
117 0 : peer.on<DataConnection>('connection').listen((conn) {
118 0 : WidgetsBinding.instance.addPostFrameCallback((_) => _listen(conn));
119 : });
120 : });
121 : }
122 :
123 0 : _listen(DataConnection conn) {
124 0 : conn.on('data').listen((data) {
125 0 : WidgetsBinding.instance.addPostFrameCallback((_) {
126 0 : _cb.forEach((_, callback) => callback(data));
127 : });
128 : });
129 0 : conn.on('binary').listen((data) {
130 0 : WidgetsBinding.instance.addPostFrameCallback((_) {
131 0 : _cbBin.forEach((_, callback) => callback(data));
132 : });
133 : });
134 : }
135 :
136 0 : send(String data, [String? uuid]) {
137 0 : WidgetsBinding.instance.addPostFrameCallback((_) {
138 : if (uuid != null) {
139 0 : if (_status[uuid]?.status == true) {
140 0 : _status[uuid]!.connection.send(data);
141 : }
142 : } else {
143 0 : final keyList = _status.keys.toList();
144 0 : for (int i = 0; i < keyList.length; i++) {
145 0 : if (_status[keyList[i]]?.status == true && _status[keyList[i]]!.connection.open) {
146 0 : _status[keyList[i]]!.connection.send(data);
147 : } else {
148 : // .. postpone message
149 : }
150 : }
151 : }
152 : });
153 : }
154 :
155 0 : ping(String uuid) {
156 0 : WidgetsBinding.instance.addPostFrameCallback((_) {
157 0 : if (_status[uuid]?.status == true) {
158 0 : _status[uuid]!.connection.sendBinary(Uint8List.fromList(getUuid()?.codeUnits ?? [0]));
159 : }
160 : });
161 : }
162 :
163 0 : String getNewUuid() {
164 0 : return const Uuid().v4();
165 : }
166 :
167 0 : String? getUuid([bool force = false]) {
168 0 : String? id = AppPreferences.get(AppPreferences.prefPeer);
169 : if (force) {
170 0 : AppPreferences.set(AppPreferences.prefPeer, id = getNewUuid());
171 : }
172 : return id;
173 : }
174 : }
|