verstak/cmd/verstak-gui/bindings_files.go

219 lines
6.4 KiB
Go

package main
import (
"fmt"
"path/filepath"
"strings"
"verstak/internal/core/activity"
"verstak/internal/core/files"
"verstak/internal/core/nodes"
syncsvc "verstak/internal/core/sync"
)
func (a *App) ListFiles(nodeID string) ([]FileDTO, error) {
if err := a.requireVault(); err != nil {
return nil, err
}
records, err := a.files.ListByNode(nodeID)
if err != nil {
return nil, err
}
result := make([]FileDTO, len(records))
for i := range records {
rec := &records[i]
result[i] = FileDTO{
ID: rec.ID,
NodeID: rec.NodeID,
Name: rec.Filename,
Path: rec.Path,
Size: rec.Size,
Mime: rec.MIME,
IsDir: rec.MIME == "inode/directory",
Missing: rec.Missing,
}
}
return result, nil
}
func (a *App) ListItems(nodeID string) ([]FileTreeItemDTO, error) {
if err := a.requireVault(); err != nil {
return nil, err
}
children, err := a.nodes.ListChildren(nodeID, false)
if err != nil {
return nil, err
}
result := make([]FileTreeItemDTO, 0, len(children))
for i := range children {
typ := children[i].Type
if typ != nodes.TypeFolder && typ != nodes.TypeFile && typ != nodes.TypeNote {
continue
}
item := FileTreeItemDTO{
ID: children[i].ID,
Name: children[i].Title,
Type: typ,
}
if typ == nodes.TypeFolder {
kids, _ := a.nodes.ListChildren(children[i].ID, false)
item.HasKids = len(kids) > 0
item.Mime = "inode/directory"
} else if typ == nodes.TypeFile {
records, _ := a.files.ListByNode(children[i].ID)
if len(records) > 0 {
item.FileID = records[0].ID
item.Size = records[0].Size
item.Mime = records[0].MIME
}
} else if typ == nodes.TypeNote {
records, _ := a.files.ListByNode(children[i].ID)
if len(records) > 0 {
item.FileID = records[0].ID
item.Size = records[0].Size
item.Mime = "text/markdown"
}
}
result = append(result, item)
}
return result, nil
}
func (a *App) AddPathCopy(nodeID, sourcePath string) ([]NodeDTO, error) {
if err := a.requireVault(); err != nil {
return nil, err
}
nodes, err := a.files.AddPathCopy(nodeID, sourcePath)
if err != nil {
return nil, err
}
for _, n := range nodes {
_ = a.activity.Record(nodeID, activity.TargetFile, n.ID, "", activity.TypeFileAdded, n.Title, `{"source":"`+sourcePath+`"}`)
_ = a.sync.RecordOp(syncsvc.EntityFile, n.ID, syncsvc.OpCreate, a.filePayload(&n))
}
return toNodeDTOs(nodes), nil
}
func (a *App) AddPathLink(nodeID, sourcePath string) ([]NodeDTO, error) {
if err := a.requireVault(); err != nil {
return nil, err
}
nodes, err := a.files.AddPathLink(nodeID, sourcePath)
if err != nil {
return nil, err
}
for _, n := range nodes {
_ = a.activity.Record(nodeID, activity.TargetFile, n.ID, "", activity.TypeFileAdded, n.Title, `{"source":"`+sourcePath+`"}`)
_ = a.sync.RecordOp(syncsvc.EntityFile, n.ID, syncsvc.OpCreate, a.filePayload(&n))
}
return toNodeDTOs(nodes), nil
}
func (a *App) DeleteFileOrFolder(nodeID string) error {
if err := a.requireVault(); err != nil {
return err
}
n, err := a.nodes.GetActive(nodeID)
if err == nil {
pid := ""
if n.ParentID != nil {
pid = *n.ParentID
}
evType := activity.TypeFileDeleted
targetType := activity.TargetFile
if n.Type == nodes.TypeFolder {
evType = activity.TypeFolderDeleted
targetType = activity.TargetFolder
}
_ = a.activity.Record(pid, targetType, nodeID, "", evType, n.Title, "")
syncEntity := syncsvc.EntityFile
if n.Type == nodes.TypeFolder {
syncEntity = syncsvc.EntityFolder
}
_ = a.sync.RecordOp(syncEntity, nodeID, syncsvc.OpDelete, nil)
}
return a.files.DeleteNodeAndChildren(nodeID)
}
func (a *App) CreateEmptyFile(parentID, filename string) (*NodeDTO, error) {
if err := a.requireVault(); err != nil {
return nil, err
}
node, err := a.files.CreateEmptyFile(parentID, filename)
if err != nil {
return nil, err
}
_ = a.activity.Record(parentID, activity.TargetFile, node.ID, "", activity.TypeFileAdded, filename, "")
_ = a.sync.RecordOp(syncsvc.EntityFile, node.ID, syncsvc.OpCreate, a.filePayload(node))
dto := toNodeDTO(node)
return &dto, nil
}
func (a *App) DuplicateNode(nodeID string) (*NodeDTO, error) {
if err := a.requireVault(); err != nil {
return nil, err
}
node, err := a.files.Duplicate(nodeID)
if err != nil {
return nil, err
}
n, err2 := a.nodes.GetActive(nodeID)
pid := ""
if err2 == nil && n.ParentID != nil {
pid = *n.ParentID
}
_ = a.activity.Record(pid, activity.TargetFile, node.ID, "", activity.TypeFileCopied, node.Title, "")
_ = a.sync.RecordOp(syncsvc.EntityFile, node.ID, syncsvc.OpCreate, a.filePayload(node))
dto := toNodeDTO(node)
return &dto, nil
}
func (a *App) ValidateName(name string) error {
return files.ValidateName(name)
}
func (a *App) CheckFileAction(fileID string) (*PreflightFileAction, error) {
if err := a.requireVault(); err != nil {
return nil, err
}
fileRec, err := a.files.Get(fileID)
if err != nil {
return nil, fmt.Errorf("get file: %w", err)
}
name := strings.ToLower(fileRec.Filename)
isMD := strings.HasSuffix(name, ".md") || strings.HasSuffix(name, ".markdown")
if !isMD {
return &PreflightFileAction{Action: "external", FileName: fileRec.Filename}, nil
}
// .md file — check for linked note
noteRec, err := a.notes.FindByFileID(fileID)
if err == nil && noteRec != nil {
noteNode, nodeErr := a.nodes.Get(noteRec.NodeID)
title := fileRec.Filename
if nodeErr == nil && noteNode != nil {
title = noteNode.Title
}
return &PreflightFileAction{Action: "note", NoteID: noteRec.NodeID, NoteTitle: title, FileName: fileRec.Filename}, nil
}
// .md inside Notes/ with no note record — auto-link
pathLower := strings.ToLower(fileRec.Path)
insideNotes := strings.Contains(pathLower, string(filepath.Separator)+"notes"+string(filepath.Separator)) ||
strings.HasPrefix(pathLower, "notes"+string(filepath.Separator))
if insideNotes {
noteNode, nodeErr := a.nodes.Get(fileRec.NodeID)
if nodeErr == nil && noteNode != nil {
_ = a.notes.LinkFile(noteNode.ID, fileID, "markdown")
return &PreflightFileAction{Action: "note", NoteID: noteNode.ID, NoteTitle: noteNode.Title, FileName: fileRec.Filename}, nil
}
}
// .md outside Notes/ — internal preview
return &PreflightFileAction{Action: "preview", FileName: fileRec.Filename}, nil
}
func (a *App) PreviewImport(sourcePath string) (*files.ImportSummary, error) {
if err := a.requireVault(); err != nil {
return nil, err
}
return a.files.PreviewImport(sourcePath)
}