Skip to content

Commit

Permalink
Update coin configs at runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
CharlVS committed Oct 16, 2023
1 parent 575ada3 commit 9adb38d
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 8 deletions.
5 changes: 2 additions & 3 deletions lib/app_config/coin_converter.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import 'dart:convert';

import 'package:flutter/services.dart';
import 'package:komodo_dex/app_config/coins_updater.dart';

import '../model/coin_type.dart';
import '../utils/utils.dart';

Future<List<dynamic>> convertCoinsConfigToAppConfig() async {
final String coins =
await rootBundle.loadString('assets/coins_config.json', cache: false);
// 561 coins
final String coins = await CoinUpdater().getConfig();
Map coinsResponse = jsonDecode(coins);
List allCoinsList = [];

Expand Down
147 changes: 147 additions & 0 deletions lib/app_config/coins_updater.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:komodo_dex/utils/log.dart';
import 'package:path_provider/path_provider.dart';

/// Provides methods for fetching coin data either from local assets or a remote Git repository.
///
/// `CoinUpdater` uses a singleton pattern to ensure a single instance throughout the app's lifecycle.
/// It checks first if there is a cached version of the data. If not, it uses the bundled asset.
/// In the background, it tries to fetch the most recent data and cache it for future launches.
///
/// Usage:
/// ```dart
/// final coinUpdater = CoinUpdater();
/// final config = await coinUpdater.getConfig();
/// final coins = await coinUpdater.getCoins();
/// ```
///
/// NB! [coinsRepoBranch] and [coinsRepoUrl] only take effect for the next
/// launch of the app. So 2x restart is needed to switch to a different branch.
///
/// TODO: Implement coin icon fetching.
class CoinUpdater {
factory CoinUpdater() => _instance;

CoinUpdater._internal();

static final CoinUpdater _instance = CoinUpdater._internal();

/// The branch of the coins repository to use.
//! QA: change branch name here and then restart twice after logging in.
static const coinsRepoBranch = 'master';

static const coinsRepoUrl =
'https://raw.githubusercontent.com/KomodoPlatform/coins';

static const isUpdateEnabled = true;

final String localAssetPathConfig = 'assets/coins_config.json';
final String localAssetPathCoins = 'assets/coins.json';

String get remotePathConfig =>
'$coinsRepoUrl/$coinsRepoBranch/utils/coins_config.json';
String get remotePathCoins => '$coinsRepoUrl/$coinsRepoBranch/coins';

String _cachedConfig;
String _cachedCoins;

Future<String> _fetchAsset(String path) async {
return await rootBundle.loadString(path);
}

Future<File> _getLocalFile(String filename) async {
final directory = await getApplicationDocumentsDirectory();
return File('${directory.path}/$filename');
}

Future<String> _fetchOrCache(
String localPath,
String remoteUrl,
String cacheName,
String cacheProperty,
) async {
try {
if (cacheProperty != null) {
return cacheProperty;
}

File cacheFile = await _getLocalFile(cacheName);

if (await cacheFile.exists()) {
cacheProperty = await cacheFile.readAsString();
return cacheProperty;
} else {
String localData = await _fetchAsset(localPath);
if (isUpdateEnabled) {
scheduleMicrotask(
() => _updateCacheInBackground(remoteUrl, cacheFile),
);
}
cacheProperty = localData;
return localData;
}
} catch (e) {
// If there's an error, first try to return the cached value,
// if that's null too, then fall back to the local asset.
if (cacheProperty != null) {
return cacheProperty;
} else {
return await _fetchAsset(localPath);
}
}
}

void _updateCacheInBackground(String remoteUrl, File cacheFile) async {
final ReceivePort receivePort = ReceivePort();
await Isolate.spawn(
_isolateEntry,
[remoteUrl, cacheFile.path],
onExit: receivePort.sendPort,
);
receivePort.listen((data) {
// Close the receive port when the isolate is done
receivePort.close();

Log(
'CoinUpdater',
'Coin updater updated coins to latest commit on branch '
'$coinsRepoBranch from $coinsRepoUrl. \n $remoteUrl',
);
});
}

static void _isolateEntry(List<String> data) async {
final String remoteUrl = data[0];
final String filePath = data[1];

final response = await http.get(Uri.parse(remoteUrl));
if (response.statusCode == 200) {
final file = File(filePath);
file.writeAsString(response.body);
}
}

Future<String> getConfig() async {
_cachedConfig = await _fetchOrCache(
localAssetPathConfig,
remotePathConfig,
'coins_config_cache.json',
_cachedConfig,
);
return _cachedConfig;
}

Future<String> getCoins() async {
_cachedCoins = await _fetchOrCache(
localAssetPathCoins,
remotePathCoins,
'coins_cache.json',
_cachedCoins,
);
return _cachedCoins;
}
}
7 changes: 3 additions & 4 deletions lib/model/coin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:collection';
import 'dart:convert';

import 'package:flutter/services.dart';
import 'package:komodo_dex/app_config/coins_updater.dart';

import '../app_config/app_config.dart';
import '../app_config/coin_converter.dart';
Expand Down Expand Up @@ -33,8 +34,7 @@ Future<LinkedHashMap<String, Coin>> get coins async {
_coinsInvoked = true;

Log('coin:29', 'Loading coins.json…');
const ci = 'assets/coins.json';
final cis = await rootBundle.loadString(ci, cache: false);
final String cis = await CoinUpdater().getCoins();
final List<dynamic> cil = json.decode(cis);
final Map<String, Map<String, dynamic>> cim = {};
for (dynamic js in cil) cim[js['coin']] = Map<String, dynamic>.from(js);
Expand Down Expand Up @@ -195,8 +195,7 @@ class Coin {
if (lightWalletDServers != null)
'light_wallet_d_servers':
List<dynamic>.from(lightWalletDServers.map<String>((x) => x)),

};
};

String getTxFeeSatoshi() {
int txFeeRes = 0;
Expand Down
3 changes: 2 additions & 1 deletion lib/services/mm_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'
show EventChannel, MethodChannel, rootBundle, SystemChannels;
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import 'package:komodo_dex/app_config/coins_updater.dart';
import 'package:package_info_plus/package_info_plus.dart';

import '../app_config/app_config.dart';
Expand Down Expand Up @@ -457,7 +458,7 @@ class MMService {

Future<List<dynamic>> readJsonCoinInit() async {
try {
return jsonDecode(await rootBundle.loadString('assets/coins.json'));
return jsonDecode(await CoinUpdater().getCoins());
} catch (e) {
if (kDebugMode) {
Log('mm_service', 'readJsonCoinInit] $e');
Expand Down

0 comments on commit 9adb38d

Please sign in to comment.