From e41f909b5edcfe449ecfbc7aeac181651add9d31 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sun, 3 Aug 2025 08:00:07 -0400 Subject: [PATCH 01/20] api changes for file i/o --- server/lib/oapi/oapi.go | 3275 ++++++++++++++++++++++++++++++--------- server/openapi.yaml | 205 +++ 2 files changed, 2709 insertions(+), 771 deletions(-) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 5adf665d..a33d4731 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -11,6 +11,7 @@ import ( "encoding/json" "fmt" "io" + "mime/multipart" "net/http" "net/url" "path" @@ -21,6 +22,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/oapi-codegen/runtime" strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" + openapi_types "github.com/oapi-codegen/runtime/types" ) // Defines values for ClickMouseRequestButton. @@ -39,6 +41,14 @@ const ( Up ClickMouseRequestClickType = "up" ) +// Defines values for FileSystemEventType. +const ( + CREATE FileSystemEventType = "CREATE" + DELETE FileSystemEventType = "DELETE" + RENAME FileSystemEventType = "RENAME" + WRITE FileSystemEventType = "WRITE" +) + // ClickMouseRequest defines model for ClickMouseRequest. type ClickMouseRequest struct { // Button Mouse button to interact with @@ -77,6 +87,24 @@ type Error struct { Message string `json:"message"` } +// FileSystemEvent Filesystem change event. +type FileSystemEvent struct { + // IsDir Whether the affected path is a directory. + IsDir *bool `json:"is_dir,omitempty"` + + // Name Base name of the file or directory affected. + Name *string `json:"name,omitempty"` + + // Path Absolute path of the file or directory. + Path string `json:"path"` + + // Type Event type. + Type FileSystemEventType `json:"type"` +} + +// FileSystemEventType Event type. +type FileSystemEventType string + // MoveMouseRequest defines model for MoveMouseRequest. type MoveMouseRequest struct { // HoldKeys Modifier keys to hold during the move @@ -100,6 +128,15 @@ type RecorderInfo struct { StartedAt *time.Time `json:"started_at"` } +// StartFsWatchRequest defines model for StartFsWatchRequest. +type StartFsWatchRequest struct { + // Path Directory to watch. + Path string `json:"path"` + + // Recursive Whether to watch recursively. + Recursive *bool `json:"recursive,omitempty"` +} + // StartRecordingRequest defines model for StartRecordingRequest. type StartRecordingRequest struct { // Framerate Recording framerate in fps (overrides server default) @@ -136,6 +173,31 @@ type InternalError = Error // NotFoundError defines model for NotFoundError. type NotFoundError = Error +// DownloadFileParams defines parameters for DownloadFile. +type DownloadFileParams struct { + Path string `form:"path" json:"path"` +} + +// ReadFileParams defines parameters for ReadFile. +type ReadFileParams struct { + // Path Absolute or relative file path to read. + Path string `form:"path" json:"path"` +} + +// WriteFileParams defines parameters for WriteFile. +type WriteFileParams struct { + // Path Destination file path. + Path string `form:"path" json:"path"` +} + +// UploadFilesMultipartBody defines parameters for UploadFiles. +type UploadFilesMultipartBody struct { + Files []struct { + DestPath string `json:"dest_path"` + File openapi_types.File `json:"file"` + } `json:"files"` +} + // DownloadRecordingParams defines parameters for DownloadRecording. type DownloadRecordingParams struct { // Id Optional recorder identifier. When omitted, the server uses the default recorder. @@ -148,6 +210,12 @@ type ClickMouseJSONRequestBody = ClickMouseRequest // MoveMouseJSONRequestBody defines body for MoveMouse for application/json ContentType. type MoveMouseJSONRequestBody = MoveMouseRequest +// UploadFilesMultipartRequestBody defines body for UploadFiles for multipart/form-data ContentType. +type UploadFilesMultipartRequestBody UploadFilesMultipartBody + +// StartFsWatchJSONRequestBody defines body for StartFsWatch for application/json ContentType. +type StartFsWatchJSONRequestBody = StartFsWatchRequest + // DeleteRecordingJSONRequestBody defines body for DeleteRecording for application/json ContentType. type DeleteRecordingJSONRequestBody = DeleteRecordingRequest @@ -240,6 +308,29 @@ type ClientInterface interface { MoveMouse(ctx context.Context, body MoveMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // DownloadFile request + DownloadFile(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ReadFile request + ReadFile(ctx context.Context, params *ReadFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // WriteFileWithBody request with any body + WriteFileWithBody(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UploadFilesWithBody request with any body + UploadFilesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + // StartFsWatchWithBody request with any body + StartFsWatchWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + StartFsWatch(ctx context.Context, body StartFsWatchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // StopFsWatch request + StopFsWatch(ctx context.Context, watchId string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // StreamFsEvents request + StreamFsEvents(ctx context.Context, watchId string, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteRecordingWithBody request with any body DeleteRecordingWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -310,6 +401,102 @@ func (c *Client) MoveMouse(ctx context.Context, body MoveMouseJSONRequestBody, r return c.Client.Do(req) } +func (c *Client) DownloadFile(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDownloadFileRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ReadFile(ctx context.Context, params *ReadFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewReadFileRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) WriteFileWithBody(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewWriteFileRequestWithBody(c.Server, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UploadFilesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUploadFilesRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) StartFsWatchWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewStartFsWatchRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) StartFsWatch(ctx context.Context, body StartFsWatchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewStartFsWatchRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) StopFsWatch(ctx context.Context, watchId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewStopFsWatchRequest(c.Server, watchId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) StreamFsEvents(ctx context.Context, watchId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewStreamFsEventsRequest(c.Server, watchId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteRecordingWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteRecordingRequestWithBody(c.Server, contentType, body) if err != nil { @@ -486,19 +673,8 @@ func NewMoveMouseRequestWithBody(server string, contentType string, body io.Read return req, nil } -// NewDeleteRecordingRequest calls the generic DeleteRecording builder with application/json body -func NewDeleteRecordingRequest(server string, body DeleteRecordingJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewDeleteRecordingRequestWithBody(server, "application/json", bodyReader) -} - -// NewDeleteRecordingRequestWithBody generates requests for DeleteRecording with any type of body -func NewDeleteRecordingRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewDownloadFileRequest generates requests for DownloadFile +func NewDownloadFileRequest(server string, params *DownloadFileParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -506,7 +682,7 @@ func NewDeleteRecordingRequestWithBody(server string, contentType string, body i return nil, err } - operationPath := fmt.Sprintf("/recording/delete") + operationPath := fmt.Sprintf("/fs/download") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -516,18 +692,34 @@ func NewDeleteRecordingRequestWithBody(server string, contentType string, body i return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewDownloadRecordingRequest generates requests for DownloadRecording -func NewDownloadRecordingRequest(server string, params *DownloadRecordingParams) (*http.Request, error) { +// NewReadFileRequest generates requests for ReadFile +func NewReadFileRequest(server string, params *ReadFileParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -535,7 +727,7 @@ func NewDownloadRecordingRequest(server string, params *DownloadRecordingParams) return nil, err } - operationPath := fmt.Sprintf("/recording/download") + operationPath := fmt.Sprintf("/fs/file") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -548,20 +740,16 @@ func NewDownloadRecordingRequest(server string, params *DownloadRecordingParams) if params != nil { queryValues := queryURL.Query() - if params.Id != nil { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) } } - } queryURL.RawQuery = queryValues.Encode() @@ -575,8 +763,8 @@ func NewDownloadRecordingRequest(server string, params *DownloadRecordingParams) return req, nil } -// NewListRecordersRequest generates requests for ListRecorders -func NewListRecordersRequest(server string) (*http.Request, error) { +// NewWriteFileRequestWithBody generates requests for WriteFile with any type of body +func NewWriteFileRequestWithBody(server string, params *WriteFileParams, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -584,7 +772,7 @@ func NewListRecordersRequest(server string) (*http.Request, error) { return nil, err } - operationPath := fmt.Sprintf("/recording/list") + operationPath := fmt.Sprintf("/fs/file") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -594,27 +782,36 @@ func NewListRecordersRequest(server string) (*http.Request, error) { return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } + if params != nil { + queryValues := queryURL.Query() - return req, nil -} + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } -// NewStartRecordingRequest calls the generic StartRecording builder with application/json body -func NewStartRecordingRequest(server string, body StartRecordingJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) if err != nil { return nil, err } - bodyReader = bytes.NewReader(buf) - return NewStartRecordingRequestWithBody(server, "application/json", bodyReader) + + req.Header.Add("Content-Type", contentType) + + return req, nil } -// NewStartRecordingRequestWithBody generates requests for StartRecording with any type of body -func NewStartRecordingRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewUploadFilesRequestWithBody generates requests for UploadFiles with any type of body +func NewUploadFilesRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -622,7 +819,7 @@ func NewStartRecordingRequestWithBody(server string, contentType string, body io return nil, err } - operationPath := fmt.Sprintf("/recording/start") + operationPath := fmt.Sprintf("/fs/upload") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -642,19 +839,19 @@ func NewStartRecordingRequestWithBody(server string, contentType string, body io return req, nil } -// NewStopRecordingRequest calls the generic StopRecording builder with application/json body -func NewStopRecordingRequest(server string, body StopRecordingJSONRequestBody) (*http.Request, error) { +// NewStartFsWatchRequest calls the generic StartFsWatch builder with application/json body +func NewStartFsWatchRequest(server string, body StartFsWatchJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewStopRecordingRequestWithBody(server, "application/json", bodyReader) + return NewStartFsWatchRequestWithBody(server, "application/json", bodyReader) } -// NewStopRecordingRequestWithBody generates requests for StopRecording with any type of body -func NewStopRecordingRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewStartFsWatchRequestWithBody generates requests for StartFsWatch with any type of body +func NewStartFsWatchRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -662,7 +859,7 @@ func NewStopRecordingRequestWithBody(server string, contentType string, body io. return nil, err } - operationPath := fmt.Sprintf("/recording/stop") + operationPath := fmt.Sprintf("/fs/watch") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -682,1001 +879,2311 @@ func NewStopRecordingRequestWithBody(server string, contentType string, body io. return req, nil } -func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { - for _, r := range c.RequestEditors { - if err := r(ctx, req); err != nil { - return err - } - } - for _, r := range additionalEditors { - if err := r(ctx, req); err != nil { - return err - } - } - return nil -} +// NewStopFsWatchRequest generates requests for StopFsWatch +func NewStopFsWatchRequest(server string, watchId string) (*http.Request, error) { + var err error -// ClientWithResponses builds on ClientInterface to offer response payloads -type ClientWithResponses struct { - ClientInterface -} + var pathParam0 string -// NewClientWithResponses creates a new ClientWithResponses, which wraps -// Client with return type handling -func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { - client, err := NewClient(server, opts...) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "watch_id", runtime.ParamLocationPath, watchId) if err != nil { return nil, err } - return &ClientWithResponses{client}, nil -} -// WithBaseURL overrides the baseURL. -func WithBaseURL(baseURL string) ClientOption { - return func(c *Client) error { - newBaseURL, err := url.Parse(baseURL) - if err != nil { - return err - } - c.Server = newBaseURL.String() - return nil + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } -} -// ClientWithResponsesInterface is the interface specification for the client with responses above. -type ClientWithResponsesInterface interface { - // ClickMouseWithBodyWithResponse request with any body - ClickMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) + operationPath := fmt.Sprintf("/fs/watch/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - ClickMouseWithResponse(ctx context.Context, body ClickMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } - // MoveMouseWithBodyWithResponse request with any body - MoveMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MoveMouseResponse, error) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } - MoveMouseWithResponse(ctx context.Context, body MoveMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*MoveMouseResponse, error) + return req, nil +} - // DeleteRecordingWithBodyWithResponse request with any body - DeleteRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteRecordingResponse, error) +// NewStreamFsEventsRequest generates requests for StreamFsEvents +func NewStreamFsEventsRequest(server string, watchId string) (*http.Request, error) { + var err error - DeleteRecordingWithResponse(ctx context.Context, body DeleteRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteRecordingResponse, error) + var pathParam0 string - // DownloadRecordingWithResponse request - DownloadRecordingWithResponse(ctx context.Context, params *DownloadRecordingParams, reqEditors ...RequestEditorFn) (*DownloadRecordingResponse, error) - - // ListRecordersWithResponse request - ListRecordersWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRecordersResponse, error) + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "watch_id", runtime.ParamLocationPath, watchId) + if err != nil { + return nil, err + } - // StartRecordingWithBodyWithResponse request with any body - StartRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StartRecordingResponse, error) + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } - StartRecordingWithResponse(ctx context.Context, body StartRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*StartRecordingResponse, error) + operationPath := fmt.Sprintf("/fs/watch/%s/events", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } - // StopRecordingWithBodyWithResponse request with any body - StopRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StopRecordingResponse, error) + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } - StopRecordingWithResponse(ctx context.Context, body StopRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*StopRecordingResponse, error) -} + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } -type ClickMouseResponse struct { - Body []byte - HTTPResponse *http.Response - JSON400 *BadRequestError - JSON500 *InternalError + return req, nil } -// Status returns HTTPResponse.Status -func (r ClickMouseResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status +// NewDeleteRecordingRequest calls the generic DeleteRecording builder with application/json body +func NewDeleteRecordingRequest(server string, body DeleteRecordingJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err } - return http.StatusText(0) + bodyReader = bytes.NewReader(buf) + return NewDeleteRecordingRequestWithBody(server, "application/json", bodyReader) } -// StatusCode returns HTTPResponse.StatusCode -func (r ClickMouseResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode +// NewDeleteRecordingRequestWithBody generates requests for DeleteRecording with any type of body +func NewDeleteRecordingRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } - return 0 -} -type MoveMouseResponse struct { - Body []byte - HTTPResponse *http.Response - JSON400 *BadRequestError - JSON500 *InternalError -} + operationPath := fmt.Sprintf("/recording/delete") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } -// Status returns HTTPResponse.Status -func (r MoveMouseResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r MoveMouseResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err } - return 0 -} -type DeleteRecordingResponse struct { - Body []byte - HTTPResponse *http.Response - JSON400 *BadRequestError - JSON404 *NotFoundError - JSON500 *InternalError -} + req.Header.Add("Content-Type", contentType) -// Status returns HTTPResponse.Status -func (r DeleteRecordingResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) + return req, nil } -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteRecordingResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} +// NewDownloadRecordingRequest generates requests for DownloadRecording +func NewDownloadRecordingRequest(server string, params *DownloadRecordingParams) (*http.Request, error) { + var err error -type DownloadRecordingResponse struct { - Body []byte - HTTPResponse *http.Response - JSON400 *BadRequestError - JSON404 *NotFoundError - JSON500 *InternalError -} + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } -// Status returns HTTPResponse.Status -func (r DownloadRecordingResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + operationPath := fmt.Sprintf("/recording/download") + if operationPath[0] == '/' { + operationPath = "." + operationPath } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r DownloadRecordingResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err } - return 0 -} -type ListRecordersResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *[]RecorderInfo - JSON500 *InternalError -} + if params != nil { + queryValues := queryURL.Query() -// Status returns HTTPResponse.Status -func (r ListRecordersResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} + if params.Id != nil { -// StatusCode returns HTTPResponse.StatusCode -func (r ListRecordersResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "id", runtime.ParamLocationQuery, *params.Id); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } -type StartRecordingResponse struct { - Body []byte - HTTPResponse *http.Response - JSON400 *BadRequestError - JSON409 *ConflictError - JSON500 *InternalError -} + } -// Status returns HTTPResponse.Status -func (r StartRecordingResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status + queryURL.RawQuery = queryValues.Encode() } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r StartRecordingResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err } - return 0 -} -type StopRecordingResponse struct { - Body []byte - HTTPResponse *http.Response - JSON400 *BadRequestError - JSON500 *InternalError + return req, nil } -// Status returns HTTPResponse.Status -func (r StopRecordingResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status +// NewListRecordersRequest generates requests for ListRecorders +func NewListRecordersRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err } - return http.StatusText(0) -} -// StatusCode returns HTTPResponse.StatusCode -func (r StopRecordingResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode + operationPath := fmt.Sprintf("/recording/list") + if operationPath[0] == '/' { + operationPath = "." + operationPath } - return 0 -} -// ClickMouseWithBodyWithResponse request with arbitrary body returning *ClickMouseResponse -func (c *ClientWithResponses) ClickMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) { - rsp, err := c.ClickMouseWithBody(ctx, contentType, body, reqEditors...) + queryURL, err := serverURL.Parse(operationPath) if err != nil { return nil, err } - return ParseClickMouseResponse(rsp) -} -func (c *ClientWithResponses) ClickMouseWithResponse(ctx context.Context, body ClickMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) { - rsp, err := c.ClickMouse(ctx, body, reqEditors...) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } - return ParseClickMouseResponse(rsp) + + return req, nil } -// MoveMouseWithBodyWithResponse request with arbitrary body returning *MoveMouseResponse -func (c *ClientWithResponses) MoveMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MoveMouseResponse, error) { - rsp, err := c.MoveMouseWithBody(ctx, contentType, body, reqEditors...) +// NewStartRecordingRequest calls the generic StartRecording builder with application/json body +func NewStartRecordingRequest(server string, body StartRecordingJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } - return ParseMoveMouseResponse(rsp) + bodyReader = bytes.NewReader(buf) + return NewStartRecordingRequestWithBody(server, "application/json", bodyReader) } -func (c *ClientWithResponses) MoveMouseWithResponse(ctx context.Context, body MoveMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*MoveMouseResponse, error) { - rsp, err := c.MoveMouse(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseMoveMouseResponse(rsp) -} +// NewStartRecordingRequestWithBody generates requests for StartRecording with any type of body +func NewStartRecordingRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error -// DeleteRecordingWithBodyWithResponse request with arbitrary body returning *DeleteRecordingResponse -func (c *ClientWithResponses) DeleteRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteRecordingResponse, error) { - rsp, err := c.DeleteRecordingWithBody(ctx, contentType, body, reqEditors...) + serverURL, err := url.Parse(server) if err != nil { return nil, err } - return ParseDeleteRecordingResponse(rsp) -} -func (c *ClientWithResponses) DeleteRecordingWithResponse(ctx context.Context, body DeleteRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteRecordingResponse, error) { - rsp, err := c.DeleteRecording(ctx, body, reqEditors...) - if err != nil { - return nil, err + operationPath := fmt.Sprintf("/recording/start") + if operationPath[0] == '/' { + operationPath = "." + operationPath } - return ParseDeleteRecordingResponse(rsp) -} -// DownloadRecordingWithResponse request returning *DownloadRecordingResponse -func (c *ClientWithResponses) DownloadRecordingWithResponse(ctx context.Context, params *DownloadRecordingParams, reqEditors ...RequestEditorFn) (*DownloadRecordingResponse, error) { - rsp, err := c.DownloadRecording(ctx, params, reqEditors...) + queryURL, err := serverURL.Parse(operationPath) if err != nil { return nil, err } - return ParseDownloadRecordingResponse(rsp) -} -// ListRecordersWithResponse request returning *ListRecordersResponse -func (c *ClientWithResponses) ListRecordersWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRecordersResponse, error) { - rsp, err := c.ListRecorders(ctx, reqEditors...) + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } - return ParseListRecordersResponse(rsp) + + req.Header.Add("Content-Type", contentType) + + return req, nil } -// StartRecordingWithBodyWithResponse request with arbitrary body returning *StartRecordingResponse -func (c *ClientWithResponses) StartRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StartRecordingResponse, error) { - rsp, err := c.StartRecordingWithBody(ctx, contentType, body, reqEditors...) +// NewStopRecordingRequest calls the generic StopRecording builder with application/json body +func NewStopRecordingRequest(server string, body StopRecordingJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } - return ParseStartRecordingResponse(rsp) + bodyReader = bytes.NewReader(buf) + return NewStopRecordingRequestWithBody(server, "application/json", bodyReader) } -func (c *ClientWithResponses) StartRecordingWithResponse(ctx context.Context, body StartRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*StartRecordingResponse, error) { - rsp, err := c.StartRecording(ctx, body, reqEditors...) +// NewStopRecordingRequestWithBody generates requests for StopRecording with any type of body +func NewStopRecordingRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) if err != nil { return nil, err } - return ParseStartRecordingResponse(rsp) -} -// StopRecordingWithBodyWithResponse request with arbitrary body returning *StopRecordingResponse -func (c *ClientWithResponses) StopRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StopRecordingResponse, error) { - rsp, err := c.StopRecordingWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err + operationPath := fmt.Sprintf("/recording/stop") + if operationPath[0] == '/' { + operationPath = "." + operationPath } - return ParseStopRecordingResponse(rsp) -} -func (c *ClientWithResponses) StopRecordingWithResponse(ctx context.Context, body StopRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*StopRecordingResponse, error) { - rsp, err := c.StopRecording(ctx, body, reqEditors...) + queryURL, err := serverURL.Parse(operationPath) if err != nil { return nil, err } - return ParseStopRecordingResponse(rsp) -} -// ParseClickMouseResponse parses an HTTP response from a ClickMouseWithResponse call -func ParseClickMouseResponse(rsp *http.Response) (*ClickMouseResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() + req, err := http.NewRequest("POST", queryURL.String(), body) if err != nil { return nil, err } - response := &ClickMouseResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } + req.Header.Add("Content-Type", contentType) - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest + return req, nil +} - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err } - response.JSON500 = &dest - } + return nil +} - return response, nil +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface } -// ParseMoveMouseResponse parses an HTTP response from a MoveMouseWithResponse call -func ParseMoveMouseResponse(rsp *http.Response) (*MoveMouseResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) if err != nil { return nil, err } + return &ClientWithResponses{client}, nil +} - response := &MoveMouseResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil } +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // ClickMouseWithBodyWithResponse request with any body + ClickMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest + ClickMouseWithResponse(ctx context.Context, body ClickMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) - } + // MoveMouseWithBodyWithResponse request with any body + MoveMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MoveMouseResponse, error) - return response, nil -} + MoveMouseWithResponse(ctx context.Context, body MoveMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*MoveMouseResponse, error) -// ParseDeleteRecordingResponse parses an HTTP response from a DeleteRecordingWithResponse call -func ParseDeleteRecordingResponse(rsp *http.Response) (*DeleteRecordingResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } + // DownloadFileWithResponse request + DownloadFileWithResponse(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*DownloadFileResponse, error) - response := &DeleteRecordingResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } + // ReadFileWithResponse request + ReadFileWithResponse(ctx context.Context, params *ReadFileParams, reqEditors ...RequestEditorFn) (*ReadFileResponse, error) - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest + // WriteFileWithBodyWithResponse request with any body + WriteFileWithBodyWithResponse(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*WriteFileResponse, error) - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest + // UploadFilesWithBodyWithResponse request with any body + UploadFilesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadFilesResponse, error) - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest + // StartFsWatchWithBodyWithResponse request with any body + StartFsWatchWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StartFsWatchResponse, error) - } + StartFsWatchWithResponse(ctx context.Context, body StartFsWatchJSONRequestBody, reqEditors ...RequestEditorFn) (*StartFsWatchResponse, error) - return response, nil -} + // StopFsWatchWithResponse request + StopFsWatchWithResponse(ctx context.Context, watchId string, reqEditors ...RequestEditorFn) (*StopFsWatchResponse, error) -// ParseDownloadRecordingResponse parses an HTTP response from a DownloadRecordingWithResponse call -func ParseDownloadRecordingResponse(rsp *http.Response) (*DownloadRecordingResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } + // StreamFsEventsWithResponse request + StreamFsEventsWithResponse(ctx context.Context, watchId string, reqEditors ...RequestEditorFn) (*StreamFsEventsResponse, error) - response := &DownloadRecordingResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } + // DeleteRecordingWithBodyWithResponse request with any body + DeleteRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteRecordingResponse, error) - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest + DeleteRecordingWithResponse(ctx context.Context, body DeleteRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteRecordingResponse, error) - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest + // DownloadRecordingWithResponse request + DownloadRecordingWithResponse(ctx context.Context, params *DownloadRecordingParams, reqEditors ...RequestEditorFn) (*DownloadRecordingResponse, error) - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest + // ListRecordersWithResponse request + ListRecordersWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRecordersResponse, error) - } + // StartRecordingWithBodyWithResponse request with any body + StartRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StartRecordingResponse, error) - return response, nil + StartRecordingWithResponse(ctx context.Context, body StartRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*StartRecordingResponse, error) + + // StopRecordingWithBodyWithResponse request with any body + StopRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StopRecordingResponse, error) + + StopRecordingWithResponse(ctx context.Context, body StopRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*StopRecordingResponse, error) } -// ParseListRecordersResponse parses an HTTP response from a ListRecordersWithResponse call -func ParseListRecordersResponse(rsp *http.Response) (*ListRecordersResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +type ClickMouseResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r ClickMouseResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - response := &ListRecordersResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// StatusCode returns HTTPResponse.StatusCode +func (r ClickMouseResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []RecorderInfo - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest +type MoveMouseResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON500 *InternalError +} - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest +// Status returns HTTPResponse.Status +func (r MoveMouseResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} +// StatusCode returns HTTPResponse.StatusCode +func (r MoveMouseResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - return response, nil +type DownloadFileResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON404 *NotFoundError } -// ParseStartRecordingResponse parses an HTTP response from a StartRecordingWithResponse call -func ParseStartRecordingResponse(rsp *http.Response) (*StartRecordingResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r DownloadFileResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - response := &StartRecordingResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// StatusCode returns HTTPResponse.StatusCode +func (r DownloadFileResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest +type ReadFileResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON404 *NotFoundError +} - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: - var dest ConflictError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON409 = &dest +// Status returns HTTPResponse.Status +func (r ReadFileResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest +// StatusCode returns HTTPResponse.StatusCode +func (r ReadFileResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type WriteFileResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON404 *NotFoundError +} +// Status returns HTTPResponse.Status +func (r WriteFileResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - return response, nil +// StatusCode returns HTTPResponse.StatusCode +func (r WriteFileResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 } -// ParseStopRecordingResponse parses an HTTP response from a StopRecordingWithResponse call -func ParseStopRecordingResponse(rsp *http.Response) (*StopRecordingResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err +type UploadFilesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON404 *NotFoundError +} + +// Status returns HTTPResponse.Status +func (r UploadFilesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } + return http.StatusText(0) +} - response := &StopRecordingResponse{ - Body: bodyBytes, - HTTPResponse: rsp, +// StatusCode returns HTTPResponse.StatusCode +func (r UploadFilesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest +type StartFsWatchResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *struct { + // WatchId Unique identifier for the directory watch + WatchId *string `json:"watch_id,omitempty"` + } + JSON400 *BadRequestError + JSON404 *NotFoundError +} - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest +// Status returns HTTPResponse.Status +func (r StartFsWatchResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} +// StatusCode returns HTTPResponse.StatusCode +func (r StartFsWatchResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode } + return 0 +} - return response, nil +type StopFsWatchResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON404 *NotFoundError } -// ServerInterface represents all server handlers. -type ServerInterface interface { - // Simulate a mouse click action on the host computer - // (POST /computer/click_mouse) - ClickMouse(w http.ResponseWriter, r *http.Request) - // Move the mouse cursor to the specified coordinates on the host computer - // (POST /computer/move_mouse) - MoveMouse(w http.ResponseWriter, r *http.Request) - // Delete a previously recorded video file - // (POST /recording/delete) - DeleteRecording(w http.ResponseWriter, r *http.Request) - // Download the most recently recorded video file - // (GET /recording/download) - DownloadRecording(w http.ResponseWriter, r *http.Request, params DownloadRecordingParams) - // List all recorders - // (GET /recording/list) - ListRecorders(w http.ResponseWriter, r *http.Request) - // Start a screen recording. Only one recording per ID can be registered at a time. - // (POST /recording/start) - StartRecording(w http.ResponseWriter, r *http.Request) - // Stop the recording - // (POST /recording/stop) - StopRecording(w http.ResponseWriter, r *http.Request) +// Status returns HTTPResponse.Status +func (r StopFsWatchResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r StopFsWatchResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type StreamFsEventsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON404 *NotFoundError +} + +// Status returns HTTPResponse.Status +func (r StreamFsEventsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r StreamFsEventsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteRecordingResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON404 *NotFoundError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r DeleteRecordingResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteRecordingResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DownloadRecordingResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON404 *NotFoundError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r DownloadRecordingResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DownloadRecordingResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListRecordersResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]RecorderInfo + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r ListRecordersResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListRecordersResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type StartRecordingResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON409 *ConflictError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r StartRecordingResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r StartRecordingResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type StopRecordingResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r StopRecordingResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r StopRecordingResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ClickMouseWithBodyWithResponse request with arbitrary body returning *ClickMouseResponse +func (c *ClientWithResponses) ClickMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) { + rsp, err := c.ClickMouseWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseClickMouseResponse(rsp) +} + +func (c *ClientWithResponses) ClickMouseWithResponse(ctx context.Context, body ClickMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) { + rsp, err := c.ClickMouse(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseClickMouseResponse(rsp) +} + +// MoveMouseWithBodyWithResponse request with arbitrary body returning *MoveMouseResponse +func (c *ClientWithResponses) MoveMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MoveMouseResponse, error) { + rsp, err := c.MoveMouseWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseMoveMouseResponse(rsp) +} + +func (c *ClientWithResponses) MoveMouseWithResponse(ctx context.Context, body MoveMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*MoveMouseResponse, error) { + rsp, err := c.MoveMouse(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseMoveMouseResponse(rsp) +} + +// DownloadFileWithResponse request returning *DownloadFileResponse +func (c *ClientWithResponses) DownloadFileWithResponse(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*DownloadFileResponse, error) { + rsp, err := c.DownloadFile(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDownloadFileResponse(rsp) +} + +// ReadFileWithResponse request returning *ReadFileResponse +func (c *ClientWithResponses) ReadFileWithResponse(ctx context.Context, params *ReadFileParams, reqEditors ...RequestEditorFn) (*ReadFileResponse, error) { + rsp, err := c.ReadFile(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseReadFileResponse(rsp) +} + +// WriteFileWithBodyWithResponse request with arbitrary body returning *WriteFileResponse +func (c *ClientWithResponses) WriteFileWithBodyWithResponse(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*WriteFileResponse, error) { + rsp, err := c.WriteFileWithBody(ctx, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseWriteFileResponse(rsp) +} + +// UploadFilesWithBodyWithResponse request with arbitrary body returning *UploadFilesResponse +func (c *ClientWithResponses) UploadFilesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadFilesResponse, error) { + rsp, err := c.UploadFilesWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUploadFilesResponse(rsp) +} + +// StartFsWatchWithBodyWithResponse request with arbitrary body returning *StartFsWatchResponse +func (c *ClientWithResponses) StartFsWatchWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StartFsWatchResponse, error) { + rsp, err := c.StartFsWatchWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseStartFsWatchResponse(rsp) +} + +func (c *ClientWithResponses) StartFsWatchWithResponse(ctx context.Context, body StartFsWatchJSONRequestBody, reqEditors ...RequestEditorFn) (*StartFsWatchResponse, error) { + rsp, err := c.StartFsWatch(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseStartFsWatchResponse(rsp) +} + +// StopFsWatchWithResponse request returning *StopFsWatchResponse +func (c *ClientWithResponses) StopFsWatchWithResponse(ctx context.Context, watchId string, reqEditors ...RequestEditorFn) (*StopFsWatchResponse, error) { + rsp, err := c.StopFsWatch(ctx, watchId, reqEditors...) + if err != nil { + return nil, err + } + return ParseStopFsWatchResponse(rsp) +} + +// StreamFsEventsWithResponse request returning *StreamFsEventsResponse +func (c *ClientWithResponses) StreamFsEventsWithResponse(ctx context.Context, watchId string, reqEditors ...RequestEditorFn) (*StreamFsEventsResponse, error) { + rsp, err := c.StreamFsEvents(ctx, watchId, reqEditors...) + if err != nil { + return nil, err + } + return ParseStreamFsEventsResponse(rsp) +} + +// DeleteRecordingWithBodyWithResponse request with arbitrary body returning *DeleteRecordingResponse +func (c *ClientWithResponses) DeleteRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteRecordingResponse, error) { + rsp, err := c.DeleteRecordingWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteRecordingResponse(rsp) +} + +func (c *ClientWithResponses) DeleteRecordingWithResponse(ctx context.Context, body DeleteRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteRecordingResponse, error) { + rsp, err := c.DeleteRecording(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteRecordingResponse(rsp) +} + +// DownloadRecordingWithResponse request returning *DownloadRecordingResponse +func (c *ClientWithResponses) DownloadRecordingWithResponse(ctx context.Context, params *DownloadRecordingParams, reqEditors ...RequestEditorFn) (*DownloadRecordingResponse, error) { + rsp, err := c.DownloadRecording(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDownloadRecordingResponse(rsp) +} + +// ListRecordersWithResponse request returning *ListRecordersResponse +func (c *ClientWithResponses) ListRecordersWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListRecordersResponse, error) { + rsp, err := c.ListRecorders(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseListRecordersResponse(rsp) +} + +// StartRecordingWithBodyWithResponse request with arbitrary body returning *StartRecordingResponse +func (c *ClientWithResponses) StartRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StartRecordingResponse, error) { + rsp, err := c.StartRecordingWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseStartRecordingResponse(rsp) +} + +func (c *ClientWithResponses) StartRecordingWithResponse(ctx context.Context, body StartRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*StartRecordingResponse, error) { + rsp, err := c.StartRecording(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseStartRecordingResponse(rsp) +} + +// StopRecordingWithBodyWithResponse request with arbitrary body returning *StopRecordingResponse +func (c *ClientWithResponses) StopRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StopRecordingResponse, error) { + rsp, err := c.StopRecordingWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseStopRecordingResponse(rsp) +} + +func (c *ClientWithResponses) StopRecordingWithResponse(ctx context.Context, body StopRecordingJSONRequestBody, reqEditors ...RequestEditorFn) (*StopRecordingResponse, error) { + rsp, err := c.StopRecording(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseStopRecordingResponse(rsp) +} + +// ParseClickMouseResponse parses an HTTP response from a ClickMouseWithResponse call +func ParseClickMouseResponse(rsp *http.Response) (*ClickMouseResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ClickMouseResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseMoveMouseResponse parses an HTTP response from a MoveMouseWithResponse call +func ParseMoveMouseResponse(rsp *http.Response) (*MoveMouseResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &MoveMouseResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseDownloadFileResponse parses an HTTP response from a DownloadFileWithResponse call +func ParseDownloadFileResponse(rsp *http.Response) (*DownloadFileResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DownloadFileResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParseReadFileResponse parses an HTTP response from a ReadFileWithResponse call +func ParseReadFileResponse(rsp *http.Response) (*ReadFileResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ReadFileResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParseWriteFileResponse parses an HTTP response from a WriteFileWithResponse call +func ParseWriteFileResponse(rsp *http.Response) (*WriteFileResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &WriteFileResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParseUploadFilesResponse parses an HTTP response from a UploadFilesWithResponse call +func ParseUploadFilesResponse(rsp *http.Response) (*UploadFilesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UploadFilesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParseStartFsWatchResponse parses an HTTP response from a StartFsWatchWithResponse call +func ParseStartFsWatchResponse(rsp *http.Response) (*StartFsWatchResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &StartFsWatchResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest struct { + // WatchId Unique identifier for the directory watch + WatchId *string `json:"watch_id,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParseStopFsWatchResponse parses an HTTP response from a StopFsWatchWithResponse call +func ParseStopFsWatchResponse(rsp *http.Response) (*StopFsWatchResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &StopFsWatchResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParseStreamFsEventsResponse parses an HTTP response from a StreamFsEventsWithResponse call +func ParseStreamFsEventsResponse(rsp *http.Response) (*StreamFsEventsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &StreamFsEventsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParseDeleteRecordingResponse parses an HTTP response from a DeleteRecordingWithResponse call +func ParseDeleteRecordingResponse(rsp *http.Response) (*DeleteRecordingResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteRecordingResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseDownloadRecordingResponse parses an HTTP response from a DownloadRecordingWithResponse call +func ParseDownloadRecordingResponse(rsp *http.Response) (*DownloadRecordingResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DownloadRecordingResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseListRecordersResponse parses an HTTP response from a ListRecordersWithResponse call +func ParseListRecordersResponse(rsp *http.Response) (*ListRecordersResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListRecordersResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []RecorderInfo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseStartRecordingResponse parses an HTTP response from a StartRecordingWithResponse call +func ParseStartRecordingResponse(rsp *http.Response) (*StartRecordingResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &StartRecordingResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest ConflictError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseStopRecordingResponse parses an HTTP response from a StopRecordingWithResponse call +func ParseStopRecordingResponse(rsp *http.Response) (*StopRecordingResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &StopRecordingResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Simulate a mouse click action on the host computer + // (POST /computer/click_mouse) + ClickMouse(w http.ResponseWriter, r *http.Request) + // Move the mouse cursor to the specified coordinates on the host computer + // (POST /computer/move_mouse) + MoveMouse(w http.ResponseWriter, r *http.Request) + // Download a file + // (GET /fs/download) + DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) + // Read file contents + // (GET /fs/file) + ReadFile(w http.ResponseWriter, r *http.Request, params ReadFileParams) + // Write or create a file + // (PUT /fs/file) + WriteFile(w http.ResponseWriter, r *http.Request, params WriteFileParams) + // Upload one or more files + // (POST /fs/upload) + UploadFiles(w http.ResponseWriter, r *http.Request) + // Watch a directory for changes + // (POST /fs/watch) + StartFsWatch(w http.ResponseWriter, r *http.Request) + // Stop watching a directory + // (DELETE /fs/watch/{watch_id}) + StopFsWatch(w http.ResponseWriter, r *http.Request, watchId string) + // Stream filesystem events for a watch + // (GET /fs/watch/{watch_id}/events) + StreamFsEvents(w http.ResponseWriter, r *http.Request, watchId string) + // Delete a previously recorded video file + // (POST /recording/delete) + DeleteRecording(w http.ResponseWriter, r *http.Request) + // Download the most recently recorded video file + // (GET /recording/download) + DownloadRecording(w http.ResponseWriter, r *http.Request, params DownloadRecordingParams) + // List all recorders + // (GET /recording/list) + ListRecorders(w http.ResponseWriter, r *http.Request) + // Start a screen recording. Only one recording per ID can be registered at a time. + // (POST /recording/start) + StartRecording(w http.ResponseWriter, r *http.Request) + // Stop the recording + // (POST /recording/stop) + StopRecording(w http.ResponseWriter, r *http.Request) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// Simulate a mouse click action on the host computer +// (POST /computer/click_mouse) +func (_ Unimplemented) ClickMouse(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Move the mouse cursor to the specified coordinates on the host computer +// (POST /computer/move_mouse) +func (_ Unimplemented) MoveMouse(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Download a file +// (GET /fs/download) +func (_ Unimplemented) DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Read file contents +// (GET /fs/file) +func (_ Unimplemented) ReadFile(w http.ResponseWriter, r *http.Request, params ReadFileParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Write or create a file +// (PUT /fs/file) +func (_ Unimplemented) WriteFile(w http.ResponseWriter, r *http.Request, params WriteFileParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Upload one or more files +// (POST /fs/upload) +func (_ Unimplemented) UploadFiles(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Watch a directory for changes +// (POST /fs/watch) +func (_ Unimplemented) StartFsWatch(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Stop watching a directory +// (DELETE /fs/watch/{watch_id}) +func (_ Unimplemented) StopFsWatch(w http.ResponseWriter, r *http.Request, watchId string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Stream filesystem events for a watch +// (GET /fs/watch/{watch_id}/events) +func (_ Unimplemented) StreamFsEvents(w http.ResponseWriter, r *http.Request, watchId string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Delete a previously recorded video file +// (POST /recording/delete) +func (_ Unimplemented) DeleteRecording(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Download the most recently recorded video file +// (GET /recording/download) +func (_ Unimplemented) DownloadRecording(w http.ResponseWriter, r *http.Request, params DownloadRecordingParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// List all recorders +// (GET /recording/list) +func (_ Unimplemented) ListRecorders(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Start a screen recording. Only one recording per ID can be registered at a time. +// (POST /recording/start) +func (_ Unimplemented) StartRecording(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Stop the recording +// (POST /recording/stop) +func (_ Unimplemented) StopRecording(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// ClickMouse operation middleware +func (siw *ServerInterfaceWrapper) ClickMouse(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ClickMouse(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// MoveMouse operation middleware +func (siw *ServerInterfaceWrapper) MoveMouse(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MoveMouse(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DownloadFile operation middleware +func (siw *ServerInterfaceWrapper) DownloadFile(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params DownloadFileParams + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DownloadFile(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ReadFile operation middleware +func (siw *ServerInterfaceWrapper) ReadFile(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params ReadFileParams + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ReadFile(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// WriteFile operation middleware +func (siw *ServerInterfaceWrapper) WriteFile(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params WriteFileParams + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.WriteFile(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UploadFiles operation middleware +func (siw *ServerInterfaceWrapper) UploadFiles(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UploadFiles(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// StartFsWatch operation middleware +func (siw *ServerInterfaceWrapper) StartFsWatch(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.StartFsWatch(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// StopFsWatch operation middleware +func (siw *ServerInterfaceWrapper) StopFsWatch(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "watch_id" ------------- + var watchId string + + err = runtime.BindStyledParameterWithOptions("simple", "watch_id", chi.URLParam(r, "watch_id"), &watchId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "watch_id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.StopFsWatch(w, r, watchId) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// StreamFsEvents operation middleware +func (siw *ServerInterfaceWrapper) StreamFsEvents(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "watch_id" ------------- + var watchId string + + err = runtime.BindStyledParameterWithOptions("simple", "watch_id", chi.URLParam(r, "watch_id"), &watchId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "watch_id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.StreamFsEvents(w, r, watchId) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteRecording operation middleware +func (siw *ServerInterfaceWrapper) DeleteRecording(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteRecording(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DownloadRecording operation middleware +func (siw *ServerInterfaceWrapper) DownloadRecording(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params DownloadRecordingParams + + // ------------- Optional query parameter "id" ------------- + + err = runtime.BindQueryParameter("form", true, false, "id", r.URL.Query(), ¶ms.Id) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DownloadRecording(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListRecorders operation middleware +func (siw *ServerInterfaceWrapper) ListRecorders(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListRecorders(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// StartRecording operation middleware +func (siw *ServerInterfaceWrapper) StartRecording(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.StartRecording(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// StopRecording operation middleware +func (siw *ServerInterfaceWrapper) StopRecording(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.StopRecording(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/computer/click_mouse", wrapper.ClickMouse) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/computer/move_mouse", wrapper.MoveMouse) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/fs/download", wrapper.DownloadFile) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/fs/file", wrapper.ReadFile) + }) + r.Group(func(r chi.Router) { + r.Put(options.BaseURL+"/fs/file", wrapper.WriteFile) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/fs/upload", wrapper.UploadFiles) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/fs/watch", wrapper.StartFsWatch) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/fs/watch/{watch_id}", wrapper.StopFsWatch) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/fs/watch/{watch_id}/events", wrapper.StreamFsEvents) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/recording/delete", wrapper.DeleteRecording) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/recording/download", wrapper.DownloadRecording) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/recording/list", wrapper.ListRecorders) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/recording/start", wrapper.StartRecording) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/recording/stop", wrapper.StopRecording) + }) + + return r } -// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. +type BadRequestErrorJSONResponse Error -type Unimplemented struct{} +type ConflictErrorJSONResponse Error -// Simulate a mouse click action on the host computer -// (POST /computer/click_mouse) -func (_ Unimplemented) ClickMouse(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) +type InternalErrorJSONResponse Error + +type NotFoundErrorJSONResponse Error + +type ClickMouseRequestObject struct { + Body *ClickMouseJSONRequestBody } -// Move the mouse cursor to the specified coordinates on the host computer -// (POST /computer/move_mouse) -func (_ Unimplemented) MoveMouse(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) +type ClickMouseResponseObject interface { + VisitClickMouseResponse(w http.ResponseWriter) error } -// Delete a previously recorded video file -// (POST /recording/delete) -func (_ Unimplemented) DeleteRecording(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) +type ClickMouse200Response struct { } -// Download the most recently recorded video file -// (GET /recording/download) -func (_ Unimplemented) DownloadRecording(w http.ResponseWriter, r *http.Request, params DownloadRecordingParams) { - w.WriteHeader(http.StatusNotImplemented) +func (response ClickMouse200Response) VisitClickMouseResponse(w http.ResponseWriter) error { + w.WriteHeader(200) + return nil } -// List all recorders -// (GET /recording/list) -func (_ Unimplemented) ListRecorders(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) +type ClickMouse400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response ClickMouse400JSONResponse) VisitClickMouseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) } -// Start a screen recording. Only one recording per ID can be registered at a time. -// (POST /recording/start) -func (_ Unimplemented) StartRecording(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) +type ClickMouse500JSONResponse struct{ InternalErrorJSONResponse } + +func (response ClickMouse500JSONResponse) VisitClickMouseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) } -// Stop the recording -// (POST /recording/stop) -func (_ Unimplemented) StopRecording(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) +type MoveMouseRequestObject struct { + Body *MoveMouseJSONRequestBody } -// ServerInterfaceWrapper converts contexts to parameters. -type ServerInterfaceWrapper struct { - Handler ServerInterface - HandlerMiddlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +type MoveMouseResponseObject interface { + VisitMoveMouseResponse(w http.ResponseWriter) error } -type MiddlewareFunc func(http.Handler) http.Handler +type MoveMouse200Response struct { +} -// ClickMouse operation middleware -func (siw *ServerInterfaceWrapper) ClickMouse(w http.ResponseWriter, r *http.Request) { +func (response MoveMouse200Response) VisitMoveMouseResponse(w http.ResponseWriter) error { + w.WriteHeader(200) + return nil +} - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ClickMouse(w, r) - })) +type MoveMouse400JSONResponse struct{ BadRequestErrorJSONResponse } - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } +func (response MoveMouse400JSONResponse) VisitMoveMouseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) - handler.ServeHTTP(w, r) + return json.NewEncoder(w).Encode(response) } -// MoveMouse operation middleware -func (siw *ServerInterfaceWrapper) MoveMouse(w http.ResponseWriter, r *http.Request) { +type MoveMouse500JSONResponse struct{ InternalErrorJSONResponse } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.MoveMouse(w, r) - })) +func (response MoveMouse500JSONResponse) VisitMoveMouseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } + return json.NewEncoder(w).Encode(response) +} - handler.ServeHTTP(w, r) +type DownloadFileRequestObject struct { + Params DownloadFileParams } -// DeleteRecording operation middleware -func (siw *ServerInterfaceWrapper) DeleteRecording(w http.ResponseWriter, r *http.Request) { +type DownloadFileResponseObject interface { + VisitDownloadFileResponse(w http.ResponseWriter) error +} - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteRecording(w, r) - })) +type DownloadFile200ApplicationoctetStreamResponse struct { + Body io.Reader + ContentLength int64 +} - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) +func (response DownloadFile200ApplicationoctetStreamResponse) VisitDownloadFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/octet-stream") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) } + w.WriteHeader(200) - handler.ServeHTTP(w, r) + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err } -// DownloadRecording operation middleware -func (siw *ServerInterfaceWrapper) DownloadRecording(w http.ResponseWriter, r *http.Request) { - - var err error +type DownloadFile400JSONResponse struct{ BadRequestErrorJSONResponse } - // Parameter object where we will unmarshal all parameters from the context - var params DownloadRecordingParams +func (response DownloadFile400JSONResponse) VisitDownloadFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) - // ------------- Optional query parameter "id" ------------- + return json.NewEncoder(w).Encode(response) +} - err = runtime.BindQueryParameter("form", true, false, "id", r.URL.Query(), ¶ms.Id) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) - return - } +type DownloadFile404JSONResponse struct{ NotFoundErrorJSONResponse } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DownloadRecording(w, r, params) - })) +func (response DownloadFile404JSONResponse) VisitDownloadFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } + return json.NewEncoder(w).Encode(response) +} - handler.ServeHTTP(w, r) +type ReadFileRequestObject struct { + Params ReadFileParams } -// ListRecorders operation middleware -func (siw *ServerInterfaceWrapper) ListRecorders(w http.ResponseWriter, r *http.Request) { +type ReadFileResponseObject interface { + VisitReadFileResponse(w http.ResponseWriter) error +} - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListRecorders(w, r) - })) +type ReadFile200ApplicationoctetStreamResponse struct { + Body io.Reader + ContentLength int64 +} - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) +func (response ReadFile200ApplicationoctetStreamResponse) VisitReadFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/octet-stream") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) } + w.WriteHeader(200) - handler.ServeHTTP(w, r) + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err } -// StartRecording operation middleware -func (siw *ServerInterfaceWrapper) StartRecording(w http.ResponseWriter, r *http.Request) { - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.StartRecording(w, r) - })) +type ReadFile400JSONResponse struct{ BadRequestErrorJSONResponse } - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } +func (response ReadFile400JSONResponse) VisitReadFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) - handler.ServeHTTP(w, r) + return json.NewEncoder(w).Encode(response) } -// StopRecording operation middleware -func (siw *ServerInterfaceWrapper) StopRecording(w http.ResponseWriter, r *http.Request) { - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.StopRecording(w, r) - })) +type ReadFile404JSONResponse struct{ NotFoundErrorJSONResponse } - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } +func (response ReadFile404JSONResponse) VisitReadFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) - handler.ServeHTTP(w, r) + return json.NewEncoder(w).Encode(response) } -type UnescapedCookieParamError struct { - ParamName string - Err error +type WriteFileRequestObject struct { + Params WriteFileParams + Body io.Reader } -func (e *UnescapedCookieParamError) Error() string { - return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +type WriteFileResponseObject interface { + VisitWriteFileResponse(w http.ResponseWriter) error } -func (e *UnescapedCookieParamError) Unwrap() error { - return e.Err +type WriteFile201Response struct { } -type UnmarshalingParamError struct { - ParamName string - Err error +func (response WriteFile201Response) VisitWriteFileResponse(w http.ResponseWriter) error { + w.WriteHeader(201) + return nil } -func (e *UnmarshalingParamError) Error() string { - return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) -} +type WriteFile400JSONResponse struct{ BadRequestErrorJSONResponse } -func (e *UnmarshalingParamError) Unwrap() error { - return e.Err -} +func (response WriteFile400JSONResponse) VisitWriteFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) -type RequiredParamError struct { - ParamName string + return json.NewEncoder(w).Encode(response) } -func (e *RequiredParamError) Error() string { - return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) -} +type WriteFile404JSONResponse struct{ NotFoundErrorJSONResponse } -type RequiredHeaderError struct { - ParamName string - Err error -} +func (response WriteFile404JSONResponse) VisitWriteFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) -func (e *RequiredHeaderError) Error() string { - return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) + return json.NewEncoder(w).Encode(response) } -func (e *RequiredHeaderError) Unwrap() error { - return e.Err +type UploadFilesRequestObject struct { + Body *multipart.Reader } -type InvalidParamFormatError struct { - ParamName string - Err error +type UploadFilesResponseObject interface { + VisitUploadFilesResponse(w http.ResponseWriter) error } -func (e *InvalidParamFormatError) Error() string { - return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +type UploadFiles201Response struct { } -func (e *InvalidParamFormatError) Unwrap() error { - return e.Err +func (response UploadFiles201Response) VisitUploadFilesResponse(w http.ResponseWriter) error { + w.WriteHeader(201) + return nil } -type TooManyValuesForParamError struct { - ParamName string - Count int +type UploadFiles400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response UploadFiles400JSONResponse) VisitUploadFilesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) } -func (e *TooManyValuesForParamError) Error() string { - return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) -} +type UploadFiles404JSONResponse struct{ NotFoundErrorJSONResponse } -// Handler creates http.Handler with routing matching OpenAPI spec. -func Handler(si ServerInterface) http.Handler { - return HandlerWithOptions(si, ChiServerOptions{}) +func (response UploadFiles404JSONResponse) VisitUploadFilesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) } -type ChiServerOptions struct { - BaseURL string - BaseRouter chi.Router - Middlewares []MiddlewareFunc - ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +type StartFsWatchRequestObject struct { + Body *StartFsWatchJSONRequestBody } -// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. -func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { - return HandlerWithOptions(si, ChiServerOptions{ - BaseRouter: r, - }) +type StartFsWatchResponseObject interface { + VisitStartFsWatchResponse(w http.ResponseWriter) error } -func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { - return HandlerWithOptions(si, ChiServerOptions{ - BaseURL: baseURL, - BaseRouter: r, - }) +type StartFsWatch201JSONResponse struct { + // WatchId Unique identifier for the directory watch + WatchId *string `json:"watch_id,omitempty"` } -// HandlerWithOptions creates http.Handler with additional options -func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { - r := options.BaseRouter +func (response StartFsWatch201JSONResponse) VisitStartFsWatchResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(201) - if r == nil { - r = chi.NewRouter() - } - if options.ErrorHandlerFunc == nil { - options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { - http.Error(w, err.Error(), http.StatusBadRequest) - } - } - wrapper := ServerInterfaceWrapper{ - Handler: si, - HandlerMiddlewares: options.Middlewares, - ErrorHandlerFunc: options.ErrorHandlerFunc, - } + return json.NewEncoder(w).Encode(response) +} - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/computer/click_mouse", wrapper.ClickMouse) - }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/computer/move_mouse", wrapper.MoveMouse) - }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/recording/delete", wrapper.DeleteRecording) - }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/recording/download", wrapper.DownloadRecording) - }) - r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/recording/list", wrapper.ListRecorders) - }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/recording/start", wrapper.StartRecording) - }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/recording/stop", wrapper.StopRecording) - }) +type StartFsWatch400JSONResponse struct{ BadRequestErrorJSONResponse } - return r -} +func (response StartFsWatch400JSONResponse) VisitStartFsWatchResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) -type BadRequestErrorJSONResponse Error + return json.NewEncoder(w).Encode(response) +} -type ConflictErrorJSONResponse Error +type StartFsWatch404JSONResponse struct{ NotFoundErrorJSONResponse } -type InternalErrorJSONResponse Error +func (response StartFsWatch404JSONResponse) VisitStartFsWatchResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) -type NotFoundErrorJSONResponse Error + return json.NewEncoder(w).Encode(response) +} -type ClickMouseRequestObject struct { - Body *ClickMouseJSONRequestBody +type StopFsWatchRequestObject struct { + WatchId string `json:"watch_id"` } -type ClickMouseResponseObject interface { - VisitClickMouseResponse(w http.ResponseWriter) error +type StopFsWatchResponseObject interface { + VisitStopFsWatchResponse(w http.ResponseWriter) error } -type ClickMouse200Response struct { +type StopFsWatch204Response struct { } -func (response ClickMouse200Response) VisitClickMouseResponse(w http.ResponseWriter) error { - w.WriteHeader(200) +func (response StopFsWatch204Response) VisitStopFsWatchResponse(w http.ResponseWriter) error { + w.WriteHeader(204) return nil } -type ClickMouse400JSONResponse struct{ BadRequestErrorJSONResponse } +type StopFsWatch400JSONResponse struct{ BadRequestErrorJSONResponse } -func (response ClickMouse400JSONResponse) VisitClickMouseResponse(w http.ResponseWriter) error { +func (response StopFsWatch400JSONResponse) VisitStopFsWatchResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type ClickMouse500JSONResponse struct{ InternalErrorJSONResponse } +type StopFsWatch404JSONResponse struct{ NotFoundErrorJSONResponse } -func (response ClickMouse500JSONResponse) VisitClickMouseResponse(w http.ResponseWriter) error { +func (response StopFsWatch404JSONResponse) VisitStopFsWatchResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) + w.WriteHeader(404) return json.NewEncoder(w).Encode(response) } -type MoveMouseRequestObject struct { - Body *MoveMouseJSONRequestBody +type StreamFsEventsRequestObject struct { + WatchId string `json:"watch_id"` } -type MoveMouseResponseObject interface { - VisitMoveMouseResponse(w http.ResponseWriter) error +type StreamFsEventsResponseObject interface { + VisitStreamFsEventsResponse(w http.ResponseWriter) error } -type MoveMouse200Response struct { +type StreamFsEvents200ResponseHeaders struct { + XSSEContentType string } -func (response MoveMouse200Response) VisitMoveMouseResponse(w http.ResponseWriter) error { +type StreamFsEvents200TexteventStreamResponse struct { + Body io.Reader + Headers StreamFsEvents200ResponseHeaders + ContentLength int64 +} + +func (response StreamFsEvents200TexteventStreamResponse) VisitStreamFsEventsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/event-stream") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.Header().Set("X-SSE-Content-Type", fmt.Sprint(response.Headers.XSSEContentType)) w.WriteHeader(200) - return nil + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err } -type MoveMouse400JSONResponse struct{ BadRequestErrorJSONResponse } +type StreamFsEvents400JSONResponse struct{ BadRequestErrorJSONResponse } -func (response MoveMouse400JSONResponse) VisitMoveMouseResponse(w http.ResponseWriter) error { +func (response StreamFsEvents400JSONResponse) VisitStreamFsEventsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type MoveMouse500JSONResponse struct{ InternalErrorJSONResponse } +type StreamFsEvents404JSONResponse struct{ NotFoundErrorJSONResponse } -func (response MoveMouse500JSONResponse) VisitMoveMouseResponse(w http.ResponseWriter) error { +func (response StreamFsEvents404JSONResponse) VisitStreamFsEventsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) + w.WriteHeader(404) return json.NewEncoder(w).Encode(response) } @@ -1910,6 +3417,27 @@ type StrictServerInterface interface { // Move the mouse cursor to the specified coordinates on the host computer // (POST /computer/move_mouse) MoveMouse(ctx context.Context, request MoveMouseRequestObject) (MoveMouseResponseObject, error) + // Download a file + // (GET /fs/download) + DownloadFile(ctx context.Context, request DownloadFileRequestObject) (DownloadFileResponseObject, error) + // Read file contents + // (GET /fs/file) + ReadFile(ctx context.Context, request ReadFileRequestObject) (ReadFileResponseObject, error) + // Write or create a file + // (PUT /fs/file) + WriteFile(ctx context.Context, request WriteFileRequestObject) (WriteFileResponseObject, error) + // Upload one or more files + // (POST /fs/upload) + UploadFiles(ctx context.Context, request UploadFilesRequestObject) (UploadFilesResponseObject, error) + // Watch a directory for changes + // (POST /fs/watch) + StartFsWatch(ctx context.Context, request StartFsWatchRequestObject) (StartFsWatchResponseObject, error) + // Stop watching a directory + // (DELETE /fs/watch/{watch_id}) + StopFsWatch(ctx context.Context, request StopFsWatchRequestObject) (StopFsWatchResponseObject, error) + // Stream filesystem events for a watch + // (GET /fs/watch/{watch_id}/events) + StreamFsEvents(ctx context.Context, request StreamFsEventsRequestObject) (StreamFsEventsResponseObject, error) // Delete a previously recorded video file // (POST /recording/delete) DeleteRecording(ctx context.Context, request DeleteRecordingRequestObject) (DeleteRecordingResponseObject, error) @@ -2018,6 +3546,200 @@ func (sh *strictHandler) MoveMouse(w http.ResponseWriter, r *http.Request) { } } +// DownloadFile operation middleware +func (sh *strictHandler) DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) { + var request DownloadFileRequestObject + + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.DownloadFile(ctx, request.(DownloadFileRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DownloadFile") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(DownloadFileResponseObject); ok { + if err := validResponse.VisitDownloadFileResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ReadFile operation middleware +func (sh *strictHandler) ReadFile(w http.ResponseWriter, r *http.Request, params ReadFileParams) { + var request ReadFileRequestObject + + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ReadFile(ctx, request.(ReadFileRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReadFile") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ReadFileResponseObject); ok { + if err := validResponse.VisitReadFileResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// WriteFile operation middleware +func (sh *strictHandler) WriteFile(w http.ResponseWriter, r *http.Request, params WriteFileParams) { + var request WriteFileRequestObject + + request.Params = params + + request.Body = r.Body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.WriteFile(ctx, request.(WriteFileRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "WriteFile") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(WriteFileResponseObject); ok { + if err := validResponse.VisitWriteFileResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// UploadFiles operation middleware +func (sh *strictHandler) UploadFiles(w http.ResponseWriter, r *http.Request) { + var request UploadFilesRequestObject + + if reader, err := r.MultipartReader(); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) + return + } else { + request.Body = reader + } + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UploadFiles(ctx, request.(UploadFilesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UploadFiles") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UploadFilesResponseObject); ok { + if err := validResponse.VisitUploadFilesResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// StartFsWatch operation middleware +func (sh *strictHandler) StartFsWatch(w http.ResponseWriter, r *http.Request) { + var request StartFsWatchRequestObject + + var body StartFsWatchJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.StartFsWatch(ctx, request.(StartFsWatchRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "StartFsWatch") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(StartFsWatchResponseObject); ok { + if err := validResponse.VisitStartFsWatchResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// StopFsWatch operation middleware +func (sh *strictHandler) StopFsWatch(w http.ResponseWriter, r *http.Request, watchId string) { + var request StopFsWatchRequestObject + + request.WatchId = watchId + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.StopFsWatch(ctx, request.(StopFsWatchRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "StopFsWatch") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(StopFsWatchResponseObject); ok { + if err := validResponse.VisitStopFsWatchResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// StreamFsEvents operation middleware +func (sh *strictHandler) StreamFsEvents(w http.ResponseWriter, r *http.Request, watchId string) { + var request StreamFsEventsRequestObject + + request.WatchId = watchId + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.StreamFsEvents(ctx, request.(StreamFsEventsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "StreamFsEvents") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(StreamFsEventsResponseObject); ok { + if err := validResponse.VisitStreamFsEventsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // DeleteRecording operation middleware func (sh *strictHandler) DeleteRecording(w http.ResponseWriter, r *http.Request) { var request DeleteRecordingRequestObject @@ -2164,34 +3886,45 @@ func (sh *strictHandler) StopRecording(w http.ResponseWriter, r *http.Request) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AA==", + "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", + "KrulH7sywgaplX7cRf4XAAD//5SX97iTKwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 6f9584e0..052c9820 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -154,6 +154,180 @@ paths: $ref: "#/components/responses/BadRequestError" "500": $ref: "#/components/responses/InternalError" + # File system operations + /fs/file: + get: + summary: Read file contents + operationId: readFile + parameters: + - name: path + in: query + required: true + schema: + type: string + description: Absolute or relative file path to read. + responses: + "200": + description: File read successfully + content: + application/octet-stream: + schema: + type: string + format: binary + "400": + $ref: "#/components/responses/BadRequestError" + "404": + $ref: "#/components/responses/NotFoundError" + put: + summary: Write or create a file + operationId: writeFile + parameters: + - name: path + in: query + required: true + schema: + type: string + description: Destination file path. + requestBody: + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "201": + description: File written successfully + "400": + $ref: "#/components/responses/BadRequestError" + "404": + $ref: "#/components/responses/NotFoundError" + + /fs/upload: + post: + summary: Upload one or more files + operationId: uploadFiles + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + files: + type: array + items: + type: object + properties: + file: + type: string + format: binary + dest_path: + type: string + required: [file, dest_path] + required: [files] + responses: + "201": + description: Files uploaded successfully + "400": + $ref: "#/components/responses/BadRequestError" + "404": + $ref: "#/components/responses/NotFoundError" + + /fs/download: + get: + summary: Download a file + operationId: downloadFile + parameters: + - name: path + in: query + required: true + schema: + type: string + responses: + "200": + description: File downloaded successfully + content: + application/octet-stream: + schema: + type: string + format: binary + "400": + $ref: "#/components/responses/BadRequestError" + "404": + $ref: "#/components/responses/NotFoundError" + + /fs/watch: + post: + summary: Watch a directory for changes + operationId: startFsWatch + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/StartFsWatchRequest" + responses: + "201": + description: Watch started successfully + content: + application/json: + schema: + type: object + properties: + watch_id: + type: string + description: Unique identifier for the directory watch + "400": + $ref: "#/components/responses/BadRequestError" + "404": + $ref: "#/components/responses/NotFoundError" + + /fs/watch/{watch_id}/events: + get: + summary: Stream filesystem events for a watch + operationId: streamFsEvents + parameters: + - name: watch_id + in: path + required: true + schema: + type: string + responses: + "200": + description: SSE stream of filesystem events + headers: + X-SSE-Content-Type: + description: Media type of SSE data events (application/json) + schema: + type: string + const: application/json + content: + text/event-stream: + schema: + $ref: "#/components/schemas/FileSystemEvent" + "400": + $ref: "#/components/responses/BadRequestError" + "404": + $ref: "#/components/responses/NotFoundError" + + /fs/watch/{watch_id}: + delete: + summary: Stop watching a directory + operationId: stopFsWatch + parameters: + - name: watch_id + in: path + required: true + schema: + type: string + responses: + "204": + description: Watch stopped successfully + "400": + $ref: "#/components/responses/BadRequestError" + "404": + $ref: "#/components/responses/NotFoundError" components: schemas: StartRecordingRequest: @@ -262,6 +436,37 @@ components: items: type: string additionalProperties: false + StartFsWatchRequest: + type: object + required: + - path + properties: + path: + type: string + description: Directory to watch. + recursive: + type: boolean + description: Whether to watch recursively. + default: false + additionalProperties: false + FileSystemEvent: + type: object + description: Filesystem change event. + required: [type, path] + properties: + type: + type: string + enum: [CREATE, WRITE, DELETE, RENAME] + description: Event type. + name: + type: string + description: Base name of the file or directory affected. + path: + type: string + description: Absolute path of the file or directory. + is_dir: + type: boolean + description: Whether the affected path is a directory. DeleteRecordingRequest: type: object properties: From a9062e4fb0d7a71edb649807028e1635b90057c4 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sun, 3 Aug 2025 08:49:59 -0400 Subject: [PATCH 02/20] implementation --- server/cmd/api/api/api.go | 5 + server/cmd/api/api/fs.go | 324 ++++++++++++++++++++++++++++++++++ server/cmd/api/api/fs_test.go | 172 ++++++++++++++++++ server/go.mod | 4 + server/go.sum | 8 + server/lib/oapi/oapi.go | 197 +++++++++++++++++---- server/openapi.yaml | 14 ++ 7 files changed, 685 insertions(+), 39 deletions(-) create mode 100644 server/cmd/api/api/fs.go create mode 100644 server/cmd/api/api/fs_test.go diff --git a/server/cmd/api/api/api.go b/server/cmd/api/api/api.go index eeed31b8..21f5794c 100644 --- a/server/cmd/api/api/api.go +++ b/server/cmd/api/api/api.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "sync" "time" "github.com/onkernel/kernel-images/server/lib/logger" @@ -18,6 +19,9 @@ type ApiService struct { recordManager recorder.RecordManager factory recorder.FFmpegRecorderFactory + // Filesystem watch management + watchMu sync.RWMutex + watches map[string]*fsWatch } var _ oapi.StrictServerInterface = (*ApiService)(nil) @@ -34,6 +38,7 @@ func New(recordManager recorder.RecordManager, factory recorder.FFmpegRecorderFa recordManager: recordManager, factory: factory, defaultRecorderID: "default", + watches: make(map[string]*fsWatch), }, nil } diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go new file mode 100644 index 00000000..aae1488f --- /dev/null +++ b/server/cmd/api/api/fs.go @@ -0,0 +1,324 @@ +package api + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/fsnotify/fsnotify" + "github.com/nrednav/cuid2" + "github.com/onkernel/kernel-images/server/lib/logger" + oapi "github.com/onkernel/kernel-images/server/lib/oapi" +) + +// fsWatch represents an in-memory directory watch. +type fsWatch struct { + path string + recursive bool + events chan oapi.FileSystemEvent + watcher *fsnotify.Watcher +} + +// addRecursive walks the directory and registers all subdirectories when recursive=true. +func addRecursive(w *fsnotify.Watcher, root string) error { + return filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return w.Add(path) + } + return nil + }) +} + +// DownloadFile serves the requested path as an octet-stream. +func (s *ApiService) DownloadFile(ctx context.Context, req oapi.DownloadFileRequestObject) (oapi.DownloadFileResponseObject, error) { + log := logger.FromContext(ctx) + path := req.Params.Path + if path == "" { + return oapi.DownloadFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "path cannot be empty"}}, nil + } + + f, err := os.Open(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return oapi.DownloadFile404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "file not found"}}, nil + } + log.Error("failed to open file", "err", err, "path", path) + return oapi.DownloadFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to open file"}}, nil + } + + stat, err := f.Stat() + if err != nil { + f.Close() + log.Error("failed to stat file", "err", err, "path", path) + return oapi.DownloadFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to stat file"}}, nil + } + + return oapi.DownloadFile200ApplicationoctetStreamResponse{ + Body: f, + ContentLength: stat.Size(), + }, nil +} + +// ReadFile returns the contents of a file specified by the path param. +func (s *ApiService) ReadFile(ctx context.Context, req oapi.ReadFileRequestObject) (oapi.ReadFileResponseObject, error) { + log := logger.FromContext(ctx) + path := req.Params.Path + if path == "" { + return oapi.ReadFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "path cannot be empty"}}, nil + } + + f, err := os.Open(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return oapi.ReadFile404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "file not found"}}, nil + } + log.Error("failed to open file", "err", err, "path", path) + return oapi.ReadFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to open file"}}, nil + } + + stat, err := f.Stat() + if err != nil { + f.Close() + log.Error("failed to stat file", "err", err, "path", path) + return oapi.ReadFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to stat file"}}, nil + } + + return oapi.ReadFile200ApplicationoctetStreamResponse{ + Body: f, + ContentLength: stat.Size(), + }, nil +} + +// WriteFile creates or overwrites a file with the supplied data stream. +func (s *ApiService) WriteFile(ctx context.Context, req oapi.WriteFileRequestObject) (oapi.WriteFileResponseObject, error) { + log := logger.FromContext(ctx) + path := req.Params.Path + if path == "" { + return oapi.WriteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "path cannot be empty"}}, nil + } + if req.Body == nil { + return oapi.WriteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "empty request body"}}, nil + } + + // create parent directories if necessary + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + log.Error("failed to create directories", "err", err, "path", path) + return oapi.WriteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to create directories"}}, nil + } + + f, err := os.Create(path) + if err != nil { + log.Error("failed to create file", "err", err, "path", path) + return oapi.WriteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to create file"}}, nil + } + defer f.Close() + + if _, err := io.Copy(f, req.Body); err != nil { + log.Error("failed to write file", "err", err, "path", path) + return oapi.WriteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "failed to write data"}}, nil + } + + return oapi.WriteFile201Response{}, nil +} + +// UploadFiles stores one or more files provided in a multipart form. This implementation +// supports parts named "file" and an accompanying part named "dest_path" which must appear +// before its corresponding file part. +func (s *ApiService) UploadFiles(ctx context.Context, req oapi.UploadFilesRequestObject) (oapi.UploadFilesResponseObject, error) { + log := logger.FromContext(ctx) + if req.Body == nil { + return oapi.UploadFiles400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "multipart body required"}}, nil + } + + reader := req.Body + var currentDest string + for { + part, err := reader.NextPart() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + log.Error("failed to read multipart part", "err", err) + return oapi.UploadFiles400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "invalid multipart payload"}}, nil + } + switch part.FormName() { + case "dest_path": + data, _ := io.ReadAll(part) + currentDest = string(data) + case "file": + if currentDest == "" { + return oapi.UploadFiles400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "dest_path must precede each file part"}}, nil + } + if err := os.MkdirAll(filepath.Dir(currentDest), 0o755); err != nil { + return oapi.UploadFiles400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: fmt.Sprintf("failed to create directories: %v", err)}}, nil + } + out, err := os.Create(currentDest) + if err != nil { + log.Error("failed to create file for upload", "err", err, "path", currentDest) + return oapi.UploadFiles500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to create file"}}, nil + } + if _, err := io.Copy(out, part); err != nil { + out.Close() + log.Error("failed to write uploaded data", "err", err, "path", currentDest) + return oapi.UploadFiles500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to write file"}}, nil + } + out.Close() + currentDest = "" // reset for next file + default: + // ignore unknown parts + } + } + + return oapi.UploadFiles201Response{}, nil +} + +// StartFsWatch is not implemented in this basic filesystem handler. It returns a 400 error to the client. +func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequestObject) (oapi.StartFsWatchResponseObject, error) { + log := logger.FromContext(ctx) + if req.Body == nil { + return oapi.StartFsWatch400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body required"}}, nil + } + + path := req.Body.Path + if path == "" { + return oapi.StartFsWatch400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "path cannot be empty"}}, nil + } + // Ensure path exists + if _, err := os.Stat(path); err != nil { + if errors.Is(err, os.ErrNotExist) { + return oapi.StartFsWatch404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "path not found"}}, nil + } + return oapi.StartFsWatch400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to stat path"}}, nil + } + + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Error("failed to create fsnotify watcher", "err", err) + return oapi.StartFsWatch500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "internal error"}}, nil + } + + recursive := req.Body.Recursive != nil && *req.Body.Recursive + if recursive { + if err := addRecursive(watcher, path); err != nil { + log.Error("failed to add directories recursively", "err", err) + watcher.Close() + return oapi.StartFsWatch500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "internal error"}}, nil + } + } else { + if err := watcher.Add(path); err != nil { + log.Error("failed to watch path", "err", err, "path", path) + watcher.Close() + return oapi.StartFsWatch500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "internal error"}}, nil + } + } + + watchID := cuid2.Generate() + w := &fsWatch{ + path: path, + recursive: recursive, + events: make(chan oapi.FileSystemEvent, 100), + watcher: watcher, + } + + // goroutine to forward events + go func() { + for { + select { + case ev, ok := <-watcher.Events: + if !ok { + return + } + var evType oapi.FileSystemEventType + switch { + case ev.Op&fsnotify.Create != 0: + evType = "CREATE" + case ev.Op&fsnotify.Write != 0: + evType = "WRITE" + case ev.Op&fsnotify.Remove != 0: + evType = "DELETE" + case ev.Op&fsnotify.Rename != 0: + evType = "RENAME" + default: + continue + } + info, _ := os.Stat(ev.Name) + isDir := info != nil && info.IsDir() + name := filepath.Base(ev.Name) + w.events <- oapi.FileSystemEvent{Type: evType, Path: ev.Name, Name: &name, IsDir: &isDir} + + // if recursive and new directory created, add watch + if recursive && evType == "CREATE" && isDir { + watcher.Add(ev.Name) + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Error("fsnotify error", "err", err) + } + } + }() + + s.watchMu.Lock() + s.watches[watchID] = w + s.watchMu.Unlock() + + return oapi.StartFsWatch201JSONResponse{WatchId: &watchID}, nil +} + +func (s *ApiService) StopFsWatch(ctx context.Context, req oapi.StopFsWatchRequestObject) (oapi.StopFsWatchResponseObject, error) { + log := logger.FromContext(ctx) + id := req.WatchId + s.watchMu.Lock() + w, ok := s.watches[id] + if ok { + delete(s.watches, id) + w.watcher.Close() + close(w.events) + } + s.watchMu.Unlock() + + if !ok { + log.Warn("stop requested for unknown watch", "watch_id", id) + return oapi.StopFsWatch404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "watch not found"}}, nil + } + + return oapi.StopFsWatch204Response{}, nil +} + +func (s *ApiService) StreamFsEvents(ctx context.Context, req oapi.StreamFsEventsRequestObject) (oapi.StreamFsEventsResponseObject, error) { + log := logger.FromContext(ctx) + id := req.WatchId + s.watchMu.RLock() + w, ok := s.watches[id] + s.watchMu.RUnlock() + if !ok { + log.Warn("stream requested for unknown watch", "watch_id", id) + return oapi.StreamFsEvents404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "watch not found"}}, nil + } + + pr, pw := io.Pipe() + go func() { + defer pw.Close() + enc := json.NewEncoder(pw) + for ev := range w.events { + // Write SSE formatted event: data: \n\n + pw.Write([]byte("data: ")) + if err := enc.Encode(ev); err != nil { + log.Error("failed to encode fs event", "err", err) + return + } + pw.Write([]byte("\n")) + } + }() + + headers := oapi.StreamFsEvents200ResponseHeaders{XSSEContentType: "application/json"} + return oapi.StreamFsEvents200TexteventStreamResponse{Body: pr, Headers: headers, ContentLength: 0}, nil +} diff --git a/server/cmd/api/api/fs_test.go b/server/cmd/api/api/fs_test.go new file mode 100644 index 00000000..8073ef4e --- /dev/null +++ b/server/cmd/api/api/fs_test.go @@ -0,0 +1,172 @@ +package api + +import ( + "bufio" + "bytes" + "context" + "io" + "mime/multipart" + "os" + "path/filepath" + "strings" + "testing" + + oapi "github.com/onkernel/kernel-images/server/lib/oapi" +) + +// TestWriteReadDownloadFile verifies that files can be written, read back, and downloaded successfully. +func TestWriteReadDownloadFile(t *testing.T) { + t.Parallel() + + ctx := context.Background() + svc := &ApiService{defaultRecorderID: "default"} + + tmpDir := t.TempDir() + filePath := filepath.Join(tmpDir, "test.txt") + content := "hello world" + + // Write the file + if resp, err := svc.WriteFile(ctx, oapi.WriteFileRequestObject{ + Params: oapi.WriteFileParams{Path: filePath}, + Body: strings.NewReader(content), + }); err != nil { + t.Fatalf("WriteFile returned error: %v", err) + } else { + if _, ok := resp.(oapi.WriteFile201Response); !ok { + t.Fatalf("unexpected response type from WriteFile: %T", resp) + } + } + + // Read the file + readResp, err := svc.ReadFile(ctx, oapi.ReadFileRequestObject{Params: oapi.ReadFileParams{Path: filePath}}) + if err != nil { + t.Fatalf("ReadFile returned error: %v", err) + } + r200, ok := readResp.(oapi.ReadFile200ApplicationoctetStreamResponse) + if !ok { + t.Fatalf("unexpected response type from ReadFile: %T", readResp) + } + data, _ := io.ReadAll(r200.Body) + if got := string(data); got != content { + t.Fatalf("ReadFile content mismatch: got %q want %q", got, content) + } + + // Download the file + dlResp, err := svc.DownloadFile(ctx, oapi.DownloadFileRequestObject{Params: oapi.DownloadFileParams{Path: filePath}}) + if err != nil { + t.Fatalf("DownloadFile returned error: %v", err) + } + d200, ok := dlResp.(oapi.DownloadFile200ApplicationoctetStreamResponse) + if !ok { + t.Fatalf("unexpected response type from DownloadFile: %T", dlResp) + } + dlData, _ := io.ReadAll(d200.Body) + if got := string(dlData); got != content { + t.Fatalf("DownloadFile content mismatch: got %q want %q", got, content) + } + + // Attempt to read non-existent file + missingResp, err := svc.ReadFile(ctx, oapi.ReadFileRequestObject{Params: oapi.ReadFileParams{Path: filepath.Join(tmpDir, "missing.txt")}}) + if err != nil { + t.Fatalf("ReadFile missing file returned error: %v", err) + } + if _, ok := missingResp.(oapi.ReadFile404JSONResponse); !ok { + t.Fatalf("expected 404 response for missing file, got %T", missingResp) + } + + // Attempt to write with empty path + badResp, err := svc.WriteFile(ctx, oapi.WriteFileRequestObject{Params: oapi.WriteFileParams{Path: ""}, Body: strings.NewReader("data")}) + if err != nil { + t.Fatalf("WriteFile bad path returned error: %v", err) + } + if _, ok := badResp.(oapi.WriteFile400JSONResponse); !ok { + t.Fatalf("expected 400 response for empty path, got %T", badResp) + } +} + +// TestUploadFiles verifies multipart upload and filesystem watch event generation. +func TestUploadFilesAndWatch(t *testing.T) { + t.Parallel() + + ctx := context.Background() + svc := &ApiService{defaultRecorderID: "default", watches: make(map[string]*fsWatch)} + + // Prepare watch + dir := t.TempDir() + recursive := true + startReq := oapi.StartFsWatchRequestObject{Body: &oapi.StartFsWatchRequest{Path: dir, Recursive: &recursive}} + startResp, err := svc.StartFsWatch(ctx, startReq) + if err != nil { + t.Fatalf("StartFsWatch error: %v", err) + } + sr201, ok := startResp.(oapi.StartFsWatch201JSONResponse) + if !ok { + t.Fatalf("unexpected response type from StartFsWatch: %T", startResp) + } + if sr201.WatchId == nil { + t.Fatalf("watch id nil") + } + watchID := *sr201.WatchId + + // Build multipart payload + var buf bytes.Buffer + mw := multipart.NewWriter(&buf) + destPath := filepath.Join(dir, "upload.txt") + + // dest_path part + if err := mw.WriteField("dest_path", destPath); err != nil { + t.Fatalf("WriteField error: %v", err) + } + // file part + fw, err := mw.CreateFormFile("file", "upload.txt") + if err != nil { + t.Fatalf("CreateFormFile error: %v", err) + } + content := "upload content" + fw.Write([]byte(content)) + mw.Close() + + uploadReq := oapi.UploadFilesRequestObject{Body: multipart.NewReader(&buf, mw.Boundary())} + if resp, err := svc.UploadFiles(ctx, uploadReq); err != nil { + t.Fatalf("UploadFiles error: %v", err) + } else { + if _, ok := resp.(oapi.UploadFiles201Response); !ok { + t.Fatalf("unexpected response type from UploadFiles: %T", resp) + } + } + + // Verify file exists + data, err := os.ReadFile(destPath) + if err != nil || string(data) != content { + t.Fatalf("uploaded file mismatch: %v", err) + } + + // Stream events (should at least receive one) + streamReq := oapi.StreamFsEventsRequestObject{WatchId: watchID} + streamResp, err := svc.StreamFsEvents(ctx, streamReq) + if err != nil { + t.Fatalf("StreamFsEvents error: %v", err) + } + st200, ok := streamResp.(oapi.StreamFsEvents200TexteventStreamResponse) + if !ok { + t.Fatalf("unexpected response type from StreamFsEvents: %T", streamResp) + } + + reader := bufio.NewReader(st200.Body) + line, err := reader.ReadString('\n') + if err != nil { + t.Fatalf("failed to read SSE line: %v", err) + } + if !strings.HasPrefix(line, "data: ") { + t.Fatalf("unexpected SSE format: %s", line) + } + + // Cleanup + stopResp, err := svc.StopFsWatch(ctx, oapi.StopFsWatchRequestObject{WatchId: watchID}) + if err != nil { + t.Fatalf("StopFsWatch error: %v", err) + } + if _, ok := stopResp.(oapi.StopFsWatch204Response); !ok { + t.Fatalf("unexpected response type from StopFsWatch: %T", stopResp) + } +} diff --git a/server/go.mod b/server/go.mod index 3ed05aeb..fbbc9592 100644 --- a/server/go.mod +++ b/server/go.mod @@ -3,10 +3,12 @@ module github.com/onkernel/kernel-images/server go 1.24.3 require ( + github.com/fsnotify/fsnotify v1.9.0 github.com/getkin/kin-openapi v0.132.0 github.com/ghodss/yaml v1.0.0 github.com/go-chi/chi/v5 v5.2.1 github.com/kelseyhightower/envconfig v1.4.0 + github.com/nrednav/cuid2 v1.1.0 github.com/oapi-codegen/runtime v1.1.1 github.com/stretchr/testify v1.9.0 golang.org/x/sync v0.15.0 @@ -25,6 +27,8 @@ require ( github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/sys v0.34.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/server/go.sum b/server/go.sum index 2adcffee..40bb015b 100644 --- a/server/go.sum +++ b/server/go.sum @@ -5,6 +5,8 @@ github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvF github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -32,6 +34,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/nrednav/cuid2 v1.1.0 h1:Y2P9Fo1Iz7lKuwcn+fS0mbxkNvEqoNLUtm0+moHCnYc= +github.com/nrednav/cuid2 v1.1.0/go.mod h1:jBjkJAI+QLM4EUGvtwGDHC1cP1QQrRNfLo/A7qJFDhA= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= @@ -51,8 +55,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index a33d4731..cd3bea86 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -1292,6 +1292,7 @@ type DownloadFileResponse struct { HTTPResponse *http.Response JSON400 *BadRequestError JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status @@ -1315,6 +1316,7 @@ type ReadFileResponse struct { HTTPResponse *http.Response JSON400 *BadRequestError JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status @@ -1338,6 +1340,7 @@ type WriteFileResponse struct { HTTPResponse *http.Response JSON400 *BadRequestError JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status @@ -1361,6 +1364,7 @@ type UploadFilesResponse struct { HTTPResponse *http.Response JSON400 *BadRequestError JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status @@ -1388,6 +1392,7 @@ type StartFsWatchResponse struct { } JSON400 *BadRequestError JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status @@ -1411,6 +1416,7 @@ type StopFsWatchResponse struct { HTTPResponse *http.Response JSON400 *BadRequestError JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status @@ -1434,6 +1440,7 @@ type StreamFsEventsResponse struct { HTTPResponse *http.Response JSON400 *BadRequestError JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status @@ -1838,6 +1845,13 @@ func ParseDownloadFileResponse(rsp *http.Response) (*DownloadFileResponse, error } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil @@ -1871,6 +1885,13 @@ func ParseReadFileResponse(rsp *http.Response) (*ReadFileResponse, error) { } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil @@ -1904,6 +1925,13 @@ func ParseWriteFileResponse(rsp *http.Response) (*WriteFileResponse, error) { } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil @@ -1937,6 +1965,13 @@ func ParseUploadFilesResponse(rsp *http.Response) (*UploadFilesResponse, error) } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil @@ -1980,6 +2015,13 @@ func ParseStartFsWatchResponse(rsp *http.Response) (*StartFsWatchResponse, error } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil @@ -2013,6 +2055,13 @@ func ParseStopFsWatchResponse(rsp *http.Response) (*StopFsWatchResponse, error) } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil @@ -2046,6 +2095,13 @@ func ParseStreamFsEventsResponse(rsp *http.Response) (*StreamFsEventsResponse, e } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil @@ -2951,6 +3007,15 @@ func (response DownloadFile404JSONResponse) VisitDownloadFileResponse(w http.Res return json.NewEncoder(w).Encode(response) } +type DownloadFile500JSONResponse struct{ InternalErrorJSONResponse } + +func (response DownloadFile500JSONResponse) VisitDownloadFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type ReadFileRequestObject struct { Params ReadFileParams } @@ -2996,6 +3061,15 @@ func (response ReadFile404JSONResponse) VisitReadFileResponse(w http.ResponseWri return json.NewEncoder(w).Encode(response) } +type ReadFile500JSONResponse struct{ InternalErrorJSONResponse } + +func (response ReadFile500JSONResponse) VisitReadFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type WriteFileRequestObject struct { Params WriteFileParams Body io.Reader @@ -3031,6 +3105,15 @@ func (response WriteFile404JSONResponse) VisitWriteFileResponse(w http.ResponseW return json.NewEncoder(w).Encode(response) } +type WriteFile500JSONResponse struct{ InternalErrorJSONResponse } + +func (response WriteFile500JSONResponse) VisitWriteFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type UploadFilesRequestObject struct { Body *multipart.Reader } @@ -3065,6 +3148,15 @@ func (response UploadFiles404JSONResponse) VisitUploadFilesResponse(w http.Respo return json.NewEncoder(w).Encode(response) } +type UploadFiles500JSONResponse struct{ InternalErrorJSONResponse } + +func (response UploadFiles500JSONResponse) VisitUploadFilesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type StartFsWatchRequestObject struct { Body *StartFsWatchJSONRequestBody } @@ -3103,6 +3195,15 @@ func (response StartFsWatch404JSONResponse) VisitStartFsWatchResponse(w http.Res return json.NewEncoder(w).Encode(response) } +type StartFsWatch500JSONResponse struct{ InternalErrorJSONResponse } + +func (response StartFsWatch500JSONResponse) VisitStartFsWatchResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type StopFsWatchRequestObject struct { WatchId string `json:"watch_id"` } @@ -3137,6 +3238,15 @@ func (response StopFsWatch404JSONResponse) VisitStopFsWatchResponse(w http.Respo return json.NewEncoder(w).Encode(response) } +type StopFsWatch500JSONResponse struct{ InternalErrorJSONResponse } + +func (response StopFsWatch500JSONResponse) VisitStopFsWatchResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type StreamFsEventsRequestObject struct { WatchId string `json:"watch_id"` } @@ -3188,6 +3298,15 @@ func (response StreamFsEvents404JSONResponse) VisitStreamFsEventsResponse(w http return json.NewEncoder(w).Encode(response) } +type StreamFsEvents500JSONResponse struct{ InternalErrorJSONResponse } + +func (response StreamFsEvents500JSONResponse) VisitStreamFsEventsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type DeleteRecordingRequestObject struct { Body *DeleteRecordingJSONRequestBody } @@ -3886,45 +4005,45 @@ func (sh *strictHandler) StopRecording(w http.ResponseWriter, r *http.Request) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "KrulH7sywgaplX7cRf4XAAD//5SX97iTKwAA", + "H4sIAAAAAAAC/9xa3XPbNhL/V3ZwfWjn9OUk91C9ObF847k67VjppHeZnAcmlyIaEmAA0I7i0f9+swBJ", + "kSL0EctOx/dkkwR2F7uL337pnkUqL5REaQ2b3jONplDSoHt4zeMr/FyisTOtlaZXkZIWpaV/eVFkIuJW", + "KDn+0yhJ70yUYs7pvx80JmzK/jZe0x/7r2bsqa1WqwGL0URaFESETYkhVBzZasDeKJlkIvpe3Gt2xPpC", + "WtSSZ9+Jdc0O5qhvUUO1cMDeKnuuShl/JzneKguOH6Nv1XKi9iYT0adLVRqs7UMCxLGgjTz7TasCtRXk", + "NwnPDA5Y0Xp1z25Ka72EXYaOJPivYBUIUgSPLNwJm7IBQ1nmbPqBZZhYNmBaLFL6m4s4zpAN2A2PPrEB", + "S5S+4zpmHwfMLgtkU2asFnJBKoxI9Gv/epP9u2WBoBJwa4BH7vWaa6zu6LEsWEUmyCBVWXz9CZcmdLxY", + "JAI10Gc6H62FuKStYFP0jNmACYu529+jXr3gWvMlPcsyv3a7KnYJLzPLpic9U5b5DWo6nBU5OuYaC+S2", + "w7eiTmpfoPO4L/1T/AGRUjoWklunrYYAFMqISmd9Sss+pX8/hNJqwDR+LoXGmIzyhRHptSHUzZ/oL+0Z", + "ZmjxCiPHYvEwTxVxX+yLGKX1hqyE1jUT0mvs+I7gNCtSLssctYhAaUiXRYpyxAas4JYuOJuy/37gw6+n", + "w/9Mhj8PP/79B9bzp1XgYM3t74qaozF8gQG32VBZvTCktHOR4XxpLOaz2wpYuoenBcYtgCjlcoGAtNAd", + "q6s5cx0L3SfwPkWbonZ640mCkcUYCm5TEAY4xEJjZJVejtbKuFEqQy6dv/M8cHFfc4NAn2qDJCJD0nlD", + "rWE1YoE7S+z7VE9vjMpKi166bZSDBMP44lQK9G3UgpU3V7PTdzM2YO+vLtzfs9kvM/fP1ezt6eUsgDIb", + "BnVfq1OEjHqpbvEItD4G0XJ1i98EaPsAxypH02NFqY3SYNWDAOdQSgcDjoca1BcyUf3rmQgpTIrxNQ/c", + "qncEypbnBdylKFt4Uu/yUS2nvSzmFocE44zwP8v4TYZsanWJAU/0ANZ/bRpgbH1vXTRjubbfKm216YHC", + "bihaEJ22nCGdz4njuXnPbZQ+zLvDd/+sAQ6r4I6oB++5RvIccYud6Fvx2YJ7FT1o9mZBrNvQxtbL7TRw", + "ZJRLNM9RcxvArKu1K9aLQEhICgM/qlvUWsRowPhEtdLAT5ST8S8iJ4B7MaEETfqHk9BFDcXYXwsvOoh1", + "sE2U3oi2Bo0RSj5SrHVCn5Xa5dAXco6RknEI9PzRWnLE1SbSjPHb9mhnp0Jy/sXFYfEVL+Tl6+0SuGBk", + "xFdnksvXB1rkZDKZdIwyCYJewNNUcayjKR0h0dl/Xy7yHGPBLWZLMFYVrgZQpYWF5hEmZQYmLS3l5CN4", + "lwoDOV+CRlNmlrTBIVJalwVlF7ciRuWUFU4qviXJ8zeYBHqyDI9eiSqGWGEJL9m/UEvM4CLnCzRw+tsF", + "G7Bb1MYLOxmdjCZ0ElWg5IVgU/ZyNBm9rJICp3pX+ZUW9diXQDklBA4AlTcj2cm7fkzlb1PiMQ9EaOxr", + "FS8frers15CrLuZRjHAvWj2IF5PJtqrRl2tQoKbYgzGp45VfHhKjITve7GusBuwfh+zrNgVchVzmOddL", + "NmVzkZcZQSUHp+dOSQlU3KYIqTIWaqs4AmsbUWayz0RNWvdEFuqljccZqMqx6GR/rXEu66wvb8tllXtn", + "Cozo2setVNHssFhixoRBmeIORBYYMNRZtYBA3V1JiqQWtWHTD/dMkIo+l6iXrK5vfLTf1PWgZbdNEPkY", + "tsMWP1CRRTs0ViPPu/7Q5G03QnIn0SanXpeITgW1DjAGU0YRGpOUWbY8xs6vJq/27+t2xB7DO2pjAXcR", + "o7Gye9hm4SvcZt0tJaXSoDHjVtxWJaWrMV1Xhrsa9f/CK+gwz90fyLTeRJXajOsYlAEveK+FxUPc4AyN", + "JWShYNBY/3Gtfkg4ONLg+yLBSbh7BHdaWIvyuTuGszZd5EijD/UdvCiLOiaEo/fvRR0SzM74nZeZFQXX", + "dkxGGcbc8q6tNtsMWdV/q1su3e8xGntd17y9AqgGuf3271aniXf6NfFQpdpt9QQomMC2IxzNgLfB849J", + "3ldASedvudI+aJjG2VxHYbuvtbskT5QshhoxhxvvYBG6zuyOfR0q336X4nOJoe7BuiV8V6njoIJso5nj", + "OjhVu+vZA5k7TKv17nTlu/sbLja+r1W+8jrP0DeNNv1NFWt3C+W7VVCrQlxjx+OSm1eBaUNlKFUUz99Q", + "c9cGoRMJuWgbbKuRxm44Y7bmrXMX+8/NzC/7jrbaTEQtfrFe2mBCsgt6NmdWgfs6n8/AkwWVeOz0Myys", + "D54ij92p79kfw/l8NnzjZRu+C45yLjEW3I1yiCCRp8BckYMfN0HsJ9bWTj356UFdYNKzeo5u6hTd07KD", + "FV7BrvPYpoM6XiNJOIJtDHOfKIhtGRmvqkC2r8+x7pX748TPsfx1kgOHQuOtUKXJlnXns91I7dnv0B5I", + "24Q7a6RmAtD0XdfBfATvU5Sgciok4oFv3PiGd2nQ+Djvu8vN9m3llUOyVoLxDZ3b/ajmFDbOi1dH19Ot", + "OYzPtTuA1XwdnlcDw+HpzsGdSvzsrjtPqaeNI/hnyTWXFjEGq+AG4er8zcuXL38esV0wP+iIMvfp0YMk", + "qVKrhwpCoryYvNh1RYUBY0WWgZBQaLXQaMwAigy5QbB6CXzBhYSMW9RddV+h1cvhaUIfegzm5WKBhrLC", + "Oy6s+7VNeyp0gwll75pI+EuwPsSuodBzjANNJ833Wo27iyjtYYiSCR8HgmjyizC2Hrf78vngLlc/IjSV", + "8q7Q0Bnu94vZ3n0lCcm3dSPlY6jUUeVZ1ibbVZu7OHsqwacOo+GRdDCKnuy6ovXPCY5y/Z/37+v+svRx", + "UiCuLXAwkcb2LyRG8KvMlq6QX2NdgRouziDikvBN40IYixpj4ESCEGTUt7IfoW4zcmtQ+2Q2DgyDvz1R", + "qiqzv3ZYR2VVJ/y4g/wvAAD//3HvbdENLQAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 052c9820..5791a968 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -178,6 +178,8 @@ paths: $ref: "#/components/responses/BadRequestError" "404": $ref: "#/components/responses/NotFoundError" + "500": + $ref: "#/components/responses/InternalError" put: summary: Write or create a file operationId: writeFile @@ -202,6 +204,8 @@ paths: $ref: "#/components/responses/BadRequestError" "404": $ref: "#/components/responses/NotFoundError" + "500": + $ref: "#/components/responses/InternalError" /fs/upload: post: @@ -233,6 +237,8 @@ paths: $ref: "#/components/responses/BadRequestError" "404": $ref: "#/components/responses/NotFoundError" + "500": + $ref: "#/components/responses/InternalError" /fs/download: get: @@ -256,6 +262,8 @@ paths: $ref: "#/components/responses/BadRequestError" "404": $ref: "#/components/responses/NotFoundError" + "500": + $ref: "#/components/responses/InternalError" /fs/watch: post: @@ -282,6 +290,8 @@ paths: $ref: "#/components/responses/BadRequestError" "404": $ref: "#/components/responses/NotFoundError" + "500": + $ref: "#/components/responses/InternalError" /fs/watch/{watch_id}/events: get: @@ -310,6 +320,8 @@ paths: $ref: "#/components/responses/BadRequestError" "404": $ref: "#/components/responses/NotFoundError" + "500": + $ref: "#/components/responses/InternalError" /fs/watch/{watch_id}: delete: @@ -328,6 +340,8 @@ paths: $ref: "#/components/responses/BadRequestError" "404": $ref: "#/components/responses/NotFoundError" + "500": + $ref: "#/components/responses/InternalError" components: schemas: StartRecordingRequest: From 33de44ee8f4851db57e801661b25031a7ff9e04d Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sun, 3 Aug 2025 16:23:16 -0400 Subject: [PATCH 03/20] file directory ops --- server/cmd/api/api/fs.go | 257 ++-- server/cmd/api/api/fs_test.go | 146 ++- server/lib/oapi/oapi.go | 2207 +++++++++++++++++++++++++++------ server/openapi.yaml | 250 +++- 4 files changed, 2324 insertions(+), 536 deletions(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index aae1488f..48c9e8a5 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -4,10 +4,13 @@ import ( "context" "encoding/json" "errors" - "fmt" "io" "os" "path/filepath" + "strconv" + "time" + + "os/user" "github.com/fsnotify/fsnotify" "github.com/nrednav/cuid2" @@ -36,36 +39,6 @@ func addRecursive(w *fsnotify.Watcher, root string) error { }) } -// DownloadFile serves the requested path as an octet-stream. -func (s *ApiService) DownloadFile(ctx context.Context, req oapi.DownloadFileRequestObject) (oapi.DownloadFileResponseObject, error) { - log := logger.FromContext(ctx) - path := req.Params.Path - if path == "" { - return oapi.DownloadFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "path cannot be empty"}}, nil - } - - f, err := os.Open(path) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return oapi.DownloadFile404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "file not found"}}, nil - } - log.Error("failed to open file", "err", err, "path", path) - return oapi.DownloadFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to open file"}}, nil - } - - stat, err := f.Stat() - if err != nil { - f.Close() - log.Error("failed to stat file", "err", err, "path", path) - return oapi.DownloadFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to stat file"}}, nil - } - - return oapi.DownloadFile200ApplicationoctetStreamResponse{ - Body: f, - ContentLength: stat.Size(), - }, nil -} - // ReadFile returns the contents of a file specified by the path param. func (s *ApiService) ReadFile(ctx context.Context, req oapi.ReadFileRequestObject) (oapi.ReadFileResponseObject, error) { log := logger.FromContext(ctx) @@ -128,55 +101,199 @@ func (s *ApiService) WriteFile(ctx context.Context, req oapi.WriteFileRequestObj return oapi.WriteFile201Response{}, nil } -// UploadFiles stores one or more files provided in a multipart form. This implementation -// supports parts named "file" and an accompanying part named "dest_path" which must appear -// before its corresponding file part. -func (s *ApiService) UploadFiles(ctx context.Context, req oapi.UploadFilesRequestObject) (oapi.UploadFilesResponseObject, error) { +// CreateDirectory creates a new directory (recursively) with an optional mode. +func (s *ApiService) CreateDirectory(ctx context.Context, req oapi.CreateDirectoryRequestObject) (oapi.CreateDirectoryResponseObject, error) { log := logger.FromContext(ctx) if req.Body == nil { - return oapi.UploadFiles400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "multipart body required"}}, nil + return oapi.CreateDirectory400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body required"}}, nil + } + path := req.Body.Path + if path == "" { + return oapi.CreateDirectory400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "path cannot be empty"}}, nil + } + // default to 0o755 + perm := os.FileMode(0o755) + if req.Body.Mode != nil { + if v, err := strconv.ParseUint(*req.Body.Mode, 8, 32); err == nil { + perm = os.FileMode(v) + } + } + if err := os.MkdirAll(path, perm); err != nil { + log.Error("failed to create directory", "err", err, "path", path) + return oapi.CreateDirectory500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to create directory"}}, nil } + return oapi.CreateDirectory201Response{}, nil +} - reader := req.Body - var currentDest string - for { - part, err := reader.NextPart() - if errors.Is(err, io.EOF) { - break +// DeleteFile removes a single file. +func (s *ApiService) DeleteFile(ctx context.Context, req oapi.DeleteFileRequestObject) (oapi.DeleteFileResponseObject, error) { + log := logger.FromContext(ctx) + if req.Body == nil { + return oapi.DeleteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body required"}}, nil + } + path := req.Body.Path + if path == "" { + return oapi.DeleteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "path cannot be empty"}}, nil + } + if err := os.Remove(path); err != nil { + if errors.Is(err, os.ErrNotExist) { + return oapi.DeleteFile404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "file not found"}}, nil } - if err != nil { - log.Error("failed to read multipart part", "err", err) - return oapi.UploadFiles400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "invalid multipart payload"}}, nil + log.Error("failed to delete file", "err", err, "path", path) + return oapi.DeleteFile500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to delete file"}}, nil + } + return oapi.DeleteFile200Response{}, nil +} + +// DeleteDirectory removes a directory and its contents. +func (s *ApiService) DeleteDirectory(ctx context.Context, req oapi.DeleteDirectoryRequestObject) (oapi.DeleteDirectoryResponseObject, error) { + log := logger.FromContext(ctx) + if req.Body == nil { + return oapi.DeleteDirectory400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body required"}}, nil + } + path := req.Body.Path + if path == "" { + return oapi.DeleteDirectory400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "path cannot be empty"}}, nil + } + if err := os.RemoveAll(path); err != nil { + if errors.Is(err, os.ErrNotExist) { + return oapi.DeleteDirectory404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "directory not found"}}, nil } - switch part.FormName() { - case "dest_path": - data, _ := io.ReadAll(part) - currentDest = string(data) - case "file": - if currentDest == "" { - return oapi.UploadFiles400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "dest_path must precede each file part"}}, nil - } - if err := os.MkdirAll(filepath.Dir(currentDest), 0o755); err != nil { - return oapi.UploadFiles400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: fmt.Sprintf("failed to create directories: %v", err)}}, nil - } - out, err := os.Create(currentDest) - if err != nil { - log.Error("failed to create file for upload", "err", err, "path", currentDest) - return oapi.UploadFiles500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to create file"}}, nil + log.Error("failed to delete directory", "err", err, "path", path) + return oapi.DeleteDirectory500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to delete directory"}}, nil + } + return oapi.DeleteDirectory200Response{}, nil +} + +// ListFiles returns FileInfo entries for the contents of a directory. +func (s *ApiService) ListFiles(ctx context.Context, req oapi.ListFilesRequestObject) (oapi.ListFilesResponseObject, error) { + log := logger.FromContext(ctx) + path := req.Params.Path + if path == "" { + return oapi.ListFiles400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "path cannot be empty"}}, nil + } + entries, err := os.ReadDir(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return oapi.ListFiles404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "directory not found"}}, nil + } + log.Error("failed to read directory", "err", err, "path", path) + return oapi.ListFiles500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to read directory"}}, nil + } + var list oapi.ListFiles + for _, entry := range entries { + info, _ := entry.Info() + fi := oapi.FileInfo{ + Name: entry.Name(), + Path: filepath.Join(path, entry.Name()), + IsDir: entry.IsDir(), + SizeBytes: 0, + ModTime: time.Time{}, + Mode: "", + } + if info != nil { + fi.SizeBytes = int(info.Size()) + fi.ModTime = info.ModTime() + fi.Mode = info.Mode().String() + } + list = append(list, fi) + } + return oapi.ListFiles200JSONResponse(list), nil +} + +// FileInfo returns metadata about a file or directory. +func (s *ApiService) FileInfo(ctx context.Context, req oapi.FileInfoRequestObject) (oapi.FileInfoResponseObject, error) { + log := logger.FromContext(ctx) + path := req.Params.Path + if path == "" { + return oapi.FileInfo400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "path cannot be empty"}}, nil + } + stat, err := os.Stat(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return oapi.FileInfo404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "path not found"}}, nil + } + log.Error("failed to stat path", "err", err, "path", path) + return oapi.FileInfo500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to stat path"}}, nil + } + fi := oapi.FileInfo{ + Name: filepath.Base(path), + Path: path, + IsDir: stat.IsDir(), + SizeBytes: int(stat.Size()), + ModTime: stat.ModTime(), + Mode: stat.Mode().String(), + } + return oapi.FileInfo200JSONResponse(fi), nil +} + +// MovePath renames or moves a file/directory. +func (s *ApiService) MovePath(ctx context.Context, req oapi.MovePathRequestObject) (oapi.MovePathResponseObject, error) { + log := logger.FromContext(ctx) + if req.Body == nil { + return oapi.MovePath400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body required"}}, nil + } + src := req.Body.SrcPath + dst := req.Body.DestPath + if src == "" || dst == "" { + return oapi.MovePath400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "src_path and dest_path required"}}, nil + } + if err := os.Rename(src, dst); err != nil { + if errors.Is(err, os.ErrNotExist) { + return oapi.MovePath404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "source not found"}}, nil + } + log.Error("failed to move path", "err", err, "src", src, "dst", dst) + return oapi.MovePath500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to move path"}}, nil + } + return oapi.MovePath200Response{}, nil +} + +// SetFilePermissions changes mode (and optionally owner/group) of a path. +func (s *ApiService) SetFilePermissions(ctx context.Context, req oapi.SetFilePermissionsRequestObject) (oapi.SetFilePermissionsResponseObject, error) { + log := logger.FromContext(ctx) + if req.Body == nil { + return oapi.SetFilePermissions400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "request body required"}}, nil + } + path := req.Body.Path + if path == "" { + return oapi.SetFilePermissions400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "path cannot be empty"}}, nil + } + // parse mode + modeVal, err := strconv.ParseUint(req.Body.Mode, 8, 32) + if err != nil { + return oapi.SetFilePermissions400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "invalid mode"}}, nil + } + if err := os.Chmod(path, os.FileMode(modeVal)); err != nil { + if errors.Is(err, os.ErrNotExist) { + return oapi.SetFilePermissions404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "path not found"}}, nil + } + log.Error("failed to chmod", "err", err, "path", path) + return oapi.SetFilePermissions500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to chmod"}}, nil + } + // chown if owner/group provided (best effort) + if req.Body.Owner != nil || req.Body.Group != nil { + uid := -1 + gid := -1 + if req.Body.Owner != nil { + if u, err := user.Lookup(*req.Body.Owner); err == nil { + if id, _ := strconv.Atoi(u.Uid); id >= 0 { + uid = id + } } - if _, err := io.Copy(out, part); err != nil { - out.Close() - log.Error("failed to write uploaded data", "err", err, "path", currentDest) - return oapi.UploadFiles500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to write file"}}, nil + } + if req.Body.Group != nil { + if g, err := user.LookupGroup(*req.Body.Group); err == nil { + if id, _ := strconv.Atoi(g.Gid); id >= 0 { + gid = id + } } - out.Close() - currentDest = "" // reset for next file - default: - // ignore unknown parts + } + // only attempt if at least one resolved + if uid != -1 || gid != -1 { + _ = os.Chown(path, uid, gid) // ignore error (likely EPERM) to keep API simpler } } - - return oapi.UploadFiles201Response{}, nil + return oapi.SetFilePermissions200Response{}, nil } // StartFsWatch is not implemented in this basic filesystem handler. It returns a 400 error to the client. diff --git a/server/cmd/api/api/fs_test.go b/server/cmd/api/api/fs_test.go index 8073ef4e..c4a8a70f 100644 --- a/server/cmd/api/api/fs_test.go +++ b/server/cmd/api/api/fs_test.go @@ -2,10 +2,8 @@ package api import ( "bufio" - "bytes" "context" "io" - "mime/multipart" "os" "path/filepath" "strings" @@ -14,8 +12,8 @@ import ( oapi "github.com/onkernel/kernel-images/server/lib/oapi" ) -// TestWriteReadDownloadFile verifies that files can be written, read back, and downloaded successfully. -func TestWriteReadDownloadFile(t *testing.T) { +// TestWriteReadFile verifies that files can be written and read back successfully. +func TestWriteReadFile(t *testing.T) { t.Parallel() ctx := context.Background() @@ -51,19 +49,7 @@ func TestWriteReadDownloadFile(t *testing.T) { t.Fatalf("ReadFile content mismatch: got %q want %q", got, content) } - // Download the file - dlResp, err := svc.DownloadFile(ctx, oapi.DownloadFileRequestObject{Params: oapi.DownloadFileParams{Path: filePath}}) - if err != nil { - t.Fatalf("DownloadFile returned error: %v", err) - } - d200, ok := dlResp.(oapi.DownloadFile200ApplicationoctetStreamResponse) - if !ok { - t.Fatalf("unexpected response type from DownloadFile: %T", dlResp) - } - dlData, _ := io.ReadAll(d200.Body) - if got := string(dlData); got != content { - t.Fatalf("DownloadFile content mismatch: got %q want %q", got, content) - } + // (Download functionality removed) // Attempt to read non-existent file missingResp, err := svc.ReadFile(ctx, oapi.ReadFileRequestObject{Params: oapi.ReadFileParams{Path: filepath.Join(tmpDir, "missing.txt")}}) @@ -84,8 +70,8 @@ func TestWriteReadDownloadFile(t *testing.T) { } } -// TestUploadFiles verifies multipart upload and filesystem watch event generation. -func TestUploadFilesAndWatch(t *testing.T) { +// TestWriteFileAndWatch verifies WriteFile operation and filesystem watch event generation. +func TestWriteFileAndWatch(t *testing.T) { t.Parallel() ctx := context.Background() @@ -108,37 +94,25 @@ func TestUploadFilesAndWatch(t *testing.T) { } watchID := *sr201.WatchId - // Build multipart payload - var buf bytes.Buffer - mw := multipart.NewWriter(&buf) - destPath := filepath.Join(dir, "upload.txt") - - // dest_path part - if err := mw.WriteField("dest_path", destPath); err != nil { - t.Fatalf("WriteField error: %v", err) - } - // file part - fw, err := mw.CreateFormFile("file", "upload.txt") - if err != nil { - t.Fatalf("CreateFormFile error: %v", err) - } - content := "upload content" - fw.Write([]byte(content)) - mw.Close() + destPath := filepath.Join(dir, "write.txt") + content := "write content" - uploadReq := oapi.UploadFilesRequestObject{Body: multipart.NewReader(&buf, mw.Boundary())} - if resp, err := svc.UploadFiles(ctx, uploadReq); err != nil { - t.Fatalf("UploadFiles error: %v", err) + // Perform WriteFile to trigger watch events + if resp, err := svc.WriteFile(ctx, oapi.WriteFileRequestObject{ + Params: oapi.WriteFileParams{Path: destPath}, + Body: strings.NewReader(content), + }); err != nil { + t.Fatalf("WriteFile error: %v", err) } else { - if _, ok := resp.(oapi.UploadFiles201Response); !ok { - t.Fatalf("unexpected response type from UploadFiles: %T", resp) + if _, ok := resp.(oapi.WriteFile201Response); !ok { + t.Fatalf("unexpected response type from WriteFile: %T", resp) } } // Verify file exists data, err := os.ReadFile(destPath) if err != nil || string(data) != content { - t.Fatalf("uploaded file mismatch: %v", err) + t.Fatalf("written file mismatch: %v", err) } // Stream events (should at least receive one) @@ -170,3 +144,91 @@ func TestUploadFilesAndWatch(t *testing.T) { t.Fatalf("unexpected response type from StopFsWatch: %T", stopResp) } } + +// TestFileDirOperations covers the new filesystem management endpoints. +func TestFileDirOperations(t *testing.T) { + t.Parallel() + + ctx := context.Background() + svc := &ApiService{} + + tmp := t.TempDir() + dirPath := filepath.Join(tmp, "mydir") + + // Create directory + modeStr := "755" + createReq := oapi.CreateDirectoryRequestObject{Body: &oapi.CreateDirectoryRequest{Path: dirPath, Mode: &modeStr}} + if resp, err := svc.CreateDirectory(ctx, createReq); err != nil { + t.Fatalf("CreateDirectory error: %v", err) + } else { + if _, ok := resp.(oapi.CreateDirectory201Response); !ok { + t.Fatalf("unexpected response type from CreateDirectory: %T", resp) + } + } + + // Write a file inside the directory + filePath := filepath.Join(dirPath, "file.txt") + content := "data" + if resp, err := svc.WriteFile(ctx, oapi.WriteFileRequestObject{Params: oapi.WriteFileParams{Path: filePath}, Body: strings.NewReader(content)}); err != nil { + t.Fatalf("WriteFile error: %v", err) + } else if _, ok := resp.(oapi.WriteFile201Response); !ok { + t.Fatalf("unexpected WriteFile resp type: %T", resp) + } + + // List files + listResp, err := svc.ListFiles(ctx, oapi.ListFilesRequestObject{Params: oapi.ListFilesParams{Path: dirPath}}) + if err != nil { + t.Fatalf("ListFiles error: %v", err) + } + lf200, ok := listResp.(oapi.ListFiles200JSONResponse) + if !ok { + t.Fatalf("unexpected ListFiles resp type: %T", listResp) + } + if len(lf200) != 1 || lf200[0].Name != "file.txt" { + t.Fatalf("ListFiles unexpected content: %+v", lf200) + } + + // FileInfo + fiResp, err := svc.FileInfo(ctx, oapi.FileInfoRequestObject{Params: oapi.FileInfoParams{Path: filePath}}) + if err != nil { + t.Fatalf("FileInfo error: %v", err) + } + fi200, ok := fiResp.(oapi.FileInfo200JSONResponse) + if !ok { + t.Fatalf("unexpected FileInfo resp: %T", fiResp) + } + if fi200.Name != "file.txt" || fi200.SizeBytes == 0 { + t.Fatalf("FileInfo unexpected: %+v", fi200) + } + + // Move file + newFilePath := filepath.Join(dirPath, "moved.txt") + moveReq := oapi.MovePathRequestObject{Body: &oapi.MovePathRequest{SrcPath: filePath, DestPath: newFilePath}} + if resp, err := svc.MovePath(ctx, moveReq); err != nil { + t.Fatalf("MovePath error: %v", err) + } else if _, ok := resp.(oapi.MovePath200Response); !ok { + t.Fatalf("unexpected MovePath resp type: %T", resp) + } + + // Set permissions + chmodReq := oapi.SetFilePermissionsRequestObject{Body: &oapi.SetFilePermissionsRequest{Path: newFilePath, Mode: "600"}} + if resp, err := svc.SetFilePermissions(ctx, chmodReq); err != nil { + t.Fatalf("SetFilePermissions error: %v", err) + } else if _, ok := resp.(oapi.SetFilePermissions200Response); !ok { + t.Fatalf("unexpected SetFilePermissions resp: %T", resp) + } + + // Delete file + if resp, err := svc.DeleteFile(ctx, oapi.DeleteFileRequestObject{Body: &oapi.DeletePathRequest{Path: newFilePath}}); err != nil { + t.Fatalf("DeleteFile error: %v", err) + } else if _, ok := resp.(oapi.DeleteFile200Response); !ok { + t.Fatalf("unexpected DeleteFile resp: %T", resp) + } + + // Delete directory + if resp, err := svc.DeleteDirectory(ctx, oapi.DeleteDirectoryRequestObject{Body: &oapi.DeletePathRequest{Path: dirPath}}); err != nil { + t.Fatalf("DeleteDirectory error: %v", err) + } else if _, ok := resp.(oapi.DeleteDirectory200Response); !ok { + t.Fatalf("unexpected DeleteDirectory resp: %T", resp) + } +} diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index cd3bea86..5c183a59 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -11,7 +11,6 @@ import ( "encoding/json" "fmt" "io" - "mime/multipart" "net/http" "net/url" "path" @@ -22,7 +21,6 @@ import ( "github.com/go-chi/chi/v5" "github.com/oapi-codegen/runtime" strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" - openapi_types "github.com/oapi-codegen/runtime/types" ) // Defines values for ClickMouseRequestButton. @@ -76,6 +74,21 @@ type ClickMouseRequestButton string // ClickMouseRequestClickType Type of click action type ClickMouseRequestClickType string +// CreateDirectoryRequest defines model for CreateDirectoryRequest. +type CreateDirectoryRequest struct { + // Mode Optional directory mode (octal string, e.g. 755). Defaults to 755. + Mode *string `json:"mode,omitempty"` + + // Path Absolute directory path to create. + Path string `json:"path"` +} + +// DeletePathRequest defines model for DeletePathRequest. +type DeletePathRequest struct { + // Path Absolute path to delete. + Path string `json:"path"` +} + // DeleteRecordingRequest defines model for DeleteRecordingRequest. type DeleteRecordingRequest struct { // Id Identifier of the recording to delete. Alphanumeric or hyphen. @@ -87,6 +100,27 @@ type Error struct { Message string `json:"message"` } +// FileInfo defines model for FileInfo. +type FileInfo struct { + // IsDir Whether the path is a directory. + IsDir bool `json:"is_dir"` + + // ModTime Last modification time. + ModTime time.Time `json:"mod_time"` + + // Mode File mode bits (e.g., "drwxr-xr-x" or "-rw-r--r--"). + Mode string `json:"mode"` + + // Name Base name of the file or directory. + Name string `json:"name"` + + // Path Absolute path. + Path string `json:"path"` + + // SizeBytes Size in bytes. 0 for directories. + SizeBytes int `json:"size_bytes"` +} + // FileSystemEvent Filesystem change event. type FileSystemEvent struct { // IsDir Whether the affected path is a directory. @@ -105,6 +139,9 @@ type FileSystemEvent struct { // FileSystemEventType Event type. type FileSystemEventType string +// ListFiles Array of file or directory information entries. +type ListFiles = []FileInfo + // MoveMouseRequest defines model for MoveMouseRequest. type MoveMouseRequest struct { // HoldKeys Modifier keys to hold during the move @@ -117,6 +154,15 @@ type MoveMouseRequest struct { Y int `json:"y"` } +// MovePathRequest defines model for MovePathRequest. +type MovePathRequest struct { + // DestPath Absolute destination path. + DestPath string `json:"dest_path"` + + // SrcPath Absolute source path. + SrcPath string `json:"src_path"` +} + // RecorderInfo defines model for RecorderInfo. type RecorderInfo struct { // FinishedAt Timestamp when recording finished @@ -128,6 +174,21 @@ type RecorderInfo struct { StartedAt *time.Time `json:"started_at"` } +// SetFilePermissionsRequest defines model for SetFilePermissionsRequest. +type SetFilePermissionsRequest struct { + // Group New group name or GID. + Group *string `json:"group,omitempty"` + + // Mode File mode bits (octal string, e.g. 644). + Mode string `json:"mode"` + + // Owner New owner username or UID. + Owner *string `json:"owner,omitempty"` + + // Path Absolute path whose permissions are to be changed. + Path string `json:"path"` +} + // StartFsWatchRequest defines model for StartFsWatchRequest. type StartFsWatchRequest struct { // Path Directory to watch. @@ -173,29 +234,31 @@ type InternalError = Error // NotFoundError defines model for NotFoundError. type NotFoundError = Error -// DownloadFileParams defines parameters for DownloadFile. -type DownloadFileParams struct { +// FileInfoParams defines parameters for FileInfo. +type FileInfoParams struct { + // Path Absolute path of the file or directory. + Path string `form:"path" json:"path"` +} + +// ListFilesParams defines parameters for ListFiles. +type ListFilesParams struct { + // Path Absolute directory path. Path string `form:"path" json:"path"` } // ReadFileParams defines parameters for ReadFile. type ReadFileParams struct { - // Path Absolute or relative file path to read. + // Path Absolute file path to read. Path string `form:"path" json:"path"` } // WriteFileParams defines parameters for WriteFile. type WriteFileParams struct { - // Path Destination file path. + // Path Destination absolute file path. Path string `form:"path" json:"path"` -} -// UploadFilesMultipartBody defines parameters for UploadFiles. -type UploadFilesMultipartBody struct { - Files []struct { - DestPath string `json:"dest_path"` - File openapi_types.File `json:"file"` - } `json:"files"` + // Mode Optional file mode (octal string, e.g. 644). Defaults to 644. + Mode *string `form:"mode,omitempty" json:"mode,omitempty"` } // DownloadRecordingParams defines parameters for DownloadRecording. @@ -210,8 +273,20 @@ type ClickMouseJSONRequestBody = ClickMouseRequest // MoveMouseJSONRequestBody defines body for MoveMouse for application/json ContentType. type MoveMouseJSONRequestBody = MoveMouseRequest -// UploadFilesMultipartRequestBody defines body for UploadFiles for multipart/form-data ContentType. -type UploadFilesMultipartRequestBody UploadFilesMultipartBody +// CreateDirectoryJSONRequestBody defines body for CreateDirectory for application/json ContentType. +type CreateDirectoryJSONRequestBody = CreateDirectoryRequest + +// DeleteDirectoryJSONRequestBody defines body for DeleteDirectory for application/json ContentType. +type DeleteDirectoryJSONRequestBody = DeletePathRequest + +// DeleteFileJSONRequestBody defines body for DeleteFile for application/json ContentType. +type DeleteFileJSONRequestBody = DeletePathRequest + +// MovePathJSONRequestBody defines body for MovePath for application/json ContentType. +type MovePathJSONRequestBody = MovePathRequest + +// SetFilePermissionsJSONRequestBody defines body for SetFilePermissions for application/json ContentType. +type SetFilePermissionsJSONRequestBody = SetFilePermissionsRequest // StartFsWatchJSONRequestBody defines body for StartFsWatch for application/json ContentType. type StartFsWatchJSONRequestBody = StartFsWatchRequest @@ -308,17 +383,39 @@ type ClientInterface interface { MoveMouse(ctx context.Context, body MoveMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // DownloadFile request - DownloadFile(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // CreateDirectoryWithBody request with any body + CreateDirectoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateDirectory(ctx context.Context, body CreateDirectoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteDirectoryWithBody request with any body + DeleteDirectoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + DeleteDirectory(ctx context.Context, body DeleteDirectoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteFileWithBody request with any body + DeleteFileWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + DeleteFile(ctx context.Context, body DeleteFileJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // FileInfo request + FileInfo(ctx context.Context, params *FileInfoParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListFiles request + ListFiles(ctx context.Context, params *ListFilesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // MovePathWithBody request with any body + MovePathWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + MovePath(ctx context.Context, body MovePathJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // ReadFile request ReadFile(ctx context.Context, params *ReadFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // WriteFileWithBody request with any body - WriteFileWithBody(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // SetFilePermissionsWithBody request with any body + SetFilePermissionsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - // UploadFilesWithBody request with any body - UploadFilesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + SetFilePermissions(ctx context.Context, body SetFilePermissionsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // StartFsWatchWithBody request with any body StartFsWatchWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -331,6 +428,9 @@ type ClientInterface interface { // StreamFsEvents request StreamFsEvents(ctx context.Context, watchId string, reqEditors ...RequestEditorFn) (*http.Response, error) + // WriteFileWithBody request with any body + WriteFileWithBody(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteRecordingWithBody request with any body DeleteRecordingWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -401,8 +501,116 @@ func (c *Client) MoveMouse(ctx context.Context, body MoveMouseJSONRequestBody, r return c.Client.Do(req) } -func (c *Client) DownloadFile(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDownloadFileRequest(c.Server, params) +func (c *Client) CreateDirectoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateDirectoryRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateDirectory(ctx context.Context, body CreateDirectoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateDirectoryRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteDirectoryWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteDirectoryRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteDirectory(ctx context.Context, body DeleteDirectoryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteDirectoryRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteFileWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteFileRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteFile(ctx context.Context, body DeleteFileJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteFileRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) FileInfo(ctx context.Context, params *FileInfoParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewFileInfoRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListFiles(ctx context.Context, params *ListFilesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListFilesRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) MovePathWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMovePathRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) MovePath(ctx context.Context, body MovePathJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewMovePathRequest(c.Server, body) if err != nil { return nil, err } @@ -425,8 +633,8 @@ func (c *Client) ReadFile(ctx context.Context, params *ReadFileParams, reqEditor return c.Client.Do(req) } -func (c *Client) WriteFileWithBody(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewWriteFileRequestWithBody(c.Server, params, contentType, body) +func (c *Client) SetFilePermissionsWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSetFilePermissionsRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -437,8 +645,8 @@ func (c *Client) WriteFileWithBody(ctx context.Context, params *WriteFileParams, return c.Client.Do(req) } -func (c *Client) UploadFilesWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUploadFilesRequestWithBody(c.Server, contentType, body) +func (c *Client) SetFilePermissions(ctx context.Context, body SetFilePermissionsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSetFilePermissionsRequest(c.Server, body) if err != nil { return nil, err } @@ -497,6 +705,18 @@ func (c *Client) StreamFsEvents(ctx context.Context, watchId string, reqEditors return c.Client.Do(req) } +func (c *Client) WriteFileWithBody(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewWriteFileRequestWithBody(c.Server, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteRecordingWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteRecordingRequestWithBody(c.Server, contentType, body) if err != nil { @@ -673,8 +893,19 @@ func NewMoveMouseRequestWithBody(server string, contentType string, body io.Read return req, nil } -// NewDownloadFileRequest generates requests for DownloadFile -func NewDownloadFileRequest(server string, params *DownloadFileParams) (*http.Request, error) { +// NewCreateDirectoryRequest calls the generic CreateDirectory builder with application/json body +func NewCreateDirectoryRequest(server string, body CreateDirectoryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateDirectoryRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateDirectoryRequestWithBody generates requests for CreateDirectory with any type of body +func NewCreateDirectoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -682,7 +913,7 @@ func NewDownloadFileRequest(server string, params *DownloadFileParams) (*http.Re return nil, err } - operationPath := fmt.Sprintf("/fs/download") + operationPath := fmt.Sprintf("/fs/create_directory") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -692,34 +923,98 @@ func NewDownloadFileRequest(server string, params *DownloadFileParams) (*http.Re return nil, err } - if params != nil { - queryValues := queryURL.Query() + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } + req.Header.Add("Content-Type", contentType) - queryURL.RawQuery = queryValues.Encode() + return req, nil +} + +// NewDeleteDirectoryRequest calls the generic DeleteDirectory builder with application/json body +func NewDeleteDirectoryRequest(server string, body DeleteDirectoryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err } + bodyReader = bytes.NewReader(buf) + return NewDeleteDirectoryRequestWithBody(server, "application/json", bodyReader) +} - req, err := http.NewRequest("GET", queryURL.String(), nil) +// NewDeleteDirectoryRequestWithBody generates requests for DeleteDirectory with any type of body +func NewDeleteDirectoryRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) if err != nil { return nil, err } + operationPath := fmt.Sprintf("/fs/delete_directory") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + return req, nil } -// NewReadFileRequest generates requests for ReadFile -func NewReadFileRequest(server string, params *ReadFileParams) (*http.Request, error) { +// NewDeleteFileRequest calls the generic DeleteFile builder with application/json body +func NewDeleteFileRequest(server string, body DeleteFileJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewDeleteFileRequestWithBody(server, "application/json", bodyReader) +} + +// NewDeleteFileRequestWithBody generates requests for DeleteFile with any type of body +func NewDeleteFileRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/fs/delete_file") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewFileInfoRequest generates requests for FileInfo +func NewFileInfoRequest(server string, params *FileInfoParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -727,7 +1022,7 @@ func NewReadFileRequest(server string, params *ReadFileParams) (*http.Request, e return nil, err } - operationPath := fmt.Sprintf("/fs/file") + operationPath := fmt.Sprintf("/fs/file_info") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -763,8 +1058,8 @@ func NewReadFileRequest(server string, params *ReadFileParams) (*http.Request, e return req, nil } -// NewWriteFileRequestWithBody generates requests for WriteFile with any type of body -func NewWriteFileRequestWithBody(server string, params *WriteFileParams, contentType string, body io.Reader) (*http.Request, error) { +// NewListFilesRequest generates requests for ListFiles +func NewListFilesRequest(server string, params *ListFilesParams) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -772,7 +1067,7 @@ func NewWriteFileRequestWithBody(server string, params *WriteFileParams, content return nil, err } - operationPath := fmt.Sprintf("/fs/file") + operationPath := fmt.Sprintf("/fs/list_files") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -800,26 +1095,120 @@ func NewWriteFileRequestWithBody(server string, params *WriteFileParams, content queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("PUT", queryURL.String(), body) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } -// NewUploadFilesRequestWithBody generates requests for UploadFiles with any type of body -func NewUploadFilesRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) +// NewMovePathRequest calls the generic MovePath builder with application/json body +func NewMovePathRequest(server string, body MovePathJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) if err != nil { return nil, err } + bodyReader = bytes.NewReader(buf) + return NewMovePathRequestWithBody(server, "application/json", bodyReader) +} - operationPath := fmt.Sprintf("/fs/upload") +// NewMovePathRequestWithBody generates requests for MovePath with any type of body +func NewMovePathRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/fs/move") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewReadFileRequest generates requests for ReadFile +func NewReadFileRequest(server string, params *ReadFileParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/fs/read_file") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewSetFilePermissionsRequest calls the generic SetFilePermissions builder with application/json body +func NewSetFilePermissionsRequest(server string, body SetFilePermissionsJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewSetFilePermissionsRequestWithBody(server, "application/json", bodyReader) +} + +// NewSetFilePermissionsRequestWithBody generates requests for SetFilePermissions with any type of body +func NewSetFilePermissionsRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/fs/set_file_permissions") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -947,6 +1336,69 @@ func NewStreamFsEventsRequest(server string, watchId string) (*http.Request, err return req, nil } +// NewWriteFileRequestWithBody generates requests for WriteFile with any type of body +func NewWriteFileRequestWithBody(server string, params *WriteFileParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/fs/write_file") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "path", runtime.ParamLocationQuery, params.Path); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + if params.Mode != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "mode", runtime.ParamLocationQuery, *params.Mode); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewDeleteRecordingRequest calls the generic DeleteRecording builder with application/json body func NewDeleteRecordingRequest(server string, body DeleteRecordingJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -1196,17 +1648,39 @@ type ClientWithResponsesInterface interface { MoveMouseWithResponse(ctx context.Context, body MoveMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*MoveMouseResponse, error) - // DownloadFileWithResponse request - DownloadFileWithResponse(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*DownloadFileResponse, error) + // CreateDirectoryWithBodyWithResponse request with any body + CreateDirectoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateDirectoryResponse, error) + + CreateDirectoryWithResponse(ctx context.Context, body CreateDirectoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateDirectoryResponse, error) + + // DeleteDirectoryWithBodyWithResponse request with any body + DeleteDirectoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteDirectoryResponse, error) + + DeleteDirectoryWithResponse(ctx context.Context, body DeleteDirectoryJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteDirectoryResponse, error) + + // DeleteFileWithBodyWithResponse request with any body + DeleteFileWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteFileResponse, error) + + DeleteFileWithResponse(ctx context.Context, body DeleteFileJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteFileResponse, error) + + // FileInfoWithResponse request + FileInfoWithResponse(ctx context.Context, params *FileInfoParams, reqEditors ...RequestEditorFn) (*FileInfoResponse, error) + + // ListFilesWithResponse request + ListFilesWithResponse(ctx context.Context, params *ListFilesParams, reqEditors ...RequestEditorFn) (*ListFilesResponse, error) + + // MovePathWithBodyWithResponse request with any body + MovePathWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MovePathResponse, error) + + MovePathWithResponse(ctx context.Context, body MovePathJSONRequestBody, reqEditors ...RequestEditorFn) (*MovePathResponse, error) // ReadFileWithResponse request ReadFileWithResponse(ctx context.Context, params *ReadFileParams, reqEditors ...RequestEditorFn) (*ReadFileResponse, error) - // WriteFileWithBodyWithResponse request with any body - WriteFileWithBodyWithResponse(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*WriteFileResponse, error) + // SetFilePermissionsWithBodyWithResponse request with any body + SetFilePermissionsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetFilePermissionsResponse, error) - // UploadFilesWithBodyWithResponse request with any body - UploadFilesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadFilesResponse, error) + SetFilePermissionsWithResponse(ctx context.Context, body SetFilePermissionsJSONRequestBody, reqEditors ...RequestEditorFn) (*SetFilePermissionsResponse, error) // StartFsWatchWithBodyWithResponse request with any body StartFsWatchWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*StartFsWatchResponse, error) @@ -1219,6 +1693,9 @@ type ClientWithResponsesInterface interface { // StreamFsEventsWithResponse request StreamFsEventsWithResponse(ctx context.Context, watchId string, reqEditors ...RequestEditorFn) (*StreamFsEventsResponse, error) + // WriteFileWithBodyWithResponse request with any body + WriteFileWithBodyWithResponse(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*WriteFileResponse, error) + // DeleteRecordingWithBodyWithResponse request with any body DeleteRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteRecordingResponse, error) @@ -1287,16 +1764,15 @@ func (r MoveMouseResponse) StatusCode() int { return 0 } -type DownloadFileResponse struct { +type CreateDirectoryResponse struct { Body []byte HTTPResponse *http.Response JSON400 *BadRequestError - JSON404 *NotFoundError JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r DownloadFileResponse) Status() string { +func (r CreateDirectoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1304,14 +1780,14 @@ func (r DownloadFileResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DownloadFileResponse) StatusCode() int { +func (r CreateDirectoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ReadFileResponse struct { +type DeleteDirectoryResponse struct { Body []byte HTTPResponse *http.Response JSON400 *BadRequestError @@ -1320,7 +1796,7 @@ type ReadFileResponse struct { } // Status returns HTTPResponse.Status -func (r ReadFileResponse) Status() string { +func (r DeleteDirectoryResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1328,14 +1804,14 @@ func (r ReadFileResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ReadFileResponse) StatusCode() int { +func (r DeleteDirectoryResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type WriteFileResponse struct { +type DeleteFileResponse struct { Body []byte HTTPResponse *http.Response JSON400 *BadRequestError @@ -1344,7 +1820,7 @@ type WriteFileResponse struct { } // Status returns HTTPResponse.Status -func (r WriteFileResponse) Status() string { +func (r DeleteFileResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1352,23 +1828,24 @@ func (r WriteFileResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r WriteFileResponse) StatusCode() int { +func (r DeleteFileResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type UploadFilesResponse struct { +type FileInfoResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *FileInfo JSON400 *BadRequestError JSON404 *NotFoundError JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r UploadFilesResponse) Status() string { +func (r FileInfoResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1376,27 +1853,24 @@ func (r UploadFilesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UploadFilesResponse) StatusCode() int { +func (r FileInfoResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type StartFsWatchResponse struct { +type ListFilesResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *struct { - // WatchId Unique identifier for the directory watch - WatchId *string `json:"watch_id,omitempty"` - } - JSON400 *BadRequestError - JSON404 *NotFoundError - JSON500 *InternalError + JSON200 *ListFiles + JSON400 *BadRequestError + JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r StartFsWatchResponse) Status() string { +func (r ListFilesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1404,14 +1878,14 @@ func (r StartFsWatchResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r StartFsWatchResponse) StatusCode() int { +func (r ListFilesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type StopFsWatchResponse struct { +type MovePathResponse struct { Body []byte HTTPResponse *http.Response JSON400 *BadRequestError @@ -1420,7 +1894,7 @@ type StopFsWatchResponse struct { } // Status returns HTTPResponse.Status -func (r StopFsWatchResponse) Status() string { +func (r MovePathResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1428,14 +1902,14 @@ func (r StopFsWatchResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r StopFsWatchResponse) StatusCode() int { +func (r MovePathResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type StreamFsEventsResponse struct { +type ReadFileResponse struct { Body []byte HTTPResponse *http.Response JSON400 *BadRequestError @@ -1444,7 +1918,7 @@ type StreamFsEventsResponse struct { } // Status returns HTTPResponse.Status -func (r StreamFsEventsResponse) Status() string { +func (r ReadFileResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1452,14 +1926,14 @@ func (r StreamFsEventsResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r StreamFsEventsResponse) StatusCode() int { +func (r ReadFileResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type DeleteRecordingResponse struct { +type SetFilePermissionsResponse struct { Body []byte HTTPResponse *http.Response JSON400 *BadRequestError @@ -1468,7 +1942,7 @@ type DeleteRecordingResponse struct { } // Status returns HTTPResponse.Status -func (r DeleteRecordingResponse) Status() string { +func (r SetFilePermissionsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1476,23 +1950,27 @@ func (r DeleteRecordingResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteRecordingResponse) StatusCode() int { +func (r SetFilePermissionsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type DownloadRecordingResponse struct { +type StartFsWatchResponse struct { Body []byte HTTPResponse *http.Response - JSON400 *BadRequestError - JSON404 *NotFoundError - JSON500 *InternalError + JSON201 *struct { + // WatchId Unique identifier for the directory watch + WatchId *string `json:"watch_id,omitempty"` + } + JSON400 *BadRequestError + JSON404 *NotFoundError + JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r DownloadRecordingResponse) Status() string { +func (r StartFsWatchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1500,22 +1978,23 @@ func (r DownloadRecordingResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DownloadRecordingResponse) StatusCode() int { +func (r StartFsWatchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListRecordersResponse struct { +type StopFsWatchResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]RecorderInfo + JSON400 *BadRequestError + JSON404 *NotFoundError JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r ListRecordersResponse) Status() string { +func (r StopFsWatchResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1523,23 +2002,23 @@ func (r ListRecordersResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListRecordersResponse) StatusCode() int { +func (r StopFsWatchResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type StartRecordingResponse struct { +type StreamFsEventsResponse struct { Body []byte HTTPResponse *http.Response JSON400 *BadRequestError - JSON409 *ConflictError + JSON404 *NotFoundError JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r StartRecordingResponse) Status() string { +func (r StreamFsEventsResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1547,22 +2026,23 @@ func (r StartRecordingResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r StartRecordingResponse) StatusCode() int { +func (r StreamFsEventsResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type StopRecordingResponse struct { +type WriteFileResponse struct { Body []byte HTTPResponse *http.Response JSON400 *BadRequestError + JSON404 *NotFoundError JSON500 *InternalError } // Status returns HTTPResponse.Status -func (r StopRecordingResponse) Status() string { +func (r WriteFileResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -1570,32 +2050,150 @@ func (r StopRecordingResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r StopRecordingResponse) StatusCode() int { +func (r WriteFileResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -// ClickMouseWithBodyWithResponse request with arbitrary body returning *ClickMouseResponse -func (c *ClientWithResponses) ClickMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) { - rsp, err := c.ClickMouseWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseClickMouseResponse(rsp) +type DeleteRecordingResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON404 *NotFoundError + JSON500 *InternalError } -func (c *ClientWithResponses) ClickMouseWithResponse(ctx context.Context, body ClickMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) { - rsp, err := c.ClickMouse(ctx, body, reqEditors...) - if err != nil { - return nil, err +// Status returns HTTPResponse.Status +func (r DeleteRecordingResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status } - return ParseClickMouseResponse(rsp) + return http.StatusText(0) } -// MoveMouseWithBodyWithResponse request with arbitrary body returning *MoveMouseResponse -func (c *ClientWithResponses) MoveMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MoveMouseResponse, error) { +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteRecordingResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DownloadRecordingResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON404 *NotFoundError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r DownloadRecordingResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DownloadRecordingResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListRecordersResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]RecorderInfo + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r ListRecordersResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListRecordersResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type StartRecordingResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON409 *ConflictError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r StartRecordingResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r StartRecordingResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type StopRecordingResponse struct { + Body []byte + HTTPResponse *http.Response + JSON400 *BadRequestError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r StopRecordingResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r StopRecordingResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ClickMouseWithBodyWithResponse request with arbitrary body returning *ClickMouseResponse +func (c *ClientWithResponses) ClickMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) { + rsp, err := c.ClickMouseWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseClickMouseResponse(rsp) +} + +func (c *ClientWithResponses) ClickMouseWithResponse(ctx context.Context, body ClickMouseJSONRequestBody, reqEditors ...RequestEditorFn) (*ClickMouseResponse, error) { + rsp, err := c.ClickMouse(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseClickMouseResponse(rsp) +} + +// MoveMouseWithBodyWithResponse request with arbitrary body returning *MoveMouseResponse +func (c *ClientWithResponses) MoveMouseWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MoveMouseResponse, error) { rsp, err := c.MoveMouseWithBody(ctx, contentType, body, reqEditors...) if err != nil { return nil, err @@ -1611,13 +2209,90 @@ func (c *ClientWithResponses) MoveMouseWithResponse(ctx context.Context, body Mo return ParseMoveMouseResponse(rsp) } -// DownloadFileWithResponse request returning *DownloadFileResponse -func (c *ClientWithResponses) DownloadFileWithResponse(ctx context.Context, params *DownloadFileParams, reqEditors ...RequestEditorFn) (*DownloadFileResponse, error) { - rsp, err := c.DownloadFile(ctx, params, reqEditors...) +// CreateDirectoryWithBodyWithResponse request with arbitrary body returning *CreateDirectoryResponse +func (c *ClientWithResponses) CreateDirectoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateDirectoryResponse, error) { + rsp, err := c.CreateDirectoryWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateDirectoryResponse(rsp) +} + +func (c *ClientWithResponses) CreateDirectoryWithResponse(ctx context.Context, body CreateDirectoryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateDirectoryResponse, error) { + rsp, err := c.CreateDirectory(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateDirectoryResponse(rsp) +} + +// DeleteDirectoryWithBodyWithResponse request with arbitrary body returning *DeleteDirectoryResponse +func (c *ClientWithResponses) DeleteDirectoryWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteDirectoryResponse, error) { + rsp, err := c.DeleteDirectoryWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteDirectoryResponse(rsp) +} + +func (c *ClientWithResponses) DeleteDirectoryWithResponse(ctx context.Context, body DeleteDirectoryJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteDirectoryResponse, error) { + rsp, err := c.DeleteDirectory(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteDirectoryResponse(rsp) +} + +// DeleteFileWithBodyWithResponse request with arbitrary body returning *DeleteFileResponse +func (c *ClientWithResponses) DeleteFileWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteFileResponse, error) { + rsp, err := c.DeleteFileWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteFileResponse(rsp) +} + +func (c *ClientWithResponses) DeleteFileWithResponse(ctx context.Context, body DeleteFileJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteFileResponse, error) { + rsp, err := c.DeleteFile(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteFileResponse(rsp) +} + +// FileInfoWithResponse request returning *FileInfoResponse +func (c *ClientWithResponses) FileInfoWithResponse(ctx context.Context, params *FileInfoParams, reqEditors ...RequestEditorFn) (*FileInfoResponse, error) { + rsp, err := c.FileInfo(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseFileInfoResponse(rsp) +} + +// ListFilesWithResponse request returning *ListFilesResponse +func (c *ClientWithResponses) ListFilesWithResponse(ctx context.Context, params *ListFilesParams, reqEditors ...RequestEditorFn) (*ListFilesResponse, error) { + rsp, err := c.ListFiles(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseListFilesResponse(rsp) +} + +// MovePathWithBodyWithResponse request with arbitrary body returning *MovePathResponse +func (c *ClientWithResponses) MovePathWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*MovePathResponse, error) { + rsp, err := c.MovePathWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseMovePathResponse(rsp) +} + +func (c *ClientWithResponses) MovePathWithResponse(ctx context.Context, body MovePathJSONRequestBody, reqEditors ...RequestEditorFn) (*MovePathResponse, error) { + rsp, err := c.MovePath(ctx, body, reqEditors...) if err != nil { return nil, err } - return ParseDownloadFileResponse(rsp) + return ParseMovePathResponse(rsp) } // ReadFileWithResponse request returning *ReadFileResponse @@ -1629,22 +2304,21 @@ func (c *ClientWithResponses) ReadFileWithResponse(ctx context.Context, params * return ParseReadFileResponse(rsp) } -// WriteFileWithBodyWithResponse request with arbitrary body returning *WriteFileResponse -func (c *ClientWithResponses) WriteFileWithBodyWithResponse(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*WriteFileResponse, error) { - rsp, err := c.WriteFileWithBody(ctx, params, contentType, body, reqEditors...) +// SetFilePermissionsWithBodyWithResponse request with arbitrary body returning *SetFilePermissionsResponse +func (c *ClientWithResponses) SetFilePermissionsWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetFilePermissionsResponse, error) { + rsp, err := c.SetFilePermissionsWithBody(ctx, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseWriteFileResponse(rsp) + return ParseSetFilePermissionsResponse(rsp) } -// UploadFilesWithBodyWithResponse request with arbitrary body returning *UploadFilesResponse -func (c *ClientWithResponses) UploadFilesWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadFilesResponse, error) { - rsp, err := c.UploadFilesWithBody(ctx, contentType, body, reqEditors...) +func (c *ClientWithResponses) SetFilePermissionsWithResponse(ctx context.Context, body SetFilePermissionsJSONRequestBody, reqEditors ...RequestEditorFn) (*SetFilePermissionsResponse, error) { + rsp, err := c.SetFilePermissions(ctx, body, reqEditors...) if err != nil { return nil, err } - return ParseUploadFilesResponse(rsp) + return ParseSetFilePermissionsResponse(rsp) } // StartFsWatchWithBodyWithResponse request with arbitrary body returning *StartFsWatchResponse @@ -1682,6 +2356,15 @@ func (c *ClientWithResponses) StreamFsEventsWithResponse(ctx context.Context, wa return ParseStreamFsEventsResponse(rsp) } +// WriteFileWithBodyWithResponse request with arbitrary body returning *WriteFileResponse +func (c *ClientWithResponses) WriteFileWithBodyWithResponse(ctx context.Context, params *WriteFileParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*WriteFileResponse, error) { + rsp, err := c.WriteFileWithBody(ctx, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseWriteFileResponse(rsp) +} + // DeleteRecordingWithBodyWithResponse request with arbitrary body returning *DeleteRecordingResponse func (c *ClientWithResponses) DeleteRecordingWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteRecordingResponse, error) { rsp, err := c.DeleteRecordingWithBody(ctx, contentType, body, reqEditors...) @@ -1817,15 +2500,15 @@ func ParseMoveMouseResponse(rsp *http.Response) (*MoveMouseResponse, error) { return response, nil } -// ParseDownloadFileResponse parses an HTTP response from a DownloadFileWithResponse call -func ParseDownloadFileResponse(rsp *http.Response) (*DownloadFileResponse, error) { +// ParseCreateDirectoryResponse parses an HTTP response from a CreateDirectoryWithResponse call +func ParseCreateDirectoryResponse(rsp *http.Response) (*CreateDirectoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DownloadFileResponse{ + response := &CreateDirectoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -1838,13 +2521,6 @@ func ParseDownloadFileResponse(rsp *http.Response) (*DownloadFileResponse, error } response.JSON400 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundError - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -1857,15 +2533,15 @@ func ParseDownloadFileResponse(rsp *http.Response) (*DownloadFileResponse, error return response, nil } -// ParseReadFileResponse parses an HTTP response from a ReadFileWithResponse call -func ParseReadFileResponse(rsp *http.Response) (*ReadFileResponse, error) { +// ParseDeleteDirectoryResponse parses an HTTP response from a DeleteDirectoryWithResponse call +func ParseDeleteDirectoryResponse(rsp *http.Response) (*DeleteDirectoryResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ReadFileResponse{ + response := &DeleteDirectoryResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -1897,15 +2573,15 @@ func ParseReadFileResponse(rsp *http.Response) (*ReadFileResponse, error) { return response, nil } -// ParseWriteFileResponse parses an HTTP response from a WriteFileWithResponse call -func ParseWriteFileResponse(rsp *http.Response) (*WriteFileResponse, error) { +// ParseDeleteFileResponse parses an HTTP response from a DeleteFileWithResponse call +func ParseDeleteFileResponse(rsp *http.Response) (*DeleteFileResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &WriteFileResponse{ + response := &DeleteFileResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -1937,20 +2613,27 @@ func ParseWriteFileResponse(rsp *http.Response) (*WriteFileResponse, error) { return response, nil } -// ParseUploadFilesResponse parses an HTTP response from a UploadFilesWithResponse call -func ParseUploadFilesResponse(rsp *http.Response) (*UploadFilesResponse, error) { +// ParseFileInfoResponse parses an HTTP response from a FileInfoWithResponse call +func ParseFileInfoResponse(rsp *http.Response) (*FileInfoResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UploadFilesResponse{ + response := &FileInfoResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest FileInfo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest BadRequestError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -1977,29 +2660,26 @@ func ParseUploadFilesResponse(rsp *http.Response) (*UploadFilesResponse, error) return response, nil } -// ParseStartFsWatchResponse parses an HTTP response from a StartFsWatchWithResponse call -func ParseStartFsWatchResponse(rsp *http.Response) (*StartFsWatchResponse, error) { +// ParseListFilesResponse parses an HTTP response from a ListFilesWithResponse call +func ParseListFilesResponse(rsp *http.Response) (*ListFilesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &StartFsWatchResponse{ + response := &ListFilesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest struct { - // WatchId Unique identifier for the directory watch - WatchId *string `json:"watch_id,omitempty"` - } + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ListFiles if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest BadRequestError @@ -2027,15 +2707,15 @@ func ParseStartFsWatchResponse(rsp *http.Response) (*StartFsWatchResponse, error return response, nil } -// ParseStopFsWatchResponse parses an HTTP response from a StopFsWatchWithResponse call -func ParseStopFsWatchResponse(rsp *http.Response) (*StopFsWatchResponse, error) { +// ParseMovePathResponse parses an HTTP response from a MovePathWithResponse call +func ParseMovePathResponse(rsp *http.Response) (*MovePathResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &StopFsWatchResponse{ + response := &MovePathResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -2067,15 +2747,15 @@ func ParseStopFsWatchResponse(rsp *http.Response) (*StopFsWatchResponse, error) return response, nil } -// ParseStreamFsEventsResponse parses an HTTP response from a StreamFsEventsWithResponse call -func ParseStreamFsEventsResponse(rsp *http.Response) (*StreamFsEventsResponse, error) { +// ParseReadFileResponse parses an HTTP response from a ReadFileWithResponse call +func ParseReadFileResponse(rsp *http.Response) (*ReadFileResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &StreamFsEventsResponse{ + response := &ReadFileResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -2107,15 +2787,15 @@ func ParseStreamFsEventsResponse(rsp *http.Response) (*StreamFsEventsResponse, e return response, nil } -// ParseDeleteRecordingResponse parses an HTTP response from a DeleteRecordingWithResponse call -func ParseDeleteRecordingResponse(rsp *http.Response) (*DeleteRecordingResponse, error) { +// ParseSetFilePermissionsResponse parses an HTTP response from a SetFilePermissionsWithResponse call +func ParseSetFilePermissionsResponse(rsp *http.Response) (*SetFilePermissionsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteRecordingResponse{ + response := &SetFilePermissionsResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -2147,20 +2827,30 @@ func ParseDeleteRecordingResponse(rsp *http.Response) (*DeleteRecordingResponse, return response, nil } -// ParseDownloadRecordingResponse parses an HTTP response from a DownloadRecordingWithResponse call -func ParseDownloadRecordingResponse(rsp *http.Response) (*DownloadRecordingResponse, error) { +// ParseStartFsWatchResponse parses an HTTP response from a StartFsWatchWithResponse call +func ParseStartFsWatchResponse(rsp *http.Response) (*StartFsWatchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DownloadRecordingResponse{ + response := &StartFsWatchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest struct { + // WatchId Unique identifier for the directory watch + WatchId *string `json:"watch_id,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest BadRequestError if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -2187,26 +2877,226 @@ func ParseDownloadRecordingResponse(rsp *http.Response) (*DownloadRecordingRespo return response, nil } -// ParseListRecordersResponse parses an HTTP response from a ListRecordersWithResponse call -func ParseListRecordersResponse(rsp *http.Response) (*ListRecordersResponse, error) { +// ParseStopFsWatchResponse parses an HTTP response from a StopFsWatchWithResponse call +func ParseStopFsWatchResponse(rsp *http.Response) (*StopFsWatchResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListRecordersResponse{ + response := &StopFsWatchResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []RecorderInfo + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseStreamFsEventsResponse parses an HTTP response from a StreamFsEventsWithResponse call +func ParseStreamFsEventsResponse(rsp *http.Response) (*StreamFsEventsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &StreamFsEventsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseWriteFileResponse parses an HTTP response from a WriteFileWithResponse call +func ParseWriteFileResponse(rsp *http.Response) (*WriteFileResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &WriteFileResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseDeleteRecordingResponse parses an HTTP response from a DeleteRecordingWithResponse call +func ParseDeleteRecordingResponse(rsp *http.Response) (*DeleteRecordingResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteRecordingResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseDownloadRecordingResponse parses an HTTP response from a DownloadRecordingWithResponse call +func ParseDownloadRecordingResponse(rsp *http.Response) (*DownloadRecordingResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DownloadRecordingResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseListRecordersResponse parses an HTTP response from a ListRecordersWithResponse call +func ParseListRecordersResponse(rsp *http.Response) (*ListRecordersResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListRecordersResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []RecorderInfo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalError @@ -2301,18 +3191,30 @@ type ServerInterface interface { // Move the mouse cursor to the specified coordinates on the host computer // (POST /computer/move_mouse) MoveMouse(w http.ResponseWriter, r *http.Request) - // Download a file - // (GET /fs/download) - DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) + // Create a new directory + // (POST /fs/create_directory) + CreateDirectory(w http.ResponseWriter, r *http.Request) + // Delete a directory + // (POST /fs/delete_directory) + DeleteDirectory(w http.ResponseWriter, r *http.Request) + // Delete a file + // (POST /fs/delete_file) + DeleteFile(w http.ResponseWriter, r *http.Request) + // Get information about a file or directory + // (GET /fs/file_info) + FileInfo(w http.ResponseWriter, r *http.Request, params FileInfoParams) + // List files in a directory + // (GET /fs/list_files) + ListFiles(w http.ResponseWriter, r *http.Request, params ListFilesParams) + // Move or rename a file or directory + // (POST /fs/move) + MovePath(w http.ResponseWriter, r *http.Request) // Read file contents - // (GET /fs/file) + // (GET /fs/read_file) ReadFile(w http.ResponseWriter, r *http.Request, params ReadFileParams) - // Write or create a file - // (PUT /fs/file) - WriteFile(w http.ResponseWriter, r *http.Request, params WriteFileParams) - // Upload one or more files - // (POST /fs/upload) - UploadFiles(w http.ResponseWriter, r *http.Request) + // Set file or directory permissions/ownership + // (POST /fs/set_file_permissions) + SetFilePermissions(w http.ResponseWriter, r *http.Request) // Watch a directory for changes // (POST /fs/watch) StartFsWatch(w http.ResponseWriter, r *http.Request) @@ -2322,6 +3224,9 @@ type ServerInterface interface { // Stream filesystem events for a watch // (GET /fs/watch/{watch_id}/events) StreamFsEvents(w http.ResponseWriter, r *http.Request, watchId string) + // Write or create a file + // (POST /fs/write_file) + WriteFile(w http.ResponseWriter, r *http.Request, params WriteFileParams) // Delete a previously recorded video file // (POST /recording/delete) DeleteRecording(w http.ResponseWriter, r *http.Request) @@ -2355,27 +3260,51 @@ func (_ Unimplemented) MoveMouse(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Download a file -// (GET /fs/download) -func (_ Unimplemented) DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) { +// Create a new directory +// (POST /fs/create_directory) +func (_ Unimplemented) CreateDirectory(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Read file contents -// (GET /fs/file) -func (_ Unimplemented) ReadFile(w http.ResponseWriter, r *http.Request, params ReadFileParams) { +// Delete a directory +// (POST /fs/delete_directory) +func (_ Unimplemented) DeleteDirectory(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Write or create a file -// (PUT /fs/file) -func (_ Unimplemented) WriteFile(w http.ResponseWriter, r *http.Request, params WriteFileParams) { +// Delete a file +// (POST /fs/delete_file) +func (_ Unimplemented) DeleteFile(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Upload one or more files -// (POST /fs/upload) -func (_ Unimplemented) UploadFiles(w http.ResponseWriter, r *http.Request) { +// Get information about a file or directory +// (GET /fs/file_info) +func (_ Unimplemented) FileInfo(w http.ResponseWriter, r *http.Request, params FileInfoParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// List files in a directory +// (GET /fs/list_files) +func (_ Unimplemented) ListFiles(w http.ResponseWriter, r *http.Request, params ListFilesParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Move or rename a file or directory +// (POST /fs/move) +func (_ Unimplemented) MovePath(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Read file contents +// (GET /fs/read_file) +func (_ Unimplemented) ReadFile(w http.ResponseWriter, r *http.Request, params ReadFileParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Set file or directory permissions/ownership +// (POST /fs/set_file_permissions) +func (_ Unimplemented) SetFilePermissions(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } @@ -2397,6 +3326,12 @@ func (_ Unimplemented) StreamFsEvents(w http.ResponseWriter, r *http.Request, wa w.WriteHeader(http.StatusNotImplemented) } +// Write or create a file +// (POST /fs/write_file) +func (_ Unimplemented) WriteFile(w http.ResponseWriter, r *http.Request, params WriteFileParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // Delete a previously recorded video file // (POST /recording/delete) func (_ Unimplemented) DeleteRecording(w http.ResponseWriter, r *http.Request) { @@ -2464,13 +3399,55 @@ func (siw *ServerInterfaceWrapper) MoveMouse(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r) } -// DownloadFile operation middleware -func (siw *ServerInterfaceWrapper) DownloadFile(w http.ResponseWriter, r *http.Request) { +// CreateDirectory operation middleware +func (siw *ServerInterfaceWrapper) CreateDirectory(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateDirectory(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteDirectory operation middleware +func (siw *ServerInterfaceWrapper) DeleteDirectory(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteDirectory(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteFile operation middleware +func (siw *ServerInterfaceWrapper) DeleteFile(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteFile(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// FileInfo operation middleware +func (siw *ServerInterfaceWrapper) FileInfo(w http.ResponseWriter, r *http.Request) { var err error // Parameter object where we will unmarshal all parameters from the context - var params DownloadFileParams + var params FileInfoParams // ------------- Required query parameter "path" ------------- @@ -2488,7 +3465,7 @@ func (siw *ServerInterfaceWrapper) DownloadFile(w http.ResponseWriter, r *http.R } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DownloadFile(w, r, params) + siw.Handler.FileInfo(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -2498,13 +3475,13 @@ func (siw *ServerInterfaceWrapper) DownloadFile(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r) } -// ReadFile operation middleware -func (siw *ServerInterfaceWrapper) ReadFile(w http.ResponseWriter, r *http.Request) { +// ListFiles operation middleware +func (siw *ServerInterfaceWrapper) ListFiles(w http.ResponseWriter, r *http.Request) { var err error // Parameter object where we will unmarshal all parameters from the context - var params ReadFileParams + var params ListFilesParams // ------------- Required query parameter "path" ------------- @@ -2522,7 +3499,7 @@ func (siw *ServerInterfaceWrapper) ReadFile(w http.ResponseWriter, r *http.Reque } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ReadFile(w, r, params) + siw.Handler.ListFiles(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -2532,13 +3509,27 @@ func (siw *ServerInterfaceWrapper) ReadFile(w http.ResponseWriter, r *http.Reque handler.ServeHTTP(w, r) } -// WriteFile operation middleware -func (siw *ServerInterfaceWrapper) WriteFile(w http.ResponseWriter, r *http.Request) { +// MovePath operation middleware +func (siw *ServerInterfaceWrapper) MovePath(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MovePath(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ReadFile operation middleware +func (siw *ServerInterfaceWrapper) ReadFile(w http.ResponseWriter, r *http.Request) { var err error // Parameter object where we will unmarshal all parameters from the context - var params WriteFileParams + var params ReadFileParams // ------------- Required query parameter "path" ------------- @@ -2556,7 +3547,7 @@ func (siw *ServerInterfaceWrapper) WriteFile(w http.ResponseWriter, r *http.Requ } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.WriteFile(w, r, params) + siw.Handler.ReadFile(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -2566,11 +3557,11 @@ func (siw *ServerInterfaceWrapper) WriteFile(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r) } -// UploadFiles operation middleware -func (siw *ServerInterfaceWrapper) UploadFiles(w http.ResponseWriter, r *http.Request) { +// SetFilePermissions operation middleware +func (siw *ServerInterfaceWrapper) SetFilePermissions(w http.ResponseWriter, r *http.Request) { handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.UploadFiles(w, r) + siw.Handler.SetFilePermissions(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -2644,6 +3635,48 @@ func (siw *ServerInterfaceWrapper) StreamFsEvents(w http.ResponseWriter, r *http handler.ServeHTTP(w, r) } +// WriteFile operation middleware +func (siw *ServerInterfaceWrapper) WriteFile(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params WriteFileParams + + // ------------- Required query parameter "path" ------------- + + if paramValue := r.URL.Query().Get("path"); paramValue != "" { + + } else { + siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"}) + return + } + + err = runtime.BindQueryParameter("form", true, true, "path", r.URL.Query(), ¶ms.Path) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err}) + return + } + + // ------------- Optional query parameter "mode" ------------- + + err = runtime.BindQueryParameter("form", true, false, "mode", r.URL.Query(), ¶ms.Mode) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "mode", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.WriteFile(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // DeleteRecording operation middleware func (siw *ServerInterfaceWrapper) DeleteRecording(w http.ResponseWriter, r *http.Request) { @@ -2847,16 +3880,28 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Post(options.BaseURL+"/computer/move_mouse", wrapper.MoveMouse) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/fs/download", wrapper.DownloadFile) + r.Post(options.BaseURL+"/fs/create_directory", wrapper.CreateDirectory) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/fs/delete_directory", wrapper.DeleteDirectory) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/fs/delete_file", wrapper.DeleteFile) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/fs/file_info", wrapper.FileInfo) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/fs/file", wrapper.ReadFile) + r.Get(options.BaseURL+"/fs/list_files", wrapper.ListFiles) }) r.Group(func(r chi.Router) { - r.Put(options.BaseURL+"/fs/file", wrapper.WriteFile) + r.Post(options.BaseURL+"/fs/move", wrapper.MovePath) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/fs/upload", wrapper.UploadFiles) + r.Get(options.BaseURL+"/fs/read_file", wrapper.ReadFile) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/fs/set_file_permissions", wrapper.SetFilePermissions) }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/fs/watch", wrapper.StartFsWatch) @@ -2867,6 +3912,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/fs/watch/{watch_id}/events", wrapper.StreamFsEvents) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/fs/write_file", wrapper.WriteFile) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/recording/delete", wrapper.DeleteRecording) }) @@ -2953,63 +4001,260 @@ func (response MoveMouse400JSONResponse) VisitMoveMouseResponse(w http.ResponseW return json.NewEncoder(w).Encode(response) } -type MoveMouse500JSONResponse struct{ InternalErrorJSONResponse } +type MoveMouse500JSONResponse struct{ InternalErrorJSONResponse } + +func (response MoveMouse500JSONResponse) VisitMoveMouseResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type CreateDirectoryRequestObject struct { + Body *CreateDirectoryJSONRequestBody +} + +type CreateDirectoryResponseObject interface { + VisitCreateDirectoryResponse(w http.ResponseWriter) error +} + +type CreateDirectory201Response struct { +} + +func (response CreateDirectory201Response) VisitCreateDirectoryResponse(w http.ResponseWriter) error { + w.WriteHeader(201) + return nil +} + +type CreateDirectory400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response CreateDirectory400JSONResponse) VisitCreateDirectoryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type CreateDirectory500JSONResponse struct{ InternalErrorJSONResponse } + +func (response CreateDirectory500JSONResponse) VisitCreateDirectoryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteDirectoryRequestObject struct { + Body *DeleteDirectoryJSONRequestBody +} + +type DeleteDirectoryResponseObject interface { + VisitDeleteDirectoryResponse(w http.ResponseWriter) error +} + +type DeleteDirectory200Response struct { +} + +func (response DeleteDirectory200Response) VisitDeleteDirectoryResponse(w http.ResponseWriter) error { + w.WriteHeader(200) + return nil +} + +type DeleteDirectory400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response DeleteDirectory400JSONResponse) VisitDeleteDirectoryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteDirectory404JSONResponse struct{ NotFoundErrorJSONResponse } + +func (response DeleteDirectory404JSONResponse) VisitDeleteDirectoryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteDirectory500JSONResponse struct{ InternalErrorJSONResponse } + +func (response DeleteDirectory500JSONResponse) VisitDeleteDirectoryResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteFileRequestObject struct { + Body *DeleteFileJSONRequestBody +} + +type DeleteFileResponseObject interface { + VisitDeleteFileResponse(w http.ResponseWriter) error +} + +type DeleteFile200Response struct { +} + +func (response DeleteFile200Response) VisitDeleteFileResponse(w http.ResponseWriter) error { + w.WriteHeader(200) + return nil +} + +type DeleteFile400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response DeleteFile400JSONResponse) VisitDeleteFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteFile404JSONResponse struct{ NotFoundErrorJSONResponse } + +func (response DeleteFile404JSONResponse) VisitDeleteFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteFile500JSONResponse struct{ InternalErrorJSONResponse } + +func (response DeleteFile500JSONResponse) VisitDeleteFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type FileInfoRequestObject struct { + Params FileInfoParams +} + +type FileInfoResponseObject interface { + VisitFileInfoResponse(w http.ResponseWriter) error +} + +type FileInfo200JSONResponse FileInfo + +func (response FileInfo200JSONResponse) VisitFileInfoResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type FileInfo400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response FileInfo400JSONResponse) VisitFileInfoResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type FileInfo404JSONResponse struct{ NotFoundErrorJSONResponse } + +func (response FileInfo404JSONResponse) VisitFileInfoResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type FileInfo500JSONResponse struct{ InternalErrorJSONResponse } + +func (response FileInfo500JSONResponse) VisitFileInfoResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type ListFilesRequestObject struct { + Params ListFilesParams +} + +type ListFilesResponseObject interface { + VisitListFilesResponse(w http.ResponseWriter) error +} + +type ListFiles200JSONResponse ListFiles + +func (response ListFiles200JSONResponse) VisitListFilesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type ListFiles400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response ListFiles400JSONResponse) VisitListFilesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type ListFiles404JSONResponse struct{ NotFoundErrorJSONResponse } + +func (response ListFiles404JSONResponse) VisitListFilesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type ListFiles500JSONResponse struct{ InternalErrorJSONResponse } -func (response MoveMouse500JSONResponse) VisitMoveMouseResponse(w http.ResponseWriter) error { +func (response ListFiles500JSONResponse) VisitListFilesResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) return json.NewEncoder(w).Encode(response) } -type DownloadFileRequestObject struct { - Params DownloadFileParams +type MovePathRequestObject struct { + Body *MovePathJSONRequestBody } -type DownloadFileResponseObject interface { - VisitDownloadFileResponse(w http.ResponseWriter) error +type MovePathResponseObject interface { + VisitMovePathResponse(w http.ResponseWriter) error } -type DownloadFile200ApplicationoctetStreamResponse struct { - Body io.Reader - ContentLength int64 +type MovePath200Response struct { } -func (response DownloadFile200ApplicationoctetStreamResponse) VisitDownloadFileResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/octet-stream") - if response.ContentLength != 0 { - w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) - } +func (response MovePath200Response) VisitMovePathResponse(w http.ResponseWriter) error { w.WriteHeader(200) - - if closer, ok := response.Body.(io.ReadCloser); ok { - defer closer.Close() - } - _, err := io.Copy(w, response.Body) - return err + return nil } -type DownloadFile400JSONResponse struct{ BadRequestErrorJSONResponse } +type MovePath400JSONResponse struct{ BadRequestErrorJSONResponse } -func (response DownloadFile400JSONResponse) VisitDownloadFileResponse(w http.ResponseWriter) error { +func (response MovePath400JSONResponse) VisitMovePathResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type DownloadFile404JSONResponse struct{ NotFoundErrorJSONResponse } +type MovePath404JSONResponse struct{ NotFoundErrorJSONResponse } -func (response DownloadFile404JSONResponse) VisitDownloadFileResponse(w http.ResponseWriter) error { +func (response MovePath404JSONResponse) VisitMovePathResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(404) return json.NewEncoder(w).Encode(response) } -type DownloadFile500JSONResponse struct{ InternalErrorJSONResponse } +type MovePath500JSONResponse struct{ InternalErrorJSONResponse } -func (response DownloadFile500JSONResponse) VisitDownloadFileResponse(w http.ResponseWriter) error { +func (response MovePath500JSONResponse) VisitMovePathResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) @@ -3070,87 +4315,43 @@ func (response ReadFile500JSONResponse) VisitReadFileResponse(w http.ResponseWri return json.NewEncoder(w).Encode(response) } -type WriteFileRequestObject struct { - Params WriteFileParams - Body io.Reader -} - -type WriteFileResponseObject interface { - VisitWriteFileResponse(w http.ResponseWriter) error -} - -type WriteFile201Response struct { -} - -func (response WriteFile201Response) VisitWriteFileResponse(w http.ResponseWriter) error { - w.WriteHeader(201) - return nil -} - -type WriteFile400JSONResponse struct{ BadRequestErrorJSONResponse } - -func (response WriteFile400JSONResponse) VisitWriteFileResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) - - return json.NewEncoder(w).Encode(response) -} - -type WriteFile404JSONResponse struct{ NotFoundErrorJSONResponse } - -func (response WriteFile404JSONResponse) VisitWriteFileResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(404) - - return json.NewEncoder(w).Encode(response) -} - -type WriteFile500JSONResponse struct{ InternalErrorJSONResponse } - -func (response WriteFile500JSONResponse) VisitWriteFileResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) - - return json.NewEncoder(w).Encode(response) -} - -type UploadFilesRequestObject struct { - Body *multipart.Reader +type SetFilePermissionsRequestObject struct { + Body *SetFilePermissionsJSONRequestBody } -type UploadFilesResponseObject interface { - VisitUploadFilesResponse(w http.ResponseWriter) error +type SetFilePermissionsResponseObject interface { + VisitSetFilePermissionsResponse(w http.ResponseWriter) error } -type UploadFiles201Response struct { +type SetFilePermissions200Response struct { } -func (response UploadFiles201Response) VisitUploadFilesResponse(w http.ResponseWriter) error { - w.WriteHeader(201) +func (response SetFilePermissions200Response) VisitSetFilePermissionsResponse(w http.ResponseWriter) error { + w.WriteHeader(200) return nil } -type UploadFiles400JSONResponse struct{ BadRequestErrorJSONResponse } +type SetFilePermissions400JSONResponse struct{ BadRequestErrorJSONResponse } -func (response UploadFiles400JSONResponse) VisitUploadFilesResponse(w http.ResponseWriter) error { +func (response SetFilePermissions400JSONResponse) VisitSetFilePermissionsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type UploadFiles404JSONResponse struct{ NotFoundErrorJSONResponse } +type SetFilePermissions404JSONResponse struct{ NotFoundErrorJSONResponse } -func (response UploadFiles404JSONResponse) VisitUploadFilesResponse(w http.ResponseWriter) error { +func (response SetFilePermissions404JSONResponse) VisitSetFilePermissionsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(404) return json.NewEncoder(w).Encode(response) } -type UploadFiles500JSONResponse struct{ InternalErrorJSONResponse } +type SetFilePermissions500JSONResponse struct{ InternalErrorJSONResponse } -func (response UploadFiles500JSONResponse) VisitUploadFilesResponse(w http.ResponseWriter) error { +func (response SetFilePermissions500JSONResponse) VisitSetFilePermissionsResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) @@ -3307,6 +4508,50 @@ func (response StreamFsEvents500JSONResponse) VisitStreamFsEventsResponse(w http return json.NewEncoder(w).Encode(response) } +type WriteFileRequestObject struct { + Params WriteFileParams + Body io.Reader +} + +type WriteFileResponseObject interface { + VisitWriteFileResponse(w http.ResponseWriter) error +} + +type WriteFile201Response struct { +} + +func (response WriteFile201Response) VisitWriteFileResponse(w http.ResponseWriter) error { + w.WriteHeader(201) + return nil +} + +type WriteFile400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response WriteFile400JSONResponse) VisitWriteFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type WriteFile404JSONResponse struct{ NotFoundErrorJSONResponse } + +func (response WriteFile404JSONResponse) VisitWriteFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type WriteFile500JSONResponse struct{ InternalErrorJSONResponse } + +func (response WriteFile500JSONResponse) VisitWriteFileResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type DeleteRecordingRequestObject struct { Body *DeleteRecordingJSONRequestBody } @@ -3536,18 +4781,30 @@ type StrictServerInterface interface { // Move the mouse cursor to the specified coordinates on the host computer // (POST /computer/move_mouse) MoveMouse(ctx context.Context, request MoveMouseRequestObject) (MoveMouseResponseObject, error) - // Download a file - // (GET /fs/download) - DownloadFile(ctx context.Context, request DownloadFileRequestObject) (DownloadFileResponseObject, error) + // Create a new directory + // (POST /fs/create_directory) + CreateDirectory(ctx context.Context, request CreateDirectoryRequestObject) (CreateDirectoryResponseObject, error) + // Delete a directory + // (POST /fs/delete_directory) + DeleteDirectory(ctx context.Context, request DeleteDirectoryRequestObject) (DeleteDirectoryResponseObject, error) + // Delete a file + // (POST /fs/delete_file) + DeleteFile(ctx context.Context, request DeleteFileRequestObject) (DeleteFileResponseObject, error) + // Get information about a file or directory + // (GET /fs/file_info) + FileInfo(ctx context.Context, request FileInfoRequestObject) (FileInfoResponseObject, error) + // List files in a directory + // (GET /fs/list_files) + ListFiles(ctx context.Context, request ListFilesRequestObject) (ListFilesResponseObject, error) + // Move or rename a file or directory + // (POST /fs/move) + MovePath(ctx context.Context, request MovePathRequestObject) (MovePathResponseObject, error) // Read file contents - // (GET /fs/file) + // (GET /fs/read_file) ReadFile(ctx context.Context, request ReadFileRequestObject) (ReadFileResponseObject, error) - // Write or create a file - // (PUT /fs/file) - WriteFile(ctx context.Context, request WriteFileRequestObject) (WriteFileResponseObject, error) - // Upload one or more files - // (POST /fs/upload) - UploadFiles(ctx context.Context, request UploadFilesRequestObject) (UploadFilesResponseObject, error) + // Set file or directory permissions/ownership + // (POST /fs/set_file_permissions) + SetFilePermissions(ctx context.Context, request SetFilePermissionsRequestObject) (SetFilePermissionsResponseObject, error) // Watch a directory for changes // (POST /fs/watch) StartFsWatch(ctx context.Context, request StartFsWatchRequestObject) (StartFsWatchResponseObject, error) @@ -3557,6 +4814,9 @@ type StrictServerInterface interface { // Stream filesystem events for a watch // (GET /fs/watch/{watch_id}/events) StreamFsEvents(ctx context.Context, request StreamFsEventsRequestObject) (StreamFsEventsResponseObject, error) + // Write or create a file + // (POST /fs/write_file) + WriteFile(ctx context.Context, request WriteFileRequestObject) (WriteFileResponseObject, error) // Delete a previously recorded video file // (POST /recording/delete) DeleteRecording(ctx context.Context, request DeleteRecordingRequestObject) (DeleteRecordingResponseObject, error) @@ -3665,25 +4925,30 @@ func (sh *strictHandler) MoveMouse(w http.ResponseWriter, r *http.Request) { } } -// DownloadFile operation middleware -func (sh *strictHandler) DownloadFile(w http.ResponseWriter, r *http.Request, params DownloadFileParams) { - var request DownloadFileRequestObject +// CreateDirectory operation middleware +func (sh *strictHandler) CreateDirectory(w http.ResponseWriter, r *http.Request) { + var request CreateDirectoryRequestObject - request.Params = params + var body CreateDirectoryJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.DownloadFile(ctx, request.(DownloadFileRequestObject)) + return sh.ssi.CreateDirectory(ctx, request.(CreateDirectoryRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "DownloadFile") + handler = middleware(handler, "CreateDirectory") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(DownloadFileResponseObject); ok { - if err := validResponse.VisitDownloadFileResponse(w); err != nil { + } else if validResponse, ok := response.(CreateDirectoryResponseObject); ok { + if err := validResponse.VisitCreateDirectoryResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -3691,25 +4956,87 @@ func (sh *strictHandler) DownloadFile(w http.ResponseWriter, r *http.Request, pa } } -// ReadFile operation middleware -func (sh *strictHandler) ReadFile(w http.ResponseWriter, r *http.Request, params ReadFileParams) { - var request ReadFileRequestObject +// DeleteDirectory operation middleware +func (sh *strictHandler) DeleteDirectory(w http.ResponseWriter, r *http.Request) { + var request DeleteDirectoryRequestObject + + var body DeleteDirectoryJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.DeleteDirectory(ctx, request.(DeleteDirectoryRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteDirectory") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(DeleteDirectoryResponseObject); ok { + if err := validResponse.VisitDeleteDirectoryResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// DeleteFile operation middleware +func (sh *strictHandler) DeleteFile(w http.ResponseWriter, r *http.Request) { + var request DeleteFileRequestObject + + var body DeleteFileJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.DeleteFile(ctx, request.(DeleteFileRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteFile") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(DeleteFileResponseObject); ok { + if err := validResponse.VisitDeleteFileResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// FileInfo operation middleware +func (sh *strictHandler) FileInfo(w http.ResponseWriter, r *http.Request, params FileInfoParams) { + var request FileInfoRequestObject request.Params = params handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.ReadFile(ctx, request.(ReadFileRequestObject)) + return sh.ssi.FileInfo(ctx, request.(FileInfoRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "ReadFile") + handler = middleware(handler, "FileInfo") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(ReadFileResponseObject); ok { - if err := validResponse.VisitReadFileResponse(w); err != nil { + } else if validResponse, ok := response.(FileInfoResponseObject); ok { + if err := validResponse.VisitFileInfoResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -3717,27 +5044,82 @@ func (sh *strictHandler) ReadFile(w http.ResponseWriter, r *http.Request, params } } -// WriteFile operation middleware -func (sh *strictHandler) WriteFile(w http.ResponseWriter, r *http.Request, params WriteFileParams) { - var request WriteFileRequestObject +// ListFiles operation middleware +func (sh *strictHandler) ListFiles(w http.ResponseWriter, r *http.Request, params ListFilesParams) { + var request ListFilesRequestObject request.Params = params - request.Body = r.Body + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ListFiles(ctx, request.(ListFilesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ListFiles") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ListFilesResponseObject); ok { + if err := validResponse.VisitListFilesResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// MovePath operation middleware +func (sh *strictHandler) MovePath(w http.ResponseWriter, r *http.Request) { + var request MovePathRequestObject + + var body MovePathJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.WriteFile(ctx, request.(WriteFileRequestObject)) + return sh.ssi.MovePath(ctx, request.(MovePathRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "WriteFile") + handler = middleware(handler, "MovePath") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(WriteFileResponseObject); ok { - if err := validResponse.VisitWriteFileResponse(w); err != nil { + } else if validResponse, ok := response.(MovePathResponseObject); ok { + if err := validResponse.VisitMovePathResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ReadFile operation middleware +func (sh *strictHandler) ReadFile(w http.ResponseWriter, r *http.Request, params ReadFileParams) { + var request ReadFileRequestObject + + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ReadFile(ctx, request.(ReadFileRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ReadFile") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ReadFileResponseObject); ok { + if err := validResponse.VisitReadFileResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -3745,30 +5127,30 @@ func (sh *strictHandler) WriteFile(w http.ResponseWriter, r *http.Request, param } } -// UploadFiles operation middleware -func (sh *strictHandler) UploadFiles(w http.ResponseWriter, r *http.Request) { - var request UploadFilesRequestObject +// SetFilePermissions operation middleware +func (sh *strictHandler) SetFilePermissions(w http.ResponseWriter, r *http.Request) { + var request SetFilePermissionsRequestObject - if reader, err := r.MultipartReader(); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err)) + var body SetFilePermissionsJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) return - } else { - request.Body = reader } + request.Body = &body handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.UploadFiles(ctx, request.(UploadFilesRequestObject)) + return sh.ssi.SetFilePermissions(ctx, request.(SetFilePermissionsRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "UploadFiles") + handler = middleware(handler, "SetFilePermissions") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(UploadFilesResponseObject); ok { - if err := validResponse.VisitUploadFilesResponse(w); err != nil { + } else if validResponse, ok := response.(SetFilePermissionsResponseObject); ok { + if err := validResponse.VisitSetFilePermissionsResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -3859,6 +5241,34 @@ func (sh *strictHandler) StreamFsEvents(w http.ResponseWriter, r *http.Request, } } +// WriteFile operation middleware +func (sh *strictHandler) WriteFile(w http.ResponseWriter, r *http.Request, params WriteFileParams) { + var request WriteFileRequestObject + + request.Params = params + + request.Body = r.Body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.WriteFile(ctx, request.(WriteFileRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "WriteFile") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(WriteFileResponseObject); ok { + if err := validResponse.VisitWriteFileResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // DeleteRecording operation middleware func (sh *strictHandler) DeleteRecording(w http.ResponseWriter, r *http.Request) { var request DeleteRecordingRequestObject @@ -4005,45 +5415,54 @@ func (sh *strictHandler) StopRecording(w http.ResponseWriter, r *http.Request) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "qiqzv3ZYR2VVJ/y4g/wvAAD//3HvbdENLQAA", + "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", + "LE/fSD4AAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 5791a968..5c1ca64b 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -155,7 +155,7 @@ paths: "500": $ref: "#/components/responses/InternalError" # File system operations - /fs/file: + /fs/read_file: get: summary: Read file contents operationId: readFile @@ -165,7 +165,8 @@ paths: required: true schema: type: string - description: Absolute or relative file path to read. + pattern: "^/.*" + description: Absolute file path to read. responses: "200": description: File read successfully @@ -180,7 +181,9 @@ paths: $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalError" - put: + + /fs/write_file: + post: summary: Write or create a file operationId: writeFile parameters: @@ -189,7 +192,15 @@ paths: required: true schema: type: string - description: Destination file path. + pattern: "^/.*" + description: Destination absolute file path. + - name: mode + in: query + required: false + schema: + type: string + pattern: "^[0-7]{3,4}$" + description: Optional file mode (octal string, e.g. 644). Defaults to 644. requestBody: required: true content: @@ -207,32 +218,83 @@ paths: "500": $ref: "#/components/responses/InternalError" - /fs/upload: + /fs/list_files: + get: + summary: List files in a directory + operationId: listFiles + parameters: + - name: path + in: query + required: true + schema: + type: string + pattern: "^/.*" + description: Absolute directory path. + responses: + "200": + description: Directory listing + content: + application/json: + schema: + $ref: "#/components/schemas/ListFiles" + "400": + $ref: "#/components/responses/BadRequestError" + "404": + $ref: "#/components/responses/NotFoundError" + "500": + $ref: "#/components/responses/InternalError" + + /fs/create_directory: post: - summary: Upload one or more files - operationId: uploadFiles + summary: Create a new directory + operationId: createDirectory requestBody: required: true content: - multipart/form-data: + application/json: schema: - type: object - properties: - files: - type: array - items: - type: object - properties: - file: - type: string - format: binary - dest_path: - type: string - required: [file, dest_path] - required: [files] + $ref: "#/components/schemas/CreateDirectoryRequest" responses: "201": - description: Files uploaded successfully + description: Directory created successfully + "400": + $ref: "#/components/responses/BadRequestError" + "500": + $ref: "#/components/responses/InternalError" + + /fs/delete_file: + post: + summary: Delete a file + operationId: deleteFile + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DeletePathRequest" + responses: + "200": + description: File deleted + "400": + $ref: "#/components/responses/BadRequestError" + "404": + $ref: "#/components/responses/NotFoundError" + "500": + $ref: "#/components/responses/InternalError" + + /fs/delete_directory: + post: + summary: Delete a directory + operationId: deleteDirectory + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DeletePathRequest" + responses: + "200": + description: Directory deleted "400": $ref: "#/components/responses/BadRequestError" "404": @@ -240,24 +302,65 @@ paths: "500": $ref: "#/components/responses/InternalError" - /fs/download: + /fs/set_file_permissions: + post: + summary: Set file or directory permissions/ownership + operationId: setFilePermissions + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SetFilePermissionsRequest" + responses: + "200": + description: Permissions updated + "400": + $ref: "#/components/responses/BadRequestError" + "404": + $ref: "#/components/responses/NotFoundError" + "500": + $ref: "#/components/responses/InternalError" + + /fs/file_info: get: - summary: Download a file - operationId: downloadFile + summary: Get information about a file or directory + operationId: fileInfo parameters: - name: path in: query required: true schema: type: string + pattern: "^/.*" + description: Absolute path of the file or directory. responses: "200": - description: File downloaded successfully + description: File information content: - application/octet-stream: + application/json: schema: - type: string - format: binary + $ref: "#/components/schemas/FileInfo" + "400": + $ref: "#/components/responses/BadRequestError" + "404": + $ref: "#/components/responses/NotFoundError" + "500": + $ref: "#/components/responses/InternalError" + + /fs/move: + post: + summary: Move or rename a file or directory + operationId: movePath + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/MovePathRequest" + responses: + "200": + description: Move successful "400": $ref: "#/components/responses/BadRequestError" "404": @@ -489,6 +592,93 @@ components: description: Identifier of the recording to delete. Alphanumeric or hyphen. pattern: "^[a-zA-Z0-9-]+$" additionalProperties: false + FileInfo: + type: object + required: [name, path, size_bytes, is_dir, mod_time, mode] + properties: + name: + type: string + description: Base name of the file or directory. + path: + type: string + description: Absolute path. + size_bytes: + type: integer + description: Size in bytes. 0 for directories. + is_dir: + type: boolean + description: Whether the path is a directory. + mod_time: + type: string + format: date-time + description: Last modification time. + mode: + type: string + description: File mode bits (e.g., "drwxr-xr-x" or "-rw-r--r--"). + + ListFiles: + type: array + description: Array of file or directory information entries. + items: + $ref: "#/components/schemas/FileInfo" + + CreateDirectoryRequest: + type: object + required: [path] + properties: + path: + type: string + description: Absolute directory path to create. + pattern: "^/.*" + mode: + type: string + description: Optional directory mode (octal string, e.g. 755). Defaults to 755. + pattern: "^[0-7]{3,4}$" + additionalProperties: false + + DeletePathRequest: + type: object + required: [path] + properties: + path: + type: string + description: Absolute path to delete. + pattern: "^/.*" + additionalProperties: false + + SetFilePermissionsRequest: + type: object + required: [path, mode] + properties: + path: + type: string + description: Absolute path whose permissions are to be changed. + pattern: "^/.*" + owner: + type: string + description: New owner username or UID. + group: + type: string + description: New group name or GID. + mode: + type: string + description: File mode bits (octal string, e.g. 644). + pattern: "^[0-7]{3,4}$" + additionalProperties: false + + MovePathRequest: + type: object + required: [src_path, dest_path] + properties: + src_path: + type: string + description: Absolute source path. + pattern: "^/.*" + dest_path: + type: string + description: Absolute destination path. + pattern: "^/.*" + additionalProperties: false responses: BadRequestError: description: Bad Request From 3e191acfaed34254db38e2827ea0ffee390bbd08 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 3 Aug 2025 20:34:48 +0000 Subject: [PATCH 04/20] Fix fs watch goroutine leaks and improve error handling Co-authored-by: rgarcia2009 --- server/cmd/api/api/fs.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index 48c9e8a5..3776effe 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -346,8 +346,13 @@ func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequ // goroutine to forward events go func() { + defer close(w.events) // ensure channel is closed exactly once when goroutine exits for { select { + case <-ctx.Done(): + // Context cancelled – stop watching to avoid leaks + watcher.Close() + return case ev, ok := <-watcher.Events: if !ok { return @@ -372,7 +377,9 @@ func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequ // if recursive and new directory created, add watch if recursive && evType == "CREATE" && isDir { - watcher.Add(ev.Name) + if err := watcher.Add(ev.Name); err != nil { + log.Error("failed to watch new directory", "err", err, "path", ev.Name) + } } case err, ok := <-watcher.Errors: if !ok { @@ -398,7 +405,7 @@ func (s *ApiService) StopFsWatch(ctx context.Context, req oapi.StopFsWatchReques if ok { delete(s.watches, id) w.watcher.Close() - close(w.events) + // channel will be closed by the event forwarding goroutine } s.watchMu.Unlock() From 8c3bed1e9f5831ab43d71665a4fc4be013197cb1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 3 Aug 2025 20:41:31 +0000 Subject: [PATCH 05/20] Improve fs watch goroutine lifecycle and resource management Co-authored-by: rgarcia2009 --- server/cmd/api/api/fs.go | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index 3776effe..b4defda9 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -344,15 +344,28 @@ func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequ watcher: watcher, } - // goroutine to forward events - go func() { - defer close(w.events) // ensure channel is closed exactly once when goroutine exits + // Start background goroutine to forward events. We intentionally decouple + // its lifetime from the HTTP request context so that the watch continues + // to run until it is explicitly stopped via StopFsWatch or until watcher + // channels are closed. + go func(s *ApiService, id string) { + // Ensure resources are cleaned up no matter how the goroutine exits. + defer func() { + // Best-effort close (idempotent). + watcher.Close() + + // Remove stale entry to avoid map/chan leak if the watch stops on + // its own (e.g. underlying fs error, watcher overflow, etc.). It + // is safe to call delete even if StopFsWatch already removed it. + s.watchMu.Lock() + delete(s.watches, id) + s.watchMu.Unlock() + + close(w.events) // close after map cleanup so readers can finish + }() + for { select { - case <-ctx.Done(): - // Context cancelled – stop watching to avoid leaks - watcher.Close() - return case ev, ok := <-watcher.Events: if !ok { return @@ -375,7 +388,7 @@ func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequ name := filepath.Base(ev.Name) w.events <- oapi.FileSystemEvent{Type: evType, Path: ev.Name, Name: &name, IsDir: &isDir} - // if recursive and new directory created, add watch + // If recursive and new directory created, add watch. if recursive && evType == "CREATE" && isDir { if err := watcher.Add(ev.Name); err != nil { log.Error("failed to watch new directory", "err", err, "path", ev.Name) @@ -388,7 +401,7 @@ func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequ log.Error("fsnotify error", "err", err) } } - }() + }(s, watchID) s.watchMu.Lock() s.watches[watchID] = w From 0953976b90092eeacb3f984cfd768d29150f54a8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 3 Aug 2025 21:00:56 +0000 Subject: [PATCH 06/20] Support custom file permissions when writing files via API Co-authored-by: rgarcia2009 --- server/cmd/api/api/fs.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index b4defda9..a439173a 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -86,7 +86,16 @@ func (s *ApiService) WriteFile(ctx context.Context, req oapi.WriteFileRequestObj return oapi.WriteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to create directories"}}, nil } - f, err := os.Create(path) + // determine desired file mode (default 0o644) + perm := os.FileMode(0o644) + if req.Params.Mode != nil { + if v, err := strconv.ParseUint(*req.Params.Mode, 8, 32); err == nil { + perm = os.FileMode(v) + } + } + + // open the file with the specified permissions + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm) if err != nil { log.Error("failed to create file", "err", err, "path", path) return oapi.WriteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to create file"}}, nil From e895649546d8af3380d1d2626ae40c78469aa518 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 3 Aug 2025 21:01:41 +0000 Subject: [PATCH 07/20] Fix file listing to handle stat errors and correctly set file metadata Co-authored-by: rgarcia2009 --- server/cmd/api/api/fs.go | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index a439173a..f8d76e4f 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -191,20 +191,30 @@ func (s *ApiService) ListFiles(ctx context.Context, req oapi.ListFilesRequestObj } var list oapi.ListFiles for _, entry := range entries { - info, _ := entry.Info() + // Retrieve FileInfo for each entry. If this fails (e.g. broken symlink, permission + // error) we surface the failure to the client instead of silently ignoring it so + // that consumers do not unknowingly operate on incomplete or unreliable metadata. + info, err := entry.Info() + if err != nil { + log.Error("failed to stat directory entry", "err", err, "dir", path, "entry", entry.Name()) + return oapi.ListFiles500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to stat directory entry"}}, nil + } + + // By specification SizeBytes should be 0 for directories. + size := 0 + if !info.IsDir() { + size = int(info.Size()) + } + fi := oapi.FileInfo{ Name: entry.Name(), Path: filepath.Join(path, entry.Name()), IsDir: entry.IsDir(), - SizeBytes: 0, - ModTime: time.Time{}, - Mode: "", - } - if info != nil { - fi.SizeBytes = int(info.Size()) - fi.ModTime = info.ModTime() - fi.Mode = info.Mode().String() + SizeBytes: size, + ModTime: info.ModTime(), + Mode: info.Mode().String(), } + list = append(list, fi) } return oapi.ListFiles200JSONResponse(list), nil From f7313f6ce9760b9bb547cbef3fc90a9739de20f0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 3 Aug 2025 21:17:11 +0000 Subject: [PATCH 08/20] Fix race condition and prevent blocking in filesystem watch events Co-authored-by: rgarcia2009 --- server/cmd/api/api/fs.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index f8d76e4f..70df2d8d 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -363,6 +363,12 @@ func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequ watcher: watcher, } + // Register the watch before starting the forwarding goroutine to avoid a + // race where the goroutine might exit before it is added to the map. + s.watchMu.Lock() + s.watches[watchID] = w + s.watchMu.Unlock() + // Start background goroutine to forward events. We intentionally decouple // its lifetime from the HTTP request context so that the watch continues // to run until it is explicitly stopped via StopFsWatch or until watcher @@ -405,7 +411,13 @@ func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequ info, _ := os.Stat(ev.Name) isDir := info != nil && info.IsDir() name := filepath.Base(ev.Name) - w.events <- oapi.FileSystemEvent{Type: evType, Path: ev.Name, Name: &name, IsDir: &isDir} + // Attempt a non-blocking send so that event production never blocks + // even if the consumer is slow or absent. When the buffer is full we + // simply drop the event, preferring liveness over completeness. + select { + case w.events <- oapi.FileSystemEvent{Type: evType, Path: ev.Name, Name: &name, IsDir: &isDir}: + default: + } // If recursive and new directory created, add watch. if recursive && evType == "CREATE" && isDir { @@ -422,10 +434,6 @@ func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequ } }(s, watchID) - s.watchMu.Lock() - s.watches[watchID] = w - s.watchMu.Unlock() - return oapi.StartFsWatch201JSONResponse{WatchId: &watchID}, nil } From f02ae7616dce1c93d669821af5766b5541a04302 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Mon, 4 Aug 2025 07:29:49 -0400 Subject: [PATCH 09/20] vet --- server/cmd/api/api/fs.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index 70df2d8d..bad2561f 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" "strconv" - "time" "os/user" From 5550c86d484f7459bbf1ec70b12b1db65556e7c0 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Mon, 4 Aug 2025 17:01:10 -0700 Subject: [PATCH 10/20] fix sse --- server/Makefile | 3 + server/lib/oapi/oapi.go | 26 +++++++- server/scripts/oapi/patch_sse_methods.go | 83 ++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 server/scripts/oapi/patch_sse_methods.go diff --git a/server/Makefile b/server/Makefile index f4fc5d8f..a45d3203 100644 --- a/server/Makefile +++ b/server/Makefile @@ -22,6 +22,9 @@ oapi-generate: $(OAPI_CODEGEN) pnpm i -g @apiture/openapi-down-convert openapi-down-convert --input openapi.yaml --output openapi-3.0.yaml $(OAPI_CODEGEN) -config ./oapi-codegen.yaml ./openapi-3.0.yaml + @echo "Fixing oapi-codegen issue https://github.com/oapi-codegen/oapi-codegen/issues/1764..." + go run ./scripts/oapi/patch_sse_methods.go -file ./lib/oapi/oapi.go -expected-replacements 1 + go fmt ./lib/oapi/oapi.go go mod tidy build: | $(BIN_DIR) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 5c183a59..52fba647 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -4477,8 +4477,30 @@ func (response StreamFsEvents200TexteventStreamResponse) VisitStreamFsEventsResp if closer, ok := response.Body.(io.ReadCloser); ok { defer closer.Close() } - _, err := io.Copy(w, response.Body) - return err + flusher, ok := w.(http.Flusher) + if !ok { + // If w doesn't support flushing, might as well use io.Copy + _, err := io.Copy(w, response.Body) + return err + } + + // Use a buffer for efficient copying and flushing + buf := make([]byte, 4096) // text/event-stream are usually very small messages + for { + n, err := response.Body.Read(buf) + if n > 0 { + if _, werr := w.Write(buf[:n]); werr != nil { + return werr + } + flusher.Flush() // Flush after each write + } + if err != nil { + if err == io.EOF { + return nil // End of file, no error + } + return err + } + } } type StreamFsEvents400JSONResponse struct{ BadRequestErrorJSONResponse } diff --git a/server/scripts/oapi/patch_sse_methods.go b/server/scripts/oapi/patch_sse_methods.go new file mode 100644 index 00000000..50db1447 --- /dev/null +++ b/server/scripts/oapi/patch_sse_methods.go @@ -0,0 +1,83 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "log" + "os" +) + +const oldBlock = ` + w.Header().Set("X-SSE-Content-Type", fmt.Sprint(response.Headers.XSSEContentType)) + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err` + +const newBlock = ` + w.Header().Set("X-SSE-Content-Type", fmt.Sprint(response.Headers.XSSEContentType)) + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + flusher, ok := w.(http.Flusher) + if !ok { + // If w doesn't support flushing, might as well use io.Copy + _, err := io.Copy(w, response.Body) + return err + } + + // Use a buffer for efficient copying and flushing + buf := make([]byte, 4096) // text/event-stream are usually very small messages + for { + n, err := response.Body.Read(buf) + if n > 0 { + if _, werr := w.Write(buf[:n]); werr != nil { + return werr + } + flusher.Flush() // Flush after each write + } + if err != nil { + if err == io.EOF { + return nil // End of file, no error + } + return err + } + }` + +func main() { + filePath := flag.String("file", "", "path to oapi.go to rewrite") + expectedReplacements := flag.Int("expected-replacements", 0, "expected number of SSE code blocks to replace") + flag.Parse() + + if *filePath == "" || *expectedReplacements <= 0 { + log.Fatal("usage: -file= -expected-replacements=") + } + path := *filePath + + data, err := os.ReadFile(path) + if err != nil { + log.Fatalf("fix_sse: failed to read %s: %v", path, err) + } + + // count occurrences of the old block before replacement + occurrences := bytes.Count(data, []byte(oldBlock)) + if occurrences != *expectedReplacements { + log.Fatalf("expected exactly %d SSE code blocks to replace, found %d in %s", *expectedReplacements, occurrences, path) + } + + updated := bytes.ReplaceAll(data, []byte(oldBlock), []byte(newBlock)) + if bytes.Equal(data, updated) { + log.Fatalf("no changes made to %s - expected to find and replace SSE code blocks", path) + } + + if err := os.WriteFile(path, updated, 0644); err != nil { + log.Fatalf("failed to write %s: %v", path, err) + } + fmt.Printf("✓ SSE flush fix applied to %s\n", path) +} From 808f925b105b369c07bc6bb1b5a39461d42849af Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 5 Aug 2025 00:51:32 +0000 Subject: [PATCH 11/20] Fix user and group lookup error handling in SetFilePermissions Co-authored-by: rgarcia2009 --- server/cmd/api/api/fs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index bad2561f..ad47c6a1 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -294,14 +294,14 @@ func (s *ApiService) SetFilePermissions(ctx context.Context, req oapi.SetFilePer gid := -1 if req.Body.Owner != nil { if u, err := user.Lookup(*req.Body.Owner); err == nil { - if id, _ := strconv.Atoi(u.Uid); id >= 0 { + if id, err := strconv.Atoi(u.Uid); err == nil && id >= 0 { uid = id } } } if req.Body.Group != nil { if g, err := user.LookupGroup(*req.Body.Group); err == nil { - if id, _ := strconv.Atoi(g.Gid); id >= 0 { + if id, err := strconv.Atoi(g.Gid); err == nil && id >= 0 { gid = id } } From 53b054a9c8565103be63ec823d00eb5d3454c29a Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Wed, 6 Aug 2025 13:51:37 -0400 Subject: [PATCH 12/20] add server to headless image (so we have file i/o there too) --- images/chromium-headful/build-docker.sh | 3 +- images/chromium-headful/build-unikernel.sh | 7 +- images/chromium-headful/common.sh | 17 ---- images/chromium-headful/run-docker.sh | 4 +- images/chromium-headful/run-unikernel.sh | 14 ++- images/chromium-headless/.gitignore | 1 + images/chromium-headless/README.md | 2 - images/chromium-headless/build-docker.sh | 15 ++- images/chromium-headless/build-unikernel.sh | 9 +- images/chromium-headless/common.sh | 14 --- images/chromium-headless/image/Dockerfile | 14 +++ images/chromium-headless/image/wrapper.sh | 27 +++++ images/chromium-headless/run-docker.sh | 30 +++++- images/chromium-headless/run-unikernel.sh | 30 ++++-- shared/cdp-test/main.py | 104 +++++++++++++++----- shared/ensure-common-build-run-vars.sh | 27 +++++ 16 files changed, 231 insertions(+), 87 deletions(-) delete mode 100755 images/chromium-headful/common.sh delete mode 100755 images/chromium-headless/common.sh create mode 100644 shared/ensure-common-build-run-vars.sh diff --git a/images/chromium-headful/build-docker.sh b/images/chromium-headful/build-docker.sh index ff92a6f8..5f1db257 100755 --- a/images/chromium-headful/build-docker.sh +++ b/images/chromium-headful/build-docker.sh @@ -4,8 +4,7 @@ set -e -o pipefail # Move to the script's directory so relative paths work regardless of the caller CWD SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) cd "$SCRIPT_DIR" - -IMAGE="${IMAGE:-onkernel/kernel-cu-test:latest}" +source ../../shared/ensure-common-build-run-vars.sh chromium-headful source ../../shared/start-buildkit.sh diff --git a/images/chromium-headful/build-unikernel.sh b/images/chromium-headful/build-unikernel.sh index ce7d26df..0b16bf99 100755 --- a/images/chromium-headful/build-unikernel.sh +++ b/images/chromium-headful/build-unikernel.sh @@ -1,7 +1,10 @@ #!/usr/bin/env bash -source common.sh -source ../../shared/erofs-utils.sh +# Move to the script's directory so relative paths work regardless of the caller CWD +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +cd "$SCRIPT_DIR" +source "$SCRIPT_DIR/../../shared/ensure-common-build-run-vars.sh" chromium-headful +source "$SCRIPT_DIR/../../shared/erofs-utils.sh" # Ensure the mkfs.erofs tool is available if ! check_mkfs_erofs; then diff --git a/images/chromium-headful/common.sh b/images/chromium-headful/common.sh deleted file mode 100755 index 5d2f1715..00000000 --- a/images/chromium-headful/common.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -set -e -o pipefail - -# Default UKC_INDEX to index.unikraft.io if not set -UKC_INDEX="${UKC_INDEX:-index.unikraft.io}" - -# fail if IMAGE, UKC_TOKEN, UKC_METRO are not set -errormsg="" -for var in IMAGE UKC_TOKEN UKC_METRO; do - if [ -z "${!var}" ]; then - errormsg+="$var " - fi -done -if [ -n "$errormsg" ]; then - echo "Required variables not set: $errormsg" - exit 1 -fi diff --git a/images/chromium-headful/run-docker.sh b/images/chromium-headful/run-docker.sh index 2a46b90e..d6ef88bd 100755 --- a/images/chromium-headful/run-docker.sh +++ b/images/chromium-headful/run-docker.sh @@ -4,9 +4,7 @@ set -ex -o pipefail # Move to the script's directory so relative paths work regardless of the caller CWD SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) cd "$SCRIPT_DIR" - -IMAGE="${IMAGE:-onkernel/kernel-cu-test:latest}" -NAME="${NAME:-kernel-cu-test}" +source ../../shared/ensure-common-build-run-vars.sh chromium-headful # Directory on host where recordings will be saved HOST_RECORDINGS_DIR="$SCRIPT_DIR/recordings" diff --git a/images/chromium-headful/run-unikernel.sh b/images/chromium-headful/run-unikernel.sh index a16e7c88..5911d653 100755 --- a/images/chromium-headful/run-unikernel.sh +++ b/images/chromium-headful/run-unikernel.sh @@ -1,11 +1,15 @@ #!/usr/bin/env bash +set -euo pipefail -source common.sh -name=chromium-headful-test -kraft cloud inst rm $name || true +# Move to the script's directory so relative paths work regardless of the caller CWD +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +cd "$SCRIPT_DIR" +source ../../shared/ensure-common-build-run-vars.sh chromium-headful + +kraft cloud inst rm $NAME || true # Name for the Kraft Cloud volume that will carry Chromium flags -volume_name="${name}-flags" +volume_name="${NAME}-flags" # ------------------------------------------------------------------------------ # Prepare Kraft Cloud volume containing Chromium flags @@ -45,7 +49,7 @@ deploy_args=( -e HOME=/ -e RUN_AS_ROOT="$RUN_AS_ROOT" \ -v "$volume_name":/chromium - -n "$name" + -n "$NAME" ) if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then diff --git a/images/chromium-headless/.gitignore b/images/chromium-headless/.gitignore index 4f1859e9..face8273 100644 --- a/images/chromium-headless/.gitignore +++ b/images/chromium-headless/.gitignore @@ -1 +1,2 @@ image/.rootfs +image/bin diff --git a/images/chromium-headless/README.md b/images/chromium-headless/README.md index c3523c8f..b627145a 100644 --- a/images/chromium-headless/README.md +++ b/images/chromium-headless/README.md @@ -5,7 +5,6 @@ 1. Build and run the image, tagging it with a name you'd like to use: ```bash -export IMAGE=onkernel/chromium-headless ./build-docker.sh ./run-docker.sh ``` @@ -25,7 +24,6 @@ uv run python main.py http://localhost:9222 1. Build and run the image, tagging it with a name you'd like to use: ```bash -export IMAGE=onkernel/chromium-headless export UKC_TOKEN= export UKC_METRO= # latest UKC also allows pushing to metro-specific indexes diff --git a/images/chromium-headless/build-docker.sh b/images/chromium-headless/build-docker.sh index e1112801..6e45ce1a 100755 --- a/images/chromium-headless/build-docker.sh +++ b/images/chromium-headless/build-docker.sh @@ -1,4 +1,15 @@ #!/usr/bin/env bash +set -e -o pipefail -source common.sh -(cd image && docker build -t $IMAGE .) +# Move to the script's directory so relative paths work regardless of the caller CWD +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +cd "$SCRIPT_DIR" +source ../../shared/ensure-common-build-run-vars.sh chromium-headless + +source ../../shared/start-buildkit.sh + +# Build the kernel-images API binary and place it into the Docker build context +source ../../shared/build-server.sh "$SCRIPT_DIR/image/bin" + +# Build (and optionally push) the Docker image +(cd image && docker build -t "$IMAGE" .) diff --git a/images/chromium-headless/build-unikernel.sh b/images/chromium-headless/build-unikernel.sh index ab7bf64f..a54c930c 100755 --- a/images/chromium-headless/build-unikernel.sh +++ b/images/chromium-headless/build-unikernel.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash -source common.sh +# Move to the script's directory so relative paths work regardless of the caller CWD +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +cd "$SCRIPT_DIR" +source ../../shared/ensure-common-build-run-vars.sh chromium-headless source ../../shared/erofs-utils.sh # Ensure the mkfs.erofs tool is present @@ -14,8 +17,10 @@ set -euo pipefail cd image/ # Build the root file system -source ../../shared/start-buildkit.sh +source ../../../shared/start-buildkit.sh rm -rf ./.rootfs || true +# Build the API binary +source ../../../shared/build-server.sh "$(pwd)/bin" app_name=chromium-headless-build docker build --platform linux/amd64 -t "$IMAGE" . docker rm cnt-"$app_name" || true diff --git a/images/chromium-headless/common.sh b/images/chromium-headless/common.sh deleted file mode 100755 index e155b7a4..00000000 --- a/images/chromium-headless/common.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -e -o pipefail - -# fail if IMAGE, UKC_TOKEN, UKC_METRO, UKC_INDEX are not set -errormsg="" -for var in IMAGE UKC_TOKEN UKC_METRO UKC_INDEX; do - if [ -z "${!var}" ]; then - errormsg+="$var " - fi -done -if [ -n "$errormsg" ]; then - echo "Required variables not set: $errormsg" - exit 1 -fi diff --git a/images/chromium-headless/image/Dockerfile b/images/chromium-headless/image/Dockerfile index 23340eb1..86a41f0c 100644 --- a/images/chromium-headless/image/Dockerfile +++ b/images/chromium-headless/image/Dockerfile @@ -34,6 +34,16 @@ RUN set -xe; \ RUN add-apt-repository -y ppa:xtradeb/apps RUN apt update -y && apt install -y chromium ncat +# Install FFmpeg (latest static build) for the recording server +RUN set -eux; \ + URL="https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz"; \ + echo "Downloading FFmpeg static build from $URL"; \ + curl -fsSL "$URL" -o /tmp/ffmpeg.tar.xz; \ + tar -xJf /tmp/ffmpeg.tar.xz -C /tmp; \ + install -m755 /tmp/ffmpeg-*/ffmpeg /usr/local/bin/ffmpeg; \ + install -m755 /tmp/ffmpeg-*/ffprobe /usr/local/bin/ffprobe; \ + rm -rf /tmp/ffmpeg* + # Remove upower to prevent spurious D-Bus activations and logs RUN apt-get -yqq purge upower || true && rm -rf /var/lib/apt/lists/* @@ -44,3 +54,7 @@ COPY ./xvfb_startup.sh /usr/bin/xvfb_startup.sh # Wrapper script set environment COPY ./wrapper.sh /usr/bin/wrapper.sh + +# Copy the kernel-images API binary built during the build process +COPY bin/kernel-images-api /usr/local/bin/kernel-images-api +ENV WITH_KERNEL_IMAGES_API=false diff --git a/images/chromium-headless/image/wrapper.sh b/images/chromium-headless/image/wrapper.sh index a2033a29..51baec59 100755 --- a/images/chromium-headless/image/wrapper.sh +++ b/images/chromium-headless/image/wrapper.sh @@ -108,6 +108,9 @@ cleanup () { echo "Cleaning up..." kill -TERM $pid 2>/dev/null || true kill -TERM $pid2 2>/dev/null || true + if [[ -n "${pid3:-}" ]]; then + kill -TERM $pid3 2>/dev/null || true + fi if [ -n "${dbus_pid:-}" ]; then kill -TERM $dbus_pid 2>/dev/null || true fi @@ -115,6 +118,7 @@ cleanup () { trap cleanup TERM INT pid= pid2= +pid3= INTERNAL_PORT=9223 CHROME_PORT=9222 # External port mapped to host echo "Starting Chromium on internal port $INTERNAL_PORT" @@ -138,11 +142,34 @@ ncat \ -l "$CHROME_PORT" \ --keep-open & pid2=$! +# Optionally start the kernel-images API server file i/o +if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then + echo "✨ Starting kernel-images API." + API_PORT="${KERNEL_IMAGES_API_PORT:-10001}" + API_FRAME_RATE="${KERNEL_IMAGES_API_FRAME_RATE:-10}" + API_DISPLAY_NUM="${KERNEL_IMAGES_API_DISPLAY_NUM:-${DISPLAY_NUM:-1}}" + API_MAX_SIZE_MB="${KERNEL_IMAGES_API_MAX_SIZE_MB:-500}" + API_OUTPUT_DIR="${KERNEL_IMAGES_API_OUTPUT_DIR:-/recordings}" + + mkdir -p "$API_OUTPUT_DIR" + + PORT="$API_PORT" \ + FRAME_RATE="$API_FRAME_RATE" \ + DISPLAY_NUM="$API_DISPLAY_NUM" \ + MAX_SIZE_MB="$API_MAX_SIZE_MB" \ + OUTPUT_DIR="$API_OUTPUT_DIR" \ + /usr/local/bin/kernel-images-api & pid3=$! +fi + # Wait for Chromium to exit; propagate its exit code wait "$pid" exit_code=$? echo "Chromium exited with code $exit_code" # Ensure ncat proxy is terminated kill -TERM "$pid2" 2>/dev/null || true +# Ensure kernel-images API server is terminated +if [[ -n "${pid3:-}" ]]; then + kill -TERM "$pid3" 2>/dev/null || true +fi exit "$exit_code" diff --git a/images/chromium-headless/run-docker.sh b/images/chromium-headless/run-docker.sh index eb79adfc..2ef34f53 100755 --- a/images/chromium-headless/run-docker.sh +++ b/images/chromium-headless/run-docker.sh @@ -1,8 +1,28 @@ #!/usr/bin/env bash +set -ex -o pipefail -source common.sh +# Move to the script's directory so relative paths work regardless of the caller CWD +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +cd "$SCRIPT_DIR" +source ../../shared/ensure-common-build-run-vars.sh chromium-headless -docker run -it --rm \ - -p 9222:9222 \ - -e WITH_DOCKER=true \ - $IMAGE /usr/bin/wrapper.sh +# Directory on host where recordings will be saved when the API is enabled +HOST_RECORDINGS_DIR="$SCRIPT_DIR/recordings" +mkdir -p "$HOST_RECORDINGS_DIR" + +RUN_ARGS=( + --name "$NAME" + --privileged + --tmpfs /dev/shm:size=2g + -p 9222:9222 + -e WITH_DOCKER=true +) + +if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then + RUN_ARGS+=( -p 10001:10001 ) + RUN_ARGS+=( -e WITH_KERNEL_IMAGES_API=true ) + RUN_ARGS+=( -v "$HOST_RECORDINGS_DIR:/recordings" ) +fi + +docker rm -f "$NAME" 2>/dev/null || true +docker run -it --rm "${RUN_ARGS[@]}" "$IMAGE" /usr/bin/wrapper.sh diff --git a/images/chromium-headless/run-unikernel.sh b/images/chromium-headless/run-unikernel.sh index 913bc0ab..9d5d6282 100755 --- a/images/chromium-headless/run-unikernel.sh +++ b/images/chromium-headless/run-unikernel.sh @@ -1,14 +1,24 @@ #!/usr/bin/env bash +set -euo pipefail -source common.sh -name="chromium-headless-test" +# Move to the script's directory so relative paths work regardless of the caller CWD +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +cd "$SCRIPT_DIR" +source ../../shared/ensure-common-build-run-vars.sh chromium-headless -kraft cloud inst rm "$name" || true +kraft cloud inst rm "$NAME" || true -kraft cloud inst create \ - --start \ - -M 1G \ - -p 9222:9222/tls \ - --vcpus 1 \ - -n "$name" \ - $IMAGE +deploy_args=( + --start + -M 1G + -p 9222:9222/tls + --vcpus 1 + -n "$NAME" +) + +if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then + deploy_args+=( -p 444:10001/tls ) + deploy_args+=( -e WITH_KERNEL_IMAGES_API=true ) +fi + +kraft cloud inst create "${deploy_args[@]}" "$IMAGE" diff --git a/shared/cdp-test/main.py b/shared/cdp-test/main.py index dfd35172..36837565 100644 --- a/shared/cdp-test/main.py +++ b/shared/cdp-test/main.py @@ -3,6 +3,7 @@ import json import re import socket +import argparse from pathlib import Path from urllib.parse import urljoin, urlparse from urllib.request import urlopen, Request @@ -10,11 +11,15 @@ import aiohttp # type: ignore import contextlib -async def run(cdp_url: str) -> None: - """Connect to an existing Chromium instance via CDP, navigate, and screenshot.""" +async def run(cdp_url: str | None = None) -> None: + """Run browser automation either by connecting to existing instance or launching new one.""" async with async_playwright() as p: - # Connect to the running browser exposed via the CDP websocket URL. - browser = await p.chromium.connect_over_cdp(cdp_url) + if cdp_url: + # Connect to the running browser exposed via the CDP websocket URL. + browser = await p.chromium.connect_over_cdp(cdp_url) + else: + # Launch a new browser instance locally with GUI + browser = await p.chromium.launch(headless=False) # Re-use the first context if present, otherwise create a fresh one. if browser.contexts: @@ -61,6 +66,43 @@ async def run(cdp_url: str) -> None: await page.screenshot(path=str(out_path), full_page=True) print(f"Screenshot saved to {out_path.resolve()}") + # Navigate to IRS forms page and download Form 1040 + print(f"Navigating to IRS forms page...", file=sys.stderr) + try: + await page.goto("https://www.irs.gov/forms-instructions", wait_until="domcontentloaded", timeout=10_000) + + # Wait for network to settle + try: + await page.wait_for_load_state("networkidle", timeout=10_000) + except PlaywrightTimeoutError: + print("networkidle state not reached within 10 s – proceeding", file=sys.stderr) + + print("Looking for Form 1040 download link...", file=sys.stderr) + + # Start waiting for the download + async with page.expect_download(timeout=10_000) as download_info: + # Click on tag with title containing "Form 1040" to initiate download + form_link = page.locator('a[title*="Form 1040"]').first + print(f"Found tag with title containing 'Form 1040'", file=sys.stderr) + await form_link.click(timeout=5_000) + print(f"Clicked on Form 1040 link", file=sys.stderr) + download = await download_info.value + print(f"Download received", file=sys.stderr) + + # Save the downloaded file + download_path = Path("downloads") + download_path.mkdir(exist_ok=True) + save_path = download_path / download.suggested_filename + await download.save_as(str(save_path)) + print(f"Form 1040 downloaded and saved to {save_path.resolve()}") + + except PlaywrightTimeoutError: + print(f"Operations on IRS forms page timed out", file=sys.stderr) + await page.screenshot(path="screenshot-irs-error.png", full_page=True) + except Exception as e: + print(f"Error downloading Form 1040: {e}", file=sys.stderr) + await page.screenshot(path="screenshot-download-error.png", full_page=True) + await browser.close() @@ -138,27 +180,43 @@ async def _keep_alive(endpoint: str) -> None: await asyncio.sleep(1) -async def _async_main(endpoint_arg: str) -> None: - """Resolve CDP URL, start keep-alive task, and run the browser automation.""" - - cdp_url = _resolve_cdp_url(endpoint_arg) - - # Start the keep-alive loop. - keep_alive_task = asyncio.create_task(_keep_alive(endpoint_arg)) - - try: - await run(cdp_url) - finally: - # Ensure the keep-alive task is cancelled cleanly when run() completes. - keep_alive_task.cancel() - with contextlib.suppress(asyncio.CancelledError): - await keep_alive_task +async def _async_main(endpoint_arg: str | None, local: bool) -> None: + """Run browser automation either locally or via CDP connection.""" + + if local: + # Run locally without CDP connection or keep-alive + await run() + else: + # Resolve CDP URL and use keep-alive for remote connection + cdp_url = _resolve_cdp_url(endpoint_arg) + + # Start the keep-alive loop. + keep_alive_task = asyncio.create_task(_keep_alive(endpoint_arg)) + + try: + await run(cdp_url) + finally: + # Ensure the keep-alive task is cancelled cleanly when run() completes. + keep_alive_task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await keep_alive_task def main() -> None: - if len(sys.argv) < 2: - print("Usage: python main.py ", file=sys.stderr) - sys.exit(1) - asyncio.run(_async_main(sys.argv[1])) + parser = argparse.ArgumentParser( + description="Browser automation script with CDP or local Chromium", + epilog="Examples:\n" + " CDP mode: python main.py localhost:9222\n" + " Local mode: python main.py --local", + formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument("endpoint", nargs="?", help="DevTools HTTP endpoint (e.g., localhost:9222)") + parser.add_argument("--local", action="store_true", help="Launch Chromium locally with GUI instead of connecting via CDP") + args = parser.parse_args() + + if not args.local and not args.endpoint: + parser.error("Either provide an endpoint or use --local flag") + + asyncio.run(_async_main(args.endpoint, args.local)) if __name__ == "__main__": main() diff --git a/shared/ensure-common-build-run-vars.sh b/shared/ensure-common-build-run-vars.sh new file mode 100644 index 00000000..4fcafa18 --- /dev/null +++ b/shared/ensure-common-build-run-vars.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -e -o pipefail + +IMAGE_TYPE=$1 +if [ -z "$IMAGE_TYPE" ]; then + echo "Usage: source ensure-common-build-run-vars.sh " + echo "e.g. source ensure-common-build-run-vars.sh chromium-headful" + echo "This will set the defaults for the image name and test instance name" + echo "You can override the defaults by setting the IMAGE and NAME variables" + exit 1 +fi +IMAGE="${IMAGE:-onkernel/${IMAGE_TYPE}-test:latest}" +NAME="${NAME:-${IMAGE_TYPE}-test}" + +UKC_INDEX="${UKC_INDEX:-index.unikraft.io}" + +# fail if UKC_TOKEN, UKC_METRO are not set +errormsg="" +for var in UKC_TOKEN UKC_METRO; do + if [ -z "${!var}" ]; then + errormsg+="$var " + fi +done +if [ -n "$errormsg" ]; then + echo "Required variables not set: $errormsg" + exit 1 +fi From 494147fe5c5a414a6d0d8679953a50782a634e74 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Wed, 6 Aug 2025 17:41:22 -0400 Subject: [PATCH 13/20] test script --- images/chromium-headful/run-docker.sh | 2 +- images/chromium-headless/run-docker.sh | 2 +- shared/cdp-test/main.py | 337 ++++++++++++++++++++++--- 3 files changed, 307 insertions(+), 34 deletions(-) diff --git a/images/chromium-headful/run-docker.sh b/images/chromium-headful/run-docker.sh index d6ef88bd..5adbd8a8 100755 --- a/images/chromium-headful/run-docker.sh +++ b/images/chromium-headful/run-docker.sh @@ -43,7 +43,7 @@ RUN_ARGS=( ) if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then - RUN_ARGS+=( -p 10001:10001 ) + RUN_ARGS+=( -p 444:10001 ) RUN_ARGS+=( -e WITH_KERNEL_IMAGES_API=true ) fi diff --git a/images/chromium-headless/run-docker.sh b/images/chromium-headless/run-docker.sh index 2ef34f53..1057b434 100755 --- a/images/chromium-headless/run-docker.sh +++ b/images/chromium-headless/run-docker.sh @@ -19,7 +19,7 @@ RUN_ARGS=( ) if [[ "${WITH_KERNEL_IMAGES_API:-}" == "true" ]]; then - RUN_ARGS+=( -p 10001:10001 ) + RUN_ARGS+=( -p 444:10001 ) RUN_ARGS+=( -e WITH_KERNEL_IMAGES_API=true ) RUN_ARGS+=( -v "$HOST_RECORDINGS_DIR:/recordings" ) fi diff --git a/shared/cdp-test/main.py b/shared/cdp-test/main.py index 36837565..e211761b 100644 --- a/shared/cdp-test/main.py +++ b/shared/cdp-test/main.py @@ -7,9 +7,95 @@ from pathlib import Path from urllib.parse import urljoin, urlparse from urllib.request import urlopen, Request -from playwright.async_api import async_playwright, TimeoutError as PlaywrightTimeoutError # type: ignore +from playwright.async_api import Route, async_playwright, TimeoutError as PlaywrightTimeoutError # type: ignore import aiohttp # type: ignore import contextlib +from typing import Optional + +# -------------- helper functions -------------- # + +async def _click_and_await_download(page, client, click_func, timeout: int = 30): + """Trigger *click_func* and wait until the browser download completes. + + Returns the suggested filename or None if unavailable/timed out. + """ + download_completed = asyncio.Event() + download_filename: str | None = None + + def _on_download_begin(event): + nonlocal download_filename + download_filename = event.get("suggestedFilename", "unknown") + print(f"Download started: {download_filename}", file=sys.stderr) + + def _on_download_progress(event): + if event.get("state") in ["completed", "canceled"]: + download_completed.set() + + client.on("Browser.downloadWillBegin", _on_download_begin) + client.on("Browser.downloadProgress", _on_download_progress) + + # Trigger the click that initiates the download. + await click_func() + + try: + await asyncio.wait_for(download_completed.wait(), timeout=timeout) + print("Download completed", file=sys.stderr) + except asyncio.TimeoutError: + print(f"Download timed out after {timeout} seconds", file=sys.stderr) + finally: + # Best-effort removal of listeners (not critical). + try: + client.off("Browser.downloadWillBegin", _on_download_begin) # type: ignore[attr-defined] + client.off("Browser.downloadProgress", _on_download_progress) # type: ignore[attr-defined] + except Exception: + pass + + return download_filename + + +async def _fetch_remote_downloads(cdp_url: str, remote_dir: str = "/tmp/downloads", local_dir: str = "downloads") -> None: + """Fetch all files from *remote_dir* (over the filesystem API) and save them to *local_dir*.""" + parsed = urlparse(cdp_url) + fs_base_url = f"https://{parsed.hostname}:444" + + async with aiohttp.ClientSession() as session: + list_url = f"{fs_base_url}/fs/list_files?path={remote_dir}" + print(f"Listing files in {remote_dir} from {list_url}", file=sys.stderr) + + async with session.get(list_url) as resp: + if resp.status == 200: + files = await resp.json() + print(f"Found {len(files)} items in {remote_dir}", file=sys.stderr) + elif resp.status == 404: + print(f"{remote_dir} directory not found or empty", file=sys.stderr) + files = [] + else: + error_text = await resp.text() + print(f"Failed to list files: {resp.status} - {error_text}", file=sys.stderr) + files = [] + + local_download_path = Path(local_dir) + local_download_path.mkdir(exist_ok=True) + + for file_info in files: + if not file_info.get("is_dir", False): + file_path = file_info.get("path") + file_name = file_info.get("name") + + if file_path and file_name: + file_url = f"{fs_base_url}/fs/read_file?path={file_path}" + print(f"Fetching {file_name} from {file_url}", file=sys.stderr) + + async with session.get(file_url) as file_resp: + if file_resp.status == 200: + content = await file_resp.read() + local_file_path = local_download_path / file_name + with open(local_file_path, "wb") as f: + f.write(content) + print(f"Downloaded file saved to {local_file_path.resolve()}") + else: + error_text = await file_resp.text() + print(f"Failed to fetch {file_name}: {file_resp.status} - {error_text}", file=sys.stderr) async def run(cdp_url: str | None = None) -> None: """Run browser automation either by connecting to existing instance or launching new one.""" @@ -30,6 +116,34 @@ async def run(cdp_url: str | None = None) -> None: # Re-use the first page if present, otherwise create a fresh one. page = context.pages[0] if context.pages else await context.new_page() + # Get CDP client from page + client = await page.context.new_cdp_session(page) + + # Configure download behavior + await client.send( + "Browser.setDownloadBehavior", + { + "behavior": "allow", + "downloadPath": "/tmp/downloads", + "eventsEnabled": True, + }, + ) + print("Set download behavior via CDP", file=sys.stderr) + + # Intercept PDF requests to ensure they're downloaded as attachments + async def handle_pdf_route(route: Route): + response = await route.fetch() + headers = response.headers + headers['content-disposition'] = 'attachment' + await route.fulfill( + response=response, + headers=headers + ) + + # Set up route interception for PDF files + await page.route('**/*.pdf', handle_pdf_route) + print("Set up PDF download interception", file=sys.stderr) + # Snapshot the page as-is for debugging purposes. print(f"Page URL: {page.url}") print(f"Taking screenshot before navigation") @@ -66,10 +180,10 @@ async def run(cdp_url: str | None = None) -> None: await page.screenshot(path=str(out_path), full_page=True) print(f"Screenshot saved to {out_path.resolve()}") - # Navigate to IRS forms page and download Form 1040 - print(f"Navigating to IRS forms page...", file=sys.stderr) + # Navigate to the test page + print(f"Navigating to download test page...", file=sys.stderr) try: - await page.goto("https://www.irs.gov/forms-instructions", wait_until="domcontentloaded", timeout=10_000) + await page.goto("https://browser-tests-alpha.vercel.app/api/download-test", wait_until="domcontentloaded", timeout=30_000) # Wait for network to settle try: @@ -77,32 +191,111 @@ async def run(cdp_url: str | None = None) -> None: except PlaywrightTimeoutError: print("networkidle state not reached within 10 s – proceeding", file=sys.stderr) - print("Looking for Form 1040 download link...", file=sys.stderr) - - # Start waiting for the download - async with page.expect_download(timeout=10_000) as download_info: - # Click on tag with title containing "Form 1040" to initiate download - form_link = page.locator('a[title*="Form 1040"]').first - print(f"Found tag with title containing 'Form 1040'", file=sys.stderr) - await form_link.click(timeout=5_000) - print(f"Clicked on Form 1040 link", file=sys.stderr) - download = await download_info.value - print(f"Download received", file=sys.stderr) - - # Save the downloaded file - download_path = Path("downloads") - download_path.mkdir(exist_ok=True) - save_path = download_path / download.suggested_filename - await download.save_as(str(save_path)) - print(f"Form 1040 downloaded and saved to {save_path.resolve()}") + # Trigger the first test download via helper + await _click_and_await_download( + page, + client, + lambda: page.click("#download"), + ) + + # Fetch the downloaded files from the filesystem API (remote mode only) + if cdp_url: + await _fetch_remote_downloads(cdp_url) + + # --------------- Second download: IRS Form 1040 --------------- # + print("Navigating to IRS Forms & Instructions page...", file=sys.stderr) + try: + await page.goto("https://www.irs.gov/forms-instructions", wait_until="domcontentloaded", timeout=30_000) + try: + await page.wait_for_load_state("networkidle", timeout=10_000) + except PlaywrightTimeoutError: + print("networkidle state not reached within 10 s – proceeding", file=sys.stderr) + + print("Looking for Form 1040 PDF link and triggering download…", file=sys.stderr) + await _click_and_await_download( + page, + client, + lambda: page.click('a[href*="f1040.pdf"]'), + ) + + # Fetch again to get the newly downloaded file + if cdp_url: + await _fetch_remote_downloads(cdp_url) + + except PlaywrightTimeoutError: + print("Navigation to IRS Forms & Instructions timed out", file=sys.stderr) + await page.screenshot(path="screenshot-irs-error.png", full_page=True) + except Exception as e: + print(f"Error during IRS download test: {e}", file=sys.stderr) + await page.screenshot(path="screenshot-irs-error.png", full_page=True) except PlaywrightTimeoutError: - print(f"Operations on IRS forms page timed out", file=sys.stderr) - await page.screenshot(path="screenshot-irs-error.png", full_page=True) + print(f"Navigation to download test page timed out", file=sys.stderr) + await page.screenshot(path="screenshot-download-error.png", full_page=True) except Exception as e: - print(f"Error downloading Form 1040: {e}", file=sys.stderr) + print(f"Error during download test: {e}", file=sys.stderr) await page.screenshot(path="screenshot-download-error.png", full_page=True) + # --------------- Upload test --------------- # + print(f"Navigating to upload test page...", file=sys.stderr) + try: + await page.goto("https://browser-tests-alpha.vercel.app/api/upload-test", wait_until="domcontentloaded", timeout=30_000) + try: + await page.wait_for_load_state("networkidle", timeout=10_000) + except PlaywrightTimeoutError: + print("networkidle state not reached within 10 s – proceeding", file=sys.stderr) + + # Determine file path to upload + local_file_path = Path("downloads") / "sandstorm.mp3" + if not local_file_path.exists(): + raise FileNotFoundError(f"File {local_file_path} not found") + + print(f"Uploading {local_file_path} …", file=sys.stderr) + file_input = page.locator("#fileUpload") + await file_input.set_input_files(str(local_file_path)) + print("Upload completed", file=sys.stderr) + + # --------------- Second upload using CDP (remote path) --------------- # + print("Navigating to upload test page for CDP upload...", file=sys.stderr) + try: + await page.goto("https://browser-tests-alpha.vercel.app/api/upload-test", wait_until="domcontentloaded", timeout=30_000) + try: + await page.wait_for_load_state("networkidle", timeout=10_000) + except PlaywrightTimeoutError: + print("networkidle state not reached within 10 s – proceeding", file=sys.stderr) + + # Use CDP commands to set the remote file path in the file input + root = await client.send("DOM.getDocument", {"depth": 1, "pierce": True}) + input_node = await client.send("DOM.querySelector", { + "nodeId": root["root"]["nodeId"], + "selector": "#fileUpload" + }) + + remote_file_path = "/tmp/downloads/f1040.pdf" + await client.send("DOM.setFileInputFiles", { + "files": [remote_file_path], + "nodeId": input_node["nodeId"] + }) + + # Wait until the file input's value is non-empty + await page.wait_for_function("document.querySelector('#fileUpload').value.includes('f1040.pdf')", timeout=10_000) + uploaded_value = await page.evaluate("document.querySelector('#fileUpload').value") + print(f"CDP upload completed, input value: {uploaded_value}", file=sys.stderr) + + except PlaywrightTimeoutError: + print("Navigation to upload test page (CDP) timed out", file=sys.stderr) + await page.screenshot(path="screenshot-upload-cdp-error.png", full_page=True) + except Exception as e: + print(f"Error during CDP upload test: {e}", file=sys.stderr) + await page.screenshot(path="screenshot-upload-cdp-error.png", full_page=True) + + except PlaywrightTimeoutError: + print("Navigation to upload test page timed out", file=sys.stderr) + await page.screenshot(path="screenshot-upload-error.png", full_page=True) + except Exception as e: + print(f"Error during upload test: {e}", file=sys.stderr) + await page.screenshot(path="screenshot-upload-error.png", full_page=True) + await browser.close() @@ -111,17 +304,15 @@ async def run(cdp_url: str | None = None) -> None: def _resolve_cdp_url(arg: str) -> str: """Resolve the provided argument to a CDP websocket URL. - If *arg* already looks like a ws:// or wss:// URL, return it unchanged. - Otherwise, treat it as a DevTools HTTP endpoint (e.g. http://localhost:9222 - or just localhost:9222), fetch /json/version, and extract the + Treat arg as a url, fetch :9222/json/version, and extract the 'webSocketDebuggerUrl'. """ # Ensure scheme. Default to http:// if none supplied. if not arg.startswith(("http://", "https://")): - arg = f"http://{arg}" + raise ValueError(f"Expected a url, got {arg}") - version_url = urljoin(arg.rstrip("/") + "/", "json/version") + version_url = urljoin(arg.rstrip("/") + ":9222/", "json/version") try: # Chromium devtools HTTP server only accepts Host headers that are an @@ -180,6 +371,79 @@ async def _keep_alive(endpoint: str) -> None: await asyncio.sleep(1) +async def _watch_filesystem(endpoint: str) -> None: + """Watch the filesystem and print events.""" + parsed = urlparse(endpoint) + fs_api_url = f"https://{parsed.hostname}:444" + print(f"Filesystem API URL: {fs_api_url}", file=sys.stderr) + + watch_id: Optional[str] = None + + async with aiohttp.ClientSession() as session: + try: + # Ensure /tmp/downloads exists on the remote filesystem + print(f"Ensuring /tmp/downloads exists via {fs_api_url}/fs/create_directory", file=sys.stderr) + async with session.post( + f"{fs_api_url}/fs/create_directory", + json={"path": "/tmp/downloads", "recursive": True}, + ) as resp: + if resp.status != 201: + text = await resp.text() + print(f"Failed to create directory /tmp/downloads: {resp.status} - {text}", file=sys.stderr) + + # Start watching the root directory + print(f"Starting filesystem watch on /tmp/downloads at {fs_api_url}/fs/watch", file=sys.stderr) + async with session.post( + f"{fs_api_url}/fs/watch", + json={"path": "/tmp/downloads", "recursive": True} + ) as resp: + if resp.status != 201: + text = await resp.text() + print(f"Failed to start filesystem watch: {resp.status} - {text}", file=sys.stderr) + return + + data = await resp.json() + watch_id = data.get("watch_id") + print(f"Filesystem watch started with ID: {watch_id}", file=sys.stderr) + + # Stream events + print(f"Streaming filesystem events from {fs_api_url}/fs/watch/{watch_id}/events", file=sys.stderr) + async with session.get( + f"{fs_api_url}/fs/watch/{watch_id}/events", + headers={"Accept": "text/event-stream"} + ) as resp: + if resp.status != 200: + text = await resp.text() + print(f"Failed to stream filesystem events: {resp.status} - {text}", file=sys.stderr) + return + + # Process SSE stream + async for line in resp.content: + line_str = line.decode('utf-8').strip() + if line_str.startswith("data: "): + try: + event_data = json.loads(line_str[6:]) # Skip "data: " prefix + print(f"FS Event: {event_data}", file=sys.stderr) + except json.JSONDecodeError: + print(f"Failed to parse event: {line_str}", file=sys.stderr) + + except Exception as exc: + print(f"Filesystem watch error: {exc}", file=sys.stderr) + finally: + # Clean up the watch if it was created + if watch_id: + try: + print(f"Cleaning up filesystem watch {watch_id}", file=sys.stderr) + async with session.delete(f"{fs_api_url}/fs/watch/{watch_id}") as resp: + if resp.status == 204 or resp.status == 200: + print(f"Filesystem watch {watch_id} cleaned up successfully", file=sys.stderr) + else: + text = await resp.text() + print(f"Failed to clean up watch: {resp.status} - {text}", file=sys.stderr) + except Exception as cleanup_exc: + print(f"Error during watch cleanup: {cleanup_exc}", file=sys.stderr) + + async def _async_main(endpoint_arg: str | None, local: bool) -> None: """Run browser automation either locally or via CDP connection.""" @@ -193,6 +457,9 @@ async def _async_main(endpoint_arg: str | None, local: bool) -> None: # Start the keep-alive loop. keep_alive_task = asyncio.create_task(_keep_alive(endpoint_arg)) + # Start the filesystem watch task + fs_watch_task = asyncio.create_task(_watch_filesystem(endpoint_arg)) + try: await run(cdp_url) finally: @@ -200,16 +467,22 @@ async def _async_main(endpoint_arg: str | None, local: bool) -> None: keep_alive_task.cancel() with contextlib.suppress(asyncio.CancelledError): await keep_alive_task + + # Cancel the filesystem watch task + fs_watch_task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await fs_watch_task def main() -> None: parser = argparse.ArgumentParser( description="Browser automation script with CDP or local Chromium", epilog="Examples:\n" - " CDP mode: python main.py localhost:9222\n" + " CDP mode: python main.py localhost\n" + " or python main.py https://url-of-remote-instance.com" " Local mode: python main.py --local", formatter_class=argparse.RawDescriptionHelpFormatter ) - parser.add_argument("endpoint", nargs="?", help="DevTools HTTP endpoint (e.g., localhost:9222)") + parser.add_argument("endpoint", nargs="?", help="HTTP endpoint (e.g., localhost). Assumed to be running the devtools protocol on 9222 and the filesystem API on 444.") parser.add_argument("--local", action="store_true", help="Launch Chromium locally with GUI instead of connecting via CDP") args = parser.parse_args() From 549e537e5f68b6e4aea3043df7717464869ab63d Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Wed, 6 Aug 2025 17:55:51 -0400 Subject: [PATCH 14/20] post -> put --- server/lib/oapi/oapi.go | 156 ++++++++++++++++++------------------- server/openapi.yaml | 12 +-- shared/cdp-test/.gitignore | 4 +- shared/cdp-test/main.py | 4 +- 4 files changed, 88 insertions(+), 88 deletions(-) diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index 52fba647..14fa6ddf 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -923,7 +923,7 @@ func NewCreateDirectoryRequestWithBody(server string, contentType string, body i return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + req, err := http.NewRequest("PUT", queryURL.String(), body) if err != nil { return nil, err } @@ -963,7 +963,7 @@ func NewDeleteDirectoryRequestWithBody(server string, contentType string, body i return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + req, err := http.NewRequest("PUT", queryURL.String(), body) if err != nil { return nil, err } @@ -1003,7 +1003,7 @@ func NewDeleteFileRequestWithBody(server string, contentType string, body io.Rea return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + req, err := http.NewRequest("PUT", queryURL.String(), body) if err != nil { return nil, err } @@ -1133,7 +1133,7 @@ func NewMovePathRequestWithBody(server string, contentType string, body io.Reade return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + req, err := http.NewRequest("PUT", queryURL.String(), body) if err != nil { return nil, err } @@ -1218,7 +1218,7 @@ func NewSetFilePermissionsRequestWithBody(server string, contentType string, bod return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + req, err := http.NewRequest("PUT", queryURL.String(), body) if err != nil { return nil, err } @@ -1389,7 +1389,7 @@ func NewWriteFileRequestWithBody(server string, params *WriteFileParams, content queryURL.RawQuery = queryValues.Encode() } - req, err := http.NewRequest("POST", queryURL.String(), body) + req, err := http.NewRequest("PUT", queryURL.String(), body) if err != nil { return nil, err } @@ -3192,13 +3192,13 @@ type ServerInterface interface { // (POST /computer/move_mouse) MoveMouse(w http.ResponseWriter, r *http.Request) // Create a new directory - // (POST /fs/create_directory) + // (PUT /fs/create_directory) CreateDirectory(w http.ResponseWriter, r *http.Request) // Delete a directory - // (POST /fs/delete_directory) + // (PUT /fs/delete_directory) DeleteDirectory(w http.ResponseWriter, r *http.Request) // Delete a file - // (POST /fs/delete_file) + // (PUT /fs/delete_file) DeleteFile(w http.ResponseWriter, r *http.Request) // Get information about a file or directory // (GET /fs/file_info) @@ -3207,13 +3207,13 @@ type ServerInterface interface { // (GET /fs/list_files) ListFiles(w http.ResponseWriter, r *http.Request, params ListFilesParams) // Move or rename a file or directory - // (POST /fs/move) + // (PUT /fs/move) MovePath(w http.ResponseWriter, r *http.Request) // Read file contents // (GET /fs/read_file) ReadFile(w http.ResponseWriter, r *http.Request, params ReadFileParams) // Set file or directory permissions/ownership - // (POST /fs/set_file_permissions) + // (PUT /fs/set_file_permissions) SetFilePermissions(w http.ResponseWriter, r *http.Request) // Watch a directory for changes // (POST /fs/watch) @@ -3225,7 +3225,7 @@ type ServerInterface interface { // (GET /fs/watch/{watch_id}/events) StreamFsEvents(w http.ResponseWriter, r *http.Request, watchId string) // Write or create a file - // (POST /fs/write_file) + // (PUT /fs/write_file) WriteFile(w http.ResponseWriter, r *http.Request, params WriteFileParams) // Delete a previously recorded video file // (POST /recording/delete) @@ -3261,19 +3261,19 @@ func (_ Unimplemented) MoveMouse(w http.ResponseWriter, r *http.Request) { } // Create a new directory -// (POST /fs/create_directory) +// (PUT /fs/create_directory) func (_ Unimplemented) CreateDirectory(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } // Delete a directory -// (POST /fs/delete_directory) +// (PUT /fs/delete_directory) func (_ Unimplemented) DeleteDirectory(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } // Delete a file -// (POST /fs/delete_file) +// (PUT /fs/delete_file) func (_ Unimplemented) DeleteFile(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } @@ -3291,7 +3291,7 @@ func (_ Unimplemented) ListFiles(w http.ResponseWriter, r *http.Request, params } // Move or rename a file or directory -// (POST /fs/move) +// (PUT /fs/move) func (_ Unimplemented) MovePath(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } @@ -3303,7 +3303,7 @@ func (_ Unimplemented) ReadFile(w http.ResponseWriter, r *http.Request, params R } // Set file or directory permissions/ownership -// (POST /fs/set_file_permissions) +// (PUT /fs/set_file_permissions) func (_ Unimplemented) SetFilePermissions(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } @@ -3327,7 +3327,7 @@ func (_ Unimplemented) StreamFsEvents(w http.ResponseWriter, r *http.Request, wa } // Write or create a file -// (POST /fs/write_file) +// (PUT /fs/write_file) func (_ Unimplemented) WriteFile(w http.ResponseWriter, r *http.Request, params WriteFileParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -3880,13 +3880,13 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Post(options.BaseURL+"/computer/move_mouse", wrapper.MoveMouse) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/fs/create_directory", wrapper.CreateDirectory) + r.Put(options.BaseURL+"/fs/create_directory", wrapper.CreateDirectory) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/fs/delete_directory", wrapper.DeleteDirectory) + r.Put(options.BaseURL+"/fs/delete_directory", wrapper.DeleteDirectory) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/fs/delete_file", wrapper.DeleteFile) + r.Put(options.BaseURL+"/fs/delete_file", wrapper.DeleteFile) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/fs/file_info", wrapper.FileInfo) @@ -3895,13 +3895,13 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/fs/list_files", wrapper.ListFiles) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/fs/move", wrapper.MovePath) + r.Put(options.BaseURL+"/fs/move", wrapper.MovePath) }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/fs/read_file", wrapper.ReadFile) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/fs/set_file_permissions", wrapper.SetFilePermissions) + r.Put(options.BaseURL+"/fs/set_file_permissions", wrapper.SetFilePermissions) }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/fs/watch", wrapper.StartFsWatch) @@ -3913,7 +3913,7 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/fs/watch/{watch_id}/events", wrapper.StreamFsEvents) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/fs/write_file", wrapper.WriteFile) + r.Put(options.BaseURL+"/fs/write_file", wrapper.WriteFile) }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/recording/delete", wrapper.DeleteRecording) @@ -4804,13 +4804,13 @@ type StrictServerInterface interface { // (POST /computer/move_mouse) MoveMouse(ctx context.Context, request MoveMouseRequestObject) (MoveMouseResponseObject, error) // Create a new directory - // (POST /fs/create_directory) + // (PUT /fs/create_directory) CreateDirectory(ctx context.Context, request CreateDirectoryRequestObject) (CreateDirectoryResponseObject, error) // Delete a directory - // (POST /fs/delete_directory) + // (PUT /fs/delete_directory) DeleteDirectory(ctx context.Context, request DeleteDirectoryRequestObject) (DeleteDirectoryResponseObject, error) // Delete a file - // (POST /fs/delete_file) + // (PUT /fs/delete_file) DeleteFile(ctx context.Context, request DeleteFileRequestObject) (DeleteFileResponseObject, error) // Get information about a file or directory // (GET /fs/file_info) @@ -4819,13 +4819,13 @@ type StrictServerInterface interface { // (GET /fs/list_files) ListFiles(ctx context.Context, request ListFilesRequestObject) (ListFilesResponseObject, error) // Move or rename a file or directory - // (POST /fs/move) + // (PUT /fs/move) MovePath(ctx context.Context, request MovePathRequestObject) (MovePathResponseObject, error) // Read file contents // (GET /fs/read_file) ReadFile(ctx context.Context, request ReadFileRequestObject) (ReadFileResponseObject, error) // Set file or directory permissions/ownership - // (POST /fs/set_file_permissions) + // (PUT /fs/set_file_permissions) SetFilePermissions(ctx context.Context, request SetFilePermissionsRequestObject) (SetFilePermissionsResponseObject, error) // Watch a directory for changes // (POST /fs/watch) @@ -4837,7 +4837,7 @@ type StrictServerInterface interface { // (GET /fs/watch/{watch_id}/events) StreamFsEvents(ctx context.Context, request StreamFsEventsRequestObject) (StreamFsEventsResponseObject, error) // Write or create a file - // (POST /fs/write_file) + // (PUT /fs/write_file) WriteFile(ctx context.Context, request WriteFileRequestObject) (WriteFileResponseObject, error) // Delete a previously recorded video file // (POST /recording/delete) @@ -5437,54 +5437,54 @@ func (sh *strictHandler) StopRecording(w http.ResponseWriter, r *http.Request) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "LE/fSD4AAA==", + "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", + "//+ThhMPQj4AAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 5c1ca64b..d56dbaa5 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -183,7 +183,7 @@ paths: $ref: "#/components/responses/InternalError" /fs/write_file: - post: + put: summary: Write or create a file operationId: writeFile parameters: @@ -245,7 +245,7 @@ paths: $ref: "#/components/responses/InternalError" /fs/create_directory: - post: + put: summary: Create a new directory operationId: createDirectory requestBody: @@ -263,7 +263,7 @@ paths: $ref: "#/components/responses/InternalError" /fs/delete_file: - post: + put: summary: Delete a file operationId: deleteFile requestBody: @@ -283,7 +283,7 @@ paths: $ref: "#/components/responses/InternalError" /fs/delete_directory: - post: + put: summary: Delete a directory operationId: deleteDirectory requestBody: @@ -303,7 +303,7 @@ paths: $ref: "#/components/responses/InternalError" /fs/set_file_permissions: - post: + put: summary: Set file or directory permissions/ownership operationId: setFilePermissions requestBody: @@ -349,7 +349,7 @@ paths: $ref: "#/components/responses/InternalError" /fs/move: - post: + put: summary: Move or rename a file or directory operationId: movePath requestBody: diff --git a/shared/cdp-test/.gitignore b/shared/cdp-test/.gitignore index aed7c280..b0081399 100644 --- a/shared/cdp-test/.gitignore +++ b/shared/cdp-test/.gitignore @@ -1,2 +1,2 @@ -screenshot.png -screenshot-before.png +screenshot* +downloads/* diff --git a/shared/cdp-test/main.py b/shared/cdp-test/main.py index e211761b..e4d720ed 100644 --- a/shared/cdp-test/main.py +++ b/shared/cdp-test/main.py @@ -383,7 +383,7 @@ async def _watch_filesystem(endpoint: str) -> None: try: # Ensure /tmp/downloads exists on the remote filesystem print(f"Ensuring /tmp/downloads exists via {fs_api_url}/fs/create_directory", file=sys.stderr) - async with session.post( + async with session.put( f"{fs_api_url}/fs/create_directory", json={"path": "/tmp/downloads", "recursive": True}, ) as resp: @@ -393,7 +393,7 @@ async def _watch_filesystem(endpoint: str) -> None: # Start watching the root directory print(f"Starting filesystem watch on /tmp/downloads at {fs_api_url}/fs/watch", file=sys.stderr) - async with session.post( + async with session.put( f"{fs_api_url}/fs/watch", json={"path": "/tmp/downloads", "recursive": True} ) as resp: From cd08bba13ddb27937afed9587f5b60987f9add2e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 Aug 2025 21:59:16 +0000 Subject: [PATCH 15/20] Improve file permission handling with direct UID/GID and fallback lookup Co-authored-by: rgarcia2009 --- server/cmd/api/api/fs.go | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index ad47c6a1..72dea21a 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -292,17 +292,34 @@ func (s *ApiService) SetFilePermissions(ctx context.Context, req oapi.SetFilePer if req.Body.Owner != nil || req.Body.Group != nil { uid := -1 gid := -1 + // Handle owner (uid) if req.Body.Owner != nil { - if u, err := user.Lookup(*req.Body.Owner); err == nil { - if id, err := strconv.Atoi(u.Uid); err == nil && id >= 0 { - uid = id + ownerStr := *req.Body.Owner + // 1. Try parsing as a numeric UID directly + if id, err := strconv.Atoi(ownerStr); err == nil && id >= 0 { + uid = id + } else { + // 2. Fall back to name lookup + if u, err := user.Lookup(ownerStr); err == nil { + if id, err := strconv.Atoi(u.Uid); err == nil && id >= 0 { + uid = id + } } } } + + // Handle group (gid) if req.Body.Group != nil { - if g, err := user.LookupGroup(*req.Body.Group); err == nil { - if id, err := strconv.Atoi(g.Gid); err == nil && id >= 0 { - gid = id + groupStr := *req.Body.Group + // 1. Try parsing as a numeric GID directly + if id, err := strconv.Atoi(groupStr); err == nil && id >= 0 { + gid = id + } else { + // 2. Fall back to name lookup + if g, err := user.LookupGroup(groupStr); err == nil { + if id, err := strconv.Atoi(g.Gid); err == nil && id >= 0 { + gid = id + } } } } From 63ccaa3b4e57c4890fdcb0aadba2d326deda2b2f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 Aug 2025 22:03:19 +0000 Subject: [PATCH 16/20] Handle write errors in SSE event streaming to prevent potential hanging Co-authored-by: rgarcia2009 --- server/cmd/api/api/fs.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index 72dea21a..eec03c01 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -490,12 +490,18 @@ func (s *ApiService) StreamFsEvents(ctx context.Context, req oapi.StreamFsEvents enc := json.NewEncoder(pw) for ev := range w.events { // Write SSE formatted event: data: \n\n - pw.Write([]byte("data: ")) + if _, err := pw.Write([]byte("data: ")); err != nil { + log.Error("failed to write SSE prefix", "err", err) + return + } if err := enc.Encode(ev); err != nil { log.Error("failed to encode fs event", "err", err) return } - pw.Write([]byte("\n")) + if _, err := pw.Write([]byte("\n")); err != nil { + log.Error("failed to write SSE terminator", "err", err) + return + } } }() From 59e236d5d1698748f94e84d8d3b01c7ea4580ffa Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 6 Aug 2025 22:07:51 +0000 Subject: [PATCH 17/20] Improve file system watcher with safe close and recursive monitoring Co-authored-by: rgarcia2009 --- server/cmd/api/api/fs.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index eec03c01..bb46473b 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -9,6 +9,8 @@ import ( "path/filepath" "strconv" + "sync" + "os/user" "github.com/fsnotify/fsnotify" @@ -23,6 +25,7 @@ type fsWatch struct { recursive bool events chan oapi.FileSystemEvent watcher *fsnotify.Watcher + closeOnce sync.Once } // addRecursive walks the directory and registers all subdirectories when recursive=true. @@ -38,6 +41,13 @@ func addRecursive(w *fsnotify.Watcher, root string) error { }) } +// Close safely shuts down the underlying fsnotify.Watcher exactly once. +func (fw *fsWatch) Close() { + fw.closeOnce.Do(func() { + _ = fw.watcher.Close() + }) +} + // ReadFile returns the contents of a file specified by the path param. func (s *ApiService) ReadFile(ctx context.Context, req oapi.ReadFileRequestObject) (oapi.ReadFileResponseObject, error) { log := logger.FromContext(ctx) @@ -393,7 +403,7 @@ func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequ // Ensure resources are cleaned up no matter how the goroutine exits. defer func() { // Best-effort close (idempotent). - watcher.Close() + w.Close() // Remove stale entry to avoid map/chan leak if the watch stops on // its own (e.g. underlying fs error, watcher overflow, etc.). It @@ -435,10 +445,11 @@ func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequ default: } - // If recursive and new directory created, add watch. + // If recursive and new directory created, add watch recursively so that + // any nested sub-directories are also monitored. if recursive && evType == "CREATE" && isDir { - if err := watcher.Add(ev.Name); err != nil { - log.Error("failed to watch new directory", "err", err, "path", ev.Name) + if err := addRecursive(watcher, ev.Name); err != nil { + log.Error("failed to recursively watch new directory", "err", err, "path", ev.Name) } } case err, ok := <-watcher.Errors: @@ -460,7 +471,7 @@ func (s *ApiService) StopFsWatch(ctx context.Context, req oapi.StopFsWatchReques w, ok := s.watches[id] if ok { delete(s.watches, id) - w.watcher.Close() + w.Close() // channel will be closed by the event forwarding goroutine } s.watchMu.Unlock() From e6af0d95128d1788a1ff53b820c61055f95b677d Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Wed, 6 Aug 2025 18:12:54 -0400 Subject: [PATCH 18/20] good bot --- shared/ensure-common-build-run-vars.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/ensure-common-build-run-vars.sh b/shared/ensure-common-build-run-vars.sh index 4fcafa18..404c6da6 100644 --- a/shared/ensure-common-build-run-vars.sh +++ b/shared/ensure-common-build-run-vars.sh @@ -7,7 +7,7 @@ if [ -z "$IMAGE_TYPE" ]; then echo "e.g. source ensure-common-build-run-vars.sh chromium-headful" echo "This will set the defaults for the image name and test instance name" echo "You can override the defaults by setting the IMAGE and NAME variables" - exit 1 + return 1 fi IMAGE="${IMAGE:-onkernel/${IMAGE_TYPE}-test:latest}" NAME="${NAME:-${IMAGE_TYPE}-test}" From 9da2bb1efeca625521b036b26a10f622e38639a3 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Wed, 6 Aug 2025 19:10:46 -0400 Subject: [PATCH 19/20] pr comments --- server/cmd/api/api/fs.go | 45 +++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index bb46473b..d1cf8267 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -1,6 +1,7 @@ package api import ( + "bytes" "context" "encoding/json" "errors" @@ -69,7 +70,7 @@ func (s *ApiService) ReadFile(ctx context.Context, req oapi.ReadFileRequestObjec if err != nil { f.Close() log.Error("failed to stat file", "err", err, "path", path) - return oapi.ReadFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to stat file"}}, nil + return oapi.ReadFile500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "unable to stat file"}}, nil } return oapi.ReadFile200ApplicationoctetStreamResponse{ @@ -100,6 +101,9 @@ func (s *ApiService) WriteFile(ctx context.Context, req oapi.WriteFileRequestObj if req.Params.Mode != nil { if v, err := strconv.ParseUint(*req.Params.Mode, 8, 32); err == nil { perm = os.FileMode(v) + } else { + log.Error("invalid mode", "mode", *req.Params.Mode) + return oapi.WriteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "invalid mode"}}, nil } } @@ -113,7 +117,7 @@ func (s *ApiService) WriteFile(ctx context.Context, req oapi.WriteFileRequestObj if _, err := io.Copy(f, req.Body); err != nil { log.Error("failed to write file", "err", err, "path", path) - return oapi.WriteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "failed to write data"}}, nil + return oapi.WriteFile500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to write data"}}, nil } return oapi.WriteFile201Response{}, nil @@ -134,6 +138,9 @@ func (s *ApiService) CreateDirectory(ctx context.Context, req oapi.CreateDirecto if req.Body.Mode != nil { if v, err := strconv.ParseUint(*req.Body.Mode, 8, 32); err == nil { perm = os.FileMode(v) + } else { + log.Error("invalid mode", "mode", *req.Body.Mode) + return oapi.CreateDirectory400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "invalid mode"}}, nil } } if err := os.MkdirAll(path, perm); err != nil { @@ -244,11 +251,17 @@ func (s *ApiService) FileInfo(ctx context.Context, req oapi.FileInfoRequestObjec log.Error("failed to stat path", "err", err, "path", path) return oapi.FileInfo500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "failed to stat path"}}, nil } + // By specification SizeBytes should be 0 for directories. + // Match behaviour of ListFiles for consistency. + size := 0 + if !stat.IsDir() { + size = int(stat.Size()) + } fi := oapi.FileInfo{ Name: filepath.Base(path), Path: path, IsDir: stat.IsDir(), - SizeBytes: int(stat.Size()), + SizeBytes: size, ModTime: stat.ModTime(), Mode: stat.Mode().String(), } @@ -357,7 +370,8 @@ func (s *ApiService) StartFsWatch(ctx context.Context, req oapi.StartFsWatchRequ if errors.Is(err, os.ErrNotExist) { return oapi.StartFsWatch404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "path not found"}}, nil } - return oapi.StartFsWatch400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to stat path"}}, nil + log.Error("failed to stat path", "err", err, "path", path) + return oapi.StartFsWatch500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "unable to stat path"}}, nil } watcher, err := fsnotify.NewWatcher() @@ -498,19 +512,22 @@ func (s *ApiService) StreamFsEvents(ctx context.Context, req oapi.StreamFsEvents pr, pw := io.Pipe() go func() { defer pw.Close() - enc := json.NewEncoder(pw) for ev := range w.events { - // Write SSE formatted event: data: \n\n - if _, err := pw.Write([]byte("data: ")); err != nil { - log.Error("failed to write SSE prefix", "err", err) - return - } - if err := enc.Encode(ev); err != nil { - log.Error("failed to encode fs event", "err", err) + // Build SSE formatted event: data: \n\n using a buffer and write in a single call + data, err := json.Marshal(ev) + if err != nil { + log.Error("failed to marshal fs event", "err", err) return } - if _, err := pw.Write([]byte("\n")); err != nil { - log.Error("failed to write SSE terminator", "err", err) + + var buf bytes.Buffer + buf.Grow(len("data: ") + len(data) + 2) // 2 for the separating newlines + buf.WriteString("data: ") + buf.Write(data) + buf.WriteString("\n\n") + + if _, err := pw.Write(buf.Bytes()); err != nil { + log.Error("failed to write SSE event", "err", err) return } } From bc286267d9ad06fa4da0f99d493ea90e22fcca03 Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Wed, 6 Aug 2025 19:19:59 -0400 Subject: [PATCH 20/20] more 400s -> 500s --- server/cmd/api/api/fs.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/cmd/api/api/fs.go b/server/cmd/api/api/fs.go index d1cf8267..3ba23fc1 100644 --- a/server/cmd/api/api/fs.go +++ b/server/cmd/api/api/fs.go @@ -63,7 +63,7 @@ func (s *ApiService) ReadFile(ctx context.Context, req oapi.ReadFileRequestObjec return oapi.ReadFile404JSONResponse{NotFoundErrorJSONResponse: oapi.NotFoundErrorJSONResponse{Message: "file not found"}}, nil } log.Error("failed to open file", "err", err, "path", path) - return oapi.ReadFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to open file"}}, nil + return oapi.ReadFile500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "unable to open file"}}, nil } stat, err := f.Stat() @@ -93,7 +93,7 @@ func (s *ApiService) WriteFile(ctx context.Context, req oapi.WriteFileRequestObj // create parent directories if necessary if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { log.Error("failed to create directories", "err", err, "path", path) - return oapi.WriteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to create directories"}}, nil + return oapi.WriteFile500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "unable to create directories"}}, nil } // determine desired file mode (default 0o644) @@ -111,7 +111,7 @@ func (s *ApiService) WriteFile(ctx context.Context, req oapi.WriteFileRequestObj f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm) if err != nil { log.Error("failed to create file", "err", err, "path", path) - return oapi.WriteFile400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{Message: "unable to create file"}}, nil + return oapi.WriteFile500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{Message: "unable to create file"}}, nil } defer f.Close()