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( RaisedButton(
child: Text("Git Clone"), child: Text("Git Clone"),
onPressed: gitClone, onPressed: () async {
gitClone("root@bcn.vhanda.in:git/test", "journal");
},
), ),
RaisedButton( RaisedButton(
child: Text("Git Pull"), child: Text("Git Pull"),
onPressed: gitPull, onPressed: () async {
gitPull("journal");
},
), ),
RaisedButton( RaisedButton(
child: Text("Git Add"), child: Text("Git Add"),
onPressed: gitAdd, onPressed: () async {
await gitAdd("journal", "1");
},
), ),
RaisedButton( RaisedButton(
child: Text("Git Push"), child: Text("Git Push"),
onPressed: gitPush, onPressed: () async {
gitPush("journal");
},
), ),
RaisedButton( RaisedButton(
child: Text("Git Commit"), child: Text("Git Commit"),
onPressed: gitCommit, 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/gitapp.dart';
import 'package:journal/state_container.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() { void main() {
runApp(new StateContainer( runApp(new StateContainer(
child: GitApp(), child: JournalApp(),
//child: GitApp(),
)); ));
} }

View File

@ -49,21 +49,34 @@ class Note implements Comparable {
@override @override
String toString() { String toString() {
return 'Note{id: $id, body: $body, createdAt: $created}'; return 'Note{id: $id, body: $body, created: $created}';
} }
@override @override
int compareTo(other) => created.compareTo(other.created); int compareTo(other) {
if (other == null) {
return -1;
}
return created.compareTo(other.created);
}
} }
class AppState { class AppState {
bool isLoading; bool isLoadingFromDisk;
bool localStateModified;
bool isLoadingRemoteState;
bool remoteStateModified;
List<Note> notes; List<Note> notes;
AppState({ AppState({
this.isLoading = false, this.isLoadingFromDisk = false,
this.localStateModified = false,
this.isLoadingRemoteState = false,
this.remoteStateModified = false,
this.notes = const [], 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/note.dart';
import 'package:journal/storage/serializers.dart'; import 'package:journal/storage/serializers.dart';
import 'package:journal/storage/notes_repository.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'; import 'package:journal/storage/git.dart';
Future<Directory> getNotesDir() async { Future<Directory> getNotesDir() async {
var appDir = await getGitBaseDirectory(); 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(); await dir.create();
return dir; return dir;
@ -40,56 +40,80 @@ class StateContainer extends StatefulWidget {
} }
class StateContainerState extends State<StateContainer> { class StateContainerState extends State<StateContainer> {
AppState appState = AppState.loading(); AppState appState = AppState();
NoteRepository noteRepo = new FileStorage(
NoteRepository noteRepo = new GitNoteRepository(
getDirectory: getNotesDir, getDirectory: getNotesDir,
noteSerializer: new MarkdownYAMLSerializer(), dirName: "journal",
fileNameGenerator: (Note note) => note.id, gitCloneUrl: "root@bcn.vhanda.in:git/test",
); );
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadNotesFromDisk();
_syncNotes();
}
void _loadNotesFromDisk() {
print("Loading Notes From Disk");
appState.isLoadingFromDisk = true;
noteRepo.listNotes().then((loadedNotes) { noteRepo.listNotes().then((loadedNotes) {
setState(() { setState(() {
appState = AppState(notes: loadedNotes); appState.isLoadingFromDisk = false;
appState.notes = loadedNotes;
}); });
}).catchError((err) { }).catchError((err, stack) {
setState(() { setState(() {
print("Load Notes Error:"); print("Load Notes From Disk Error: " + err.toString());
print(err); print(stack.toString());
appState.isLoading = false; appState.isLoadingFromDisk = false;
}); });
}); });
} }
void addNote(Note note) { void _syncNotes() {
setState(() { print("Starting to syncNOtes");
note.id = new Uuid().v4(); this.noteRepo.sync().then((loaded) {
appState.notes.insert(0, note); print("NotesRepo Synced: " + loaded.toString());
noteRepo.addNote(note); _loadNotesFromDisk();
}).catchError((err) {
print("NotesRepo Sync: " + err.toString());
}); });
} }
void addNote(Note note) {
insertNote(0, note);
}
void removeNote(Note note) { void removeNote(Note note) {
setState(() { setState(() {
appState.notes.remove(note); appState.notes.remove(note);
noteRepo.removeNote(note); noteRepo.removeNote(note).then((NoteRepoResult _) {
_syncNotes();
});
}); });
} }
void insertNote(int index, Note note) { void insertNote(int index, Note note) {
setState(() { setState(() {
print("insertNote: " + note.toString());
if (note.id == null || note.id.isEmpty) {
note.id = new Uuid().v4();
}
appState.notes.insert(index, note); appState.notes.insert(index, note);
noteRepo.addNote(note); noteRepo.addNote(note).then((NoteRepoResult _) {
_syncNotes();
});
}); });
} }
// FIXME: Implement this!
void updateNote(Note note) { void updateNote(Note note) {
setState(() { 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); var lister = dir.list(recursive: false);
await for (var fileEntity in lister) { await for (var fileEntity in lister) {
Note note = await _loadNote(fileEntity); 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); notes.add(note);
} }
@ -47,30 +54,33 @@ class FileStorage implements NoteRepository {
} }
@override @override
Future<bool> addNote(Note note) async { Future<NoteRepoResult> addNote(Note note) async {
final dir = await getDirectory(); final dir = await getDirectory();
var filePath = p.join(dir.path, fileNameGenerator(note)); var filePath = p.join(dir.path, fileNameGenerator(note));
var file = new File(filePath); var file = new File(filePath);
if (file == null) {
return NoteRepoResult(error: true);
}
var contents = noteSerializer.encode(note); var contents = noteSerializer.encode(note);
await file.writeAsString(contents); await file.writeAsString(contents);
return true; return NoteRepoResult(noteFilePath: filePath, error: false);
} }
@override @override
Future<bool> removeNote(Note note) async { Future<NoteRepoResult> removeNote(Note note) async {
final dir = await getDirectory(); final dir = await getDirectory();
var filePath = p.join(dir.path, fileNameGenerator(note)); var filePath = p.join(dir.path, fileNameGenerator(note));
var file = new File(filePath); var file = new File(filePath);
await file.delete(); await file.delete();
return true; return NoteRepoResult(noteFilePath: filePath, error: false);
} }
@override @override
Future<bool> updateNote(Note note) async { Future<NoteRepoResult> updateNote(Note note) async {
return addNote(note); return addNote(note);
} }
@ -81,8 +91,8 @@ class FileStorage implements NoteRepository {
Future<Directory> saveNotes(List<Note> notes) async { Future<Directory> saveNotes(List<Note> notes) async {
final dir = await getDirectory(); final dir = await getDirectory();
await dir.delete(recursive: true); // FIXME: Why do we need to delete everything?
await dir.create(); // await dir.delete(recursive: true);
for (var note in notes) { for (var note in notes) {
var filePath = p.join(dir.path, fileNameGenerator(note)); var filePath = p.join(dir.path, fileNameGenerator(note));

View File

@ -13,12 +13,12 @@ Future<Directory> getGitBaseDirectory() async {
return new Directory(path); return new Directory(path);
} }
Future gitClone() async { Future gitClone(String cloneUrl, String folderName) async {
print("Going to git clone"); print("Going to git clone");
try { try {
await _platform.invokeMethod('gitClone', { await _platform.invokeMethod('gitClone', {
'cloneUrl': "root@bcn.vhanda.in:git/test", 'cloneUrl': cloneUrl,
'folderName': "journal", 'folderName': folderName,
}); });
print("Done"); print("Done");
} on PlatformException catch (e) { } on PlatformException catch (e) {
@ -36,11 +36,11 @@ Future generateSSHKeys() async {
} }
} }
Future gitPull() async { Future gitPull(String folderName) async {
print("Going to git pull"); print("Going to git pull");
try { try {
await _platform.invokeMethod('gitPull', { await _platform.invokeMethod('gitPull', {
'folderName': "journal", 'folderName': folderName,
}); });
print("Done"); print("Done");
} on PlatformException catch (e) { } on PlatformException catch (e) {
@ -48,12 +48,12 @@ Future gitPull() async {
} }
} }
Future gitAdd() async { Future gitAdd(String gitFolder, String filePattern) async {
print("Going to git add"); print("Going to git add: " + filePattern);
try { try {
await _platform.invokeMethod('gitAdd', { await _platform.invokeMethod('gitAdd', {
'folderName': "journal", 'folderName': gitFolder,
'filePattern': ".", 'filePattern': filePattern,
}); });
print("Done"); print("Done");
} on PlatformException catch (e) { } 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"); print("Going to git rm");
try { try {
await _platform.invokeMethod('gitRm', { await _platform.invokeMethod('gitRm', {
'folderName': "journal", 'folderName': gitFolder,
'filePattern': "1", 'filePattern': filePattern,
}); });
print("Done"); print("Done");
} on PlatformException catch (e) { } on PlatformException catch (e) {
@ -74,11 +74,11 @@ Future gitRm() async {
} }
} }
Future gitPush() async { Future gitPush(String folderName) async {
print("Going to git push"); print("Going to git push");
try { try {
await _platform.invokeMethod('gitPush', { await _platform.invokeMethod('gitPush', {
'folderName': "journal", 'folderName': folderName,
}); });
print("Done"); print("Done");
} on PlatformException catch (e) { } 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"); print("Going to git commit");
try { try {
await _platform.invokeMethod('gitCommit', { await _platform.invokeMethod('gitCommit', {
'folderName': "journal", 'folderName': gitFolder,
'authorName': "Vishesh Handa", 'authorName': authorName,
'authorEmail': "noemail@example.com", 'authorEmail': authorEmail,
'message': "Default message from GitJournal", 'message': message,
}); });
print("Done"); print("Done");
} on PlatformException catch (e) { } 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 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:journal/note.dart'; import 'package:journal/note.dart';
class NoteRepoResult {
bool error;
String noteFilePath;
NoteRepoResult({
@required this.error,
this.noteFilePath,
});
}
abstract class NoteRepository { abstract class NoteRepository {
// TODO: Better error message! // TODO: Better error message!
Future<bool> sync(); Future<bool> sync();
Future<bool> addNote(Note note); Future<NoteRepoResult> addNote(Note note);
Future<bool> updateNote(Note note); Future<NoteRepoResult> updateNote(Note note);
Future<bool> removeNote(Note note); Future<NoteRepoResult> removeNote(Note note);
Future<List<Note>> listNotes(); Future<List<Note>> listNotes();
} }