Add public external file open API
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/verstak/verstak-desktop/internal/core/capability"
|
||||
"github.com/verstak/verstak-desktop/internal/core/contribution"
|
||||
"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/permissions"
|
||||
"github.com/verstak/verstak-desktop/internal/core/plugin"
|
||||
@@ -40,6 +41,7 @@ type App struct {
|
||||
vault *vault.Vault
|
||||
storage *storage.Storage
|
||||
files *corefiles.Service
|
||||
externalOpen externalOpenService
|
||||
appSettings *appsettings.Manager
|
||||
pluginState *pluginstate.Manager
|
||||
workbench *coreworkbench.Router
|
||||
@@ -48,6 +50,11 @@ type App struct {
|
||||
debug bool
|
||||
}
|
||||
|
||||
type externalOpenService interface {
|
||||
OpenPath(path string) error
|
||||
ShowInFolder(path string, isDir bool) error
|
||||
}
|
||||
|
||||
// NewApp creates a new App instance.
|
||||
func NewApp(
|
||||
capReg *capability.Registry,
|
||||
@@ -73,6 +80,7 @@ func NewApp(
|
||||
vault: vaultService,
|
||||
storage: storageService,
|
||||
files: filesService,
|
||||
externalOpen: externalopen.NewService(),
|
||||
appSettings: appSettingsMgr,
|
||||
pluginState: pluginStateMgr,
|
||||
workbench: coreworkbench.NewRouter(workbenchPrefsFromSettings(appSettingsMgr)),
|
||||
@@ -749,6 +757,50 @@ func (a *App) TrashVaultPath(pluginID, relativePath string) (corefiles.TrashResu
|
||||
return result, ""
|
||||
}
|
||||
|
||||
// OpenVaultPathExternal opens a vault-relative file or folder in the OS default app.
|
||||
func (a *App) OpenVaultPathExternal(pluginID, relativePath string) string {
|
||||
if _, err := a.requirePluginAccess(pluginID, "files.openExternal"); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if a.files == nil {
|
||||
return "files service not initialized"
|
||||
}
|
||||
target, err := a.files.ResolveExternalOpenTarget(relativePath)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if err := a.externalOpenService().OpenPath(target.AbsolutePath); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ShowVaultPathInFolder reveals a vault-relative file or folder in the OS file manager.
|
||||
func (a *App) ShowVaultPathInFolder(pluginID, relativePath string) string {
|
||||
if _, err := a.requirePluginAccess(pluginID, "files.openExternal"); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if a.files == nil {
|
||||
return "files service not initialized"
|
||||
}
|
||||
target, err := a.files.ResolveExternalOpenTarget(relativePath)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
isDir := target.Metadata.Type == corefiles.FileTypeFolder
|
||||
if err := a.externalOpenService().ShowInFolder(target.AbsolutePath, isDir); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *App) externalOpenService() externalOpenService {
|
||||
if a.externalOpen != nil {
|
||||
return a.externalOpen
|
||||
}
|
||||
return externalopen.NewService()
|
||||
}
|
||||
|
||||
func (a *App) recordFileSyncOp(entityType, entityID, opType string, payload interface{}) error {
|
||||
if a.syncSvc == nil {
|
||||
return nil
|
||||
|
||||
@@ -82,6 +82,22 @@ func newFilesTestApp(t *testing.T, perms []string) (*App, string) {
|
||||
}, v.GetVaultPath()
|
||||
}
|
||||
|
||||
type testExternalOpenService struct {
|
||||
open func(path string) error
|
||||
}
|
||||
|
||||
func newTestExternalOpenService(open func(path string) error) *testExternalOpenService {
|
||||
return &testExternalOpenService{open: open}
|
||||
}
|
||||
|
||||
func (s *testExternalOpenService) OpenPath(path string) error {
|
||||
return s.open(path)
|
||||
}
|
||||
|
||||
func (s *testExternalOpenService) ShowInFolder(path string, _ bool) error {
|
||||
return s.open(path)
|
||||
}
|
||||
|
||||
func newSyncFilesTestApp(t *testing.T, perms []string, deviceID string) (*App, string) {
|
||||
t.Helper()
|
||||
app, root := newFilesTestApp(t, perms)
|
||||
@@ -358,6 +374,61 @@ func TestFilesBridgeReadWriteListMoveTrash(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilesBridgeOpenExternalUsesVaultPathPolicyAndPermission(t *testing.T) {
|
||||
app, root := newFilesTestApp(t, []string{"files.openExternal"})
|
||||
filePath := filepath.Join(root, "Docs", "one.txt")
|
||||
if err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filePath, []byte("hello"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile: %v", err)
|
||||
}
|
||||
|
||||
var opened []string
|
||||
app.externalOpen = newTestExternalOpenService(func(path string) error {
|
||||
opened = append(opened, path)
|
||||
return nil
|
||||
})
|
||||
|
||||
if errStr := app.OpenVaultPathExternal("files.plugin", "Docs/one.txt"); errStr != "" {
|
||||
t.Fatalf("OpenVaultPathExternal: %s", errStr)
|
||||
}
|
||||
if len(opened) != 1 || opened[0] != filePath {
|
||||
t.Fatalf("opened = %#v, want %q", opened, filePath)
|
||||
}
|
||||
|
||||
if errStr := app.OpenVaultPathExternal("files.plugin", ".verstak/vault.json"); errStr == "" || !strings.Contains(errStr, "reserved-path") {
|
||||
t.Fatalf("reserved path error = %q, want reserved-path", errStr)
|
||||
}
|
||||
if len(opened) != 1 {
|
||||
t.Fatalf("reserved path should not open, opened = %#v", opened)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilesBridgeShowInFolderUsesVaultPathPolicyAndPermission(t *testing.T) {
|
||||
app, root := newFilesTestApp(t, []string{"files.openExternal"})
|
||||
filePath := filepath.Join(root, "Docs", "one.txt")
|
||||
if err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filePath, []byte("hello"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile: %v", err)
|
||||
}
|
||||
|
||||
var shown []string
|
||||
app.externalOpen = newTestExternalOpenService(func(path string) error {
|
||||
shown = append(shown, path)
|
||||
return nil
|
||||
})
|
||||
|
||||
if errStr := app.ShowVaultPathInFolder("files.plugin", "Docs/one.txt"); errStr != "" {
|
||||
t.Fatalf("ShowVaultPathInFolder: %s", errStr)
|
||||
}
|
||||
if len(shown) != 1 || shown[0] != filePath {
|
||||
t.Fatalf("shown = %#v, want %q", shown, filePath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilesBridgePermissions(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -411,6 +482,18 @@ func TestFilesBridgePermissions(t *testing.T) {
|
||||
call: func(app *App) string { _, errStr := app.TrashVaultPath("files.plugin", "one.txt"); return errStr },
|
||||
wantPhrase: "files.delete",
|
||||
},
|
||||
{
|
||||
name: "open external requires openExternal",
|
||||
perms: []string{"files.read", "files.write", "files.delete"},
|
||||
call: func(app *App) string { return app.OpenVaultPathExternal("files.plugin", "one.txt") },
|
||||
wantPhrase: "files.openExternal",
|
||||
},
|
||||
{
|
||||
name: "show in folder requires openExternal",
|
||||
perms: []string{"files.read", "files.write", "files.delete"},
|
||||
call: func(app *App) string { return app.ShowVaultPathInFolder("files.plugin", "one.txt") },
|
||||
wantPhrase: "files.openExternal",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
Reference in New Issue
Block a user