waveterm/pkg/service/fileservice/fileservice.go
Evan Simkowitz 04c4f0a203
Create and rename files and dirs in dirpreview (#1156)
New context menu options are available in the directory preview to
create and rename files and directories

It's missing three pieces of functionality, none of which are a
regression:
- Editing or creating an entry does not update the focused index. Focus
index right now is pretty dumb, it doesn't factor in the column sorting
so if you change that, the selected item will change to whatever is now
at that index. We should update this so we use the actual file name to
determine which element to focus and let the table determine which index
to then highlight given the current sorting algo
- Open in native preview should not be an option on remote connections
with the exception of WSL, where it should resolve the file in the
Windows filesystem, rather than the WSL one
- We should catch CRUD errors in the dir preview and display a popup
2024-12-02 22:23:44 -08:00

187 lines
5.6 KiB
Go

package fileservice
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"time"
"github.com/wavetermdev/waveterm/pkg/filestore"
"github.com/wavetermdev/waveterm/pkg/tsgen/tsgenmeta"
"github.com/wavetermdev/waveterm/pkg/wconfig"
"github.com/wavetermdev/waveterm/pkg/wshrpc"
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshserver"
"github.com/wavetermdev/waveterm/pkg/wshutil"
)
const MaxFileSize = 10 * 1024 * 1024 // 10M
const DefaultTimeout = 2 * time.Second
type FileService struct{}
type FullFile struct {
Info *wshrpc.FileInfo `json:"info"`
Data64 string `json:"data64"` // base64 encoded
}
func (fs *FileService) SaveFile_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
Desc: "save file",
ArgNames: []string{"connection", "path", "data64"},
}
}
func (fs *FileService) SaveFile(connection string, path string, data64 string) error {
if connection == "" {
connection = wshrpc.LocalConnName
}
connRoute := wshutil.MakeConnectionRouteId(connection)
client := wshserver.GetMainRpcClient()
writeData := wshrpc.CommandRemoteWriteFileData{Path: path, Data64: data64}
return wshclient.RemoteWriteFileCommand(client, writeData, &wshrpc.RpcOpts{Route: connRoute})
}
func (fs *FileService) StatFile_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
Desc: "get file info",
ArgNames: []string{"connection", "path"},
}
}
func (fs *FileService) StatFile(connection string, path string) (*wshrpc.FileInfo, error) {
if connection == "" {
connection = wshrpc.LocalConnName
}
connRoute := wshutil.MakeConnectionRouteId(connection)
client := wshserver.GetMainRpcClient()
return wshclient.RemoteFileInfoCommand(client, path, &wshrpc.RpcOpts{Route: connRoute})
}
func (fs *FileService) Mkdir(connection string, path string) error {
if connection == "" {
connection = wshrpc.LocalConnName
}
connRoute := wshutil.MakeConnectionRouteId(connection)
client := wshserver.GetMainRpcClient()
return wshclient.RemoteMkdirCommand(client, path, &wshrpc.RpcOpts{Route: connRoute})
}
func (fs *FileService) TouchFile(connection string, path string) error {
if connection == "" {
connection = wshrpc.LocalConnName
}
connRoute := wshutil.MakeConnectionRouteId(connection)
client := wshserver.GetMainRpcClient()
return wshclient.RemoteFileTouchCommand(client, path, &wshrpc.RpcOpts{Route: connRoute})
}
func (fs *FileService) Rename(connection string, path string, newPath string) error {
if connection == "" {
connection = wshrpc.LocalConnName
}
connRoute := wshutil.MakeConnectionRouteId(connection)
client := wshserver.GetMainRpcClient()
return wshclient.RemoteFileRenameCommand(client, [2]string{path, newPath}, &wshrpc.RpcOpts{Route: connRoute})
}
func (fs *FileService) ReadFile_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
Desc: "read file",
ArgNames: []string{"connection", "path"},
}
}
func (fs *FileService) ReadFile(connection string, path string) (*FullFile, error) {
if connection == "" {
connection = wshrpc.LocalConnName
}
connRoute := wshutil.MakeConnectionRouteId(connection)
client := wshserver.GetMainRpcClient()
streamFileData := wshrpc.CommandRemoteStreamFileData{Path: path}
rtnCh := wshclient.RemoteStreamFileCommand(client, streamFileData, &wshrpc.RpcOpts{Route: connRoute})
fullFile := &FullFile{}
firstPk := true
isDir := false
var fileBuf bytes.Buffer
var fileInfoArr []*wshrpc.FileInfo
for respUnion := range rtnCh {
if respUnion.Error != nil {
return nil, respUnion.Error
}
resp := respUnion.Response
if firstPk {
firstPk = false
// first packet has the fileinfo
if len(resp.FileInfo) != 1 {
return nil, fmt.Errorf("stream file protocol error, first pk fileinfo len=%d", len(resp.FileInfo))
}
fullFile.Info = resp.FileInfo[0]
if fullFile.Info.IsDir {
isDir = true
}
continue
}
if isDir {
if len(resp.FileInfo) == 0 {
continue
}
fileInfoArr = append(fileInfoArr, resp.FileInfo...)
} else {
if resp.Data64 == "" {
continue
}
decoder := base64.NewDecoder(base64.StdEncoding, bytes.NewReader([]byte(resp.Data64)))
_, err := io.Copy(&fileBuf, decoder)
if err != nil {
return nil, fmt.Errorf("stream file, failed to decode base64 data %q: %w", resp.Data64, err)
}
}
}
if isDir {
fiBytes, err := json.Marshal(fileInfoArr)
if err != nil {
return nil, fmt.Errorf("unable to serialize files %s", path)
}
fullFile.Data64 = base64.StdEncoding.EncodeToString(fiBytes)
} else {
// we can avoid this re-encoding if we ensure the remote side always encodes chunks of 3 bytes so we don't get padding chars
fullFile.Data64 = base64.StdEncoding.EncodeToString(fileBuf.Bytes())
}
return fullFile, nil
}
func (fs *FileService) GetWaveFile(id string, path string) (any, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
file, err := filestore.WFS.Stat(ctx, id, path)
if err != nil {
return nil, fmt.Errorf("error getting file: %w", err)
}
return file, nil
}
func (fs *FileService) DeleteFile_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
Desc: "delete file",
ArgNames: []string{"connection", "path"},
}
}
func (fs *FileService) DeleteFile(connection string, path string) error {
if connection == "" {
connection = wshrpc.LocalConnName
}
connRoute := wshutil.MakeConnectionRouteId(connection)
client := wshserver.GetMainRpcClient()
return wshclient.RemoteFileDeleteCommand(client, path, &wshrpc.RpcOpts{Route: connRoute})
}
func (fs *FileService) GetFullConfig() wconfig.FullConfigType {
watcher := wconfig.GetWatcher()
return watcher.GetFullConfig()
}