/
todo-file.service.ts
160 lines (139 loc) · 4.68 KB
/
todo-file.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import { Injectable, OnDestroy } from '@angular/core';
import { TodoTxt, TodoTxtItem } from 'jstodotxt';
import { Subject, Subscription, interval } from 'rxjs';
import { FileService } from './file.service';
import { RouterService } from './router.service';
import { SettingsService } from './settings.service';
import { Task, TaskData, getExtensions } from './task';
import { showToast } from './helpers/toast';
const FILE_WATCH_INTERVAL = 60 * 1000;
@Injectable({
providedIn: 'root',
})
export class TodoFileService implements OnDestroy {
content = '';
fileLoaded: Promise<void>;
fileChanged: Subject<boolean>;
private watcher: Subscription;
private todoItems: TodoTxtItem[] = [];
constructor(
private file: FileService,
private router: RouterService,
private settings: SettingsService,
) {
this.fileChanged = new Subject();
// Initial load
if (settings.path) {
this.initialLoad();
}
// Periodic reload
this.watcher = interval(FILE_WATCH_INTERVAL).subscribe(() => {
if (!settings.path) {
// No path to watch
return;
}
this.load(true);
});
}
initialLoad(): Promise<void> {
// Called on init and every time the file is switched
this.fileLoaded = this.load();
return this.fileLoaded;
}
ngOnDestroy(): void {
// Stop file watcher when service is destroyed
this.watcher.unsubscribe();
}
getTask(taskId: number): Task {
return new Task(this.todoItems[taskId - 1]);
}
getTasks(): Task[] {
return this.todoItems.map((todoItem, index) => {
const task = new Task(todoItem);
// Set IDs (line number, starting with 1, similar to todo.sh)
// TODO: index can change if file has been updated from another device
// TODO: use UUIDs?
task.id = index + 1;
return task;
});
}
getProjects(): string[] {
const projects = new Set<string>();
this.todoItems.forEach((todoItem: TodoTxtItem) => {
(todoItem.projects || []).forEach((project: string) => {
projects.add(project);
});
});
return Array.from(projects).sort();
}
getContexts(): string[] {
const contexts = new Set<string>();
this.todoItems.forEach((todoItem: TodoTxtItem) => {
(todoItem.contexts || []).forEach((context: string) => {
contexts.add(context);
});
});
return Array.from(contexts).sort();
}
async createTask(taskData: TaskData): Promise<void> {
const task = Task.create(taskData);
await this.appendTask(task);
}
async appendTask(task: Task): Promise<void> {
// Append to the end of file
this.todoItems.push(task.todoItem);
await this.save();
}
async updateTask(taskId: number, taskData: TaskData): Promise<void> {
const task = new Task(this.todoItems[taskId - 1]);
task.update(taskData);
await this.save();
}
async replaceTask(taskId: number, task: Task): Promise<void> {
this.todoItems[taskId - 1] = task.todoItem;
await this.save();
}
async removeTask(taskId: number): Promise<void> {
delete this.todoItems[taskId - 1]; // Keeps task IDs intact
await this.save();
}
async load(watch = false): Promise<void> {
let content;
try {
content = await this.file.read(this.settings.path);
} catch (error) {
if (watch) {
// Ignore error
console.warn(error);
} else {
this.router.navigate(['/settings']);
}
return;
}
if (watch && this.content && this.content === content) {
// No changes for watcher
return;
}
this.content = content;
if (this.content === '') {
this.todoItems = [];
} else {
this.todoItems = TodoTxt.parse(this.content, getExtensions());
}
this.fileChanged.next(true); // true = IDs are probably changed
try {
showToast('File loaded');
} catch (error) {
// Ignore error if view is not ready
console.warn(error);
}
}
private async save(): Promise<void> {
this.content = TodoTxt.render(this.todoItems);
await this.file.write(this.settings.path, this.content);
this.fileChanged.next(false); // false => IDs are not changed
}
async create(): Promise<string> {
return this.file.create('todo.txt');
}
}