Skip to content

Commit

Permalink
use Automaton for easier code management.
Browse files Browse the repository at this point in the history
This should help, if more features are coming. Processor is now the single point of reusable code. Automaton.ts is the stuff, who processes the steps. So now you can go to a specific step and they do not interrupt each other. The automaton stops if there is happening any error.
  • Loading branch information
Heiss committed Jun 26, 2024
1 parent 5a9337a commit 9ef73cd
Show file tree
Hide file tree
Showing 9 changed files with 462 additions and 245 deletions.
4 changes: 1 addition & 3 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {Notice, Plugin} from 'obsidian';
import {DEFAULT_SETTINGS, SettingTab, VikunjaPluginSettings} from "./src/settings/SettingTab";
import {MainModal} from "./src/modals/mainModal";
import {DEFAULT_SETTINGS, SettingTab, VikunjaPluginSettings} from "./src/settings/settingTab";
import {Tasks} from "./src/vikunja/tasks";
import {Processor} from "./src/processing/processor";
import {UserUser} from "./vikunja_sdk";
Expand Down Expand Up @@ -96,7 +95,6 @@ export default class VikunjaPlugin extends Plugin {
// Called when the user clicks the icon.
new Notice('Start syncing with Vikunja');
await this.processor.exec();
new Notice('Syncing with Vikunja finished');
});

// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "vikunja-sync",
"name": "Vikunja Sync",
"version": "1.0.3",
"version": "1.0.4",
"minAppVersion": "0.15.0",
"description": "Integrates Vikunja into Obsidian as Task Management Platform.",
"author": "Peter Heiss",
Expand Down
79 changes: 79 additions & 0 deletions src/processing/automaton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import VikunjaPlugin from "../../main";
import {App} from "obsidian";
import {ModelsTask} from "../../vikunja_sdk";
import {PluginTask} from "../vaultSearcher/vaultSearcher";
import {GetTasks} from "./getTasks";
import {RemoveTasks} from "./removeTasks";
import UpdateTasks from "./updateTasks";
import CreateTasks from "./createTasks";
import {Processor} from "./processor";

interface StepsOutput {
localTasks: PluginTask[];
vikunjaTasks: ModelsTask[];
}

interface IAutomatonSteps {
step(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]): Promise<StepsOutput>;

}

enum AutomatonStatus {
READY,
RUNNING,
ERROR,
FINISHED,
}

class Automaton {
app: App;
plugin: VikunjaPlugin;
steps: IAutomatonSteps[];
currentStep: number = 0;
status: AutomatonStatus;
processor: Processor;

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

this.steps = [
new GetTasks(app, plugin, processor),
new RemoveTasks(app, plugin),
new CreateTasks(app, plugin, processor),
new UpdateTasks(app, plugin, processor),
];

this.status = AutomatonStatus.READY;
}

async run() {
let localTasks: PluginTask[] = [];
let vikunjaTasks: ModelsTask[] = [];
this.status = AutomatonStatus.RUNNING;

while (this.currentStep < this.steps.length) {
let output: StepsOutput;
try {
output = await this.execStep(localTasks, vikunjaTasks);
} catch (e) {
this.status = AutomatonStatus.ERROR;
console.error("Automaton: Error in step " + this.currentStep + ", Error: " + e);
return;
}
localTasks = output.localTasks;
vikunjaTasks = output.vikunjaTasks;
}

this.status = AutomatonStatus.FINISHED;
}

private async execStep(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]): Promise<StepsOutput> {
return this.steps[this.currentStep++].step(localTasks, vikunjaTasks);
}

}

export {Automaton, AutomatonStatus};
export type {IAutomatonSteps, StepsOutput,};
118 changes: 118 additions & 0 deletions src/processing/createTasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {IAutomatonSteps, StepsOutput} from "./automaton";
import {PluginTask} from "../vaultSearcher/vaultSearcher";
import {ModelsTask} from "../../vikunja_sdk";
import {App, moment, Notice, TFile} from "obsidian";
import {
appHasDailyNotesPluginLoaded,
createDailyNote,
getAllDailyNotes,
getDailyNote
} from "obsidian-daily-notes-interface";
import {chooseOutputFile} from "../enums";
import VikunjaPlugin from "../../main";
import {Processor} from "./processor";

class CreateTasks implements IAutomatonSteps {
app: App;
plugin: VikunjaPlugin;
processor: Processor;

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

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

return {localTasks, vikunjaTasks};
}

private async createTasks(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]) {
if (this.plugin.settings.debugging) console.log("Step CreateTask: Creating labels in Vikunja", localTasks);
localTasks = await this.createLabels(localTasks);

if (this.plugin.settings.debugging) console.log("Step CreateTask: Creating tasks in Vikunja and vault", localTasks, vikunjaTasks);
await this.pullTasksFromVikunjaToVault(localTasks, vikunjaTasks);
await this.pushTasksFromVaultToVikunja(localTasks, vikunjaTasks);
}

private async createLabels(localTasks: PluginTask[]) {
return await Promise.all(localTasks
.map(async task => {
if (!task.task) throw new Error("Task is not defined");
if (!task.task.labels) return task;

task.task.labels = await this.plugin.labelsApi.getAndCreateLabels(task.task.labels);
if (this.plugin.settings.debugging) console.log("Step CreateTask: Preparing labels for local tasks for vikunja update", task);
return task;
}
));
}

private async pushTasksFromVaultToVikunja(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]) {
const tasksToPushToVikunja = localTasks.filter(task => !vikunjaTasks.find(vikunjaTask => vikunjaTask.id === task.task.id));
if (this.plugin.settings.debugging) console.log("Step CreateTask: Pushing tasks to vikunja", tasksToPushToVikunja);
const createdTasksInVikunja = await this.plugin.tasksApi.createTasks(tasksToPushToVikunja.map(task => task.task));
if (this.plugin.settings.debugging) console.log("Step CreateTask: Created tasks in vikunja", createdTasksInVikunja);

const tasksToUpdateInVault = localTasks.map(task => {
const createdTask = createdTasksInVikunja.find((vikunjaTask: ModelsTask) => vikunjaTask.title === task.task.title);
if (createdTask) {
task.task = createdTask;
}
return task;
});
for (const task of tasksToUpdateInVault) {
await this.processor.updateToVault(task);
}
}

private async pullTasksFromVikunjaToVault(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]) {
if (this.plugin.settings.debugging) console.log("Step CreateTask: Pulling tasks from vikunja to vault, vault tasks", localTasks, "vikunja tasks", vikunjaTasks);

const tasksToPushToVault = vikunjaTasks.filter(task => !localTasks.find(vaultTask => vaultTask.task.id === task.id));
if (this.plugin.settings.debugging) console.log("Step CreateTask: Pushing tasks to vault", tasksToPushToVault);

const createdTasksInVault: PluginTask[] = [];
for (const task of tasksToPushToVault) {
let file: TFile;
const chosenFile = this.app.vault.getFileByPath(this.plugin.settings.chosenOutputFile);
const date = moment();
const dailies = getAllDailyNotes()

switch (this.plugin.settings.chooseOutputFile) {
case chooseOutputFile.File:
if (!chosenFile) throw new Error("Output file not found");
file = chosenFile;
break;
case chooseOutputFile.DailyNote:
if (!appHasDailyNotesPluginLoaded()) {
new Notice("Daily notes core plugin is not loaded. So we cannot create daily note. Please install daily notes core plugin. Interrupt now.")
continue;
}

file = getDailyNote(date, dailies)
if (file == null) {
file = await createDailyNote(date)
}
break;
default:
throw new Error("No valid chooseOutputFile selected");
}
const pluginTask: PluginTask = {
file: file,
lineno: 0,
task: task
};
createdTasksInVault.push(pluginTask);
}

for (const task of createdTasksInVault) {
await this.processor.saveToVault(task);
}
}
}

export default CreateTasks;
48 changes: 48 additions & 0 deletions src/processing/getTasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {PluginTask, VaultSearcher} from "src/vaultSearcher/vaultSearcher";
import {ModelsTask} from "vikunja_sdk";
import {IAutomatonSteps, StepsOutput} from "./automaton";
import VikunjaPlugin from "../../main";
import {App} from "obsidian";
import {TaskParser} from "../taskFormats/taskFormats";
import {Processor} from "./processor";

class GetTasks implements IAutomatonSteps {
app: App;
plugin: VikunjaPlugin;
vaultSearcher: VaultSearcher;
taskParser: TaskParser;
processor: Processor;

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

this.vaultSearcher = this.processor.getVaultSearcher();
this.taskParser = this.processor.getTaskParser();
}

async step(_1: PluginTask[], _2: ModelsTask[]): Promise<StepsOutput> {
// Get all tasks in vikunja and vault
if (this.plugin.settings.debugging) console.log("Step GetTask: Pulling tasks from vault");
const localTasks = await this.getTasksFromVault();
if (this.plugin.settings.debugging) console.log("Step GetTask: Got tasks from vault", localTasks);

if (this.plugin.settings.debugging) console.log("Step GetTask: Pulling tasks from Vikunja");
const vikunjaTasks = await this.getTasksFromVikunja();
if (this.plugin.settings.debugging) console.log("Step GetTask: Got tasks from Vikunja", vikunjaTasks);
return {localTasks, vikunjaTasks};
}

private async getTasksFromVault(): Promise<PluginTask[]> {
return await this.vaultSearcher.getTasks(this.taskParser);
}

private async getTasksFromVikunja(): Promise<ModelsTask[]> {
return await this.plugin.tasksApi.getAllTasks();
}

}


export {GetTasks};
Loading

0 comments on commit 9ef73cd

Please sign in to comment.