import 'dart:async'; import 'dart:convert'; import 'dart:developer' as developer; import 'package:http/http.dart' as http; import 'dart:async'; import 'package:sqflite/sqflite.dart'; // import 'package:sqflite/sqflite.dart'; // import 'package:path_provider/path_provider.dart' show MissingPlatformDirectoryException, getApplicationDocumentsDirectory; // import 'package:path/path.dart' show join; import 'package:tetra_stats/services/crud_exceptions.dart'; import 'package:tetra_stats/services/sqlite_db_controller.dart'; import 'package:tetra_stats/data_objects/tetrio.dart'; const String dbName = "TetraStats.db"; const String tetrioUsersTable = "tetrioUsers"; const String idCol = "id"; const String nickCol = "nickname"; const String statesCol = "jsonStates"; const String createTetrioUsersTable = ''' CREATE TABLE IF NOT EXISTS "tetrioUsers" ( "id" TEXT UNIQUE, "nickname" TEXT, "jsonStates" TEXT, PRIMARY KEY("id") );'''; class TetrioService extends DB { Map> _players = {}; static final TetrioService _shared = TetrioService._sharedInstance(); factory TetrioService() => _shared; late final StreamController>> _tetrioStreamController; TetrioService._sharedInstance() { _tetrioStreamController = StreamController>>.broadcast(onListen: () { _tetrioStreamController.sink.add(_players); }); } @override Future open() async { await super.open(); await _cachePlayers(); } Stream>> get allPlayers => _tetrioStreamController.stream; Future _cachePlayers() async { final allPlayers = await getAllPlayers(); _players = allPlayers.toList().first; // ??? _tetrioStreamController.add(_players); developer.log("_cachePlayers: $_players", name: "services/tetrio_crud"); } Future deletePlayer({required String id, required DB udb}) async { await ensureDbIsOpen(); final db = udb.getDatabaseOrThrow(); final deletedPlayer = await db.delete(tetrioUsersTable, where: '$idCol = ?', whereArgs: [id.toLowerCase()]); if (deletedPlayer != 1) { throw CouldNotDeletePlayer(); } else { _players.removeWhere((key, value) => key == id); _tetrioStreamController.add(_players); } } // Future > getOrCreatePlayer({required String id}) async { // try{ // final player = await getPlayer(id: id); // return player; // } on TetrioPlayerNotExist{ // final player = await createPlayer(tetrioPlayer: tetrioPlayer) // } // } Future createPlayer(TetrioPlayer tetrioPlayer) async { ensureDbIsOpen(); final db = getDatabaseOrThrow(); final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId.toLowerCase()]); if (results.isNotEmpty) { throw TetrioPlayerAlreadyExist(); } final Map statesJson = {tetrioPlayer.state.millisecondsSinceEpoch.toString(): tetrioPlayer.toJson()}; db.insert(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)}); _players.addEntries({ tetrioPlayer.userId: [tetrioPlayer] }.entries); _tetrioStreamController.add(_players); } Future storeState(TetrioPlayer tetrioPlayer) async { ensureDbIsOpen(); final db = getDatabaseOrThrow(); late List states; try { states = await getPlayer(tetrioPlayer.userId); } on TetrioPlayerNotExist { await createPlayer(tetrioPlayer); states = await getPlayer(tetrioPlayer.userId); } if (!_players[tetrioPlayer.userId]!.last.isSameState(tetrioPlayer)) states.add(tetrioPlayer); final Map statesJson = {}; for (var e in states) { statesJson.addEntries({e.state.millisecondsSinceEpoch.toString(): e.toJson()}.entries); } db.update(tetrioUsersTable, {idCol: tetrioPlayer.userId, nickCol: tetrioPlayer.username, statesCol: jsonEncode(statesJson)}, where: '$idCol = ?', whereArgs: [tetrioPlayer.userId]); _players[tetrioPlayer.userId]!.add(tetrioPlayer); _tetrioStreamController.add(_players); } Future> getPlayer(String id) async { ensureDbIsOpen(); final db = getDatabaseOrThrow(); List states = []; final results = await db.query(tetrioUsersTable, limit: 1, where: '$idCol = ?', whereArgs: [id.toLowerCase()]); if (results.isEmpty) { throw TetrioPlayerNotExist(); } else { dynamic rawStates = results.first['jsonStates'] as String; rawStates = json.decode(rawStates); rawStates.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k)), false))); _players.removeWhere((key, value) => key == id); _players.addEntries({states.last.userId: states}.entries); _tetrioStreamController.add(_players); return states; } } Future fetchPlayer(String user, bool addToDB) async { var url = Uri.https('ch.tetr.io', 'api/users/${user.toLowerCase().trim()}'); final response = await http.get(url); if (response.statusCode == 200) { if (jsonDecode(response.body)['success']) { TetrioPlayer player = TetrioPlayer.fromJson( jsonDecode(response.body)['data']['user'], DateTime.fromMillisecondsSinceEpoch(jsonDecode(response.body)['cache']['cached_at'], isUtc: true), true); if (addToDB) { await ensureDbIsOpen(); storeState(player); } return player; } else { developer.log("fetchTetrioPlayer User dosen't exist", name: "services/tetrio_crud", error: response.body); throw Exception("User doesn't exist"); } } else { developer.log("fetchTetrioPlayer Failed to fetch player", name: "services/tetrio_crud", error: response.statusCode); throw Exception('Failed to fetch player'); } } // Future _ensureDbIsOpen() async { // try { // await open(); // } on DatabaseAlreadyOpen { // // empty // } // } Future>>> getAllPlayers() async { await ensureDbIsOpen(); final db = getDatabaseOrThrow(); final players = await db.query(tetrioUsersTable); Map> data = {}; //developer.log("getAllPlayers: $players", name: "services/tetrio_crud"); return players.map((row) { // what the fuck am i doing here? var test = json.decode(row['jsonStates'] as String); List states = []; test.forEach((k, v) => states.add(TetrioPlayer.fromJson(v, DateTime.fromMillisecondsSinceEpoch(int.parse(k)), false))); data.addEntries({states.last.userId: states}.entries); return data; }); } }