mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 13:23:14 +01:00
Add external label service support to label maker (#913)
* Add external label service support to label maker * Make external label service fetch to include user agent, limit response size and allow any image type * Fix linting errors * Fix "response body closed" closing the Body to soon
This commit is contained in:
@@ -29,7 +29,7 @@ func generateOrPrint(ctrl *V1Controller, w http.ResponseWriter, r *http.Request,
|
||||
_, err = w.Write([]byte("Printed!"))
|
||||
return err
|
||||
} else {
|
||||
return labelmaker.GenerateLabel(w, ¶ms)
|
||||
return labelmaker.GenerateLabel(w, ¶ms, ctrl.config)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,8 @@ type LabelMakerConf struct {
|
||||
PrintCommand *string `yaml:"string"`
|
||||
AdditionalInformation *string `yaml:"string"`
|
||||
DynamicLength bool `yaml:"bool" conf:"default:true"`
|
||||
LabelServiceUrl *string `yaml:"label_service_url"`
|
||||
LabelServiceTimeout *time.Duration `yaml:"label_service_timeout"`
|
||||
}
|
||||
|
||||
type BarcodeAPIConf struct {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"image/png"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -138,11 +140,18 @@ func wrapText(text string, face font.Face, maxWidth int, maxHeight int, lineHeig
|
||||
return wrappedLines, ""
|
||||
}
|
||||
|
||||
func GenerateLabel(w io.Writer, params *GenerateParameters) error {
|
||||
func GenerateLabel(w io.Writer, params *GenerateParameters, cfg *config.Config) error {
|
||||
if err := params.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If LabelServiceUrl is configured, fetch the label from the URL instead of generating it
|
||||
if cfg != nil && cfg.LabelMaker.LabelServiceUrl != nil && *cfg.LabelMaker.LabelServiceUrl != "" {
|
||||
log.Printf("LabelServiceUrl configured: %s", *cfg.LabelMaker.LabelServiceUrl)
|
||||
|
||||
return fetchLabelFromURL(w, *cfg.LabelMaker.LabelServiceUrl, params, cfg)
|
||||
}
|
||||
|
||||
bodyText := params.DescriptionText
|
||||
if params.AdditionalInformation != nil {
|
||||
bodyText = bodyText + "\n" + *params.AdditionalInformation
|
||||
@@ -218,7 +227,7 @@ func GenerateLabel(w io.Writer, params *GenerateParameters) error {
|
||||
// Create the actual image with calculated height
|
||||
bounds := image.Rect(0, 0, params.Width, requiredHeight)
|
||||
img := image.NewRGBA(bounds)
|
||||
draw.Draw(img, bounds, &image.Uniform{color.White}, image.Point{}, draw.Src)
|
||||
draw.Draw(img, bounds, &image.Uniform{C: color.White}, image.Point{}, draw.Src)
|
||||
|
||||
// Draw QR code onto the image
|
||||
draw.Draw(img,
|
||||
@@ -279,6 +288,98 @@ func createContext(font *truetype.Font, size float64, img *image.RGBA, dpi float
|
||||
return c
|
||||
}
|
||||
|
||||
// fetchLabelFromURL fetches an image from the specified URL and writes it to the writer
|
||||
func fetchLabelFromURL(w io.Writer, serviceURL string, params *GenerateParameters, cfg *config.Config) error {
|
||||
// Parse the base URL
|
||||
baseURL, err := url.Parse(serviceURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse service URL %s: %w", serviceURL, err)
|
||||
}
|
||||
|
||||
// Build query parameters with the same attributes passed to print command
|
||||
query := url.Values{}
|
||||
query.Set("Width", fmt.Sprintf("%d", params.Width))
|
||||
query.Set("Height", fmt.Sprintf("%d", params.Height))
|
||||
query.Set("QrSize", fmt.Sprintf("%d", params.QrSize))
|
||||
query.Set("Margin", fmt.Sprintf("%d", params.Margin))
|
||||
query.Set("ComponentPadding", fmt.Sprintf("%d", params.ComponentPadding))
|
||||
query.Set("TitleText", params.TitleText)
|
||||
query.Set("TitleFontSize", fmt.Sprintf("%f", params.TitleFontSize))
|
||||
query.Set("DescriptionText", params.DescriptionText)
|
||||
query.Set("DescriptionFontSize", fmt.Sprintf("%f", params.DescriptionFontSize))
|
||||
query.Set("Dpi", fmt.Sprintf("%f", params.Dpi))
|
||||
query.Set("URL", params.URL)
|
||||
query.Set("DynamicLength", fmt.Sprintf("%t", params.DynamicLength))
|
||||
|
||||
// Add AdditionalInformation if it exists
|
||||
if params.AdditionalInformation != nil {
|
||||
query.Set("AdditionalInformation", *params.AdditionalInformation)
|
||||
}
|
||||
|
||||
// Set the query parameters
|
||||
baseURL.RawQuery = query.Encode()
|
||||
finalServiceURL := baseURL.String()
|
||||
|
||||
log.Printf("Fetching label from URL: %s", finalServiceURL)
|
||||
|
||||
// Use configured timeout or default to 30 seconds
|
||||
timeout := 30 * time.Second
|
||||
if cfg != nil && cfg.LabelMaker.LabelServiceTimeout != nil {
|
||||
timeout = *cfg.LabelMaker.LabelServiceTimeout
|
||||
}
|
||||
|
||||
// Create HTTP client with configurable timeout
|
||||
client := &http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
// Create HTTP request with custom headers
|
||||
req, err := http.NewRequest("GET", finalServiceURL, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request for URL %s: %w", finalServiceURL, err)
|
||||
}
|
||||
|
||||
// Set custom headers
|
||||
req.Header.Set("User-Agent", "Homebox-LabelMaker/1.0")
|
||||
req.Header.Set("Accept", "image/*")
|
||||
|
||||
// Make HTTP request to the label service
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch label from URL %s: %w", finalServiceURL, err)
|
||||
}
|
||||
|
||||
// Check if the response status is OK
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("label service returned status %d for URL %s", resp.StatusCode, finalServiceURL)
|
||||
}
|
||||
|
||||
// Check if the response is an image
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
if !strings.HasPrefix(contentType, "image/") {
|
||||
return fmt.Errorf("label service returned invalid content type %s, expected image/*", contentType)
|
||||
}
|
||||
|
||||
// Set default max response size (10MB)
|
||||
maxResponseSize := int64(10 << 20)
|
||||
if cfg != nil {
|
||||
maxResponseSize = cfg.Web.MaxUploadSize << 20
|
||||
}
|
||||
limitedReader := io.LimitReader(resp.Body, maxResponseSize)
|
||||
|
||||
// Copy the response body to the writer
|
||||
_, err = io.Copy(w, limitedReader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write fetched label data: %w", err)
|
||||
}
|
||||
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
log.Printf("failed to close response body: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PrintLabel(cfg *config.Config, params *GenerateParameters) error {
|
||||
tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("label-%d.png", time.Now().UnixNano()))
|
||||
f, err := os.OpenFile(tmpFile, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
@@ -292,7 +393,7 @@ func PrintLabel(cfg *config.Config, params *GenerateParameters) error {
|
||||
}
|
||||
}()
|
||||
|
||||
err = GenerateLabel(f, params)
|
||||
err = GenerateLabel(f, params, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user