mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-27 17:29:50 +08:00
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.
This commit is contained in:
107
lib/note.dart
107
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<Note> {
|
||||
String filePath;
|
||||
String filePath = "";
|
||||
DateTime created;
|
||||
String body;
|
||||
String body = "";
|
||||
|
||||
Map<String, dynamic> extraProperties = <String, dynamic>{};
|
||||
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 = <String, dynamic>{};
|
||||
}
|
||||
// FIXME: Make it an ordered Map
|
||||
Map<String, dynamic> props = {};
|
||||
|
||||
Note({this.created, this.body, this.filePath, this.props}) {
|
||||
created = created ?? DateTime(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
props = props ?? <String, dynamic>{};
|
||||
body = body ?? "";
|
||||
|
||||
assert(filePath != null);
|
||||
}
|
||||
|
||||
bool hasValidDate() {
|
||||
@ -21,18 +34,27 @@ class Note implements Comparable<Note> {
|
||||
return created.year > 10;
|
||||
}
|
||||
|
||||
factory Note.fromJson(Map<String, dynamic> json) {
|
||||
String filePath = "";
|
||||
if (json.containsKey("filePath")) {
|
||||
filePath = json["filePath"].toString();
|
||||
json.remove("filePath");
|
||||
Future<NoteLoadState> 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<Note> {
|
||||
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<String, dynamic> toJson() {
|
||||
var json = Map<String, dynamic>.from(extraProperties);
|
||||
var createdStr = toIso8601WithTimezone(created);
|
||||
if (!createdStr.startsWith("00")) {
|
||||
json['created'] = createdStr;
|
||||
// FIXME: What about error handling?
|
||||
Future<void> 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<void> 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<Note> {
|
||||
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<Note> {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Note{filePath: $filePath, body: $body, created: $created, extraProperties: $extraProperties}';
|
||||
return 'Note{filePath: $filePath, body: $body, created: $created, props: $props}';
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
53
test/note_storage_test.dart
Normal file
53
test/note_storage_test.dart
Normal file
@ -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 = <Note>[];
|
||||
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>[
|
||||
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 = <Note>[];
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user