one commit - added fileview backend changes

This commit is contained in:
MrStashley 2024-05-03 18:17:41 -07:00
parent 46b9c22f10
commit 8e907066ad
8 changed files with 854 additions and 45 deletions

318
diff.txt Normal file
View File

@ -0,0 +1,318 @@
diff --git a/src/app/workspace/cmdinput/textareainput.tsx b/src/app/workspace/cmdinput/textareainput.tsx
index c5461a90..0d43a66c 100644
--- a/src/app/workspace/cmdinput/textareainput.tsx
+++ b/src/app/workspace/cmdinput/textareainput.tsx
@@ -131,165 +131,177 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: ()
this.checkHeight(false);
this.updateSP();
let keybindManager = GlobalModel.keybindManager;
- keybindManager.registerKeybinding("pane", "cmdinput", "any", (waveEvent) => {
- return mobx.action(() => {
- let inputRef = this.mainInputRef.current;
- if (util.isModKeyPress(waveEvent)) {
- return false;
- }
- let model = GlobalModel;
- let inputModel = model.inputModel;
- let ctrlMod = waveEvent.control || waveEvent.cmd || waveEvent.shift;
- let curLine = inputModel.getCurLine();
-
- let lastTab = this.lastTab;
- this.lastTab = keybindManager.checkKeyPressed(waveEvent, "cmdinput:autocomplete");
- let lastHist = this.lastHistoryUpDown;
- this.lastHistoryUpDown = false;
-
- if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:autocomplete")) {
- if (lastTab) {
- GlobalModel.submitCommand(
- "_compgen",
- null,
- [curLine],
- { comppos: String(curLine.length), compshow: "1", nohist: "1" },
- true
- );
- } else {
- GlobalModel.submitCommand(
- "_compgen",
- null,
- [curLine],
- { comppos: String(curLine.length), nohist: "1" },
- true
- );
+ if (GlobalModel.activeMainView.get() == "session") {
+ console.log("session");
+ keybindManager.registerKeybinding("pane", "cmdinput", "any", (waveEvent) => {
+ return mobx.action(() => {
+ let inputRef = this.mainInputRef.current;
+ if (util.isModKeyPress(waveEvent)) {
+ return false;
}
- return true;
- }
- if (keybindManager.checkKeyPressed(waveEvent, "generic:confirm")) {
- if (!ctrlMod) {
- if (GlobalModel.inputModel.isEmpty()) {
- let activeWindow = GlobalModel.getScreenLinesForActiveScreen();
- let activeScreen = GlobalModel.getActiveScreen();
- if (activeScreen != null && activeWindow != null && activeWindow.lines.length > 0) {
- activeScreen.setSelectedLine(0);
- GlobalCommandRunner.screenSelectLine("E");
- }
+ let model = GlobalModel;
+ let inputModel = model.inputModel;
+ let ctrlMod = waveEvent.control || waveEvent.cmd || waveEvent.shift;
+ let curLine = inputModel.getCurLine();
+
+ let lastTab = this.lastTab;
+ this.lastTab = keybindManager.checkKeyPressed(waveEvent, "cmdinput:autocomplete");
+ let lastHist = this.lastHistoryUpDown;
+ this.lastHistoryUpDown = false;
+
+ if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:autocomplete")) {
+ if (lastTab) {
+ GlobalModel.submitCommand(
+ "_compgen",
+ null,
+ [curLine],
+ { comppos: String(curLine.length), compshow: "1", nohist: "1" },
+ true
+ );
} else {
- setTimeout(() => GlobalModel.inputModel.uiSubmitCommand(), 0);
+ GlobalModel.submitCommand(
+ "_compgen",
+ null,
+ [curLine],
+ { comppos: String(curLine.length), nohist: "1" },
+ true
+ );
}
return true;
}
- inputRef.setRangeText("\n", inputRef.selectionStart, inputRef.selectionEnd, "end");
- GlobalModel.inputModel.setCurLine(inputRef.value);
- return true;
- }
- if (keybindManager.checkKeyPressed(waveEvent, "generic:cancel")) {
- let inputModel = GlobalModel.inputModel;
- inputModel.toggleInfoMsg();
- console.log("hello?", inputModel.inputMode.get());
- if (inputModel.inputMode.get() != null) {
- inputModel.resetInputMode();
- console.log("hello? 2");
- }
- console.log("hello 3?");
- inputModel.closeAIAssistantChat(true);
- console.log("hello 4?");
- return true;
- }
- if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:expandInput")) {
- let inputModel = GlobalModel.inputModel;
- inputModel.toggleExpandInput();
- return true;
- }
- if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:clearInput")) {
- inputModel.resetInput();
- return true;
- }
- if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:cutLineLeftOfCursor")) {
- this.controlU();
- return true;
- }
- if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:previousHistoryItem")) {
- this.controlP();
- return true;
- }
- if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:nextHistoryItem")) {
- this.controlN();
- return true;
- }
- if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:cutWordLeftOfCursor")) {
- this.controlW();
- return true;
- }
- if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:paste")) {
- this.controlY();
- return true;
- }
- if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:openHistory")) {
- inputModel.openHistory();
- return true;
- }
- if (keybindManager.checkKeysPressed(waveEvent, ["generic:selectAbove", "generic:selectBelow"])) {
- if (!inputModel.isHistoryLoaded()) {
- if (keybindManager.checkKeyPressed(waveEvent, "generic:selectAbove")) {
- this.lastHistoryUpDown = true;
- inputModel.loadHistory(false, 1, "screen");
+ if (keybindManager.checkKeyPressed(waveEvent, "generic:confirm")) {
+ if (!ctrlMod) {
+ if (GlobalModel.inputModel.isEmpty()) {
+ let activeWindow = GlobalModel.getScreenLinesForActiveScreen();
+ let activeScreen = GlobalModel.getActiveScreen();
+ if (activeScreen != null && activeWindow != null && activeWindow.lines.length > 0) {
+ activeScreen.setSelectedLine(0);
+ GlobalCommandRunner.screenSelectLine("E");
+ }
+ } else {
+ setTimeout(() => GlobalModel.inputModel.uiSubmitCommand(), 0);
+ }
+ return true;
}
+ inputRef.setRangeText("\n", inputRef.selectionStart, inputRef.selectionEnd, "end");
+ GlobalModel.inputModel.setCurLine(inputRef.value);
return true;
}
- // invisible history movement
- let linePos = this.getLinePos(inputRef);
- if (keybindManager.checkKeyPressed(waveEvent, "generic:selectAbove")) {
- if (!lastHist && linePos.linePos > 1) {
- // regular arrow
- return false;
+ if (keybindManager.checkKeyPressed(waveEvent, "generic:cancel")) {
+ let inputModel = GlobalModel.inputModel;
+ inputModel.toggleInfoMsg();
+ if (inputModel.inputMode.get() != null) {
+ inputModel.resetInputMode();
}
- inputModel.moveHistorySelection(1);
- this.lastHistoryUpDown = true;
+ inputModel.closeAIAssistantChat(true);
return true;
}
- if (keybindManager.checkKeyPressed(waveEvent, "generic:selectBelow")) {
- if (!lastHist && linePos.linePos < linePos.numLines) {
- // regular arrow
- return false;
+ if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:expandInput")) {
+ let inputModel = GlobalModel.inputModel;
+ inputModel.toggleExpandInput();
+ return true;
+ }
+ if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:clearInput")) {
+ inputModel.resetInput();
+ return true;
+ }
+ if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:cutLineLeftOfCursor")) {
+ this.controlU();
+ return true;
+ }
+ if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:previousHistoryItem")) {
+ this.controlP();
+ return true;
+ }
+ if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:nextHistoryItem")) {
+ this.controlN();
+ return true;
+ }
+ if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:cutWordLeftOfCursor")) {
+ this.controlW();
+ return true;
+ }
+ if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:paste")) {
+ this.controlY();
+ return true;
+ }
+ if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:openHistory")) {
+ inputModel.openHistory();
+ return true;
+ }
+ if (keybindManager.checkKeysPressed(waveEvent, ["generic:selectAbove", "generic:selectBelow"])) {
+ if (!inputModel.isHistoryLoaded()) {
+ if (keybindManager.checkKeyPressed(waveEvent, "generic:selectAbove")) {
+ this.lastHistoryUpDown = true;
+ inputModel.loadHistory(false, 1, "screen");
+ }
+ return true;
+ }
+ // invisible history movement
+ let linePos = this.getLinePos(inputRef);
+ if (keybindManager.checkKeyPressed(waveEvent, "generic:selectAbove")) {
+ if (!lastHist && linePos.linePos > 1) {
+ // regular arrow
+ return false;
+ }
+ inputModel.moveHistorySelection(1);
+ this.lastHistoryUpDown = true;
+ return true;
+ }
+ if (keybindManager.checkKeyPressed(waveEvent, "generic:selectBelow")) {
+ if (!lastHist && linePos.linePos < linePos.numLines) {
+ // regular arrow
+ return false;
+ }
+ inputModel.moveHistorySelection(-1);
+ this.lastHistoryUpDown = true;
+ return true;
+ }
+ }
+ if (
+ keybindManager.checkKeysPressed(waveEvent, [
+ "generic:selectPageAbove",
+ "generic:selectPageBelow",
+ ])
+ ) {
+ let infoScroll = inputModel.hasScrollingInfoMsg();
+ if (infoScroll) {
+ let div = document.querySelector(".cmd-input-info");
+ let amt = pageSize(div);
+ scrollDiv(
+ div,
+ keybindManager.checkKeyPressed(waveEvent, "generic:selectPageAbove") ? -amt : amt
+ );
}
- inputModel.moveHistorySelection(-1);
- this.lastHistoryUpDown = true;
return true;
}
- }
- if (
- keybindManager.checkKeysPressed(waveEvent, ["generic:selectPageAbove", "generic:selectPageBelow"])
- ) {
- let infoScroll = inputModel.hasScrollingInfoMsg();
- if (infoScroll) {
- let div = document.querySelector(".cmd-input-info");
- let amt = pageSize(div);
- scrollDiv(
- div,
- keybindManager.checkKeyPressed(waveEvent, "generic:selectPageAbove") ? -amt : amt
- );
+ if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:openAIChat")) {
+ inputModel.openAIAssistantChat();
+ return true;
}
- return true;
- }
- if (keybindManager.checkKeyPressed(waveEvent, "cmdinput:openAIChat")) {
- inputModel.openAIAssistantChat();
- return true;
- }
- // console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e);
- return false;
- })();
- });
+ // console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e);
+ return false;
+ })();
+ });
+ } else {
+ console.log("not session");
+ keybindManager.unregisterDomain("cmdinput");
+ }
}
componentWillUnmount(): void {
+ console.log("component unmounted??");
let keybindManager = GlobalModel.keybindManager;
keybindManager.unregisterDomain("cmdinput");
}
componentDidUpdate() {
+ console.log("component update");
+ if (GlobalModel.activeMainView.get() != "session") {
+ console.log("unregistering");
+ let keybindManager = GlobalModel.keybindManager;
+ keybindManager.unregisterDomain("cmdinput");
+ }
let activeScreen = GlobalModel.getActiveScreen();
if (activeScreen != null) {
let focusType = activeScreen.focusType.get();

4
prettify.sh Executable file
View File

@ -0,0 +1,4 @@
dirs=$(git diff main --name-only --diff-filter d | grep -e '\.[tj]sx\?$' | xargs)
echo dirs: $dirs
node_modules/prettier/bin-prettier.js --write $dirs

5
prettifyAndPush.sh Executable file
View File

@ -0,0 +1,5 @@
dirs=$(git diff main --name-only --diff-filter d | grep -e '\.[tj]sx\?$' | xargs)
node_modules/prettier/bin-prettier.js --write $dirs
git add $dirs
git commit --amend
git push

View File

@ -64,6 +64,8 @@ const (
FileStatPacketStr = "filestat"
LogPacketStr = "log" // logging packet (sent from waveshell back to server)
ShellStatePacketStr = "shellstate"
ListDirPacketStr = "listdir"
SearchDirPacketStr = "searchdir"
RpcInputPacketStr = "rpcinput" // rpc-followup
SudoRequestPacketStr = "sudorequest"
SudoResponsePacketStr = "sudoresponse"
@ -120,6 +122,8 @@ func init() {
TypeStrToFactory[WriteFileDonePacketStr] = reflect.TypeOf(WriteFileDonePacketType{})
TypeStrToFactory[LogPacketStr] = reflect.TypeOf(LogPacketType{})
TypeStrToFactory[ShellStatePacketStr] = reflect.TypeOf(ShellStatePacketType{})
TypeStrToFactory[ListDirPacketStr] = reflect.TypeOf(ListDirPacketType{})
TypeStrToFactory[SearchDirPacketStr] = reflect.TypeOf(SearchDirPacketType{})
TypeStrToFactory[FileStatPacketStr] = reflect.TypeOf(FileStatPacketType{})
TypeStrToFactory[RpcInputPacketStr] = reflect.TypeOf(RpcInputPacketType{})
TypeStrToFactory[SudoRequestPacketStr] = reflect.TypeOf(SudoRequestPacketType{})
@ -133,6 +137,8 @@ func init() {
var _ RpcPacketType = (*ReInitPacketType)(nil)
var _ RpcPacketType = (*StreamFilePacketType)(nil)
var _ RpcPacketType = (*WriteFilePacketType)(nil)
var _ RpcPacketType = (*ListDirPacketType)(nil)
var _ RpcPacketType = (*SearchDirPacketType)(nil)
var _ RpcResponsePacketType = (*CmdStartPacketType)(nil)
var _ RpcResponsePacketType = (*ResponsePacketType)(nil)
@ -449,8 +455,12 @@ func MakeFileStatPacketType() *FileStatPacketType {
return &FileStatPacketType{Type: FileStatPacketStr}
}
func MakeFileStatPacketFromFileInfo(finfo fs.FileInfo, err string, done bool) *FileStatPacketType {
func MakeFileStatPacketFromFileInfo(listDirPk *ListDirPacketType, finfo fs.FileInfo, err string, done bool) *FileStatPacketType {
resp := MakeFileStatPacketType()
if listDirPk != nil {
resp.RespId = listDirPk.ReqId
resp.Path = listDirPk.Path
}
resp.Error = err
resp.Done = done
@ -464,6 +474,50 @@ func MakeFileStatPacketFromFileInfo(finfo fs.FileInfo, err string, done bool) *F
return resp
}
type ListDirPacketType struct {
Type string `json:"type"`
ReqId string `json:"reqid"`
Path string `json:"path"`
}
func (*ListDirPacketType) GetType() string {
return ListDirPacketStr
}
func (p *ListDirPacketType) GetReqId() string {
return p.ReqId
}
func MakeListDirPacket() *ListDirPacketType {
return &ListDirPacketType{Type: ListDirPacketStr}
}
type SearchDirPacketType struct {
Type string `json:"type"`
ReqId string `json:"reqid"`
Path string `json:"path"`
SearchQuery string `json:"searchquery"`
}
func (*SearchDirPacketType) GetType() string {
return SearchDirPacketStr
}
func (p *SearchDirPacketType) GetReqId() string {
return p.ReqId
}
func (p *SearchDirPacketType) ConvertToListDir() *ListDirPacketType {
rtn := MakeListDirPacket()
rtn.ReqId = p.ReqId
rtn.Path = p.Path
return rtn
}
func MakeSearchDirPacket() *SearchDirPacketType {
return &SearchDirPacketType{Type: SearchDirPacketStr}
}
type StreamFilePacketType struct {
Type string `json:"type"`
ReqId string `json:"reqid"`

View File

@ -12,6 +12,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
@ -660,6 +661,97 @@ func (m *MServer) streamFile(pk *packet.StreamFilePacketType) {
return
}
func (m *MServer) writeListDirErrPacket(err error, reqId string) {
resp := packet.MakeFileStatPacketType()
resp.RespId = reqId
resp.Error = fmt.Sprintf("Error in list dir: %v", err)
resp.Done = true
m.Sender.SendPacket(resp)
}
func (m *MServer) ListDir(listDirPk *packet.ListDirPacketType) {
dirEntries, err := os.ReadDir(listDirPk.Path)
var readDirError string = ""
if err != nil {
readDirError = fmt.Sprintf("error in list dir: %v", err)
}
curDirStat, err := os.Stat(listDirPk.Path)
if err != nil {
m.writeListDirErrPacket(err, listDirPk.ReqId)
}
resp := packet.MakeFileStatPacketFromFileInfo(listDirPk, curDirStat, readDirError, false)
resp.Name = "."
m.Sender.SendPacket(resp)
curDirStat, err = os.Stat(filepath.Join(listDirPk.Path, ".."))
if err != nil {
m.writeListDirErrPacket(err, listDirPk.ReqId)
return
}
resp = packet.MakeFileStatPacketFromFileInfo(listDirPk, curDirStat, readDirError, len(dirEntries) == 0)
resp.Name = ".."
m.Sender.SendPacket(resp)
for index := 0; index < len(dirEntries); index++ {
dirEntry := dirEntries[index]
dirEntryFileInfo, err := dirEntry.Info()
if err != nil {
m.writeListDirErrPacket(err, listDirPk.ReqId)
return
}
done := index == len(dirEntries)-1
resp = packet.MakeFileStatPacketFromFileInfo(listDirPk, dirEntryFileInfo, readDirError, done)
m.Sender.SendPacket(resp)
}
}
func (m *MServer) SearchDir(searchDirPk *packet.SearchDirPacketType) {
searchEmpty := true
foundRoot := false
err := filepath.WalkDir(searchDirPk.Path, func(path string, dirEntry fs.DirEntry, err error) error {
if err != nil {
errString := fmt.Sprintf("%v", err)
if strings.Contains(errString, "operation not permitted") {
return filepath.SkipDir
}
}
fileName := filepath.Base(path)
match, err := regexp.MatchString(searchDirPk.SearchQuery, fileName)
if err != nil {
return err
}
if match {
base.Logf("matched file: %v %v", path, searchDirPk.SearchQuery)
// special case where walkdir includes the current path, which messes up the stat pk
rootName := filepath.Base(searchDirPk.Path)
if !foundRoot && fileName == rootName {
foundRoot = true
return nil
}
dirEntryFileInfo, err := dirEntry.Info()
if err != nil {
return err
}
searchEmpty = false
resp := packet.MakeFileStatPacketFromFileInfo(searchDirPk.ConvertToListDir(), dirEntryFileInfo, "", false)
m.Sender.SendPacket(resp)
}
return nil
})
if err != nil {
m.writeListDirErrPacket(err, searchDirPk.ReqId)
} else {
searchError := ""
if searchEmpty {
searchError = "none"
}
resp := packet.MakeFileStatPacketType()
resp.Error = searchError
resp.Done = true
m.Sender.SendPacket(resp)
}
}
func int64Min(v1 int64, v2 int64) int64 {
if v1 < v2 {
return v1
@ -703,6 +795,14 @@ func (m *MServer) ProcessRpcPacket(pk packet.RpcPacketType) {
go m.writeFile(writePk, wfc)
return
}
if listDirPk, ok := pk.(*packet.ListDirPacketType); ok {
go m.ListDir(listDirPk)
return
}
if searchDirPk, ok := pk.(*packet.SearchDirPacketType); ok {
go m.SearchDir(searchDirPk)
return
}
m.Sender.SendErrorResponse(reqId, fmt.Errorf("invalid rpc type '%s'", pk.GetType()))
}

View File

@ -1033,7 +1033,7 @@ func configDirHandler(w http.ResponseWriter, r *http.Request) {
var files []*packet.FileStatPacketType
for index := 0; index < len(entries); index++ {
curEntry := entries[index]
curFile := packet.MakeFileStatPacketFromFileInfo(curEntry, "", false)
curFile := packet.MakeFileStatPacketFromFileInfo(nil, curEntry, "", false)
files = append(files, curFile)
}
dirListJson, err := json.Marshal(files)

View File

@ -279,6 +279,7 @@ func init() {
registerCmdFn("set", SetCommand)
registerCmdFn("view:stat", ViewStatCommand)
registerCmdFn("view:dir", ViewDirCommand)
registerCmdFn("view:test", ViewTestCommand)
registerCmdFn("edit:test", EditTestCommand)
@ -287,6 +288,8 @@ func init() {
registerCmdFn("codeedit", CodeEditCommand)
registerCmdFn("codeview", CodeEditCommand)
registerCmdFn("searchdir", SearchDirCommand)
registerCmdFn("imageview", ImageViewCommand)
registerCmdFn("mdview", MarkdownViewCommand)
registerCmdFn("markdownview", MarkdownViewCommand)
@ -1323,6 +1326,15 @@ func doCopyLocalFileToRemote(ctx context.Context, cmd *sstore.CmdType, remoteWsh
return
}
defer localFile.Close()
fileStat, err := localFile.Stat()
if err != nil {
writeStringToPty(ctx, cmd, fmt.Sprintf("error: could not get file stat: %v", err), &outputPos)
return
}
if fileStat.IsDir() {
writeStringToPty(ctx, cmd, "Cant copy a directory, try zipping it up first", &outputPos)
return
}
writePk := packet.MakeWriteFilePacket()
writePk.ReqId = uuid.New().String()
writePk.Path = destPath
@ -1337,11 +1349,6 @@ func doCopyLocalFileToRemote(ctx context.Context, cmd *sstore.CmdType, remoteWsh
writeStringToPty(ctx, cmd, fmt.Sprintf("Write ready packet error: %v\r\n", err), &outputPos)
return
}
fileStat, err := localFile.Stat()
if err != nil {
writeStringToPty(ctx, cmd, fmt.Sprintf("error: could not get file stat: %v", err), &outputPos)
return
}
fileSizeBytes := fileStat.Size()
bytesWritten := int64(0)
lastFileTransferPercentage := float64(0)
@ -1434,6 +1441,10 @@ func doCopyRemoteFileToRemote(ctx context.Context, cmd *sstore.CmdType, sourceWs
writeStringToPty(ctx, cmd, fmt.Sprintf("Response packet has error: %v\r\n", err), &outputPos)
return
}
if resp.Info.IsDir {
writeStringToPty(ctx, cmd, "Cant copy a directory, try zipping it up first", &outputPos)
return
}
fileSizeBytes := resp.Info.Size
if fileSizeBytes == 0 {
writeStringToPty(ctx, cmd, "Source file does not exist or is empty - exiting\r\n", &outputPos)
@ -1525,6 +1536,10 @@ func doCopyLocalFileToLocal(ctx context.Context, cmd *sstore.CmdType, sourcePath
writeStringToPty(ctx, cmd, fmt.Sprintf("error getting filestat %v", err), &outputPos)
return
}
if sourceFileStat.IsDir() {
writeStringToPty(ctx, cmd, "Cant copy a directory, try zipping it up first", &outputPos)
return
}
fileSizeBytes := sourceFileStat.Size()
writeStringToPty(ctx, cmd, fmt.Sprintf("Source File Size: %v\r\n", prettyPrintByteSize(fileSizeBytes)), &outputPos)
destFile, err := os.Create(destPath)
@ -1571,6 +1586,10 @@ func doCopyRemoteFileToLocal(ctx context.Context, cmd *sstore.CmdType, remoteWsh
writeStringToPty(ctx, cmd, fmt.Sprintf("Response packet has error: %v\r\n", err), &outputPos)
return
}
if resp.Info.IsDir {
writeStringToPty(ctx, cmd, "Cant copy a directory, try zipping it up first", &outputPos)
return
}
fileSizeBytes := resp.Info.Size
if fileSizeBytes == 0 {
writeStringToPty(ctx, cmd, "Source file doesn't exist or file is empty - exiting\r\n", &outputPos)
@ -1610,6 +1629,7 @@ func doCopyRemoteFileToLocal(ctx context.Context, cmd *sstore.CmdType, remoteWsh
fileTransferPercentage = float64(bytesWritten) / float64(fileSizeBytes)
if fileTransferPercentage-lastFileTransferPercentage > float64(0.05) {
log.Printf("updating percentage\n")
writeStringToPty(ctx, cmd, "-", &outputPos)
lastFileTransferPercentage = fileTransferPercentage
}
@ -1647,6 +1667,15 @@ func parseCopyFileParam(info string) (remote string, path string, err error) {
}
}
func fileIsDir(ctx context.Context, ids resolvedIds, remote *ResolvedRemote, filePath string) bool {
fileStat, err := getSingleFileStat(ctx, ids, remote, filePath)
log.Printf("file is dir: %v", err)
if err != nil {
return false
}
return fileStat.IsDir
}
func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
if len(pk.Args) == 0 {
return nil, fmt.Errorf("usage: /copyfile [file to copy] local=[path to copy to on local]")
@ -1716,9 +1745,10 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
sourceFullPath = filepath.Join(sourceCwd, sourcePathWithHome)
}
}
if destPath[len(destPath)-1:] == "/" {
if destPath[len(destPath)-1:] == "/" || fileIsDir(ctx, ids, destRemoteId, destPath) {
sourceFileName := filepath.Base(sourceFullPath)
destPath = filepath.Join(destPath, sourceFileName)
log.Printf("destPath: %v", destPath)
}
destWsh := destRemoteId.Waveshell
if destWsh == nil {
@ -1746,15 +1776,15 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
cmd, err := makeDynCmd(ctx, "copy file", ids, pk.GetRawStr(), *pkTermOpts, nil)
writeStringToPty(ctx, cmd, outputStr, &outputPos)
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil, err
return nil, fmt.Errorf("cannot make termopts: %w", err)
}
update, err := addLineForCmd(ctx, "/copy file", false, ids, cmd, "", nil)
pkTermOpts := convertTermOpts(termOpts)
cmd, err := makeDynCmd(ctx, "copy file", ids, pk.GetRawStr(), *pkTermOpts, nil)
writeStringToPty(ctx, cmd, outputStr, &outputPos)
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil, err
}
update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive))
if destRemote != ConnectedRemote && destRemoteId != nil && !destRemoteId.RState.IsConnected() {
writeStringToPty(ctx, cmd, fmt.Sprintf("Attempting to autoconnect to remote %v\r\n", destRemote), &outputPos)
err = destRemoteId.Waveshell.TryAutoConnect()
@ -1773,8 +1803,7 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
writeStringToPty(ctx, cmd, "Auto connect successful\r\n", &outputPos)
}
}
scbus.MainUpdateBus.DoScreenUpdate(cmd.ScreenId, update)
update = scbus.MakeUpdatePacket()
update := scbus.MakeUpdatePacket()
if destRemote == LocalRemote && sourceRemote == LocalRemote {
go doCopyLocalFileToLocal(context.Background(), cmd, sourceFullPath, destFullPath, outputPos)
} else if destRemote == LocalRemote && sourceRemote != LocalRemote {
@ -5039,6 +5068,10 @@ func SetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.Up
func makeStreamFilePk(ids resolvedIds, pk *scpacket.FeCommandPacketType) (*packet.StreamFilePacketType, error) {
cwd := ids.Remote.FeState["cwd"]
fileArg := pk.Args[0]
return makeStreamFilePkFromParams(cwd, fileArg)
}
func makeStreamFilePkFromParams(cwd string, fileArg string) (*packet.StreamFilePacketType, error) {
if fileArg == "" {
return nil, fmt.Errorf("/view:stat file argument must be set (cannot be empty)")
}
@ -5050,6 +5083,7 @@ func makeStreamFilePk(ids resolvedIds, pk *scpacket.FeCommandPacketType) (*packe
streamPk.Path = filepath.Join(cwd, fileArg)
}
return streamPk, nil
}
func ViewStatCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
@ -5060,42 +5094,18 @@ func ViewStatCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
if err != nil {
return nil, err
}
streamPk, err := makeStreamFilePk(ids, pk)
fileArg := pk.Args[0]
statPk, streamPk, err := getFileStat(ctx, ids, fileArg)
if err != nil {
return nil, err
}
streamPk.StatOnly = true
wsh := ids.Remote.Waveshell
iter, err := wsh.StreamFile(ctx, streamPk)
if err != nil {
return nil, fmt.Errorf("/view:stat error: %v", err)
}
defer iter.Close()
respIf, err := iter.Next(ctx)
if err != nil {
return nil, fmt.Errorf("/view:stat error getting response: %v", err)
}
resp, ok := respIf.(*packet.StreamFileResponseType)
if !ok {
return nil, fmt.Errorf("/view:stat error, bad response packet type: %T", respIf)
}
if resp.Error != "" {
return nil, fmt.Errorf("/view:stat error: %s", resp.Error)
}
if resp.Info == nil {
return nil, fmt.Errorf("/view:stat error, no file info")
}
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "path", resp.Info.Name))
buf.WriteString(fmt.Sprintf(" %-15s %d\n", "size", resp.Info.Size))
modTs := time.UnixMilli(resp.Info.ModTs)
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "path", statPk.Name))
buf.WriteString(fmt.Sprintf(" %-15s %d\n", "size", statPk.Size))
modTs := statPk.ModTs
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "modts", modTs.Format(TsFormatStr)))
buf.WriteString(fmt.Sprintf(" %-15s %v\n", "isdir", resp.Info.IsDir))
modeStr := fs.FileMode(resp.Info.Perm).String()
if len(modeStr) > 9 {
modeStr = modeStr[len(modeStr)-9:]
}
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "perms", modeStr))
buf.WriteString(fmt.Sprintf(" %-15s %v\n", "isdir", statPk.IsDir))
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "perms", statPk.ModeStr))
update := scbus.MakeUpdatePacket()
update.AddUpdate(sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("view stat %q", streamPk.Path),
@ -5104,6 +5114,62 @@ func ViewStatCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scb
return update, nil
}
func getSingleFileStat(ctx context.Context, ids resolvedIds, remote *ResolvedRemote, fileArg string) (*packet.FileStatPacketType, error) {
if remote.RemoteCopy.IsLocal() {
fileStat, err := os.Stat(fileArg)
if err != nil {
return nil, err
}
statPk := packet.MakeFileStatPacketFromFileInfo(nil, fileStat, "", true)
return statPk, nil
} else {
statPk, _, err := getFileStat(ctx, ids, fileArg)
if err != nil {
return nil, err
}
return statPk, nil
}
}
func getFileStat(ctx context.Context, ids resolvedIds, fileArg string) (*packet.FileStatPacketType, *packet.StreamFilePacketType, error) {
streamPk, err := makeStreamFilePkFromParams(ids.Remote.FeState["cwd"], fileArg)
if err != nil {
return nil, nil, err
}
streamPk.StatOnly = true
wsh := ids.Remote.Waveshell
iter, err := wsh.StreamFile(ctx, streamPk)
if err != nil {
return nil, nil, fmt.Errorf("/view:stat error: %v", err)
}
defer iter.Close()
respIf, err := iter.Next(ctx)
if err != nil {
return nil, nil, fmt.Errorf("/view:stat error getting response: %v", err)
}
resp, ok := respIf.(*packet.StreamFileResponseType)
if !ok {
return nil, nil, fmt.Errorf("/view:stat error, bad response packet type: %T", respIf)
}
if resp.Error != "" {
return nil, nil, fmt.Errorf("/view:stat error: %s", resp.Error)
}
if resp.Info == nil {
return nil, nil, fmt.Errorf("/view:stat error, no file info")
}
statPk := packet.MakeFileStatPacketType()
statPk.Name = resp.Info.Name
statPk.Size = resp.Info.Size
statPk.ModTs = time.UnixMilli(resp.Info.ModTs)
statPk.IsDir = resp.Info.IsDir
statPk.Perm = resp.Info.Perm
statPk.ModeStr = fs.FileMode(statPk.Perm).String()
if len(statPk.ModeStr) > 9 {
statPk.ModeStr = statPk.ModeStr[len(statPk.ModeStr)-9:]
}
return statPk, streamPk, nil
}
func ViewTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
if len(pk.Args) == 0 {
return nil, fmt.Errorf("/view:test requires an argument (file name)")
@ -5388,6 +5454,209 @@ func MarkdownViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
return update, nil
}
func StatDir(ctx context.Context, ids resolvedIds, path string, fileCallback func(pk *packet.FileStatPacketType, done bool, err error)) {
statPk, _, err := getFileStat(ctx, ids, path)
log.Printf("statPk: %\n", statPk)
if err != nil {
fileCallback(nil, true, err)
return
}
if !statPk.IsDir {
fileCallback(statPk, true, nil)
return
} else {
cwd := ids.Remote.FeState["cwd"]
listDirPk := packet.MakeListDirPacket()
listDirPk.ReqId = uuid.New().String()
if filepath.IsAbs(path) {
listDirPk.Path = path
} else {
listDirPk.Path = filepath.Join(cwd, path)
}
msh := ids.Remote.Waveshell
listDirIter, err := msh.ListDir(ctx, listDirPk)
if err != nil {
fileCallback(nil, true, err)
return
}
defer listDirIter.Close()
for {
respIf, err := listDirIter.Next(ctx)
if err != nil {
fileCallback(nil, true, err)
return
}
resp, ok := respIf.(*packet.FileStatPacketType)
if !ok || resp == nil {
fileCallback(nil, true, fmt.Errorf("error unmarshalling file stat response type %v %v", resp, respIf))
return
}
if resp.Error != "" {
err = fmt.Errorf(resp.Error)
} else {
err = nil
}
fileCallback(resp, resp.Done, err)
if resp.Done {
return
}
}
}
}
func SearchDir(ctx context.Context, ids resolvedIds, path string, searchQuery string, fileCallback func(pk *packet.FileStatPacketType, done bool, err error)) {
log.Printf("running searchdir %v %v \n", path, searchQuery)
statPk, _, err := getFileStat(ctx, ids, path)
if err != nil {
fileCallback(nil, true, err)
return
}
if !statPk.IsDir {
match, err := regexp.MatchString(searchQuery, statPk.Name)
if err != nil {
fileCallback(nil, true, err)
}
if match {
fileCallback(statPk, true, nil)
} else {
fileCallback(nil, true, nil)
}
return
}
cwd := ids.Remote.FeState["cwd"]
searchDirPk := packet.MakeSearchDirPacket()
searchDirPk.ReqId = uuid.New().String()
if filepath.IsAbs(path) {
searchDirPk.Path = path
} else {
searchDirPk.Path = filepath.Join(cwd, path)
}
searchDirPk.SearchQuery = searchQuery
msh := ids.Remote.Waveshell
searchDirIter, err := msh.SearchDir(ctx, searchDirPk)
if err != nil {
fileCallback(nil, true, err)
return
}
defer searchDirIter.Close()
for {
respIf, err := searchDirIter.Next(ctx)
log.Printf("got next\n")
if err != nil {
fileCallback(nil, true, err)
return
}
resp, ok := respIf.(*packet.FileStatPacketType)
if !ok || resp == nil {
fileCallback(nil, true, fmt.Errorf("error unmarshalling file stat response type %v %v", resp, respIf))
return
}
log.Printf("resp: %v\n", resp)
if resp.Error == "none" {
fileCallback(nil, true, nil)
return
} else if resp.Error != "" {
err = fmt.Errorf(resp.Error)
} else {
err = nil
}
if resp.Done {
return
}
fileCallback(resp, resp.Done, err)
}
}
func SearchDirCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_RemoteConnected)
if err != nil {
return nil, err
}
path := ids.Remote.FeState["cwd"]
if len(pk.Args) > 0 {
path = pk.Args[0]
}
var searchQuery string
if len(pk.Args) > 1 {
searchQuery = pk.Args[1]
} else {
return nil, fmt.Errorf("no search query specified - usage /searchdir [path] [query]")
}
lineId := pk.Kwargs["lineid"]
screenId := pk.Kwargs["screenid"]
cmd, err := sstore.GetCmdByScreenId(ctx, screenId, lineId)
if err != nil {
return nil, err
}
outputPty := resolveBool(pk.Kwargs["outputpty"], false)
if outputPty {
ids.Remote.Waveshell.ResetDataPos(base.MakeCommandKey(screenId, lineId))
err = sstore.ClearCmdPtyFile(ctx, screenId, lineId)
if err != nil {
return nil, fmt.Errorf("error clearing existing pty file: %v", err)
}
}
go func() {
var outputPos int64
searchCtx := context.Background()
SearchDir(searchCtx, ids, path, searchQuery, func(statPk *packet.FileStatPacketType, done bool, err error) {
log.Printf("got callback\n")
if err != nil {
log.Printf("got error: %v\n", err)
} else {
err = writePacketToPty(searchCtx, cmd, statPk, &outputPos)
if err != nil {
log.Printf("err writing packet to pty: %v\n", err)
}
}
})
}()
update := scbus.MakeUpdatePacket()
return update, nil
}
func ViewDirCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_RemoteConnected)
if err != nil {
return nil, err
}
path := ids.Remote.FeState["cwd"]
if len(pk.Args) > 0 {
path = pk.Args[0]
}
lineId := pk.Kwargs["lineid"]
screenId := pk.Kwargs["screenid"]
cmd, err := sstore.GetCmdByScreenId(ctx, screenId, lineId)
if err != nil {
return nil, err
}
outputPty := resolveBool(pk.Kwargs["outputpty"], false)
if outputPty {
ids.Remote.Waveshell.ResetDataPos(base.MakeCommandKey(screenId, lineId))
err = sstore.ClearCmdPtyFile(ctx, screenId, lineId)
if err != nil {
return nil, fmt.Errorf("error clearing existing pty file: %v", err)
}
}
go func() {
var outputPos int64
statCtx := context.Background()
StatDir(statCtx, ids, path, func(statPk *packet.FileStatPacketType, done bool, err error) {
if err != nil {
log.Printf("got error: %v\n", err)
} else {
err = writePacketToPty(statCtx, cmd, statPk, &outputPos)
if err != nil {
log.Printf("err writing packet to pty: %v\n", err)
}
}
})
}()
update := scbus.MakeUpdatePacket()
return update, nil
}
func EditTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
if len(pk.Args) == 0 {
return nil, fmt.Errorf("/edit:test requires an argument (file name)")
@ -5982,6 +6251,52 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc
if err != nil {
return nil, fmt.Errorf("error updating client ai base url: %v", err)
}
feOpts := clientData.FeOpts
feOpts.SudoPwStore = strings.ToLower(sudoPwStoreStr)
err = sstore.UpdateClientFeOpts(ctx, feOpts)
if err != nil {
return nil, fmt.Errorf("error updating client feopts: %v", err)
}
// clear all sudo pw if turning off
if feOpts.SudoPwStore == "off" {
for _, proc := range remote.GetRemoteMap() {
proc.ClearCachedSudoPw()
}
}
varsUpdated = append(varsUpdated, "sudopwstore")
}
if sudoPwTimeoutStr, found := pk.Kwargs["sudopwtimeout"]; found {
oldPwTimeout := clientData.FeOpts.SudoPwTimeoutMs / 1000 / 60 // ms to minutes
if oldPwTimeout == 0 {
oldPwTimeout = sstore.DefaultSudoTimeout
}
newSudoPwTimeout, err := resolveNonNegInt(sudoPwTimeoutStr, sstore.DefaultSudoTimeout)
if err != nil {
return nil, fmt.Errorf("invalid sudo pw timeout, must be a number greater than 0: %v", err)
}
if newSudoPwTimeout == 0 {
return nil, fmt.Errorf("invalid sudo pw timeout, must be a number greater than 0")
}
feOpts := clientData.FeOpts
feOpts.SudoPwTimeoutMs = newSudoPwTimeout * 60 * 1000 // minutes to ms
err = sstore.UpdateClientFeOpts(ctx, feOpts)
if err != nil {
return nil, fmt.Errorf("error updating client feopts: %v", err)
}
for _, proc := range remote.GetRemoteMap() {
proc.ChangeSudoTimeout(int64(newSudoPwTimeout - oldPwTimeout))
}
varsUpdated = append(varsUpdated, "sudopwtimeout")
}
if sudoPwClearOnSleepStr, found := pk.Kwargs["sudopwclearonsleep"]; found {
newSudoPwClearOnSleep := resolveBool(sudoPwClearOnSleepStr, true)
feOpts := clientData.FeOpts
feOpts.NoSudoPwClearOnSleep = !newSudoPwClearOnSleep
err = sstore.UpdateClientFeOpts(ctx, feOpts)
if err != nil {
return nil, fmt.Errorf("error updating client feopts: %v", err)
}
varsUpdated = append(varsUpdated, "sudopwclearonsleep")
}
if aiTimeoutStr, found := CheckOptionAlias(pk.Kwargs, "openaitimeout", "aitimeout"); found {
aiTimeout, err := strconv.ParseFloat(aiTimeoutStr, 64)

View File

@ -1522,6 +1522,18 @@ func (wsh *WaveshellProc) StreamFile(ctx context.Context, streamPk *packet.Strea
return wsh.PacketRpcIter(ctx, streamPk)
}
func (msh *WaveshellProc) ListDir(ctx context.Context, listDirPk *packet.ListDirPacketType) (*packet.RpcResponseIter, error) {
return msh.PacketRpcIter(ctx, listDirPk)
}
func (msh *WaveshellProc) SearchDir(ctx context.Context, searchDirPk *packet.SearchDirPacketType) (*packet.RpcResponseIter, error) {
return msh.PacketRpcIter(ctx, searchDirPk)
}
func (wsh *WaveshellProc) StreamFile(ctx context.Context, streamPk *packet.StreamFilePacketType) (*packet.RpcResponseIter, error) {
return wsh.PacketRpcIter(ctx, streamPk)
}
func addScVarsToState(state *packet.ShellState) *packet.ShellState {
if state == nil {
return nil
@ -2210,6 +2222,7 @@ func (wsh *WaveshellProc) PacketRpcIter(ctx context.Context, pk packet.RpcPacket
if pk == nil {
return nil, fmt.Errorf("PacketRpc passed nil packet")
}
log.Printf("sending packet: %v", pk)
reqId := pk.GetReqId()
wsh.ServerProc.Output.RegisterRpcSz(reqId, RpcIterChannelSize)
err := wsh.ServerProc.Input.SendPacketCtx(ctx, pk)