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() }