toolheim/mobile-app/lib/data/github_adapter.dart
2019-07-10 22:28:51 +02:00

177 lines
5.6 KiB
Dart

import 'dart:collection';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:toolheim/data/warband_roaster.dart';
import 'package:yaml/yaml.dart';
class GitHubAdapter extends ChangeNotifier {
String _repository = 'Labernator/Mordheim';
String _path = 'Mordheim-BorderTownBurning/Warband Rosters';
List<String> _syncErrors = new List<String>();
DateTime _lastSync;
List<WarbandRoaster> _roasters = [];
String _activePlayerName;
String get repository => _repository;
String get path => _path;
DateTime get lastSync => _lastSync;
UnmodifiableListView<String> get syncErrors => _syncErrors;
UnmodifiableListView<WarbandRoaster> get roasters =>
UnmodifiableListView(_roasters);
WarbandRoaster get activeRoaster => _roasters.firstWhere((roaster) {
return roaster.playerName == _activePlayerName;
});
void changeActiveRoaster(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();
Stream<Map<String, String>> 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<String> 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']
};
}
}
}
_roasters.clear();
notifyListeners();
await for (Map<String, String> 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);
notifyListeners();
} catch (e) {
_syncErrors.add(e.toString());
}
}
// Sort by CP
_roasters.sort((a, b) => b.campaignPoints.compareTo(a.campaignPoints));
// Select first as active player if no active player is selected
_activePlayerName = _roasters.first.playerName;
_lastSync = DateTime.now();
notifyListeners();
}
void 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();
notifyListeners();
}
}