waveterm/pkg/service/fileservice/fileservice.go

121 lines
3.3 KiB
Go

// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package fileservice
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/wavetermdev/thenextwave/pkg/filestore"
"github.com/wavetermdev/thenextwave/pkg/util/utilfn"
"github.com/wavetermdev/thenextwave/pkg/wavebase"
)
const MaxFileSize = 10 * 1024 * 1024 // 10M
const DefaultTimeout = 2 * time.Second
type FileService struct{}
type FileInfo struct {
Path string `json:"path"` // cleaned path
NotFound bool `json:"notfound,omitempty"`
Size int64 `json:"size"`
Mode os.FileMode `json:"mode"`
ModTime int64 `json:"modtime"`
IsDir bool `json:"isdir,omitempty"`
MimeType string `json:"mimetype,omitempty"`
}
type FullFile struct {
Info *FileInfo `json:"info"`
Data64 string `json:"data64,omitempty"` // base64 encoded
}
func (fs *FileService) StatFile(path string) (*FileInfo, error) {
cleanedPath := filepath.Clean(wavebase.ExpandHomeDir(path))
finfo, err := os.Stat(cleanedPath)
if os.IsNotExist(err) {
return &FileInfo{Path: wavebase.ReplaceHomeDir(path), NotFound: true}, nil
}
if err != nil {
return nil, fmt.Errorf("cannot stat file %q: %w", path, err)
}
mimeType := utilfn.DetectMimeType(cleanedPath)
return &FileInfo{
Path: cleanedPath,
Size: finfo.Size(),
Mode: finfo.Mode(),
ModTime: finfo.ModTime().UnixMilli(),
IsDir: finfo.IsDir(),
MimeType: mimeType,
}, nil
}
func (fs *FileService) ReadFile(path string) (*FullFile, error) {
finfo, err := fs.StatFile(path)
if err != nil {
return nil, fmt.Errorf("cannot stat file %q: %w", path, err)
}
if finfo.NotFound {
return &FullFile{Info: finfo}, nil
}
if finfo.Size > MaxFileSize {
return nil, fmt.Errorf("file %q is too large to read, use /wave/stream-file", path)
}
if finfo.IsDir {
innerFilesEntries, err := os.ReadDir(finfo.Path)
if err != nil {
return nil, fmt.Errorf("unable to parse directory %s", finfo.Path)
}
var innerFilesInfo []FileInfo
for _, innerFileEntry := range innerFilesEntries {
innerFileInfoInt, _ := innerFileEntry.Info()
fullFilePath := filepath.Join(finfo.Path, innerFileInfoInt.Name())
innerFileInfo := FileInfo{
Path: fullFilePath,
Size: innerFileInfoInt.Size(),
Mode: innerFileInfoInt.Mode(),
ModTime: innerFileInfoInt.ModTime().UnixMilli(),
IsDir: innerFileInfoInt.IsDir(),
MimeType: "",
}
innerFilesInfo = append(innerFilesInfo, innerFileInfo)
}
filesSerialized, err := json.Marshal(innerFilesInfo)
if err != nil {
return nil, fmt.Errorf("unable to serialize files %s", finfo.Path)
}
return &FullFile{
Info: finfo,
Data64: base64.StdEncoding.EncodeToString(filesSerialized),
}, nil
}
cleanedPath := filepath.Clean(wavebase.ExpandHomeDir(path))
barr, err := os.ReadFile(cleanedPath)
if err != nil {
return nil, fmt.Errorf("cannot read file %q: %w", path, err)
}
return &FullFile{
Info: finfo,
Data64: base64.StdEncoding.EncodeToString(barr),
}, 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
}