Skip to content

Commit

Permalink
first RC for cache, tests needed
Browse files Browse the repository at this point in the history
  • Loading branch information
Heiss committed Aug 1, 2024
1 parent f07f6b6 commit c9f71e1
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 38 deletions.
27 changes: 23 additions & 4 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {UserUser} from "./vikunja_sdk";
import {Label} from "./src/vikunja/labels";
import Commands from "./src/commands";
import {Projects} from "./src/vikunja/projects";
import VaultTaskCache from "./src/settings/VaultTaskCache";

// Remember to rename these classes and interfaces!

Expand All @@ -17,6 +18,7 @@ export default class VikunjaPlugin extends Plugin {
processor: Processor;
commands: Commands;
projectsApi: Projects;
cache: VaultTaskCache;

async onload() {
await this.loadSettings();
Expand Down Expand Up @@ -88,6 +90,7 @@ export default class VikunjaPlugin extends Plugin {
this.userObject = undefined;
this.labelsApi = new Label(this.app, this);
this.projectsApi = new Projects(this.app, this);
this.cache = new VaultTaskCache(this.app, this);
}

private setupCommands() {
Expand Down Expand Up @@ -122,14 +125,30 @@ export default class VikunjaPlugin extends Plugin {
})
}

private async handleEditorChange() {
if (this.settings.debugging) console.log("Editor changed");
private async handleEditorChange(data: any) {
if (this.settings.debugging) console.log("Editor changed", data);
const currentFile = this.app.workspace.getActiveFile();
if (!currentFile) {
if (this.settings.debugging) console.log("No file open");
return;
}

const tasks = await this.processor.getVaultSearcher().getTasksFromFile(this.processor.getTaskParser(), currentFile);
for (const task of tasks) {
if (task.task.id) {
const cachedTask = this.cache.get(task.task.id);
if (cachedTask === undefined || !cachedTask.isEquals(task)) {
this.cache.update(task);
}
}
}
// FIXME the update line stuff should be communicated in settings
return;
//await this.checkLastLineForUpdate();
}

private async handleUpDownEvent(evt: KeyboardEvent) {
if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown' || evt.key === 'PageUp' || evt.key === 'PageDown') {
if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown' || evt.key === 'PageUp' || evt.key === 'PageDown' || evt.key === "Enter") {
if (this.settings.debugging) console.log("Line changed via keys");
await this.checkLastLineForUpdate();
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/processing/checkCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {ModelsTask} from "vikunja_sdk";
import {IAutomatonSteps, StepsOutput} from "./automaton";
import VikunjaPlugin from "../../main";
import {App} from "obsidian";
import {Processor} from "./processor";
import {compareModelTasks, Processor} from "./processor";

export default class CheckCache implements IAutomatonSteps {
plugin: VikunjaPlugin;
Expand All @@ -17,18 +17,18 @@ export default class CheckCache implements IAutomatonSteps {
}

async step(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]): Promise<StepsOutput> {
this.updateCacheFromVault(localTasks, vikunjaTasks);
this.updateCacheFromVault(localTasks);
return {localTasks, vikunjaTasks};
}

updateCacheFromVault(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]) {
updateCacheFromVault(localTasks: PluginTask[]) {
const tasksWithId = localTasks.filter(task => {
if (task.task.id === undefined) return false; // task has no id, so it is not in the cache, because not synced to vikunja

const elem = this.plugin.settings.cache.get(task.task.id)
const elem = this.plugin.cache.get(task.task.id)
if (elem === undefined) return false; // task is not in the cache, because not synced to vikunja

return !elem.isEquals(task); // filter elem, if it is equal to task in cache. False filters out.
return !compareModelTasks(elem.task, task.task); // filter elem, if it is equal to task in cache. False filters out.
}
);

Expand Down
28 changes: 18 additions & 10 deletions src/processing/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,7 @@ class Processor {
}
});

if (task.task.id !== undefined) {
this.plugin.settings.cache.set(task.task.id, task);
}
this.plugin.cache.update(task);
}

getTaskContent(task: PluginTask): string {
Expand Down Expand Up @@ -183,17 +181,17 @@ class Processor {
if (updatedTask.task.id === undefined) {
return undefined;
}
const cacheTask = this.plugin.settings.cache.get(updatedTask.task.id);
const cacheTask = this.plugin.cache.get(updatedTask.task.id);
if (cacheTask === undefined) {
if (this.plugin.settings.debugging) console.error("Processor: Should not be here, because if this task is not in cache, but has an id, it circumvented the cache.")
return undefined;
}
if (cacheTask.isEquals(updatedTask)) {
if (compareModelTasks(updatedTask.task, cacheTask.task)) {
// Cache and current task are equal, so no update is needed
return undefined;
}
// no guard check fires, so there is an update.
this.plugin.settings.cache.set(updatedTask.task.id, updatedTask);
this.plugin.cache.update(updatedTask);
} catch (e) {
if (this.plugin.settings.debugging) console.log("Processor: Error while parsing task", e);
}
Expand All @@ -215,9 +213,7 @@ class Processor {
return lines.join("\n");
});

if (task.task.id !== undefined) {
this.plugin.settings.cache.set(task.task.id, task);
}
this.plugin.cache.update(task);
}

getVaultSearcher(): VaultSearcher {
Expand Down Expand Up @@ -319,7 +315,19 @@ class Processor {
await this.plugin.tasksApi.deleteTask(task.task);
}
}
}

function compareModelTasks(local: ModelsTask, vikunja: ModelsTask): boolean {
const title = local.title === vikunja.title;
const description = local.description === vikunja.description;
const dueDate = local.dueDate === vikunja.dueDate;
const labels = local.labels?.filter(label => vikunja.labels?.find(vikunjaLabel => vikunjaLabel.title === label.title)).length === local.labels?.length;
const priority = local.priority === vikunja.priority;
const status = local.done === vikunja.done;
const doneAt = local.doneAt === vikunja.doneAt;
const updated = local.updated === vikunja.updated;

return title && description && dueDate && labels && priority && status && doneAt && updated;
}

export {Processor};
export {Processor, compareModelTasks};
9 changes: 5 additions & 4 deletions src/processing/updateTasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@ class UpdateTasks implements IAutomatonSteps {
*/
private splitTaskAfterUpdatedStatus(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]): UpdatedSplit {
if (this.plugin.settings.debugging) console.log("Step UpdateTask: Find tasks which have updates on the other platform");
const cacheLocalTasks = this.plugin.cache.getCachedTasks();

// TODO Check for localTasks which circumvent the cache, how do i find this out!?

let tasksToUpdateInVault: PluginTask[] = [];
let tasksToUpdateInVikunja: PluginTask[] = [];
for (const task of localTasks) {
for (const task of cacheLocalTasks) {
const vikunjaTask = vikunjaTasks.find(vikunjaTask => vikunjaTask.id === task.task.id);
if (this.plugin.settings.debugging) console.log("Step UpdateTask: found Vikunja task", vikunjaTask, " for Vault task", task.task);
if (!vikunjaTask) continue;
Expand Down Expand Up @@ -90,9 +93,7 @@ class UpdateTasks implements IAutomatonSteps {
private async updateTasksInVikunja(updateTasks: PluginTask[]) {
if (this.plugin.settings.debugging) console.log("Step UpdateTask: Update tasks in vikunja");

for (const task of updateTasks) {
await this.plugin.tasksApi.updateTask(task.task);
}
await Promise.all(updateTasks.map(task => this.plugin.tasksApi.updateTask(task.task)));
}

private async updateTasksInVault(updateTasks: PluginTask[]) {
Expand Down
51 changes: 51 additions & 0 deletions src/settings/VaultTaskCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import VikunjaPlugin from "../../main";
import {App, moment} from "obsidian";
import {PluginTask} from "../vaultSearcher/vaultSearcher";

/*
* This class is used to cache tasks which are updated in Vault, but not in Vikunja.
* This should help to identify modifications in vault without Obsidian.
* Also it makes it possible to update only modified tasks. See issue #9 for more details.
*/
export default class VaultTaskCache {
plugin: VikunjaPlugin;
app: App;

constructor(app: App, plugin: VikunjaPlugin) {
this.app = app;
this.plugin = plugin;
}

update(local: PluginTask) {
if (local.task.id === undefined) {
throw new Error("VaultTaskCache: Task id is not defined");
}
const currentDate = moment().format("YYYY-MM-DDTHH:mm:ss[Z]");
if (this.plugin.settings.debugging) console.log("VaultTaskCache: Updating task", local.task.id, "with updated date", currentDate);
local.task.updated = currentDate;

this.plugin.settings.cache.set(local.task.id, local);
}

get(id: number): PluginTask | undefined {
return this.plugin.settings.cache.get(id);
}

/*
* Useful, when tasks are updated in vikunja and so the task in the cache is outdated.
*/
delete(id: number) {
this.plugin.settings.cache.delete(id);
}

/*
* Do not forget to call delete after processing the tasks.
*/
getCachedTasks(): PluginTask[] {
return Array.from(this.plugin.settings.cache.values());
}

reset() {
this.plugin.settings.cache.clear();
}
}
2 changes: 1 addition & 1 deletion src/settings/mainSetting.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {App, Notice, PluginSettingTab, Setting} from "obsidian";
import VikunjaPlugin from "../../main";
import {backendToFindTasks, chooseOutputFile, supportedTasksPluginsFormat} from "../enums";
import {ModelsProject, ModelsProjectView} from "../../vikunja_sdk";
import {ModelsProject, ModelsProjectView, ModelsTask} from "../../vikunja_sdk";
import {appHasDailyNotesPluginLoaded} from "obsidian-daily-notes-interface";
import {PluginTask} from "../vaultSearcher/vaultSearcher";

Expand Down
31 changes: 28 additions & 3 deletions src/vaultSearcher/dataviewSearcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {App, moment} from "obsidian";
import {App, moment, TFile} from "obsidian";
import VikunjaPlugin from "../../main";
import {DataviewApi, getAPI} from "obsidian-dataview";
import {PluginTask, VaultSearcher} from "./vaultSearcher";
Expand All @@ -15,10 +15,31 @@ export class DataviewSearcher implements VaultSearcher {
this.dataviewPlugin = getAPI(this.app);
}

async getTasksFromFile(parser: TaskParser, file: TFile): Promise<PluginTask[]> {
const dv = this.dataviewPlugin;
let tasks = undefined;

const page = dv.page(file.path);
if (page === undefined) {
console.error("DataviewSearcher: Could not find page for file", file);
return [];
}
if (page.file.tasks === undefined) {
console.error("DataviewSearcher: Could not find tasks for page", page);
return [];
}
tasks = page.file.tasks.values;
return await this.parseTasks(tasks, parser);
}

async getTasks(parser: TaskParser): Promise<PluginTask[]> {
const dv = this.dataviewPlugin;
const tasks = dv.pages().file.tasks.values;

return await this.parseTasks(tasks, parser);
}

private async parseTasks(tasks: any, parser: TaskParser) {
if (this.plugin.settings.debugging) console.log("DataviewSearcher: Found dataview tasks", tasks);

const tasksFormatted: PluginTask[] = [];
Expand All @@ -34,13 +55,17 @@ export class DataviewSearcher implements VaultSearcher {
console.error("DataviewSearcher: Could not find file for task", task);
continue;
}
const cachedTask = this.plugin.settings.cache.get(task.id);
let cachedTask = undefined;
const id = parsed.id;
if (id !== undefined) {
cachedTask = this.plugin.cache.get(id);
}
if (cachedTask !== undefined) {
if (this.plugin.settings.debugging) console.log("DataviewSearcher: Found cached task", cachedTask);
parsed.updated = cachedTask.task.updated;
} else {
if (this.plugin.settings.debugging) console.log("DataviewSearcher: Fallback to file modified date");
parsed.updated = moment(file.stat.mtime).format("YYYY-MM-DDTHH:mm:ss[Z]");
parsed.updated = moment(file.stat.ctime).format("YYYY-MM-DDTHH:mm:ss[Z]");
}

const vaultParsed = new PluginTask(file, task.line, parsed);
Expand Down
14 changes: 4 additions & 10 deletions src/vaultSearcher/vaultSearcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {ModelsTask} from "../../vikunja_sdk";
import {TaskParser} from "../taskFormats/taskFormats";
import {TFile} from "obsidian";
import {compareModelTasks} from "../processing/processor";

class PluginTask {
file: TFile;
Expand All @@ -22,21 +23,14 @@ class PluginTask {
}

isTaskEqual(vikunja: ModelsTask): boolean {
const title = this.task.title === vikunja.title;
const description = this.task.description === vikunja.description;
const dueDate = this.task.dueDate === vikunja.dueDate;
const labels = this.task.labels?.filter(label => vikunja.labels?.find(vikunjaLabel => vikunjaLabel.title === label.title)).length === this.task.labels?.length;
const priority = this.task.priority === vikunja.priority;
const status = this.task.done === vikunja.done;
const doneAt = this.task.doneAt === vikunja.doneAt;
const updated = this.task.updated === vikunja.updated;

return title && description && dueDate && labels && priority && status && doneAt && updated;
return compareModelTasks(this.task, vikunja);
}
}

interface VaultSearcher {
getTasks(parser: TaskParser): Promise<PluginTask[]>;

getTasksFromFile(parser: TaskParser, file: TFile): Promise<PluginTask[]>;
}

export type {VaultSearcher};
Expand Down
2 changes: 1 addition & 1 deletion src/vikunja/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Tasks implements VikunjaAPI {
await this.addLabelToTask(task);

const param: TasksIdPostRequest = {id: task.id, task: task};
return this.tasksApi.tasksIdPost(param);
return await this.tasksApi.tasksIdPost(param);
}

async updateTasks(tasks: ModelsTask[]): Promise<ModelsTask[]> {
Expand Down

0 comments on commit c9f71e1

Please sign in to comment.