Follow a conversion with convert {ask}, each call under 25 s, and leave the SDK's call timeout alone (issue 1600)
This commit is contained in:
@@ -16,11 +16,15 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/objectlink"
|
||||
)
|
||||
|
||||
// ConvertFollow is how long the device process follows one conversion, in calls of under 25 s each.
|
||||
const ConvertFollow = 3 * time.Minute
|
||||
|
||||
// ConvertTypes are the types document-converter takes (clause 7).
|
||||
var ConvertTypes = []string{"docx", "doc", "xlsx", "xls", "pptx", "ppt", "odt", "ods", "odp", "rtf", "txt", "html"}
|
||||
|
||||
@@ -157,12 +161,34 @@ func (w *deviceWorker) toPDF(ctx context.Context, objects Objects, key string, d
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("the document (%s) could not be converted: %w", typ, err)
|
||||
}
|
||||
// The converter answers within 25 s, the tool runner cutting every call at 30 s; a conversion not done by then is
|
||||
// answered as converting with its ask id, and is read again with convert {ask}, each call under 25 s, for as long
|
||||
// as the device process follows a conversion.
|
||||
follow := w.ConvertFollow
|
||||
if follow <= 0 {
|
||||
follow = ConvertFollow
|
||||
}
|
||||
until := time.Now().Add(follow)
|
||||
var ans struct {
|
||||
SHA256 string `json:"sha256"`
|
||||
Error string `json:"error"`
|
||||
State string `json:"state"`
|
||||
Ask string `json:"ask"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &ans); err != nil {
|
||||
return nil, "", fmt.Errorf("the converter's answer is not JSON: %w", err)
|
||||
for {
|
||||
ans.State, ans.Ask, ans.SHA256, ans.Error = "", "", "", ""
|
||||
if err := json.Unmarshal(raw, &ans); err != nil {
|
||||
return nil, "", fmt.Errorf("the converter's answer is not JSON: %w", err)
|
||||
}
|
||||
if ans.State != "converting" {
|
||||
break
|
||||
}
|
||||
if ans.Ask == "" || !time.Now().Before(until) {
|
||||
return nil, "", fmt.Errorf("the document (%s) was still converting after %s (ask %s): nothing is printed", typ, follow, ans.Ask)
|
||||
}
|
||||
if raw, err = w.Convert(map[string]any{"ask": ans.Ask}); err != nil {
|
||||
return nil, "", fmt.Errorf("the document (%s) could not be converted: %w", typ, err)
|
||||
}
|
||||
}
|
||||
if ans.Error != "" {
|
||||
return nil, "", fmt.Errorf("the document (%s) could not be converted: %s", typ, ans.Error)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -47,8 +48,10 @@ func withConverter(r *printerRig, pdf []byte) *converterFake {
|
||||
c := &converterFake{r: r, pdf: pdf}
|
||||
r.worker.Convert = func(body map[string]any) (json.RawMessage, error) {
|
||||
c.calls = append(c.calls, body)
|
||||
target, _ := url.Parse(body["target"].(string))
|
||||
r.objects.Put(context.Background(), strings.TrimPrefix(target.Path, "/"), "application/pdf", c.pdf)
|
||||
if body["target"] != nil { // a start puts the PDF; a read of an ask (convert {ask}) puts nothing
|
||||
target, _ := url.Parse(body["target"].(string))
|
||||
r.objects.Put(context.Background(), strings.TrimPrefix(target.Path, "/"), "application/pdf", c.pdf)
|
||||
}
|
||||
if c.answer != nil {
|
||||
return c.answer(body, c.pdf)
|
||||
}
|
||||
@@ -215,3 +218,51 @@ func TestTheDeviceProcessInvokesTheConverter(t *testing.T) {
|
||||
}
|
||||
t.Fatal("no device process")
|
||||
}
|
||||
|
||||
// 1600 (review round 1): a conversion answered as converting is followed with convert {ask}, each call its own, until
|
||||
// it is done; one still converting after the device process's bound is not printed, and the process.CallTimeout of the
|
||||
// SDK is not touched.
|
||||
func TestAConversionAnsweredAsConvertingIsFollowedWithItsAsk(t *testing.T) {
|
||||
fake := newIPPFake(t, 61, 5)
|
||||
r := newRig(t, `{"printer":"192.0.2.10"}`, "pw")
|
||||
doc := docx(t)
|
||||
key := sha256Hex(doc)
|
||||
r.objects.Put(t.Context(), key, "application/octet-stream", doc)
|
||||
pdf := pdfOf(700)
|
||||
conv := withConverter(r, pdf)
|
||||
rounds := 0
|
||||
conv.answer = func(body map[string]any, stored []byte) (json.RawMessage, error) {
|
||||
if body["ask"] != nil {
|
||||
rounds++
|
||||
if rounds < 3 {
|
||||
return json.RawMessage(`{"state": "converting", "ask": "` + body["ask"].(string) + `"}`), nil
|
||||
}
|
||||
return json.Marshal(map[string]any{"size": len(stored), "sha256": sha256Hex(stored)})
|
||||
}
|
||||
return json.RawMessage(`{"state": "converting", "ask": "a1b2c3a1b2c3a1b2c3a1b2c3"}`), nil
|
||||
}
|
||||
if _, err := r.call(t, "printer.print", map[string]any{"object": key}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(conv.calls) != 4 || conv.calls[1]["ask"] != "a1b2c3a1b2c3a1b2c3a1b2c3" || conv.calls[1]["source"] != nil {
|
||||
t.Errorf("calls = %v", conv.calls)
|
||||
}
|
||||
if _, sent := fake.request(t); string(sent) != string(pdf) {
|
||||
t.Error("the converted PDF was not printed")
|
||||
}
|
||||
|
||||
// Always converting: given up at the bound, nothing printed.
|
||||
fake.body = nil
|
||||
r.worker.ConvertFollow = 30 * time.Millisecond
|
||||
conv.answer = func(body map[string]any, _ []byte) (json.RawMessage, error) {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
return json.RawMessage(`{"state": "converting", "ask": "a1b2c3a1b2c3a1b2c3a1b2c3"}`), nil
|
||||
}
|
||||
if _, err := r.call(t, "printer.print", map[string]any{"object": key}); err == nil || !strings.Contains(err.Error(), "still converting") || fake.body != nil {
|
||||
t.Errorf("error = %v, printed %v", err, fake.body != nil)
|
||||
}
|
||||
src, _ := os.ReadFile("deviceproc.go")
|
||||
if strings.Contains(string(src), "CallTimeout") {
|
||||
t.Error("deviceproc.go sets the SDK's call timeout")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ type deviceWorker struct {
|
||||
Capture func(ctx context.Context, req ScanRequest) ([]Capture, error)
|
||||
// Convert calls the seat document-converter's convert verb as this process's own account (clause 7); nil: none.
|
||||
Convert func(body map[string]any) (json.RawMessage, error)
|
||||
// ConvertFollow is how long a conversion is followed; zero is ConvertFollow (a test makes it shorter).
|
||||
ConvertFollow time.Duration
|
||||
// Fetcher is the guarded fetch of a web link (fetch.go); it reads its facts when it is first needed.
|
||||
Fetcher func() (*Fetcher, error)
|
||||
// answerPart is the raw size of a document part in an answer; zero is AnswerPartSize (a test makes it larger).
|
||||
@@ -612,8 +614,6 @@ func runDevice(ctx context.Context, account process.Account, w *deviceWorker) er
|
||||
}
|
||||
defer conn.Close()
|
||||
w.Answers = conn.State(AnswersState)
|
||||
// A conversion takes longer than the default call bound (30 s); the tool side waits ConvertWait for the converter.
|
||||
process.CallTimeout = ConvertCallWait
|
||||
w.Convert = func(body map[string]any) (json.RawMessage, error) {
|
||||
return conn.Call("seat:document-converter.convert", body)
|
||||
}
|
||||
|
||||
@@ -36,9 +36,6 @@ const (
|
||||
// DeviceProcessID is the device process's id as module.json's resource "device" names it: the controller
|
||||
// delivers its bus account as the own secret broker-device.
|
||||
DeviceProcessID = "device"
|
||||
// ConvertCallWait is how long the device process waits for document-converter.convert (the tool side of
|
||||
// only-office waits two minutes for the converter).
|
||||
ConvertCallWait = 130 * time.Second
|
||||
// PrintWait is how long printer.print waits for the device process: a fetch and a conversion come before the job.
|
||||
PrintWait = 4 * time.Minute
|
||||
// AnswerWait is how long a verb waits for the device process; ScanWait how long a scan does.
|
||||
|
||||
Reference in New Issue
Block a user