feat: watch vault file changes
This commit is contained in:
+33
-2
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/verstak/verstak-desktop/internal/core/events"
|
||||
"github.com/verstak/verstak-desktop/internal/core/externalopen"
|
||||
corefiles "github.com/verstak/verstak-desktop/internal/core/files"
|
||||
"github.com/verstak/verstak-desktop/internal/core/filewatcher"
|
||||
"github.com/verstak/verstak-desktop/internal/core/permissions"
|
||||
"github.com/verstak/verstak-desktop/internal/core/plugin"
|
||||
"github.com/verstak/verstak-desktop/internal/core/pluginstate"
|
||||
@@ -60,6 +61,7 @@ type App struct {
|
||||
workbench *coreworkbench.Router
|
||||
workspace *workspace.Manager
|
||||
syncSvc *syncsvc.Service
|
||||
fileWatcher *filewatcher.Service
|
||||
debug bool
|
||||
activityEvents map[string]bool
|
||||
}
|
||||
@@ -100,10 +102,12 @@ func NewApp(
|
||||
workbench: coreworkbench.NewRouter(workbenchPrefsFromSettings(appSettingsMgr)),
|
||||
workspace: workspaceMgr,
|
||||
syncSvc: syncService,
|
||||
fileWatcher: filewatcher.NewService(bus, 0),
|
||||
debug: debugEnabled,
|
||||
activityEvents: make(map[string]bool),
|
||||
}
|
||||
app.ensureActivityProviderSubscriptions()
|
||||
app.startFileWatcherForOpenVault()
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -699,7 +703,11 @@ func (a *App) CreateVault(path string) error {
|
||||
if a.vault == nil {
|
||||
return fmt.Errorf("vault service not initialized")
|
||||
}
|
||||
return a.vault.CreateVault(path)
|
||||
if err := a.vault.CreateVault(path); err != nil {
|
||||
return err
|
||||
}
|
||||
a.startFileWatcherForOpenVault()
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenVault opens an existing vault at the given path.
|
||||
@@ -707,7 +715,11 @@ func (a *App) OpenVault(path string) error {
|
||||
if a.vault == nil {
|
||||
return fmt.Errorf("vault service not initialized")
|
||||
}
|
||||
return a.vault.OpenVault(path)
|
||||
if err := a.vault.OpenVault(path); err != nil {
|
||||
return err
|
||||
}
|
||||
a.startFileWatcherForOpenVault()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseVault closes the current vault.
|
||||
@@ -715,6 +727,9 @@ func (a *App) CloseVault() error {
|
||||
if a.vault == nil {
|
||||
return fmt.Errorf("vault service not initialized")
|
||||
}
|
||||
if a.fileWatcher != nil {
|
||||
a.fileWatcher.Stop()
|
||||
}
|
||||
a.vault.CloseVault()
|
||||
return nil
|
||||
}
|
||||
@@ -1408,9 +1423,25 @@ func (a *App) SetCurrentVault(path string) string {
|
||||
log.Printf("[api] SetCurrentVault: failed to register workspace capability: %v", err)
|
||||
}
|
||||
}
|
||||
a.startFileWatcherForOpenVault()
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *App) startFileWatcherForOpenVault() {
|
||||
if a == nil || a.vault == nil || a.eventBus == nil {
|
||||
return
|
||||
}
|
||||
if a.vault.GetVaultStatus() != vault.StatusOpen {
|
||||
return
|
||||
}
|
||||
if a.fileWatcher == nil {
|
||||
a.fileWatcher = filewatcher.NewService(a.eventBus, 0)
|
||||
}
|
||||
if err := a.fileWatcher.Start(a.vault.GetVaultPath()); err != nil {
|
||||
log.Printf("[api] file watcher start failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Workspace API ─────────────────────────────────────────
|
||||
|
||||
// ListWorkspaces returns top-level physical workspace folders.
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/verstak/verstak-desktop/internal/core/appsettings"
|
||||
"github.com/verstak/verstak-desktop/internal/core/capability"
|
||||
@@ -1067,6 +1068,63 @@ func TestSetCurrentVaultInitializesWorkspaceWhenMissingAtStartup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCurrentVaultStartsLiveFileWatcher(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
vaultParent := filepath.Join(tmpDir, "vault-parent")
|
||||
if err := os.MkdirAll(vaultParent, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bus := events.NewBus()
|
||||
vaultService := vault.NewVault(bus)
|
||||
if err := vaultService.CreateVault(vaultParent); err != nil {
|
||||
t.Fatalf("CreateVault: %v", err)
|
||||
}
|
||||
vaultService.CloseVault()
|
||||
|
||||
settings := appsettings.NewManager(filepath.Join(tmpDir, "config.json"))
|
||||
if err := settings.Load(); err != nil {
|
||||
t.Fatalf("settings Load: %v", err)
|
||||
}
|
||||
|
||||
received := make(chan events.Event, 4)
|
||||
bus.Subscribe("file.changed", func(event events.Event) {
|
||||
received <- event
|
||||
})
|
||||
|
||||
app := &App{
|
||||
capRegistry: capability.NewRegistry(),
|
||||
eventBus: bus,
|
||||
vault: vaultService,
|
||||
appSettings: settings,
|
||||
}
|
||||
if errStr := app.SetCurrentVault(vaultParent); errStr != "" {
|
||||
t.Fatalf("SetCurrentVault: %s", errStr)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if app.fileWatcher != nil {
|
||||
app.fileWatcher.Stop()
|
||||
}
|
||||
})
|
||||
|
||||
if err := os.WriteFile(filepath.Join(vaultService.GetVaultPath(), "external.md"), []byte("changed"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case event := <-received:
|
||||
payload, ok := event.Payload.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("payload type = %T, want map", event.Payload)
|
||||
}
|
||||
if payload["path"] != "external.md" || payload["operation"] != "external.create" {
|
||||
t.Fatalf("event payload = %#v", payload)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("timed out waiting for watcher event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkspaceAPIUsesTopLevelFoldersAndMetadataSnapshot(t *testing.T) {
|
||||
app, vaultDir := newFilesTestApp(t, []string{"files.read"})
|
||||
app.workspace = workspace.NewManager(vaultDir)
|
||||
|
||||
Reference in New Issue
Block a user