From 6f8a149d57c906a1cc62d49008434e453f9bf92f Mon Sep 17 00:00:00 2001 From: Vishesh Handa Date: Thu, 26 Sep 2019 16:42:52 +0200 Subject: [PATCH] Refactor Note class The loading/saving/deleting of the Note class is now inbuilt. It's not offloaded to a NoteRepo. We're always going to be storing them on disk, for now. It's also way easier to manage them this way. --- lib/note.dart | 107 ++++++++++++++++++++---------------- test/file_storage_test.dart | 52 ------------------ test/note_storage_test.dart | 53 ++++++++++++++++++ 3 files changed, 113 insertions(+), 99 deletions(-) delete mode 100644 test/file_storage_test.dart create mode 100644 test/note_storage_test.dart diff --git a/lib/note.dart b/lib/note.dart index 22d2ca1f..b1645b96 100644 --- a/lib/note.dart +++ b/lib/note.dart @@ -1,19 +1,32 @@ +import 'dart:io'; + +import 'package:journal/storage/serializers.dart'; import 'package:journal/datetime_utils.dart'; +enum NoteLoadState { + None, + Loading, + Loaded, + NotExists, +} + class Note implements Comparable { - String filePath; + String filePath = ""; DateTime created; - String body; + String body = ""; - Map extraProperties = {}; + var _loadState = NoteLoadState.None; + var _serializer = MarkdownYAMLSerializer(); - Note({this.created, this.body, this.filePath, this.extraProperties}) { - if (created == null) { - created = DateTime(0, 0, 0, 0, 0, 0, 0, 0); - } - if (extraProperties == null) { - extraProperties = {}; - } + // FIXME: Make it an ordered Map + Map props = {}; + + Note({this.created, this.body, this.filePath, this.props}) { + created = created ?? DateTime(0, 0, 0, 0, 0, 0, 0, 0); + props = props ?? {}; + body = body ?? ""; + + assert(filePath != null); } bool hasValidDate() { @@ -21,18 +34,27 @@ class Note implements Comparable { return created.year > 10; } - factory Note.fromJson(Map json) { - String filePath = ""; - if (json.containsKey("filePath")) { - filePath = json["filePath"].toString(); - json.remove("filePath"); + Future load() async { + if (_loadState == NoteLoadState.Loading) { + return _loadState; } - DateTime created; - if (json.containsKey("created")) { - var createdStr = json['created'].toString(); + final file = File(filePath); + if (!file.existsSync()) { + _loadState = NoteLoadState.NotExists; + return _loadState; + } + + final string = await file.readAsString(); + var noteData = _serializer.decode(string); + + body = noteData.body; + props = noteData.props; + + if (props.containsKey("created")) { + var createdStr = props['created'].toString(); try { - created = DateTime.parse(json['created']).toLocal(); + created = DateTime.parse(props['created']).toLocal(); } catch (ex) { // Ignore it } @@ -46,46 +68,37 @@ class Note implements Comparable { created = DateTime.parse(createdStr); } } - - json.remove("created"); } if (created == null) { created = DateTime(0, 0, 0, 0, 0, 0, 0, 0); } - String body = ""; - if (json.containsKey("body")) { - body = json['body']; - json.remove("body"); - } - - return Note( - filePath: filePath, - created: created, - body: body, - extraProperties: json, - ); + _loadState = NoteLoadState.Loaded; + return _loadState; } - Map toJson() { - var json = Map.from(extraProperties); - var createdStr = toIso8601WithTimezone(created); - if (!createdStr.startsWith("00")) { - json['created'] = createdStr; + // FIXME: What about error handling? + Future save() async { + if (hasValidDate()) { + props['created'] = toIso8601WithTimezone(created); } - json['body'] = body; - json['filePath'] = filePath; - return json; + var file = File(filePath); + var contents = _serializer.encode(NoteData(body, props)); + await file.writeAsString(contents); } + // FIXME: What about error handling? + Future remove() async { + var file = File(filePath); + await file.delete(); + } + + // FIXME: Can't this part be auto-generated? @override int get hashCode => - filePath.hashCode ^ - created.hashCode ^ - body.hashCode ^ - extraProperties.hashCode; + filePath.hashCode ^ created.hashCode ^ body.hashCode ^ props.hashCode; @override bool operator ==(Object other) => @@ -95,7 +108,7 @@ class Note implements Comparable { filePath == other.filePath && body == other.body && created == other.created && - _equalMaps(extraProperties, other.extraProperties); + _equalMaps(props, other.props); static bool _equalMaps(Map a, Map b) { if (a.length != b.length) return false; @@ -105,7 +118,7 @@ class Note implements Comparable { @override String toString() { - return 'Note{filePath: $filePath, body: $body, created: $created, extraProperties: $extraProperties}'; + return 'Note{filePath: $filePath, body: $body, created: $created, props: $props}'; } @override diff --git a/test/file_storage_test.dart b/test/file_storage_test.dart deleted file mode 100644 index 29973886..00000000 --- a/test/file_storage_test.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'dart:io'; - -import 'package:journal/note.dart'; -import 'package:journal/storage/file_storage.dart'; -import 'package:journal/storage/serializers.dart'; -import 'package:path/path.dart' as p; -import 'package:test/test.dart'; - -DateTime nowWithoutMicro() { - var dt = DateTime.now(); - return DateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second); -} - -void main() { - group('FileStorage', () { - var notes = [ - Note(filePath: "1.md", body: "test", created: nowWithoutMicro()), - Note(filePath: "2.md", body: "test2", created: nowWithoutMicro()), - ]; - - Directory tempDir; - FileStorage storage; - - setUpAll(() async { - tempDir = await Directory.systemTemp.createTemp('__storage_test__'); - storage = FileStorage( - baseDirectory: tempDir.path, - noteSerializer: JsonNoteSerializer(), - ); - }); - - tearDownAll(() async { - tempDir.deleteSync(recursive: true); - }); - - test('Should persist Notes to disk', () async { - var dir = await storage.saveNotes(notes); - expect(dir.listSync(recursive: true).length, 2); - - expect(File(p.join(dir.path, "1.md")).existsSync(), isTrue); - expect(File(p.join(dir.path, "2.md")).existsSync(), isTrue); - }); - - test('Should load Notes from disk', () async { - var loadedNotes = await storage.listNotes(); - loadedNotes.sort(); - notes.sort(); - - expect(loadedNotes, notes); - }); - }); -} diff --git a/test/note_storage_test.dart b/test/note_storage_test.dart new file mode 100644 index 00000000..1be7bb31 --- /dev/null +++ b/test/note_storage_test.dart @@ -0,0 +1,53 @@ +import 'dart:io'; + +import 'package:journal/note.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +void main() { + group('NoteStorage', () { + var notes = []; + String n1Path; + String n2Path; + Directory tempDir; + + setUpAll(() async { + tempDir = await Directory.systemTemp.createTemp('__storage_test__'); + + var dt = DateTime(2019, 12, 2, 5, 4, 2); + n1Path = p.join(tempDir.path, "1.md"); + n2Path = p.join(tempDir.path, "2.md"); + notes = [ + Note(filePath: n1Path, body: "test", created: dt), + Note(filePath: n2Path, body: "test2", created: dt), + ]; + }); + + tearDownAll(() async { + tempDir.deleteSync(recursive: true); + }); + + test('Should persist and load Notes from disk', () async { + await Future.forEach(notes, (Note note) async { + await note.save(); + }); + expect(tempDir.listSync(recursive: true).length, 2); + expect(File(n1Path).existsSync(), isTrue); + expect(File(n2Path).existsSync(), isTrue); + + var loadedNotes = []; + await Future.forEach(notes, (origNote) async { + var note = Note(filePath: origNote.filePath); + var r = await note.load(); + expect(r, NoteLoadState.Loaded); + + loadedNotes.add(note); + }); + + loadedNotes.sort(); + notes.sort(); + + expect(loadedNotes, notes); + }); + }); +}