diff --git a/IO/SettingsIO.ts b/IO/SettingsIO.ts new file mode 100644 index 0000000..e9c31d1 --- /dev/null +++ b/IO/SettingsIO.ts @@ -0,0 +1,194 @@ +import type { AutoMoverSettings } from "Settings/Settings"; +import { Notice, TFile } from "obsidian"; +import type { App } from "obsidian"; + +class SettingsIO { + private static instance: SettingsIO; + private app: App; + + private constructor() {} + + public static getInstance(): SettingsIO { + if (!SettingsIO.instance) { + SettingsIO.instance = new SettingsIO(); + } + return SettingsIO.instance; + } + + public init(app: App): void { + this.app = app; + } + + /** + * Exports the plugin settings to a JSON file. + * + * @param settings - The settings object to export. + * @returns A promise that resolves to true if the export was successful, false otherwise. + */ + public async exportSettings(settings: AutoMoverSettings) { + try { + if (!this.app) { + throw new Error("App reference not set"); + } + + const settingsData = JSON.stringify(settings, null, 2); + + const electron = (window as any).require + ? (window as any).require("electron") + : null; + const remote = electron ? electron.remote : null; + + if (!remote || !remote.dialog) { + // Fall back to saving in vault if Electron APIs are not available + return this.exportToVault(settingsData); + } + + const { canceled, filePath } = await remote.dialog.showSaveDialog({ + title: "Export AutoMover Settings", + defaultPath: "AutoMover_settings.json", + filters: [{ name: "JSON Files", extensions: ["json"] }], + properties: ["createDirectory"], + }); + + if (canceled || !filePath) { + return false; + } + + const fs = require("node:fs"); + fs.writeFileSync(filePath, settingsData); + + new Notice("Settings exported successfully"); + return true; + } catch (error) { + console.error("Failed to export settings:", error); + new Notice("Failed to export settings"); + return false; + } + } + + /** + * Exports the settings to the vault. + * Exports the settings to a file named AutoMover_settings.json + * + * @param settingsData - The settings data to export. + * @returns A promise that resolves to true if the export was successful, false otherwise. + */ + private async exportToVault(settingsData: string): Promise { + try { + const filename = "AutoMover_settings.json"; + await this.app.vault.create(filename, settingsData); + new Notice(`Settings exported to vault: ${filename}`); + return true; + } catch (error) { + console.error("Failed to export to vault:", error); + new Notice("Failed to export settings to vault"); + return false; + } + } + + /** + * Imports settings from a JSON file. + * + * @returns The imported settings or null if the import failed. + */ + public async importSettings(): Promise { + try { + if (!this.app) { + throw new Error("App reference not set"); + } + + const electron = (window as any).require + ? (window as any).require("electron") + : null; + const remote = electron ? electron.remote : null; + + if (!remote || !remote.dialog) { + // Fall back to importing from vault if Electron APIs are not available + return this.importFromVault(); + } + + const { canceled, filePaths } = await remote.dialog.showOpenDialog({ + title: "Import AutoMover Settings", + filters: [{ name: "JSON Files", extensions: ["json"] }], + properties: ["openFile"], + }); + + if (canceled || !filePaths || filePaths.length === 0) { + return null; + } + + const fs = require("node:fs"); + const fileContent = fs.readFileSync(filePaths[0], "utf8"); + + const importedSettings = JSON.parse(fileContent); + + if (!this.validateSettings(importedSettings)) { + new Notice("Invalid settings file format"); + return null; + } + + new Notice("Settings imported successfully"); + return importedSettings; + } catch (error) { + console.error("Failed to import settings:", error); + new Notice("Failed to import settings"); + return null; + } + } + + /** + * Fallback method to import settings from the vault. + * from the file AutoMover_settings.json + * + * @returns The imported settings or null if the import failed. + */ + private async importFromVault(): Promise { + try { + const filename = "AutoMover_settings.json"; + const file = this.app.vault.getAbstractFileByPath(filename); + + if (!file || !(file instanceof TFile)) { + new Notice(`Could not find ${filename} in vault`); + return null; + } + + const fileContent = await this.app.vault.read(file); + const importedSettings = JSON.parse(fileContent); + + if (!this.validateSettings(importedSettings)) { + new Notice("Invalid settings file format"); + return null; + } + + new Notice("Settings imported from vault successfully"); + return importedSettings; + } catch (error) { + console.error("Failed to import from vault:", error); + new Notice("Failed to import settings from vault"); + return null; + } + } + + /** + * Validates the settings object to ensure it has the correct structure. + * + * @param settings - The settings object to validate. + * @return True if the settings object is valid, false otherwise. + */ + private validateSettings(settings: any): settings is AutoMoverSettings { + if (!settings) return false; + + if (typeof settings.moveOnOpen !== "boolean") return false; + if (!Array.isArray(settings.movingRules)) return false; + if (!Array.isArray(settings.exclusionRules)) return false; + if (typeof settings.automaticMoving !== "boolean") return false; + + if (settings.timer !== null && typeof settings.timer !== "number") + return false; + + return true; + } +} + +const settingsIO = SettingsIO.getInstance(); +export default settingsIO; diff --git a/Settings/SettingsTab.ts b/Settings/SettingsTab.ts index 0596ad9..2838a69 100644 --- a/Settings/SettingsTab.ts +++ b/Settings/SettingsTab.ts @@ -1,8 +1,9 @@ -import type AutoMoverPlugin from "main"; +import settingsIO from "IO/SettingsIO"; import { ExclusionRule } from "Models/ExclusionRule"; import { MovingRule } from "Models/MovingRule"; -import * as obsidian from "obsidian"; import timerUtil from "Utils/TimerUtil"; +import type AutoMoverPlugin from "main"; +import * as obsidian from "obsidian"; export class SettingsTab extends obsidian.PluginSettingTab { plugin: AutoMoverPlugin; @@ -17,6 +18,34 @@ export class SettingsTab extends obsidian.PluginSettingTab { containerEl.empty(); new obsidian.Setting(containerEl).setName("Automatic moving").setHeading(); + new obsidian.Setting(containerEl) + .setName("Export/Import of settings") + .setDesc( + `Import will replace the current settings. + If you aren't prompted to choose a location, then the file will be exported to/imported from the vault root as AutoMover_settings.json.`, + ) + .addButton((button) => { + button.setButtonText("Export settings"); + button.onClick(async () => { + settingsIO.exportSettings(this.plugin.settings); + }); + }) + .addButton((button) => { + button.setButtonText("Import settings"); + button.onClick(async () => { + const importedSettings = await settingsIO.importSettings(); + if (importedSettings) { + this.plugin.settings = importedSettings; + await this.plugin.saveData(this.plugin.settings); + this.plugin.app.workspace.trigger( + "AutoMover:automatic-moving-update", + this.plugin.settings, + ); + this.display(); + } + }); + }); + new obsidian.Setting(containerEl) .setName("Move on open") .setDesc("Should the file be moved when it is opened?") diff --git a/main.ts b/main.ts index aec7cbc..07a84a0 100644 --- a/main.ts +++ b/main.ts @@ -1,11 +1,11 @@ +import settingsIO from "IO/SettingsIO"; import * as Settings from "Settings/Settings"; import { SettingsTab } from "Settings/SettingsTab"; +import exclusionMatcherUtil from "Utils/ExclusionMatcherUtil"; import movingUtil from "Utils/MovingUtil"; import ruleMatcherUtil from "Utils/RuleMatcherUtil"; -import * as obsidian from "obsidian"; -import exclusionMatcherUtil from "Utils/ExclusionMatcherUtil"; import timerUtil from "Utils/TimerUtil"; -import { addIcon } from "obsidian"; +import * as obsidian from "obsidian"; export default class AutoMoverPlugin extends obsidian.Plugin { settings: Settings.AutoMoverSettings; @@ -13,6 +13,7 @@ export default class AutoMoverPlugin extends obsidian.Plugin { async onload() { // console.log("loading plugin"); movingUtil.init(this.app); + settingsIO.init(this.app); /** * Loading settings and creating the settings tab */