Compare commits

..

4 Commits

Author SHA1 Message Date
Cole Lashley 25259428ab Merge branch 'main' into cole/file-backend-changes 2024-05-06 19:44:58 -07:00
MrStashley 15f6084db4 fixed rebase artifacts 2024-05-06 19:26:42 -07:00
MrStashley 1dba3f2612 removed rebase artifacts 2024-05-03 18:18:01 -07:00
MrStashley 8e907066ad one commit - added fileview backend changes 2024-05-03 18:17:41 -07:00
15 changed files with 2277 additions and 15770 deletions
-14710
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -38,7 +38,7 @@
"monaco-editor": "0.48.0",
"mustache": "^4.2.0",
"node-fetch": "^3.2.10",
"overlayscrollbars": "^2.7.3",
"overlayscrollbars": "^2.6.1",
"overlayscrollbars-react": "^0.5.5",
"papaparse": "^5.4.1",
"react": "^18.1.0",
@@ -48,7 +48,6 @@
"remark-gfm": "^4.0.0",
"sprintf-js": "^1.1.2",
"throttle-debounce": "^5.0.0",
"tinycolor2": "^1.6.0",
"tsx-control-statements": "^5.1.1",
"uuid": "^9.0.0",
"winston": "^3.8.2",
+1 -4
View File
@@ -47,10 +47,7 @@ class CodeBlockMarkdown extends React.Component<
let clickHandler: (e: React.MouseEvent<HTMLElement>, blockIndex: number) => void;
let inputModel = GlobalModel.inputModel;
clickHandler = (e: React.MouseEvent<HTMLElement>, blockIndex: number) => {
const sel = window.getSelection();
if (sel?.toString().length == 0) {
inputModel.setCodeSelectSelectedCodeBlock(blockIndex);
}
inputModel.setCodeSelectSelectedCodeBlock(blockIndex);
};
let selected = this.blockIndex == this.props.codeSelectSelectedIndex;
return (
+1 -1
View File
@@ -23,7 +23,7 @@ let MagicLayout = {
MainSidebarDefaultWidth: 240,
RightSidebarMinWidth: 0,
RightSidebarMaxWidth: 400,
RightSidebarMaxWidth: 300,
RightSidebarSnapThreshold: 90,
RightSidebarDragResistance: 50,
RightSidebarDefaultWidth: 240,
-97
View File
@@ -1,97 +0,0 @@
.sidebar-aichat {
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
.titlebar {
background-color: var(--app-panel-bg-color);
color: var(--term-blue);
padding: 6px 15px;
display: flex;
flex: 0 0 auto;
flex-direction: row;
width: 100%;
border-bottom: 1px solid var(--app-border-color);
font: var(--base-font);
user-select: none;
cursor: default;
line-height: 18px;
overflow: hidden;
.title-string {
font-weight: bold;
}
}
> .content {
flex-flow: column nowrap;
flex: 1;
margin-bottom: 0;
overflow-y: auto;
.chat-window {
display: flex;
// margin-bottom: 5px;
flex-direction: column;
height: 100%;
// This is the filler that will push the chat messages to the bottom until the chat window is full
.filler {
flex: 1 1 auto;
}
}
.chat-msg {
padding: calc(var(--termpad) * 2);
display: flex;
.chat-msg-header {
display: flex;
margin-bottom: 2px;
i {
margin-right: 0.5em;
}
}
}
.chat-msg-assistant {
color: var(--app-text-color);
pre {
white-space: pre-wrap;
word-break: break-word;
max-width: 100%;
overflow-x: auto;
margin-left: 0;
}
}
.chat-msg-error {
color: var(--cmdinput-text-error);
font-family: var(--markdown-font);
font-size: 14px;
}
}
.chat-input {
padding: 16px 15px 7px;
flex: 0 0 auto;
border-top: 1px solid var(--app-border-color);
.chat-textarea {
color: var(--app-text-primary-color);
background-color: var(--cmdinput-textarea-bg);
resize: none;
width: 100%;
border: transparent;
outline: none;
overflow: auto;
overflow-wrap: anywhere;
font-family: var(--termfontfamily);
font-weight: normal;
line-height: var(--termlineheight);
}
}
}
-327
View File
@@ -1,327 +0,0 @@
// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react";
import * as mobxReact from "mobx-react";
import * as mobx from "mobx";
import { GlobalModel } from "@/models";
import { boundMethod } from "autobind-decorator";
import { If, For } from "tsx-control-statements/components";
import { Markdown } from "@/elements";
import type { OverlayScrollbars } from "overlayscrollbars";
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
import tinycolor from "tinycolor2";
import "./aichat.less";
class AIChatKeybindings extends React.Component<{ AIChatObject: AIChat }, {}> {
componentDidMount(): void {
const AIChatObject = this.props.AIChatObject;
const keybindManager = GlobalModel.keybindManager;
const inputModel = GlobalModel.inputModel;
keybindManager.registerKeybinding("pane", "aichat", "generic:confirm", (waveEvent) => {
AIChatObject.onEnterKeyPressed();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:expandTextInput", (waveEvent) => {
AIChatObject.onExpandInputPressed();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:cancel", (waveEvent) => {
inputModel.closeAuxView();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "aichat:clearHistory", (waveEvent) => {
inputModel.clearAIAssistantChat();
return true;
});
keybindManager.registerKeybinding("pane", "aichat", "generic:selectAbove", (waveEvent) => {
return AIChatObject.onArrowUpPressed();
});
keybindManager.registerKeybinding("pane", "aichat", "generic:selectBelow", (waveEvent) => {
return AIChatObject.onArrowDownPressed();
});
}
componentWillUnmount(): void {
GlobalModel.keybindManager.unregisterDomain("aichat");
}
render() {
return null;
}
}
@mobxReact.observer
class ChatContent extends React.Component<{}, {}> {
chatListKeyCount: number = 0;
containerRef: React.RefObject<OverlayScrollbarsComponentRef> = React.createRef();
chatWindowRef: React.RefObject<HTMLDivElement> = React.createRef();
osInstance: OverlayScrollbars = null;
componentDidUpdate() {
this.chatListKeyCount = 0;
if (this.containerRef?.current && this.osInstance) {
const { viewport } = this.osInstance.elements();
viewport.scrollTo({
behavior: "auto",
top: this.chatWindowRef.current.scrollHeight,
});
}
}
componentWillUnmount() {
if (this.osInstance) {
this.osInstance.destroy();
this.osInstance = null;
}
}
@boundMethod
onScrollbarInitialized(instance) {
this.osInstance = instance;
const { viewport } = instance.elements();
viewport.scrollTo({
behavior: "auto",
top: this.chatWindowRef.current.scrollHeight,
});
}
renderError(err: string): any {
return <div className="chat-msg-error">{err}</div>;
}
renderChatMessage(chatItem: OpenAICmdInfoChatMessageType): any {
const curKey = "chatmsg-" + this.chatListKeyCount;
this.chatListKeyCount++;
const senderClassName = chatItem.isassistantresponse ? "chat-msg-assistant" : "chat-msg-user";
const msgClassName = `chat-msg ${senderClassName}`;
let innerHTML: React.JSX.Element = (
<>
<div className="chat-msg-header">
<i className="fa-sharp fa-solid fa-user"></i>
</div>
<div className="msg-text">{chatItem.userquery}</div>
</>
);
if (chatItem.isassistantresponse) {
if (chatItem.assistantresponse.error != null && chatItem.assistantresponse.error !== "") {
innerHTML = this.renderError(chatItem.assistantresponse.error);
} else {
innerHTML = (
<>
<div className="chat-msg-header">
<i className="fa-sharp fa-solid fa-sparkles"></i>
</div>
<Markdown text={chatItem.assistantresponse.message} codeSelect />
</>
);
}
}
const cssVar = GlobalModel.isDev ? "--app-panel-bg-color-dev" : "--app-panel-bg-color";
const panelBgColor = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim();
const color = tinycolor(panelBgColor);
const newColor = color.isValid() ? tinycolor(panelBgColor).darken(6).toString() : "none";
const backgroundColor = this.chatListKeyCount % 2 === 0 ? "none" : newColor;
return (
<div className={msgClassName} key={curKey} style={{ backgroundColor }}>
{innerHTML}
</div>
);
}
render() {
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
const chitem: OpenAICmdInfoChatMessageType = null;
return (
<OverlayScrollbarsComponent
ref={this.containerRef}
className="content"
options={{ scrollbars: { autoHide: "leave" } }}
events={{ initialized: this.onScrollbarInitialized }}
>
<div ref={this.chatWindowRef} className="chat-window">
<div className="filler"></div>
<For each="chitem" index="idx" of={chatMessageItems}>
{this.renderChatMessage(chitem)}
</For>
</div>
</OverlayScrollbarsComponent>
);
}
}
@mobxReact.observer
class AIChat extends React.Component<{}, {}> {
textAreaRef: React.RefObject<HTMLTextAreaElement> = React.createRef();
termFontSize: number = 14;
constructor(props) {
super(props);
mobx.makeObservable(this);
}
componentDidMount() {
const inputModel = GlobalModel.inputModel;
if (this.textAreaRef.current != null) {
this.textAreaRef.current.focus();
// inputModel.setCmdInfoChatRefs(this.textAreaRef, this.chatWindowScrollRef);
}
this.requestChatUpdate();
this.onTextAreaChange(null);
}
requestChatUpdate() {
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
if (chatMessageItems == null || chatMessageItems.length == 0) {
this.submitChatMessage("");
}
}
submitChatMessage(messageStr: string) {
const curLine = GlobalModel.inputModel.curLine;
const prtn = GlobalModel.submitChatInfoCommand(messageStr, curLine, false);
prtn.then((rtn) => {
if (!rtn.success) {
console.log("submit chat command error: " + rtn.error);
}
}).catch((_) => {});
}
getLinePos(elem: any): { numLines: number; linePos: number } {
const numLines = elem.value.split("\n").length;
const linePos = elem.value.substr(0, elem.selectionStart).split("\n").length;
return { numLines, linePos };
}
@mobx.action.bound
onTextAreaFocused(e: any) {
GlobalModel.inputModel.setAuxViewFocus(true);
this.onTextAreaChange(e);
}
@mobx.action.bound
onTextAreaBlur(e: any) {
mobx.action(() => {
GlobalModel.inputModel.setAuxViewFocus(false);
})();
}
// Adjust the height of the textarea to fit the text
@boundMethod
onTextAreaChange(e: any) {
// Calculate the bounding height of the text area
const textAreaMaxLines = 4;
const textAreaLineHeight = this.termFontSize * 1.5;
const textAreaMinHeight = textAreaLineHeight;
const textAreaMaxHeight = textAreaLineHeight * textAreaMaxLines;
// Get the height of the wrapped text area content. Courtesy of https://stackoverflow.com/questions/995168/textarea-to-resize-based-on-content-length
this.textAreaRef.current.style.height = "1px";
const scrollHeight: number = this.textAreaRef.current.scrollHeight;
// Set the new height of the text area, bounded by the min and max height.
const newHeight = Math.min(Math.max(scrollHeight, textAreaMinHeight), textAreaMaxHeight);
this.textAreaRef.current.style.height = newHeight + "px";
GlobalModel.inputModel.codeSelectDeselectAll();
}
onEnterKeyPressed() {
const inputModel = GlobalModel.inputModel;
const currentRef = this.textAreaRef.current;
if (currentRef == null) {
return;
}
if (inputModel.getCodeSelectSelectedIndex() == -1) {
const messageStr = currentRef.value;
this.submitChatMessage(messageStr);
currentRef.value = "";
} else {
mobx.action(() => {
inputModel.grabCodeSelectSelection();
inputModel.setAuxViewFocus(false);
})();
}
}
onExpandInputPressed() {
const currentRef = this.textAreaRef.current;
if (currentRef == null) {
return;
}
currentRef.setRangeText("\n", currentRef.selectionStart, currentRef.selectionEnd, "end");
GlobalModel.inputModel.codeSelectDeselectAll();
}
onArrowUpPressed(): boolean {
const currentRef = this.textAreaRef.current;
if (currentRef == null) {
return false;
}
if (this.getLinePos(currentRef).linePos > 1) {
// normal up arrow
GlobalModel.inputModel.codeSelectDeselectAll();
return false;
}
GlobalModel.inputModel.codeSelectSelectNextOldestCodeBlock();
return true;
}
onArrowDownPressed(): boolean {
const currentRef = this.textAreaRef.current;
const inputModel = GlobalModel.inputModel;
if (currentRef == null) {
return false;
}
if (inputModel.getCodeSelectSelectedIndex() == inputModel.codeSelectBottom) {
GlobalModel.inputModel.codeSelectDeselectAll();
return false;
}
inputModel.codeSelectSelectNextNewestCodeBlock();
return true;
}
@mobx.action
@boundMethod
onKeyDown(e: any) {}
renderError(err: string): any {
return <div className="chat-msg-error">{err}</div>;
}
render() {
const chatMessageItems = GlobalModel.inputModel.AICmdInfoChatItems.slice();
return (
<div className="sidebar-aichat">
<AIChatKeybindings AIChatObject={this}></AIChatKeybindings>
<div className="titlebar">
<div className="title-string">Wave AI</div>
</div>
<If condition={chatMessageItems.length > 0}>
<ChatContent />
</If>
<div className="chat-input">
<textarea
key="main"
ref={this.textAreaRef}
autoComplete="off"
autoCorrect="off"
autoFocus={true}
id="chat-cmd-input"
onChange={this.onTextAreaChange}
onKeyDown={this.onKeyDown}
style={{ fontSize: this.termFontSize }}
className="chat-textarea"
placeholder="Send a Message..."
></textarea>
</div>
</div>
);
}
}
export { AIChat };
+44 -50
View File
@@ -13,64 +13,58 @@
font-weight: var(--sidebar-font-weight);
border-left: 1px solid var(--app-border-color);
.sidebar-content {
.header {
height: 39px;
display: flex;
flex-direction: column;
height: 100%;
align-items: center;
justify-content: flex-end;
padding: 0 5px;
border-bottom: 1px solid var(--app-border-color);
}
.rsb-modes {
display: flex;
flex-direction: row;
padding: 5px 10px;
gap: 5px;
border-bottom: 1px solid var(--app-border-color);
.icon-container {
padding: 2px;
cursor: pointer;
}
.icon-container:hover {
background-color: var(--app-selected-mask-color);
}
}
&.collapsed {
display: none;
}
.keybind-debug-pane {
padding: 10px;
overflow: hidden;
width: 100%;
.header {
height: 39px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 5px;
border-bottom: 1px solid var(--app-border-color);
.keybind-pane-title {
font-size: 18px;
font-weight: bold;
padding-bottom: 5px;
}
.rsb-modes {
display: flex;
flex-direction: row;
padding: 5px 10px;
gap: 5px;
.icon-container {
padding: 2px;
cursor: pointer;
}
.icon-container:hover {
background-color: var(--app-selected-mask-color);
}
.keybind-level {
margin-top: 10px;
font-weight: bold;
font-size: 16px;
}
&.collapsed {
display: none;
}
.keybind-debug-pane {
padding: 10px;
.keybind-domain {
font-size: 14px;
margin-left: 20px;
white-space: nowrap;
overflow: hidden;
width: 100%;
.keybind-pane-title {
font-size: 18px;
font-weight: bold;
padding-bottom: 5px;
}
.keybind-level {
margin-top: 10px;
font-weight: bold;
font-size: 16px;
}
.keybind-domain {
font-size: 14px;
margin-left: 20px;
white-space: nowrap;
overflow: hidden;
}
}
}
}
+21 -32
View File
@@ -11,7 +11,6 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
import { GlobalModel } from "@/models";
import { ResizableSidebar, Button } from "@/elements";
import { WaveBookDisplay } from "./wavebook";
import { AIChat } from "./aichat";
import "./right.less";
@@ -78,45 +77,35 @@ class RightSideBar extends React.Component<RightSideBarProps, {}> {
{(toggleCollapse) => (
<React.Fragment>
<div className="header">
<div className="rsb-modes">
<div
className="icon-container"
title="Show Keybinding Debugger"
onClick={() => this.setMode("ai")}
>
<i className="fa-sharp fa-regular fa-sparkles fa-fw" />
</div>
<div className="flex-spacer" />
<If condition={GlobalModel.isDev}>
<div
className="icon-container"
title="Show Keybinding Debugger"
onClick={() => this.setMode("keybind")}
>
<i className="fa-fw fa-sharp fa-keyboard fa-solid" />
</div>
</If>
<div
className="icon-container"
title="Show Keybinding Debugger"
onClick={() => this.setMode("wavebook")}
>
<i className="fa-sharp fa-solid fa-book-sparkles"></i>
</div>
</div>
<Button className="secondary ghost" onClick={toggleCollapse}>
<i className="fa-sharp fa-regular fa-xmark"></i>
</Button>
</div>
{/* <If condition={this.mode.get() == "keybind"}>
<div className="rsb-modes">
<div className="flex-spacer" />
<If condition={GlobalModel.isDev}>
<div
className="icon-container"
title="Show Keybinding Debugger"
onClick={() => this.setMode("keybind")}
>
<i className="fa-fw fa-sharp fa-keyboard fa-solid" />
</div>
</If>
<div
className="icon-container"
title="Show Keybinding Debugger"
onClick={() => this.setMode("wavebook")}
>
<i className="fa-sharp fa-solid fa-book-sparkles"></i>
</div>
</div>
<If condition={this.mode.get() == "keybind"}>
<KeybindDevPane></KeybindDevPane>
</If> */}
</If>
<If condition={this.mode.get() == "wavebook"}>
<WaveBookDisplay></WaveBookDisplay>
</If>
<If condition={this.mode.get() == "ai"}>
<AIChat />
</If>
</React.Fragment>
)}
</ResizableSidebar>
+1 -1
View File
@@ -10,7 +10,7 @@ import "./auxview.less";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
interface AuxiliaryCmdViewProps {
title?: string;
title: string;
className?: string;
iconClass?: string;
titleBarContents?: React.ReactElement[];
+55 -1
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"`
+100
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()))
}
+1 -1
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)
File diff suppressed because it is too large Load Diff
+9
View File
@@ -1522,6 +1522,14 @@ 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 addScVarsToState(state *packet.ShellState) *packet.ShellState {
if state == nil {
return nil
@@ -2210,6 +2218,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)
+1689 -497
View File
File diff suppressed because it is too large Load Diff