import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:toolheim/warband_roaster.dart'; import 'package:yaml/yaml.dart'; enum SyncState { unknown, running, success, error, } class GitHubAdapter { // TODO: Store this in the SharedPreferences final String repository; final String path; static SyncState syncState = SyncState.unknown; List syncErrors = new List(); static DateTime lastSync; GitHubAdapter(this.repository, this.path); /// 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. Future> search() async { syncErrors.clear(); Stream> roasterStream() 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 roaster 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'] }; } } } List roasters = new List(); // TODO: Read params from share preferences await for (Map player in roasterStream()) { http.Response response = await http.get( "https://raw.githubusercontent.com/" + repository + '/master/' + player['filePath']); try { YamlMap yamlObject = loadYaml(response.body); WarbandRoaster roaster = WarbandRoaster.fromJson(yamlObject); if (player['player'] != '') { roaster.playerName = player['player']; } roaster.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. roaster.lastSyncVersion = roaster.currentVersion; roasters.add(roaster); } catch (e) { syncErrors.add(e.toString()); } } return roasters; } Future update() async { // 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(); return SyncState.success; } }