mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-05 19:08:59 +01:00
b136c915df
* working on cmd restart logic * button to restart command * bind Cmd-R to restart selected command, and Cmd-Shift-R to restart last command. Browser Refresh is now Option-R. also fix 'clear' command to not delete running commands (like archive). some small changes to keyboard utility code to always set 'alt' and 'meta' appropriately. use 'cmd' and 'option' for crossplatform bindings * focus restarted line * update termopts, use current winsize to set termopts for new command * add cmd.restartts to track restart time * display restarted time in line w/ tooltip with original time * add restartts to line:show
228 lines
5.4 KiB
Go
228 lines
5.4 KiB
Go
// Copyright 2023, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package sstore
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/wavetermdev/waveterm/waveshell/pkg/cirfile"
|
|
"github.com/wavetermdev/waveterm/waveshell/pkg/shexec"
|
|
"github.com/wavetermdev/waveterm/wavesrv/pkg/scbase"
|
|
)
|
|
|
|
func CreateCmdPtyFile(ctx context.Context, screenId string, lineId string, maxSize int64) error {
|
|
ptyOutFileName, err := scbase.PtyOutFile(screenId, lineId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f, err := cirfile.CreateCirFile(ptyOutFileName, maxSize)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return f.Close()
|
|
}
|
|
|
|
func StatCmdPtyFile(ctx context.Context, screenId string, lineId string) (*cirfile.Stat, error) {
|
|
ptyOutFileName, err := scbase.PtyOutFile(screenId, lineId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cirfile.StatCirFile(ctx, ptyOutFileName)
|
|
}
|
|
|
|
func ClearCmdPtyFile(ctx context.Context, screenId string, lineId string) error {
|
|
ptyOutFileName, err := scbase.PtyOutFile(screenId, lineId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stat, err := cirfile.StatCirFile(ctx, ptyOutFileName)
|
|
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
|
return err
|
|
}
|
|
os.Remove(ptyOutFileName) // ignore error
|
|
var maxSize int64 = shexec.DefaultMaxPtySize
|
|
if stat != nil {
|
|
maxSize = stat.MaxSize
|
|
}
|
|
err = CreateCmdPtyFile(ctx, screenId, lineId, maxSize)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func AppendToCmdPtyBlob(ctx context.Context, screenId string, lineId string, data []byte, pos int64) (*PtyDataUpdate, error) {
|
|
if screenId == "" {
|
|
return nil, fmt.Errorf("cannot append to PtyBlob, screenid is not set")
|
|
}
|
|
if pos < 0 {
|
|
return nil, fmt.Errorf("invalid seek pos '%d' in AppendToCmdPtyBlob", pos)
|
|
}
|
|
ptyOutFileName, err := scbase.PtyOutFile(screenId, lineId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f, err := cirfile.OpenCirFile(ptyOutFileName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
err = f.WriteAt(ctx, data, pos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data64 := base64.StdEncoding.EncodeToString(data)
|
|
update := &PtyDataUpdate{
|
|
ScreenId: screenId,
|
|
LineId: lineId,
|
|
PtyPos: pos,
|
|
PtyData64: data64,
|
|
PtyDataLen: int64(len(data)),
|
|
}
|
|
err = MaybeInsertPtyPosUpdate(ctx, screenId, lineId)
|
|
if err != nil {
|
|
// just log
|
|
log.Printf("error inserting ptypos update %s/%s: %v\n", screenId, lineId, err)
|
|
}
|
|
return update, nil
|
|
}
|
|
|
|
// returns (real-offset, data, err)
|
|
func ReadFullPtyOutFile(ctx context.Context, screenId string, lineId string) (int64, []byte, error) {
|
|
ptyOutFileName, err := scbase.PtyOutFile(screenId, lineId)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
f, err := cirfile.OpenCirFile(ptyOutFileName)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
defer f.Close()
|
|
return f.ReadAll(ctx)
|
|
}
|
|
|
|
// returns (real-offset, data, err)
|
|
func ReadPtyOutFile(ctx context.Context, screenId string, lineId string, offset int64, maxSize int64) (int64, []byte, error) {
|
|
ptyOutFileName, err := scbase.PtyOutFile(screenId, lineId)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
f, err := cirfile.OpenCirFile(ptyOutFileName)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
defer f.Close()
|
|
return f.ReadAtWithMax(ctx, offset, maxSize)
|
|
}
|
|
|
|
type SessionDiskSizeType struct {
|
|
NumFiles int
|
|
TotalSize int64
|
|
ErrorCount int
|
|
Location string
|
|
}
|
|
|
|
func directorySize(dirName string) (SessionDiskSizeType, error) {
|
|
var rtn SessionDiskSizeType
|
|
rtn.Location = dirName
|
|
entries, err := os.ReadDir(dirName)
|
|
if err != nil {
|
|
return rtn, err
|
|
}
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
rtn.ErrorCount++
|
|
continue
|
|
}
|
|
finfo, err := entry.Info()
|
|
if err != nil {
|
|
rtn.ErrorCount++
|
|
continue
|
|
}
|
|
rtn.NumFiles++
|
|
rtn.TotalSize += finfo.Size()
|
|
}
|
|
return rtn, nil
|
|
}
|
|
|
|
func SessionDiskSize(sessionId string) (SessionDiskSizeType, error) {
|
|
sessionDir, err := scbase.EnsureSessionDir(sessionId)
|
|
if err != nil {
|
|
return SessionDiskSizeType{}, err
|
|
}
|
|
return directorySize(sessionDir)
|
|
}
|
|
|
|
func FullSessionDiskSize() (map[string]SessionDiskSizeType, error) {
|
|
sdir := scbase.GetSessionsDir()
|
|
entries, err := os.ReadDir(sdir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rtn := make(map[string]SessionDiskSizeType)
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
continue
|
|
}
|
|
name := entry.Name()
|
|
_, err = uuid.Parse(name)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
diskSize, err := directorySize(path.Join(sdir, name))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
rtn[name] = diskSize
|
|
}
|
|
return rtn, nil
|
|
}
|
|
|
|
func DeletePtyOutFile(ctx context.Context, screenId string, lineId string) error {
|
|
ptyOutFileName, err := scbase.PtyOutFile(screenId, lineId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = os.Remove(ptyOutFileName)
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func GoDeleteScreenDirs(screenIds ...string) {
|
|
go func() {
|
|
for _, screenId := range screenIds {
|
|
deleteScreenDirMakeCtx(screenId)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func deleteScreenDirMakeCtx(screenId string) {
|
|
ctx, cancelFn := context.WithTimeout(context.Background(), time.Minute)
|
|
defer cancelFn()
|
|
err := DeleteScreenDir(ctx, screenId)
|
|
if err != nil {
|
|
log.Printf("error deleting screendir %s: %v\n", screenId, err)
|
|
}
|
|
}
|
|
|
|
func DeleteScreenDir(ctx context.Context, screenId string) error {
|
|
screenDir, err := scbase.EnsureScreenDir(screenId)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting screendir: %w", err)
|
|
}
|
|
log.Printf("delete screen dir, remove-all %s\n", screenDir)
|
|
return os.RemoveAll(screenDir)
|
|
}
|