Skip to content
Snippets Groups Projects
Commit d61ffe6a authored by vjrj's avatar vjrj
Browse files

More work in node manager

parent a1b3edec
No related branches found
No related tags found
No related merge requests found
package dev.feichtinger.flutterproductionboilerplate.flutter_production_boilerplate package org.comunes.ginkgo
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
......
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/http.dart'; import 'package:http/http.dart';
import '../g1/duniter_node_manager.dart';
String get duniterNet { String get duniterNet {
return dotenv.get('NET'); return DuniterNodeManager().fastestNode;
} }
String get duniterLookupUrl { String get duniterLookupUrl {
return '${duniterNet}wot/lookup/'; return '${duniterNet}/wot/lookup/';
} }
String get duniterNetworkPeers { String get duniterNetworkPeers {
return '${duniterNet}network/peers'; return '${duniterNet}/network/peers';
} }
String duniterAccountAvatar(String publickey) { String duniterAccountAvatar(String publickey) {
return '${duniterNet}node/peers/$publickey/avatar'; return '${duniterNet}/node/peers/$publickey/avatar';
} }
Future<String> getAvatar(String publicKey) async { Future<String> getAvatar(String publicKey) async {
......
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:http/http.dart'; import 'package:http/http.dart';
import '../main.dart'; import '../main.dart';
class DuniterNodeManager { class DuniterNodeManager {
DuniterNodeManager() { factory DuniterNodeManager() {
_loadNodes(); return _instance;
}
DuniterNodeManager._internal() {
_startResetErrorsTimer(); _startResetErrorsTimer();
} }
final String _peerListUrl = 'https://nodes.duniter.org/network/peers'; void init() {
List<String> _nodes = <String>[]; loadNodes();
}
String get fastestNode {
return _fastestNode!;
}
static final DuniterNodeManager _instance = DuniterNodeManager._internal();
final String _peerListUrl = 'https://g1.duniter.org/network/peers';
final List<String> _nodes = <String>[];
int _currentNodeIndex = 0; int _currentNodeIndex = 0;
final int _retryCount = 3; final int _retryCount = 3;
Map<String, int> _nodeErrors = <String, int>{}; Map<String, int> _nodeErrors = <String, int>{};
...@@ -42,22 +56,55 @@ class DuniterNodeManager { ...@@ -42,22 +56,55 @@ class DuniterNodeManager {
throw Exception('No nodes available'); throw Exception('No nodes available');
} }
Future<void> _loadNodes() async { Future<void> loadNodes() async {
try { try {
final Response response = await http.get(Uri.parse(_peerListUrl)); final Response response = await http.get(Uri.parse(_peerListUrl));
if (response.statusCode == 200) { if (response.statusCode == 200) {
final List<dynamic> peerList = final Map<String, dynamic> peerList =
jsonDecode(response.body) as List<dynamic>; jsonDecode(response.body) as Map<String, dynamic>;
_nodes = peerList final List<dynamic> peers = (peerList['peers'] as List<dynamic>)
.where((dynamic peer) => .where((dynamic peer) =>
(peer as Map<String, dynamic>)['currency'] == 'g1') (peer as Map<String, dynamic>)['currency'] == 'g1')
.map((dynamic peer) => .where((dynamic peer) =>
'http://${(peer as Map<String, dynamic>)['host']}:${peer['port']}/') (peer as Map<String, dynamic>)['version'] == 10)
.where((dynamic peer) =>
(peer as Map<String, dynamic>)['status'] == 'UP')
.toList(); .toList();
for (final dynamic peer in peers) {
if (peer['endpoints'] != null) {
final List<String> endpoints =
List<String>.from(peer['endpoints'] as List<dynamic>);
for (int j = 0; j < endpoints.length; j++) {
if (endpoints[j].startsWith('BMAS')) {
String endpoint = endpoints[j].replaceAll('BMAS ', '');
if (endpoint.contains(' ')) {
endpoint = endpoint.substring(0, endpoint.indexOf(' '));
}
endpoint = 'https://${endpoint.replaceAll(':443', '')}';
_nodes.add(endpoint);
final Duration latency = await _pingNode(endpoint);
if (_fastestNode == null || latency < _fastestLatency!) {
_fastestNode = endpoint;
_fastestLatency = latency;
if (!kReleaseMode) {
logger('Current faster node $_fastestNode');
}
}
}
}
}
}
_resetNodeErrors(null); _resetNodeErrors(null);
} }
logger('Loaded ${_nodes.length} duniter nodes');
if (!kReleaseMode) {
logger(_nodes);
}
} catch (e) { } catch (e) {
logger('Error: $e'); logger('Error: $e');
rethrow;
} }
} }
...@@ -95,19 +142,7 @@ class DuniterNodeManager { ...@@ -95,19 +142,7 @@ class DuniterNodeManager {
_cancelResetErrorsTimer(); _cancelResetErrorsTimer();
} }
Future<String?> getFastestNode() async { Future<Duration> _pingNode(String node) async {
for (final String node in _nodes) {
final Duration latency = await pingNode(node);
if (_fastestNode == null || latency < _fastestLatency!) {
_fastestNode = node;
_fastestLatency = latency;
}
}
return _fastestNode;
}
Future<Duration> pingNode(String node) async {
try { try {
final Stopwatch stopwatch = Stopwatch()..start(); final Stopwatch stopwatch = Stopwatch()..start();
await http.get(Uri.parse('$node/network/peers/self/ping')); await http.get(Uri.parse('$node/network/peers/self/ping'));
......
import 'dart:io'; import 'dart:io';
import 'package:connectivity_wrapper/connectivity_wrapper.dart'; import 'package:connectivity_wrapper/connectivity_wrapper.dart';
import 'package:cron/cron.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:easy_logger/easy_logger.dart'; import 'package:easy_logger/easy_logger.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -16,6 +17,7 @@ import 'package:responsive_framework/responsive_wrapper.dart'; ...@@ -16,6 +17,7 @@ import 'package:responsive_framework/responsive_wrapper.dart';
import 'config/theme.dart'; import 'config/theme.dart';
import 'cubit/theme_cubit.dart'; import 'cubit/theme_cubit.dart';
import 'g1/duniter_node_manager.dart';
import 'shared_prefs.dart'; import 'shared_prefs.dart';
import 'ui/screens/skeleton_screen.dart'; import 'ui/screens/skeleton_screen.dart';
...@@ -67,6 +69,13 @@ void main() async { ...@@ -67,6 +69,13 @@ void main() async {
await HydratedStorage.build(storageDirectory: tmpDir); await HydratedStorage.build(storageDirectory: tmpDir);
} }
final Cron cron = Cron();
cron.schedule(Schedule.parse('*/45 * * * *'), () async {
// Every 45m check for faster node (maybe it something costly in terms of
// bandwidth
DuniterNodeManager().loadNodes();
});
runApp( runApp(
EasyLocalization( EasyLocalization(
path: 'assets/translations', path: 'assets/translations',
...@@ -153,7 +162,7 @@ class MyApp extends StatefulWidget { ...@@ -153,7 +162,7 @@ class MyApp extends StatefulWidget {
} }
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
final bool _skip = false; final bool _skipIntro = !kReleaseMode;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -178,7 +187,7 @@ class _MyAppState extends State<MyApp> { ...@@ -178,7 +187,7 @@ class _MyAppState extends State<MyApp> {
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: MediaQuery( home: MediaQuery(
data: const MediaQueryData(), data: const MediaQueryData(),
child: _skip ? const SkeletonScreen() : const AppIntro(), child: _skipIntro ? const SkeletonScreen() : const AppIntro(),
), ),
builder: (BuildContext buildContext, Widget? widget) { builder: (BuildContext buildContext, Widget? widget) {
return ResponsiveWrapper.builder( return ResponsiveWrapper.builder(
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ionicons/ionicons.dart'; import 'package:ionicons/ionicons.dart';
import '../../config/config.dart'; import '../../config/api.dart';
import '../widgets/fifth_screen/grid_item.dart'; import '../widgets/fifth_screen/grid_item.dart';
import '../widgets/fifth_screen/info_card.dart'; import '../widgets/fifth_screen/info_card.dart';
import '../widgets/fifth_screen/link_card.dart'; import '../widgets/fifth_screen/link_card.dart';
...@@ -20,7 +20,7 @@ class FifthScreen extends StatelessWidget { ...@@ -20,7 +20,7 @@ class FifthScreen extends StatelessWidget {
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
children: <Widget>[ children: <Widget>[
const Header(text: 'bottom_nav_fifth'), const Header(text: 'bottom_nav_fifth'),
InfoCard(title: duniterNet, icon: Icons.hub), InfoCard(title: duniterNet, icon: Icons.hub, translate: false),
LinkCard( LinkCard(
title: 'code_card_title', title: 'code_card_title',
icon: Icons.code_rounded, icon: Icons.code_rounded,
......
import 'package:another_flushbar/flushbar.dart'; import 'package:another_flushbar/flushbar.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../g1/duniter_node_manager.dart';
import '../widgets/first_screen/credit_card.dart'; import '../widgets/first_screen/credit_card.dart';
import '../widgets/first_screen/pay_contact_search_bar.dart'; import '../widgets/first_screen/pay_contact_search_bar.dart';
import '../widgets/header.dart'; import '../widgets/header.dart';
...@@ -19,9 +21,9 @@ class _FirstScreenState extends State<FirstScreen> { ...@@ -19,9 +21,9 @@ class _FirstScreenState extends State<FirstScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) async {
// ignore: always_specify_types DuniterNodeManager().init();
if (_showFlushbar) if (_showFlushbar && kReleaseMode) {
Flushbar<void>( Flushbar<void>(
message: tr('demo-title'), message: tr('demo-title'),
title: tr('demo-desc'), title: tr('demo-desc'),
...@@ -37,6 +39,7 @@ class _FirstScreenState extends State<FirstScreen> { ...@@ -37,6 +39,7 @@ class _FirstScreenState extends State<FirstScreen> {
} }
}, },
).show(context); ).show(context);
}
}); });
return Material( return Material(
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
......
...@@ -3,10 +3,15 @@ import 'package:easy_localization/easy_localization.dart'; ...@@ -3,10 +3,15 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class InfoCard extends StatelessWidget { class InfoCard extends StatelessWidget {
const InfoCard({super.key, required this.title, required this.icon}); const InfoCard(
{super.key,
required this.title,
required this.icon,
this.translate = true});
final String title; final String title;
final IconData icon; final IconData icon;
final bool translate;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -32,7 +37,7 @@ class InfoCard extends StatelessWidget { ...@@ -32,7 +37,7 @@ class InfoCard extends StatelessWidget {
Icon(icon, color: Theme.of(context).colorScheme.primary), Icon(icon, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
tr(title), translate ? tr(title) : title,
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.titleMedium! .titleMedium!
......
...@@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; ...@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:simple_barcode_scanner/simple_barcode_scanner.dart'; import 'package:simple_barcode_scanner/simple_barcode_scanner.dart';
import '../../../config/config.dart'; import '../../../config/api.dart';
import 'circular_icon.dart'; import 'circular_icon.dart';
class SearchDialog extends StatefulWidget { class SearchDialog extends StatefulWidget {
......
...@@ -113,6 +113,14 @@ packages: ...@@ -113,6 +113,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
cron:
dependency: "direct main"
description:
name: cron
sha256: d98aa8cdad0cccdb6b098e6a1fb89339c180d8a229145fa4cd8c6fc538f0e35f
url: "https://pub.dev"
source: hosted
version: "0.5.1"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
......
...@@ -50,6 +50,7 @@ dependencies: ...@@ -50,6 +50,7 @@ dependencies:
shared_preferences: ^2.0.18 shared_preferences: ^2.0.18
another_flushbar: ^1.12.29 another_flushbar: ^1.12.29
vibration: ^1.7.6 vibration: ^1.7.6
cron: ^0.5.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment