2024-05-14 18:37:41 +02:00
|
|
|
package fileservice
|
|
|
|
|
|
|
|
import (
|
2024-06-03 22:03:21 +02:00
|
|
|
"context"
|
2024-05-14 21:29:41 +02:00
|
|
|
"encoding/base64"
|
2024-05-20 20:39:23 +02:00
|
|
|
"encoding/json"
|
2024-05-14 21:29:41 +02:00
|
|
|
"fmt"
|
2024-06-27 21:30:08 +02:00
|
|
|
"log"
|
2024-05-14 18:37:41 +02:00
|
|
|
"os"
|
2024-05-16 09:29:58 +02:00
|
|
|
"path/filepath"
|
2024-06-03 22:03:21 +02:00
|
|
|
"time"
|
2024-05-14 18:37:41 +02:00
|
|
|
|
2024-06-03 22:03:21 +02:00
|
|
|
"github.com/wavetermdev/thenextwave/pkg/filestore"
|
2024-05-16 09:29:58 +02:00
|
|
|
"github.com/wavetermdev/thenextwave/pkg/util/utilfn"
|
2024-05-14 18:37:41 +02:00
|
|
|
"github.com/wavetermdev/thenextwave/pkg/wavebase"
|
2024-06-20 08:59:41 +02:00
|
|
|
"github.com/wavetermdev/thenextwave/pkg/wconfig"
|
2024-05-14 18:37:41 +02:00
|
|
|
)
|
|
|
|
|
2024-05-17 07:48:23 +02:00
|
|
|
const MaxFileSize = 10 * 1024 * 1024 // 10M
|
2024-06-03 22:03:21 +02:00
|
|
|
const DefaultTimeout = 2 * time.Second
|
2024-05-17 07:48:23 +02:00
|
|
|
|
2024-05-14 18:37:41 +02:00
|
|
|
type FileService struct{}
|
|
|
|
|
2024-05-16 09:29:58 +02:00
|
|
|
type FileInfo struct {
|
|
|
|
Path string `json:"path"` // cleaned path
|
2024-06-27 21:30:08 +02:00
|
|
|
Name string `json:"name"`
|
2024-05-16 09:29:58 +02:00
|
|
|
NotFound bool `json:"notfound,omitempty"`
|
|
|
|
Size int64 `json:"size"`
|
|
|
|
Mode os.FileMode `json:"mode"`
|
2024-06-22 09:41:49 +02:00
|
|
|
ModeStr string `json:"modestr"`
|
2024-05-16 09:29:58 +02:00
|
|
|
ModTime int64 `json:"modtime"`
|
|
|
|
IsDir bool `json:"isdir,omitempty"`
|
|
|
|
MimeType string `json:"mimetype,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type FullFile struct {
|
|
|
|
Info *FileInfo `json:"info"`
|
2024-06-05 02:58:29 +02:00
|
|
|
Data64 string `json:"data64"` // base64 encoded
|
2024-05-16 09:29:58 +02:00
|
|
|
}
|
|
|
|
|
2024-07-18 08:41:33 +02:00
|
|
|
func (fs *FileService) SaveFile(path string, data64 string) error {
|
|
|
|
cleanedPath := filepath.Clean(wavebase.ExpandHomeDir(path))
|
|
|
|
data, err := base64.StdEncoding.DecodeString(data64)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to decode base64 data: %w", err)
|
|
|
|
}
|
|
|
|
err = os.WriteFile(cleanedPath, data, 0644)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to write file %q: %w", path, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-05-16 09:29:58 +02:00
|
|
|
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)
|
|
|
|
}
|
2024-05-20 20:39:23 +02:00
|
|
|
mimeType := utilfn.DetectMimeType(cleanedPath)
|
2024-05-16 09:29:58 +02:00
|
|
|
return &FileInfo{
|
2024-05-20 20:39:23 +02:00
|
|
|
Path: cleanedPath,
|
2024-06-27 21:30:08 +02:00
|
|
|
Name: finfo.Name(),
|
2024-05-16 09:29:58 +02:00
|
|
|
Size: finfo.Size(),
|
|
|
|
Mode: finfo.Mode(),
|
2024-06-22 09:41:49 +02:00
|
|
|
ModeStr: finfo.Mode().String(),
|
2024-05-16 09:29:58 +02:00
|
|
|
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
|
|
|
|
}
|
2024-05-17 07:48:23 +02:00
|
|
|
if finfo.Size > MaxFileSize {
|
|
|
|
return nil, fmt.Errorf("file %q is too large to read, use /wave/stream-file", path)
|
|
|
|
}
|
2024-05-20 20:39:23 +02:00
|
|
|
if finfo.IsDir {
|
|
|
|
innerFilesEntries, err := os.ReadDir(finfo.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to parse directory %s", finfo.Path)
|
|
|
|
}
|
2024-06-22 09:41:49 +02:00
|
|
|
if len(innerFilesEntries) > 1000 {
|
2024-06-24 23:34:31 +02:00
|
|
|
innerFilesEntries = innerFilesEntries[:1000]
|
2024-06-22 09:41:49 +02:00
|
|
|
}
|
2024-05-20 20:39:23 +02:00
|
|
|
var innerFilesInfo []FileInfo
|
2024-06-27 21:30:08 +02:00
|
|
|
parent := filepath.Dir(finfo.Path)
|
|
|
|
parentFileInfo, err := fs.StatFile(parent)
|
|
|
|
if err == nil && parent != finfo.Path {
|
|
|
|
log.Printf("adding parent")
|
|
|
|
parentFileInfo.Name = ".."
|
|
|
|
innerFilesInfo = append(innerFilesInfo, *parentFileInfo)
|
|
|
|
}
|
2024-05-20 20:39:23 +02:00
|
|
|
for _, innerFileEntry := range innerFilesEntries {
|
2024-07-18 00:24:43 +02:00
|
|
|
innerFileInfoInt, err := innerFileEntry.Info()
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("unable to get file info for (innerFileInfo) %s: %v", innerFileEntry.Name(), err)
|
|
|
|
continue
|
|
|
|
}
|
2024-06-22 09:41:49 +02:00
|
|
|
mimeType := utilfn.DetectMimeType(filepath.Join(finfo.Path, innerFileInfoInt.Name()))
|
|
|
|
var fileSize int64
|
|
|
|
if mimeType == "directory" {
|
|
|
|
fileSize = -1
|
|
|
|
} else {
|
|
|
|
fileSize = innerFileInfoInt.Size()
|
|
|
|
}
|
2024-05-20 20:39:23 +02:00
|
|
|
innerFileInfo := FileInfo{
|
2024-06-27 21:30:08 +02:00
|
|
|
Path: filepath.Join(finfo.Path, innerFileInfoInt.Name()),
|
|
|
|
Name: innerFileInfoInt.Name(),
|
2024-06-22 09:41:49 +02:00
|
|
|
Size: fileSize,
|
2024-05-20 20:39:23 +02:00
|
|
|
Mode: innerFileInfoInt.Mode(),
|
2024-06-22 09:41:49 +02:00
|
|
|
ModeStr: innerFileInfoInt.Mode().String(),
|
2024-05-20 20:39:23 +02:00
|
|
|
ModTime: innerFileInfoInt.ModTime().UnixMilli(),
|
|
|
|
IsDir: innerFileInfoInt.IsDir(),
|
2024-06-22 09:41:49 +02:00
|
|
|
MimeType: mimeType,
|
2024-05-20 20:39:23 +02:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
2024-05-16 09:29:58 +02:00
|
|
|
cleanedPath := filepath.Clean(wavebase.ExpandHomeDir(path))
|
|
|
|
barr, err := os.ReadFile(cleanedPath)
|
2024-05-14 21:29:41 +02:00
|
|
|
if err != nil {
|
2024-05-16 09:29:58 +02:00
|
|
|
return nil, fmt.Errorf("cannot read file %q: %w", path, err)
|
2024-05-14 21:29:41 +02:00
|
|
|
}
|
2024-05-16 09:29:58 +02:00
|
|
|
return &FullFile{
|
|
|
|
Info: finfo,
|
|
|
|
Data64: base64.StdEncoding.EncodeToString(barr),
|
|
|
|
}, nil
|
2024-05-14 18:37:41 +02:00
|
|
|
}
|
2024-06-03 22:03:21 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2024-06-20 08:59:41 +02:00
|
|
|
|
2024-06-25 22:53:55 +02:00
|
|
|
func (fs *FileService) DeleteFile(path string) error {
|
|
|
|
cleanedPath := filepath.Clean(wavebase.ExpandHomeDir(path))
|
|
|
|
return os.Remove(cleanedPath)
|
|
|
|
}
|
|
|
|
|
2024-07-19 01:55:04 +02:00
|
|
|
func (fs *FileService) GetSettingsConfig() wconfig.SettingsConfigType {
|
2024-06-20 08:59:41 +02:00
|
|
|
watcher := wconfig.GetWatcher()
|
|
|
|
return watcher.GetSettingsConfig()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *FileService) AddWidget(newWidget wconfig.WidgetsConfigType) error {
|
|
|
|
watcher := wconfig.GetWatcher()
|
|
|
|
return watcher.AddWidget(newWidget)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *FileService) RemoveWidget(idx uint) error {
|
|
|
|
watcher := wconfig.GetWatcher()
|
|
|
|
return watcher.RmWidget(idx)
|
|
|
|
}
|