toolheim/mobile-app/lib/data/github_adapter.dart

213 lines
6.3 KiB
Dart

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<String> _syncErrors = new List<String>();
DateTime _lastSync;
List<WarbandRoster> _rosters = [];
WarbandRoster _activeRoster;
String get repository => PrefService.getString('repository');
String get path => PrefService.getString('path');
bool _syncinProgress = false;
bool get isSyncInProgress => _syncinProgress;
DateTime get lastSync => _lastSync;
List<String> get syncErrors => _syncErrors;
UnmodifiableListView<WarbandRoster> get rosters =>
UnmodifiableListView(_rosters);
WarbandRoster get activeRoster => _activeRoster;
set activeRoster(WarbandRoster roster) {
_activeRoster = roster;
notifyListeners();
}
/// 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<String> warbandFileStream() 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 null;
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 null;
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'])) {
yield searchResult['path'].toString();
}
}
}
storage.clear();
_rosters.clear();
notifyListeners();
if (_syncErrors.length == 0) {
await for (String filePath in warbandFileStream()) {
WarbandRoster roster = await fetchWarband(filePath);
Version latestVersion = await getLatestCommit(filePath);
roster.playerName = getPlayerNameFromFilePath(filePath);
roster.currentVersion = latestVersion;
// On a search, we drop all previous information about the warbands,
// So, 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'] + '-warband-yaml', response.body);
//storage.setItem(
// player['player'] + '-current-version', roster.currentVersion);
//storage.setItem(
// player['player'] + '-last-sync-version', roster.lastSyncVersion);
notifyListeners();
}
}
// 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) {
_activeRoster = _rosters.first;
}
_lastSync = DateTime.now();
_syncinProgress = false;
storage.setItem('lastSync', _lastSync.toIso8601String());
notifyListeners();
}
void update() async {
_syncinProgress = true;
// TODO: Loop through the found warbands and update it.
_lastSync = DateTime.now();
_syncinProgress = false;
notifyListeners();
}
String getPlayerNameFromFilePath(String filePath) {
// We try to get the name of the player from the name of the folder
// in which the file resists
List<String> pathParts = filePath.substring(path.length + 1).split('/');
String playerName = 'Lonely Recluse';
if (pathParts.length >= 2) {
playerName = pathParts.first;
}
return playerName;
}
Future<Version> getLatestCommit(String filePath) async {
// Fetch last change and some metainformation of the file
http.Response response = await http.get("https://api.github.com/repos/" +
repository +
"/commits?path=" +
filePath);
if (response.statusCode != 200) {
_syncErrors.add('Could not load the warband metadata from GitHub.');
return null;
}
// 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());
return null;
}
// No commits available
if (commits.length == 0) {
return null;
}
dynamic latestCommit = commits.first;
return new Version(
latestCommit['sha'],
latestCommit['commit']['committer']['date'],
latestCommit['commit']['author']['name'],
latestCommit['commit']['message']);
}
Future<WarbandRoster> fetchWarband(String filePath) async {
http.Response response;
try {
response = await http.get("https://raw.githubusercontent.com/" +
repository +
'/master/' +
filePath);
} catch (e) {
// We ignore this error, because it will handle from the _syncErrors
// later (see below).
}
try {
if (response != null) {
YamlMap yamlObject = loadYaml(response.body);
return WarbandRoster.fromJson(yamlObject);
}
} catch (e) {
_syncErrors.add(e.toString());
}
return null;
}
void readWarband(WarbandRoster roster, String yamlContent) {
// TODO: Read the warband from the shared preferences
}
}