mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-07-18 11:20:16 +08:00
289 lines
6.9 KiB
Dart
289 lines
6.9 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:path/path.dart';
|
|
|
|
import 'note.dart';
|
|
import 'note_fs_entity.dart';
|
|
|
|
class NotesFolder with ChangeNotifier implements Comparable<NotesFolder> {
|
|
final NotesFolder parent;
|
|
String folderPath;
|
|
|
|
List<NoteFSEntity> _entities = [];
|
|
Map<String, NoteFSEntity> _entityMap = {};
|
|
|
|
NotesFolder(this.parent, this.folderPath);
|
|
|
|
@override
|
|
void dispose() {
|
|
_entities.forEach((e) {
|
|
if (e.isFolder) {
|
|
e.folder.removeListener(_entityChanged);
|
|
} else {
|
|
e.note.removeListener(_entityChanged);
|
|
}
|
|
});
|
|
super.dispose();
|
|
}
|
|
|
|
void _entityChanged() {
|
|
notifyListeners();
|
|
}
|
|
|
|
bool get isEmpty {
|
|
return _entities.isEmpty;
|
|
}
|
|
|
|
String get name {
|
|
return basename(folderPath);
|
|
}
|
|
|
|
bool get hasSubFolders {
|
|
return _entities.firstWhere((e) => e.isFolder, orElse: () => null) != null;
|
|
}
|
|
|
|
bool get hasNotes {
|
|
return _entities.firstWhere((e) => e.isNote, orElse: () => null) != null;
|
|
}
|
|
|
|
bool get hasNotesRecursive {
|
|
bool has = hasNotes;
|
|
if (has) return true;
|
|
|
|
for (var i = 0; i < _entities.length; i++) {
|
|
var e = _entities[i];
|
|
if (e.isNote) continue;
|
|
|
|
has = has || e.folder.hasNotes;
|
|
if (has) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return has;
|
|
}
|
|
|
|
int get numberOfNotes {
|
|
int i = 0;
|
|
_entities.forEach((e) {
|
|
if (e.isNote) i++;
|
|
});
|
|
return i;
|
|
}
|
|
|
|
List<Note> getNotes() {
|
|
return _entities.where((e) => e.isNote).map((e) => e.note).toList();
|
|
}
|
|
|
|
List<NotesFolder> getFolders() {
|
|
var list = _entities.where((e) => e.isFolder).map((e) => e.folder).toList();
|
|
list.sort();
|
|
return list;
|
|
}
|
|
|
|
// FIXME: This asynchronously loads everything. Maybe it should just list them, and the individual _entities
|
|
// should be loaded as required?
|
|
Future<void> loadRecursively() async {
|
|
await load();
|
|
_entities.forEach((e) {
|
|
if (e.isFolder) {
|
|
e.folder.loadRecursively();
|
|
} else {
|
|
// FIXME: Collected all the Errors, and report them back, along with "WHY", and the contents of the Note
|
|
// Each of these needs to be reported to crashlytics, as Note loading should never fail
|
|
e.note.load();
|
|
}
|
|
});
|
|
}
|
|
|
|
// FIXME: This should not reconstruct the Notes or NotesFolders once constructed.
|
|
Future<void> load() async {
|
|
Set<String> pathsFound = {};
|
|
|
|
var entitiesAdded = false;
|
|
var entitiesRemoved = false;
|
|
|
|
final dir = Directory(folderPath);
|
|
var lister = dir.list(recursive: false, followLinks: false);
|
|
await for (var fsEntity in lister) {
|
|
if (fsEntity is Link) {
|
|
continue;
|
|
}
|
|
|
|
// If already seen before
|
|
var existingNoteFSEntity = _entityMap[fsEntity.path];
|
|
if (existingNoteFSEntity != null) {
|
|
pathsFound.add(fsEntity.path);
|
|
continue;
|
|
}
|
|
|
|
if (fsEntity is Directory) {
|
|
var subFolder = NotesFolder(this, fsEntity.path);
|
|
if (subFolder.name.startsWith('.')) {
|
|
continue;
|
|
}
|
|
subFolder.addListener(_entityChanged);
|
|
|
|
var noteFSEntity = NoteFSEntity(folder: subFolder);
|
|
_entities.add(noteFSEntity);
|
|
_entityMap[fsEntity.path] = noteFSEntity;
|
|
|
|
pathsFound.add(fsEntity.path);
|
|
entitiesAdded = true;
|
|
continue;
|
|
}
|
|
|
|
var note = Note(this, fsEntity.path);
|
|
if (!note.filePath.toLowerCase().endsWith('.md')) {
|
|
continue;
|
|
}
|
|
note.addListener(_entityChanged);
|
|
|
|
var noteFSEntity = NoteFSEntity(note: note);
|
|
_entities.add(noteFSEntity);
|
|
_entityMap[fsEntity.path] = noteFSEntity;
|
|
|
|
pathsFound.add(fsEntity.path);
|
|
entitiesAdded = true;
|
|
}
|
|
|
|
Set<String> pathsRemoved = _entityMap.keys.toSet().difference(pathsFound);
|
|
pathsRemoved.forEach((path) {
|
|
var e = _entityMap[path];
|
|
assert(e != null);
|
|
|
|
if (e.isFolder) {
|
|
e.folder.removeListener(_entityChanged);
|
|
} else {
|
|
e.note.removeListener(_entityChanged);
|
|
}
|
|
|
|
_entityMap.remove(path);
|
|
});
|
|
_entities.removeWhere((e) {
|
|
String path = e.isFolder ? e.folder.folderPath : e.note.filePath;
|
|
return pathsRemoved.contains(path);
|
|
});
|
|
|
|
entitiesRemoved = pathsRemoved.isNotEmpty;
|
|
if (entitiesAdded || entitiesRemoved) {
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
void add(Note note) {
|
|
assert(note.parent == this);
|
|
note.addListener(_entityChanged);
|
|
|
|
var entity = NoteFSEntity(note: note);
|
|
_entities.add(entity);
|
|
_entityMap[note.filePath] = entity;
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
void insert(int index, Note note) {
|
|
assert(note.parent == this);
|
|
assert(index >= 0);
|
|
note.addListener(_entityChanged);
|
|
|
|
if (_entities.isEmpty) {
|
|
var entity = NoteFSEntity(note: note);
|
|
_entities.add(entity);
|
|
_entityMap[note.filePath] = entity;
|
|
notifyListeners();
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i < _entities.length; i++) {
|
|
var e = _entities[i];
|
|
if (e is NotesFolder) continue;
|
|
|
|
if (index == 0) {
|
|
var entity = NoteFSEntity(note: note);
|
|
_entities.insert(i, entity);
|
|
_entityMap[note.filePath] = entity;
|
|
notifyListeners();
|
|
return;
|
|
}
|
|
index--;
|
|
}
|
|
}
|
|
|
|
void remove(Note note) {
|
|
assert(note.parent == this);
|
|
note.removeListener(_entityChanged);
|
|
|
|
var i = _entities.indexWhere((e) {
|
|
if (e.isFolder) return false;
|
|
return e.note.filePath == note.filePath;
|
|
});
|
|
assert(i != -1);
|
|
|
|
_entities.removeAt(i);
|
|
_entityMap.remove(note.filePath);
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
void create() {
|
|
// Git doesn't track Directories, only files, so we create an empty .gitignore file
|
|
// in the directory instead.
|
|
var gitIgnoreFilePath = p.join(folderPath, ".gitignore");
|
|
var file = File(gitIgnoreFilePath);
|
|
if (!file.existsSync()) {
|
|
file.createSync(recursive: true);
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
void addFolder(NotesFolder folder) {
|
|
assert(folder.parent == this);
|
|
folder.addListener(_entityChanged);
|
|
|
|
var entity = NoteFSEntity(folder: folder);
|
|
_entities.add(entity);
|
|
_entityMap[folder.folderPath] = entity;
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
void removeFolder(NotesFolder folder) {
|
|
folder.removeListener(_entityChanged);
|
|
|
|
var i = _entities.indexWhere((e) {
|
|
if (e.isNote) return false;
|
|
return e.folder.folderPath == folder.folderPath;
|
|
});
|
|
assert(i != -1);
|
|
|
|
_entities.removeAt(i);
|
|
_entityMap.remove(folder.folderPath);
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
void rename(String newName) {
|
|
var dir = Directory(folderPath);
|
|
var parentDirName = dirname(folderPath);
|
|
dir.renameSync(folderPath);
|
|
folderPath = p.join(parentDirName, newName);
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
String pathSpec() {
|
|
if (parent == null) {
|
|
return "";
|
|
}
|
|
return p.join(parent.pathSpec(), name);
|
|
}
|
|
|
|
@override
|
|
int compareTo(NotesFolder other) {
|
|
return folderPath.compareTo(other.folderPath);
|
|
}
|
|
}
|