import 'dart:collection'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:preferences/preferences.dart'; import 'package:localstorage/localstorage.dart'; import 'package:yaml/yaml.dart'; import 'package:toolheim/data/warband_roster.dart'; class GitHubAdapter extends ChangeNotifier { final LocalStorage storage = new LocalStorage('rosters'); List _syncErrors = new List(); DateTime _lastSync; List _rosters = []; String _activePlayerName; String get repository => PrefService.getString('repository'); String get path => PrefService.getString('path'); bool _syncinProgress = false; bool get isSyncInProgress => _syncinProgress; DateTime get lastSync => _lastSync; List get syncErrors => _syncErrors; UnmodifiableListView get rosters => UnmodifiableListView(_rosters); WarbandRoster activeRoster() { if (_activePlayerName == null || _rosters.length == 0) { return null; } return _rosters.firstWhere((roster) { return roster.playerName == _activePlayerName; }, orElse: () => null); } void changeActiveRoster(String playerName) { _activePlayerName = playerName; notifyListeners(); } // TODO: Add persistence layer here /// Search for warband files in the GitHub repository /// /// This method will search for matching files and check their content in a /// subfolder (see fields [repository] and [path]). If the file /// contain errors or can't read, a sync error message will be written into /// the [syncErrors] list. void search() async { _syncErrors.clear(); _syncinProgress = true; Stream> rosterStream() async* { // Get all files which could be potential warband files (end with // mordheim.yml and contain the word "heros"). http.Response response = await http.get( "https://api.github.com/search/code?q=heros+repo:" + repository + "+filename:mordheim.yml+path:\"" + path + "\""); // GitHub is not reachable if (response.statusCode != 200) { _syncErrors.add('Could not find any warband roster files.'); yield {}; return; } // No valid response from GitHub dynamic searchResults; try { searchResults = jsonDecode(response.body); } on FormatException catch (e) { _syncErrors.add('Could not parse GitHub response. ' + e.toString()); yield {}; return; } // Find suitable files for examination RegExp fileRegex = new RegExp(r"[a-zA-Z]+\.mordheim\.ya?ml"); for (dynamic searchResult in searchResults['items']) { if (fileRegex.hasMatch(searchResult['name'])) { // We try to get the name of the player from the name of the folder // in which the file resists String completePath = searchResult['path']; List pathParts = completePath.substring(path.length + 1).split('/'); String playerName; if (pathParts.length >= 2) { playerName = pathParts.first; } // Fetch last change and some metainformation of the file http.Response response = await http.get( "https://api.github.com/repos/" + repository + "/commits?path=" + completePath); if (response.statusCode != 200) { _syncErrors.add('Could not load the warband metadata from GitHub.'); continue; } // No valid response from GitHub dynamic commits; try { commits = jsonDecode(response.body); } on FormatException catch (e) { _syncErrors.add('Could not parse GitHub response. ' + e.toString()); continue; } // No commits available if (commits.length == 0) { continue; } dynamic latestCommit = commits.first; yield { 'filePath': completePath.toString(), 'shaHash': latestCommit['sha'], 'player': playerName.toString(), 'author': latestCommit['commit']['author']['name'], 'date': latestCommit['commit']['committer']['date'], 'message': latestCommit['commit']['message'] }; } } } _rosters.clear(); notifyListeners(); if (_syncErrors.length == 0) { await for (Map player in rosterStream()) { http.Response response; try { response = await http.get("https://raw.githubusercontent.com/" + repository + '/master/' + player['filePath']); } catch (e) { // TODO: Ignore this error, we catch it elsewhere. } try { if (response != null) { YamlMap yamlObject = loadYaml(response.body); WarbandRoster roster = WarbandRoster.fromJson(yamlObject); if (player['player'] != '') { roster.playerName = player['player']; } roster.currentVersion = new Version(player['shaHash'], player['date'], player['author'], player['message']); // On a search, we drop all previous information about the warbands, // Sp, lastSyncVersion is equal to the currentVersion. roster.lastSyncVersion = roster.currentVersion; _rosters.add(roster); //https://github.com/lesnitsky/flutter_localstorage/blob/master/example/lib/main.dart // FIXME: store it correctly //storage.setItem(player['player'], roster); notifyListeners(); } } catch (e) { _syncErrors.add(e.toString()); } } } // Sort by CP _rosters.sort((a, b) => b.campaignPoints.compareTo(a.campaignPoints)); // Select first as active player if no active player is selected if (_rosters.length > 0) { _activePlayerName = _rosters.first.playerName; } _lastSync = DateTime.now(); _syncinProgress = false; storage.setItem('lastSync', _lastSync.toIso8601String()); notifyListeners(); } void update() async { _syncinProgress = true; // TODO: Search for warband yml files // TODO: Check if it is in the right format // TODO: Store it into the database if valid _lastSync = DateTime.now(); _syncinProgress = false; notifyListeners(); } }