diff --git a/main.ts b/main.ts index 6491910..e550549 100644 --- a/main.ts +++ b/main.ts @@ -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"; @@ -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. diff --git a/manifest.json b/manifest.json index 51b8851..f7e21dc 100644 --- a/manifest.json +++ b/manifest.json @@ -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", diff --git a/src/processing/automaton.ts b/src/processing/automaton.ts new file mode 100644 index 0000000..d3a32f1 --- /dev/null +++ b/src/processing/automaton.ts @@ -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; + +} + +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 { + return this.steps[this.currentStep++].step(localTasks, vikunjaTasks); + } + +} + +export {Automaton, AutomatonStatus}; +export type {IAutomatonSteps, StepsOutput,}; diff --git a/src/processing/createTasks.ts b/src/processing/createTasks.ts new file mode 100644 index 0000000..adf1aae --- /dev/null +++ b/src/processing/createTasks.ts @@ -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 { + 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; diff --git a/src/processing/getTasks.ts b/src/processing/getTasks.ts new file mode 100644 index 0000000..355657f --- /dev/null +++ b/src/processing/getTasks.ts @@ -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 { + // 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 { + return await this.vaultSearcher.getTasks(this.taskParser); + } + + private async getTasksFromVikunja(): Promise { + return await this.plugin.tasksApi.getAllTasks(); + } + +} + + +export {GetTasks}; diff --git a/src/processing/processor.ts b/src/processing/processor.ts index da95dfa..9247167 100644 --- a/src/processing/processor.ts +++ b/src/processing/processor.ts @@ -1,22 +1,13 @@ import Plugin from "../../main"; -import {App, MarkdownView, moment, Notice, TFile} from "obsidian"; -import {backendToFindTasks, chooseOutputFile, supportedTasksPluginsFormat} from "../enums"; +import {App, MarkdownView, Notice} from "obsidian"; import {PluginTask, VaultSearcher} from "../vaultSearcher/vaultSearcher"; -import {DataviewSearcher} from "../vaultSearcher/dataviewSearcher"; import {TaskFormatter, TaskParser} from "../taskFormats/taskFormats"; +import {Automaton, AutomatonStatus, IAutomatonSteps} from "./automaton"; +import UpdateTasks from "./updateTasks"; import {EmojiTaskFormatter, EmojiTaskParser} from "../taskFormats/emojiTaskFormat"; -import {ModelsTask} from "../../vikunja_sdk"; -import { - appHasDailyNotesPluginLoaded, - createDailyNote, - getAllDailyNotes, - getDailyNote -} from "obsidian-daily-notes-interface"; - -interface UpdatedSplit { - tasksToUpdateInVault: PluginTask[]; - tasksToUpdateInVikunja: PluginTask[]; -} +import {backendToFindTasks, supportedTasksPluginsFormat} from "../enums"; +import {DataviewSearcher} from "../vaultSearcher/dataviewSearcher"; + class Processor { app: App; @@ -26,6 +17,7 @@ class Processor { taskFormatter: TaskFormatter; private alreadyUpdateTasksOnStartup = false; private lastLineChecked: Map; + private automaton: Automaton; constructor(app: App, plugin: Plugin) { this.app = app; @@ -34,12 +26,16 @@ class Processor { this.vaultSearcher = this.getVaultSearcher(); this.taskParser = this.getTaskParser(); this.taskFormatter = this.getTaskFormatter(); + this.lastLineChecked = new Map(); } - + /* + * The main method to sync tasks between Vikunja and Obsidian. + */ async exec() { if (this.plugin.settings.debugging) console.log("Processor: Start processing"); + if (this.plugin.foundProblem) { new Notice("Vikunja Plugin: Found problems in plugin. Have to be fixed first. Syncing is stopped."); if (this.plugin.settings.debugging) console.log("Processor: Found problems in plugin. Have to be fixed first."); @@ -52,57 +48,23 @@ class Processor { //this.plugin.userObject = await new User(this.app, this.plugin).getUser(); } - // Get all tasks in vikunja and vault - if (this.plugin.settings.debugging) console.log("Processor: Pulling tasks from vault"); - let localTasks = await this.vaultSearcher.getTasks(this.taskParser); - if (this.plugin.settings.debugging) console.log("Processor: Got tasks from vault", localTasks); - - if (this.plugin.settings.debugging) console.log("Processor: Pulling tasks from Vikunja"); - const vikunjaTasksBeforeDeletion = await this.plugin.tasksApi.getAllTasks(); - if (this.plugin.settings.debugging) console.log("Processor: Got tasks from Vikunja", vikunjaTasksBeforeDeletion); - - const { - tasksToUpdateInVault, - tasksToUpdateInVikunja - } = this.splitTaskAfterUpdatedStatus(localTasks, vikunjaTasksBeforeDeletion); - if (this.plugin.settings.debugging) console.log("Processor: Split tasks after updated status, outstanding updates in vault", tasksToUpdateInVault, "outstanding updates in vikunja", tasksToUpdateInVikunja); + if (this.plugin.settings.debugging) console.log("Processor: Reset automaton"); + this.automaton = new Automaton(this.app, this.plugin, this); - // Processing steps - if (this.plugin.settings.debugging) console.log("Processor: Deleting tasks and labels in Vikunja"); - const deletedVikunjaTasks = await this.removeTasksInVikunjaIfNotInVault(localTasks, vikunjaTasksBeforeDeletion); - await this.removeLabelsInVikunjaIfNotInVault(localTasks, vikunjaTasksBeforeDeletion); - // Filter out deleted tasks - const vikunjaTasks = vikunjaTasksBeforeDeletion.filter(task => !deletedVikunjaTasks.find(deletedTask => deletedTask.id === task.id)); + await this.automaton.run(); - if (this.plugin.settings.debugging) console.log("Processor: Creating labels in Vikunja", localTasks); - localTasks = await this.createLabels(localTasks); - - if (this.plugin.settings.debugging) console.log("Processor: Creating tasks in Vikunja and vault", localTasks, vikunjaTasks); - await this.pullTasksFromVikunjaToVault(localTasks, vikunjaTasks); - await this.pushTasksFromVaultToVikunja(localTasks, vikunjaTasks); - - if (this.plugin.settings.debugging) console.log("Processor: Updating tasks in vault and Vikunja"); - await this.updateTasksInVikunja(tasksToUpdateInVikunja); - await this.updateTasksInVault(tasksToUpdateInVault); + switch (this.automaton.status) { + case AutomatonStatus.ERROR: + new Notice("Error while syncing tasks"); + break; + case AutomatonStatus.FINISHED: + new Notice("Finished syncing tasks"); + break; + } if (this.plugin.settings.debugging) console.log("Processor: End processing"); } - async getTaskContent(task: PluginTask) { - const content: string = await this.taskFormatter.format(task.task); - return `${content} `; - } - - async updateToVault(task: PluginTask) { - const newTask = await this.getTaskContent(task); - - await this.app.vault.process(task.file, data => { - const lines = data.split("\n"); - lines.splice(task.lineno, 1, newTask); - return lines.join("\n"); - }); - } - async saveToVault(task: PluginTask) { const newTask = await this.getTaskContent(task); @@ -122,13 +84,9 @@ class Processor { }); } - getTaskFormatter(): EmojiTaskFormatter { - switch (this.plugin.settings.useTasksFormat) { - case supportedTasksPluginsFormat.Emoji: - return new EmojiTaskFormatter(this.app, this.plugin); - default: - throw new Error("No valid TaskFormat selected"); - } + async getTaskContent(task: PluginTask) { + const content: string = await this.taskFormatter.format(task.task); + return `${content} `; } /* @@ -155,14 +113,8 @@ class Processor { const localTasks = await this.vaultSearcher.getTasks(this.taskParser); const vikunjaTasks = await this.plugin.tasksApi.getAllTasks(); - const { - tasksToUpdateInVault, - tasksToUpdateInVikunja - } = this.splitTaskAfterUpdatedStatus(localTasks, vikunjaTasks); - - - await this.updateTasksInVault(tasksToUpdateInVault); - await this.updateTasksInVikunja(tasksToUpdateInVikunja); + const updateStep: IAutomatonSteps = new UpdateTasks(this.app, this.plugin, this); + await updateStep.step(localTasks, vikunjaTasks); this.alreadyUpdateTasksOnStartup = true; } @@ -224,20 +176,17 @@ class Processor { return pluginTask; } - 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; + async updateToVault(task: PluginTask) { + const newTask = await this.getTaskContent(task); - task.task.labels = await this.plugin.labelsApi.getAndCreateLabels(task.task.labels); - if (this.plugin.settings.debugging) console.log("Processor: Preparing labels for local tasks for vikunja update", task); - return task; - } - )); + await this.app.vault.process(task.file, (data: string) => { + const lines = data.split("\n"); + lines.splice(task.lineno, 1, newTask); + return lines.join("\n"); + }); } - private getVaultSearcher() { + getVaultSearcher(): VaultSearcher { let vaultSearcher: VaultSearcher; switch (this.plugin.settings.backendToFindTasks) { case backendToFindTasks.Dataview: @@ -250,7 +199,16 @@ class Processor { return vaultSearcher; } - private getTaskParser() { + getTaskFormatter(): EmojiTaskFormatter { + switch (this.plugin.settings.useTasksFormat) { + case supportedTasksPluginsFormat.Emoji: + return new EmojiTaskFormatter(this.app, this.plugin); + default: + throw new Error("No valid TaskFormat selected"); + } + } + + getTaskParser() { let taskParser: TaskParser; switch (this.plugin.settings.useTasksFormat) { case supportedTasksPluginsFormat.Emoji: @@ -262,159 +220,6 @@ class Processor { return taskParser; } - 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("Processor: Pushing tasks to vikunja", tasksToPushToVikunja); - const createdTasksInVikunja = await this.plugin.tasksApi.createTasks(tasksToPushToVikunja.map(task => task.task)); - if (this.plugin.settings.debugging) console.log("Processor: Created tasks in vikunja", createdTasksInVikunja); - - const tasksToUpdateInVault = localTasks.map(task => { - const createdTask = createdTasksInVikunja.find(vikunjaTask => vikunjaTask.title === task.task.title); - if (createdTask) { - task.task = createdTask; - } - return task; - }); - for (const task of tasksToUpdateInVault) { - await this.updateToVault(task); - } - } - - private async pullTasksFromVikunjaToVault(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]) { - if (this.plugin.settings.debugging) console.log("Processor: 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("Processor: 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.saveToVault(task); - } - } - - private async updateTasksInVikunja(updateTasks: PluginTask[]) { - if (this.plugin.settings.debugging) console.log("Processor: Update tasks in vikunja"); - - for (const task of updateTasks) { - await this.plugin.tasksApi.updateTask(task.task); - } - } - - private async updateTasksInVault(updateTasks: PluginTask[]) { - if (this.plugin.settings.debugging) console.log("Processor: Update tasks in vault"); - - for (const task of updateTasks) { - await this.updateToVault(task); - } - } - - /* - * Remove tasks in Vikunja if they are not in the vault anymore. - * Returns the tasks which are not in the vault anymore. Filter it yourself if needed. - */ - private async removeTasksInVikunjaIfNotInVault(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]): Promise { - // Check placed here, so no wrong deletion happens - if (!this.plugin.settings.removeTasksIfInVaultNotFound) { - if (this.plugin.settings.debugging) console.log("Processor: Not deleting tasks in vikunja if ID not found in vault"); - return []; - } - - let tasksToDeleteInVikunja = vikunjaTasks.filter(task => !localTasks.find(vaultTask => vaultTask.task.id === task.id)); - if (this.plugin.settings.debugging) console.log("Processor: Deleting tasks in vikunja", tasksToDeleteInVikunja); - - if (this.plugin.settings.removeTasksOnlyInDefaultProject) { - tasksToDeleteInVikunja = tasksToDeleteInVikunja.filter(task => task.projectId === this.plugin.settings.defaultVikunjaProject); - } - await this.plugin.tasksApi.deleteTasks(tasksToDeleteInVikunja); - - return tasksToDeleteInVikunja; - } - - private async removeLabelsInVikunjaIfNotInVault(localTasks: PluginTask[], _vikunjaTasks: ModelsTask[]) { - if (!this.plugin.settings.removeLabelsIfInVaultNotUsed) { - if (this.plugin.settings.debugging) console.log("Processor: Not deleting labels in vikunja if ID not found in vault"); - return; - } - - for (const task of localTasks) { - if (!task.task.labels) continue; - - const labels = task.task.labels; - if (this.plugin.settings.debugging) console.log("Processor: Found labels which are used in Vault", labels); - await this.plugin.labelsApi.deleteUnusedLabels(labels); - } - } - - /* - * Split tasks into two groups: - * - tasksToUpdateInVault: Tasks which have updates in Vikunja - * - tasksToUpdateInVikunja: Tasks which have updates in the vault - * - * tasksToUpdateInVault has already all informations needed for vault update. - */ - private splitTaskAfterUpdatedStatus(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]): UpdatedSplit { - if (this.plugin.settings.debugging) console.log("Processor: Find tasks which have updates on the other platform"); - - let tasksToUpdateInVault: PluginTask[] = []; - let tasksToUpdateInVikunja: PluginTask[] = []; - for (const task of localTasks) { - const vikunjaTask = vikunjaTasks.find(vikunjaTask => vikunjaTask.id === task.task.id); - if (!vikunjaTask) continue; - if (!vikunjaTask || !vikunjaTask.updated || !task.task.updated) { - if (this.plugin.settings.debugging) console.log("Task updated field is not defined", task, vikunjaTask); - throw new Error("Task updated field is not defined"); - } - - let comparison; - if (vikunjaTask.updated > task.task.updated) { - task.task = vikunjaTask; - tasksToUpdateInVault.push(task); - comparison = "Vikunja"; - } else { - tasksToUpdateInVikunja.push(task); - comparison = "Vault"; - } - if (this.plugin.settings.debugging) console.log(`Processor: Task updated will be updated in ${comparison}, updated on vikunja`, vikunjaTask.updated, " updated on vault", task.task.updated); - } - - return { - tasksToUpdateInVault: tasksToUpdateInVault, - tasksToUpdateInVikunja: tasksToUpdateInVikunja - }; - } } export {Processor}; diff --git a/src/processing/removeTasks.ts b/src/processing/removeTasks.ts new file mode 100644 index 0000000..a3398fa --- /dev/null +++ b/src/processing/removeTasks.ts @@ -0,0 +1,69 @@ +import {IAutomatonSteps, StepsOutput} from "./automaton"; +import {PluginTask} from "../vaultSearcher/vaultSearcher"; +import {ModelsTask} from "../../vikunja_sdk"; +import {App} from "obsidian"; +import VikunjaPlugin from "../../main"; + +class RemoveTasks implements IAutomatonSteps { + app: App; + plugin: VikunjaPlugin; + + constructor(app: App, plugin: VikunjaPlugin) { + this.app = app; + this.plugin = plugin; + } + + async step(localTasks: PluginTask[], vikunjaTasksBeforeDeletion: ModelsTask[]): Promise { + + const vikunjaTasks = await this.removeTasksInVikunja(localTasks, vikunjaTasksBeforeDeletion); + + return {localTasks, vikunjaTasks}; + } + + private async removeTasksInVikunja(localTasks: PluginTask[], vikunjaTasksBeforeDeletion: ModelsTask[]) { + if (this.plugin.settings.debugging) console.log("Step RemoveTask: Deleting tasks and labels in Vikunja"); + const deletedVikunjaTasks = await this.removeTasksInVikunjaIfNotInVault(localTasks, vikunjaTasksBeforeDeletion); + await this.removeLabelsInVikunjaIfNotInVault(localTasks, vikunjaTasksBeforeDeletion); + // Filter out deleted tasks + return vikunjaTasksBeforeDeletion.filter(task => !deletedVikunjaTasks.find(deletedTask => deletedTask.id === task.id)); + } + + /* + * Remove tasks in Vikunja if they are not in the vault anymore. + * Returns the tasks which are not in the vault anymore. Filter it yourself if needed. + */ + private async removeTasksInVikunjaIfNotInVault(localTasks: PluginTask[], vikunjaTasks: ModelsTask[]): Promise { + // Check placed here, so no wrong deletion happens + if (!this.plugin.settings.removeTasksIfInVaultNotFound) { + if (this.plugin.settings.debugging) console.log("Step RemoveTask: Not deleting tasks in vikunja if ID not found in vault"); + return []; + } + + let tasksToDeleteInVikunja = vikunjaTasks.filter(task => !localTasks.find(vaultTask => vaultTask.task.id === task.id)); + if (this.plugin.settings.debugging) console.log("Step RemoveTask: Deleting tasks in vikunja", tasksToDeleteInVikunja); + + if (this.plugin.settings.removeTasksOnlyInDefaultProject) { + tasksToDeleteInVikunja = tasksToDeleteInVikunja.filter(task => task.projectId === this.plugin.settings.defaultVikunjaProject); + } + await this.plugin.tasksApi.deleteTasks(tasksToDeleteInVikunja); + + return tasksToDeleteInVikunja; + } + + private async removeLabelsInVikunjaIfNotInVault(localTasks: PluginTask[], _vikunjaTasks: ModelsTask[]) { + if (!this.plugin.settings.removeLabelsIfInVaultNotUsed) { + if (this.plugin.settings.debugging) console.log("Step RemoveTask: Not deleting labels in vikunja if ID not found in vault"); + return; + } + + for (const task of localTasks) { + if (!task.task.labels) continue; + + const labels = task.task.labels; + if (this.plugin.settings.debugging) console.log("Step RemoveTask: Found labels which are used in Vault", labels); + await this.plugin.labelsApi.deleteUnusedLabels(labels); + } + } +} + +export {RemoveTasks}; diff --git a/src/processing/updateTasks.ts b/src/processing/updateTasks.ts new file mode 100644 index 0000000..32828ff --- /dev/null +++ b/src/processing/updateTasks.ts @@ -0,0 +1,100 @@ +import {IAutomatonSteps, StepsOutput} from "./automaton"; +import {PluginTask} from "../vaultSearcher/vaultSearcher"; +import {ModelsTask} from "../../vikunja_sdk"; +import {App} from "obsidian"; +import VikunjaPlugin from "../../main"; +import {Processor} from "./processor"; + +interface UpdatedSplit { + tasksToUpdateInVault: PluginTask[]; + tasksToUpdateInVikunja: PluginTask[]; +} + +class UpdateTasks 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 { + if (this.plugin.settings.debugging) console.log("Step UpdateTask: Updating tasks in vault and Vikunja"); + const { + tasksToUpdateInVault, + tasksToUpdateInVikunja + } = this.splitTaskAfterUpdatedStatus(localTasks, vikunjaTasks); + if (this.plugin.settings.debugging) console.log("Step UpdateTask: Split tasks after updated status, outstanding updates in vault", tasksToUpdateInVault, "outstanding updates in vikunja", tasksToUpdateInVikunja); + + await this.updateTasks(tasksToUpdateInVikunja, tasksToUpdateInVault); + + return {localTasks, vikunjaTasks}; + } + + + /* + * Split tasks into two groups: + * - tasksToUpdateInVault: Tasks which have updates in Vikunja + * - tasksToUpdateInVikunja: Tasks which have updates in the vault + * + * tasksToUpdateInVault has already all informations needed for vault update. + */ + 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"); + + let tasksToUpdateInVault: PluginTask[] = []; + let tasksToUpdateInVikunja: PluginTask[] = []; + for (const task of localTasks) { + const vikunjaTask = vikunjaTasks.find(vikunjaTask => vikunjaTask.id === task.task.id); + if (!vikunjaTask) continue; + if (!vikunjaTask || !vikunjaTask.updated || !task.task.updated) { + if (this.plugin.settings.debugging) console.log("Step UpdateTask: updated field is not defined", task, vikunjaTask); + throw new Error("Task updated field is not defined"); + } + + let comparison; + if (vikunjaTask.updated > task.task.updated) { + task.task = vikunjaTask; + tasksToUpdateInVault.push(task); + comparison = "Vikunja"; + } else { + tasksToUpdateInVikunja.push(task); + comparison = "Vault"; + } + if (this.plugin.settings.debugging) console.log(`Step UpdateTask: Task updated will be updated in ${comparison}, updated on vikunja`, vikunjaTask.updated, " updated on vault", task.task.updated); + } + + return { + tasksToUpdateInVault: tasksToUpdateInVault, + tasksToUpdateInVikunja: tasksToUpdateInVikunja + }; + } + + private async updateTasks(tasksToUpdateInVikunja: PluginTask[], tasksToUpdateInVault: PluginTask[]) { + if (this.plugin.settings.debugging) console.log("Step UpdateTask: Updating tasks in vault and Vikunja"); + await this.updateTasksInVikunja(tasksToUpdateInVikunja); + await this.updateTasksInVault(tasksToUpdateInVault); + } + + 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); + } + } + + private async updateTasksInVault(updateTasks: PluginTask[]) { + if (this.plugin.settings.debugging) console.log("Step UpdateTask: Update tasks in vault"); + + for (const task of updateTasks) { + await this.processor.updateToVault(task); + } + } + +} + +export default UpdateTasks; diff --git a/src/settings/SettingTab.ts b/src/settings/settingTab.ts similarity index 100% rename from src/settings/SettingTab.ts rename to src/settings/settingTab.ts