Hookup the Journal app to git

Now notes are saved in the git repo, and immediately synced. This is not
the best implementation, as the notes are being reloaded a lot, and
the error handling is terrible (I miss golang). But it's the first
working poc.
This commit is contained in:
Vishesh Handa
2019-01-09 12:55:53 +01:00
parent 706f5bfacc
commit 4bb02b12d6
8 changed files with 249 additions and 78 deletions

View File

@ -26,23 +26,37 @@ buildGitButtons() {
),
RaisedButton(
child: Text("Git Clone"),
onPressed: gitClone,
onPressed: () async {
gitClone("root@bcn.vhanda.in:git/test", "journal");
},
),
RaisedButton(
child: Text("Git Pull"),
onPressed: gitPull,
onPressed: () async {
gitPull("journal");
},
),
RaisedButton(
child: Text("Git Add"),
onPressed: gitAdd,
onPressed: () async {
await gitAdd("journal", "1");
},
),
RaisedButton(
child: Text("Git Push"),
onPressed: gitPush,
onPressed: () async {
gitPush("journal");
},
),
RaisedButton(
child: Text("Git Commit"),
onPressed: gitCommit,
),
child: Text("Git Commit"),
onPressed: () async {
gitCommit(
gitFolder: "journal",
authorEmail: "noemail@example.com",
authorName: "Vishesh Handa",
message: "Default message from GitJournal",
);
}),
];
}

View File

@ -5,24 +5,9 @@ import 'package:journal/app.dart';
import 'package:journal/gitapp.dart';
import 'package:journal/state_container.dart';
/*
import 'note.dart';
Future<List<Note>> fetchNotes() async {
final response = await http.get('http://192.168.1.132:8000/notes');
final responseJson = json.decode(response.body);
var notes = <Note>[];
for (var postJson in responseJson) {
notes.add(new Note.fromJson(postJson));
}
return notes;
}
*/
void main() {
runApp(new StateContainer(
child: GitApp(),
child: JournalApp(),
//child: GitApp(),
));
}

View File

@ -49,21 +49,34 @@ class Note implements Comparable {
@override
String toString() {
return 'Note{id: $id, body: $body, createdAt: $created}';
return 'Note{id: $id, body: $body, created: $created}';
}
@override
int compareTo(other) => created.compareTo(other.created);
int compareTo(other) {
if (other == null) {
return -1;
}
return created.compareTo(other.created);
}
}
class AppState {
bool isLoading;
bool isLoadingFromDisk;
bool localStateModified;
bool isLoadingRemoteState;
bool remoteStateModified;
List<Note> notes;
AppState({
this.isLoading = false,
this.isLoadingFromDisk = false,
this.localStateModified = false,
this.isLoadingRemoteState = false,
this.remoteStateModified = false,
this.notes = const [],
});
factory AppState.loading() => AppState(isLoading: true);
//factory AppState.loading() => AppState(isLoading: true);
}

View File

@ -9,12 +9,12 @@ import 'package:uuid/uuid.dart';
import 'package:journal/note.dart';
import 'package:journal/storage/serializers.dart';
import 'package:journal/storage/notes_repository.dart';
import 'package:journal/storage/file_storage.dart';
import 'package:journal/storage/git_storage.dart';
import 'package:journal/storage/git.dart';
Future<Directory> getNotesDir() async {
var appDir = await getGitBaseDirectory();
var dir = new Directory(p.join(appDir.path, "notes"));
var dir = new Directory(p.join(appDir.path, "journal"));
await dir.create();
return dir;
@ -40,56 +40,80 @@ class StateContainer extends StatefulWidget {
}
class StateContainerState extends State<StateContainer> {
AppState appState = AppState.loading();
NoteRepository noteRepo = new FileStorage(
AppState appState = AppState();
NoteRepository noteRepo = new GitNoteRepository(
getDirectory: getNotesDir,
noteSerializer: new MarkdownYAMLSerializer(),
fileNameGenerator: (Note note) => note.id,
dirName: "journal",
gitCloneUrl: "root@bcn.vhanda.in:git/test",
);
@override
void initState() {
super.initState();
_loadNotesFromDisk();
_syncNotes();
}
void _loadNotesFromDisk() {
print("Loading Notes From Disk");
appState.isLoadingFromDisk = true;
noteRepo.listNotes().then((loadedNotes) {
setState(() {
appState = AppState(notes: loadedNotes);
appState.isLoadingFromDisk = false;
appState.notes = loadedNotes;
});
}).catchError((err) {
}).catchError((err, stack) {
setState(() {
print("Load Notes Error:");
print(err);
appState.isLoading = false;
print("Load Notes From Disk Error: " + err.toString());
print(stack.toString());
appState.isLoadingFromDisk = false;
});
});
}
void addNote(Note note) {
setState(() {
note.id = new Uuid().v4();
appState.notes.insert(0, note);
noteRepo.addNote(note);
void _syncNotes() {
print("Starting to syncNOtes");
this.noteRepo.sync().then((loaded) {
print("NotesRepo Synced: " + loaded.toString());
_loadNotesFromDisk();
}).catchError((err) {
print("NotesRepo Sync: " + err.toString());
});
}
void addNote(Note note) {
insertNote(0, note);
}
void removeNote(Note note) {
setState(() {
appState.notes.remove(note);
noteRepo.removeNote(note);
noteRepo.removeNote(note).then((NoteRepoResult _) {
_syncNotes();
});
});
}
void insertNote(int index, Note note) {
setState(() {
print("insertNote: " + note.toString());
if (note.id == null || note.id.isEmpty) {
note.id = new Uuid().v4();
}
appState.notes.insert(index, note);
noteRepo.addNote(note);
noteRepo.addNote(note).then((NoteRepoResult _) {
_syncNotes();
});
});
}
// FIXME: Implement this!
void updateNote(Note note) {
setState(() {
noteRepo.updateNote(note);
noteRepo.updateNote(note).then((NoteRepoResult _) {
_syncNotes();
});
});
}

View File

@ -29,6 +29,13 @@ class FileStorage implements NoteRepository {
var lister = dir.list(recursive: false);
await for (var fileEntity in lister) {
Note note = await _loadNote(fileEntity);
if (note == null) {
continue;
}
if (note.id == null) {
String filename = p.basename(fileEntity.path);
note.id = filename;
}
notes.add(note);
}
@ -47,30 +54,33 @@ class FileStorage implements NoteRepository {
}
@override
Future<bool> addNote(Note note) async {
Future<NoteRepoResult> addNote(Note note) async {
final dir = await getDirectory();
var filePath = p.join(dir.path, fileNameGenerator(note));
var file = new File(filePath);
if (file == null) {
return NoteRepoResult(error: true);
}
var contents = noteSerializer.encode(note);
await file.writeAsString(contents);
return true;
return NoteRepoResult(noteFilePath: filePath, error: false);
}
@override
Future<bool> removeNote(Note note) async {
Future<NoteRepoResult> removeNote(Note note) async {
final dir = await getDirectory();
var filePath = p.join(dir.path, fileNameGenerator(note));
var file = new File(filePath);
await file.delete();
return true;
return NoteRepoResult(noteFilePath: filePath, error: false);
}
@override
Future<bool> updateNote(Note note) async {
Future<NoteRepoResult> updateNote(Note note) async {
return addNote(note);
}
@ -81,8 +91,8 @@ class FileStorage implements NoteRepository {
Future<Directory> saveNotes(List<Note> notes) async {
final dir = await getDirectory();
await dir.delete(recursive: true);
await dir.create();
// FIXME: Why do we need to delete everything?
// await dir.delete(recursive: true);
for (var note in notes) {
var filePath = p.join(dir.path, fileNameGenerator(note));

View File

@ -13,12 +13,12 @@ Future<Directory> getGitBaseDirectory() async {
return new Directory(path);
}
Future gitClone() async {
Future gitClone(String cloneUrl, String folderName) async {
print("Going to git clone");
try {
await _platform.invokeMethod('gitClone', {
'cloneUrl': "root@bcn.vhanda.in:git/test",
'folderName': "journal",
'cloneUrl': cloneUrl,
'folderName': folderName,
});
print("Done");
} on PlatformException catch (e) {
@ -36,11 +36,11 @@ Future generateSSHKeys() async {
}
}
Future gitPull() async {
Future gitPull(String folderName) async {
print("Going to git pull");
try {
await _platform.invokeMethod('gitPull', {
'folderName': "journal",
'folderName': folderName,
});
print("Done");
} on PlatformException catch (e) {
@ -48,12 +48,12 @@ Future gitPull() async {
}
}
Future gitAdd() async {
print("Going to git add");
Future gitAdd(String gitFolder, String filePattern) async {
print("Going to git add: " + filePattern);
try {
await _platform.invokeMethod('gitAdd', {
'folderName': "journal",
'filePattern': ".",
'folderName': gitFolder,
'filePattern': filePattern,
});
print("Done");
} on PlatformException catch (e) {
@ -61,12 +61,12 @@ Future gitAdd() async {
}
}
Future gitRm() async {
Future gitRm(String gitFolder, String filePattern) async {
print("Going to git rm");
try {
await _platform.invokeMethod('gitRm', {
'folderName': "journal",
'filePattern': "1",
'folderName': gitFolder,
'filePattern': filePattern,
});
print("Done");
} on PlatformException catch (e) {
@ -74,11 +74,11 @@ Future gitRm() async {
}
}
Future gitPush() async {
Future gitPush(String folderName) async {
print("Going to git push");
try {
await _platform.invokeMethod('gitPush', {
'folderName': "journal",
'folderName': folderName,
});
print("Done");
} on PlatformException catch (e) {
@ -86,14 +86,19 @@ Future gitPush() async {
}
}
Future gitCommit() async {
Future gitCommit({
String gitFolder,
String authorName,
String authorEmail,
String message,
}) async {
print("Going to git commit");
try {
await _platform.invokeMethod('gitCommit', {
'folderName': "journal",
'authorName': "Vishesh Handa",
'authorEmail': "noemail@example.com",
'message': "Default message from GitJournal",
'folderName': gitFolder,
'authorName': authorName,
'authorEmail': authorEmail,
'message': message,
});
print("Done");
} on PlatformException catch (e) {

View File

@ -0,0 +1,108 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as p;
import 'package:journal/note.dart';
import 'package:journal/storage/git.dart';
import 'package:journal/storage/serializers.dart';
import 'package:journal/storage/file_storage.dart';
import 'package:journal/storage/notes_repository.dart';
class GitNoteRepository implements NoteRepository {
final FileStorage _fileStorage;
final String gitCloneUrl;
final String dirName;
bool cloned = false;
bool checkForCloned = false;
final Future<Directory> Function() getDirectory;
GitNoteRepository({
@required this.gitCloneUrl,
@required this.dirName,
@required this.getDirectory,
}) : _fileStorage = FileStorage(
noteSerializer: new MarkdownYAMLSerializer(),
fileNameGenerator: (Note note) => note.id,
getDirectory: getDirectory,
) {
// FIXME: This isn't correct. The gitUrl might not be cloned at this point!
}
@override
Future<NoteRepoResult> addNote(Note note) async {
print("Calling gitStorage addNote");
var result = await _fileStorage.addNote(note);
if (result.error) {
return result;
}
var baseDir = await this.getDirectory();
var filePath = result.noteFilePath.replaceFirst(baseDir.path + "/", "");
await gitAdd(this.dirName, filePath);
await gitCommit(
gitFolder: this.dirName,
authorEmail: "noemail@example.com",
authorName: "Vishesh Handa",
message: "Added Journal entry",
);
return result;
}
@override
Future<NoteRepoResult> removeNote(Note note) async {
var result = await _fileStorage.addNote(note);
if (result.error) {
return result;
}
var baseDir = await this.getDirectory();
var filePath = result.noteFilePath.replaceFirst(baseDir.path + "/", "");
await gitRm(this.dirName, filePath);
await gitCommit(
gitFolder: this.dirName,
authorEmail: "noemail@example.com",
authorName: "Vishesh Handa",
message: "Added Journal entry",
);
return result;
}
@override
Future<NoteRepoResult> updateNote(Note note) async {
return this.addNote(note);
}
@override
Future<List<Note>> listNotes() {
return _fileStorage.listNotes();
}
@override
Future<bool> sync() async {
print("Starting Sync");
if (!checkForCloned) {
var baseDir = await this.getDirectory();
var dotGitDir = new Directory(p.join(baseDir.path, ".git"));
cloned = await dotGitDir.exists();
checkForCloned = true;
}
if (!cloned) {
await gitClone(this.gitCloneUrl, this.dirName);
cloned = true;
return true;
}
await gitPull(this.dirName);
await gitPush(this.dirName);
return true;
}
}

View File

@ -1,13 +1,25 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:journal/note.dart';
class NoteRepoResult {
bool error;
String noteFilePath;
NoteRepoResult({
@required this.error,
this.noteFilePath,
});
}
abstract class NoteRepository {
// TODO: Better error message!
Future<bool> sync();
Future<bool> addNote(Note note);
Future<bool> updateNote(Note note);
Future<bool> removeNote(Note note);
Future<NoteRepoResult> addNote(Note note);
Future<NoteRepoResult> updateNote(Note note);
Future<NoteRepoResult> removeNote(Note note);
Future<List<Note>> listNotes();
}