From 2c19b50f9d726782ff691dc9f1fdf034eb98bb25 Mon Sep 17 00:00:00 2001 From: Vitali Date: Sun, 26 Jul 2026 13:08:47 +0000 Subject: [PATCH] Fix truncated upload of post-processed G-Code (Content-Length mismatch) When SMFix post-processing changes a G-Code file's size and no fixed file was pre-saved to disk (the default -- no -o/OutputDir), Upload() streamed the fixed content but announced the *original*, pre-fix size as the multipart FileSize/Content-Length: StreamContent only updates payload.Size asynchronously inside its pipe goroutine, whereas req.FileUpload.FileSize is read when the struct is built. The printer then received a size-mismatched multipart body, silently discarded it (HTTP still returned OK to sm2uploader) and kept the previously loaded job -- so a following start printed the *old* file. The effect scaled with size; large fixed uploads were hit the hardest. Fix: for the post-processed-without-FixedFile path, materialize the fixed content up front via GetContent and set FileSize to its exact length. Non-fixed uploads and OutputDir/FixedFile uploads keep streaming unchanged (their payload.Size is already correct). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_018zWUoJhzouLTnkrpZE7rkJ --- connector_http.go | 67 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/connector_http.go b/connector_http.go index 4a604e6..f7486dc 100644 --- a/connector_http.go +++ b/connector_http.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "io" "log" @@ -152,24 +153,54 @@ func (hc *HTTPConnector) Upload(payload *Payload) (err error) { log.SetOutput(os.Stderr) }() - file := req.FileUpload{ - ParamName: "file", - FileName: payload.Name, - GetFileContent: func() (io.ReadCloser, error) { - rc, err := payload.StreamContent(NoFix) - if !NoFix && err == nil && payload.ShouldBeFix() { - log.SetOutput(os.Stderr) - log.Printf("G-Code fixed") - log.SetOutput(w) - } else if err != nil { - log.SetOutput(os.Stderr) - log.Printf("G-Code fix error(ignored): %s", err) - log.SetOutput(w) - } - return rc, err - }, - FileSize: payload.Size, - // ContentType: "application/octet-stream", + var file req.FileUpload + if !NoFix && payload.ShouldBeFix() && payload.FixedFile == "" { + // FileSize below is captured when the FileUpload struct is built, so it must + // already equal the exact number of bytes that will be streamed. For a + // post-processed (SMFix) G-Code with no pre-saved fixed file, StreamContent + // only learns the fixed size asynchronously in its pipe goroutine -- too + // late. payload.Size (the original, pre-fix size) would then be sent as the + // Content-Length, the printer receives a size-mismatched upload, silently + // discards it and keeps the previously loaded job. Materialize this case up + // front so both the streamed content and FileSize are exact. + content, cerr := payload.GetContent(NoFix) + if cerr != nil { + log.SetOutput(os.Stderr) + log.Printf("G-Code fix error: %s", cerr) + return cerr + } + log.SetOutput(os.Stderr) + log.Printf("G-Code fixed") + log.SetOutput(w) + file = req.FileUpload{ + ParamName: "file", + FileName: payload.Name, + GetFileContent: func() (io.ReadCloser, error) { + return io.NopCloser(bytes.NewReader(content)), nil + }, + FileSize: int64(len(content)), + } + } else { + // Non-fixed uploads, and files already pre-processed to disk (OutputDir), + // have a correct payload.Size, so stream them directly. + file = req.FileUpload{ + ParamName: "file", + FileName: payload.Name, + GetFileContent: func() (io.ReadCloser, error) { + rc, err := payload.StreamContent(NoFix) + if !NoFix && err == nil && payload.ShouldBeFix() { + log.SetOutput(os.Stderr) + log.Printf("G-Code fixed") + log.SetOutput(w) + } else if err != nil { + log.SetOutput(os.Stderr) + log.Printf("G-Code fix error(ignored): %s", err) + log.SetOutput(w) + } + return rc, err + }, + FileSize: payload.Size, + } } r := hc.request(0) r.SetFileUpload(file)