chore: simplify drivesomethinggreater (#30391)
This commit is contained in:
parent
471cebdb12
commit
5409ed21a8
5 changed files with 55 additions and 106 deletions
|
|
@ -3,6 +3,7 @@ package request
|
|||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
|
|
@ -100,6 +101,13 @@ func headerContainsToken(h http.Header, key, token string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// binaryContent reports whether the response carries a binary body whose raw
|
||||
// bytes are not useful in the trace log, such as a downloaded archive.
|
||||
func binaryContent(h http.Header) bool {
|
||||
mediatype, _, _ := mime.ParseMediaType(h.Get("Content-Type"))
|
||||
return mediatype == "application/octet-stream"
|
||||
}
|
||||
|
||||
// copy of http.drainBody
|
||||
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
|
||||
if b == nil || b == http.NoBody {
|
||||
|
|
@ -174,12 +182,14 @@ func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
resMetric.WithLabelValues(req.URL.Hostname(), strconv.Itoa(resp.StatusCode)).Add(1)
|
||||
|
||||
if !isWebSocketReq {
|
||||
// skip binary bodies (e.g. downloaded archives) - their raw bytes are noise in the trace log
|
||||
logBody := !binaryContent(resp.Header)
|
||||
if LogHeaders {
|
||||
if body, err := httputil.DumpResponse(resp, true); err == nil {
|
||||
if body, err := httputil.DumpResponse(resp, logBody); err == nil {
|
||||
bld.WriteString("\n\n")
|
||||
bld.Write(bytes.TrimSpace(body[:min(LogMaxLen, len(body))]))
|
||||
}
|
||||
} else {
|
||||
} else if logBody {
|
||||
if save, resp.Body, err = drainBody(resp.Body); err == nil {
|
||||
err = dump(save, bld)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,36 +120,12 @@ func TestResolveBrand(t *testing.T) {
|
|||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestContentDatasets(t *testing.T) {
|
||||
list := []dataset{
|
||||
{Name: "20260531090000_WVWZZZ.zip"},
|
||||
{Name: "20260531080000_WVWZZZ.zip"},
|
||||
{Name: "20260531091500_WVWZZZ_no_content_found.zip"},
|
||||
}
|
||||
|
||||
content, err := contentDatasets(list)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, content, 2, "no-content placeholder dropped")
|
||||
assert.Equal(t, "20260531080000_WVWZZZ.zip", content[0].Name, "oldest first")
|
||||
assert.Equal(t, "20260531090000_WVWZZZ.zip", content[1].Name, "newest last")
|
||||
assert.Equal(t, time.Date(2026, 5, 31, 8, 0, 0, 0, time.UTC), content[0].Timestamp, "timestamp parsed")
|
||||
|
||||
// no-content placeholders are skipped without parsing
|
||||
empty, err := contentDatasets([]dataset{{Name: "x_no_content_found.zip"}})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, empty)
|
||||
|
||||
// a content dataset with an unparseable timestamp is an error
|
||||
_, err = contentDatasets([]dataset{{Name: "no-timestamp.zip"}})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPending(t *testing.T) {
|
||||
content := make([]dataset, 0, 11)
|
||||
for i := range 10 {
|
||||
content = append(content, dataset{
|
||||
Name: fmt.Sprintf("20260531%02d0000_WVWZZZ.zip", i),
|
||||
Timestamp: time.Date(2026, 5, 31, i, 0, 0, 0, time.UTC),
|
||||
CreatedOn: time.Date(2026, 5, 31, i, 0, 0, 0, time.UTC),
|
||||
}) // hour i, oldest first
|
||||
}
|
||||
|
||||
|
|
@ -163,13 +139,13 @@ func TestPending(t *testing.T) {
|
|||
assert.Len(t, pending(content[:3], time.Time{}), 3)
|
||||
|
||||
// high-water at the newest merged dataset: nothing new to download
|
||||
after := content[len(content)-1].Timestamp
|
||||
after := content[len(content)-1].CreatedOn
|
||||
assert.Empty(t, pending(content, after))
|
||||
|
||||
// a newer dataset arrives
|
||||
content = append(content, dataset{
|
||||
Name: "20260531100000_WVWZZZ.zip",
|
||||
Timestamp: time.Date(2026, 5, 31, 10, 0, 0, 0, time.UTC),
|
||||
CreatedOn: time.Date(2026, 5, 31, 10, 0, 0, 0, time.UTC),
|
||||
})
|
||||
got = pending(content, after)
|
||||
require.Len(t, got, 1)
|
||||
|
|
@ -197,32 +173,6 @@ func TestMerge(t *testing.T) {
|
|||
assert.Equal(t, "200", dst[FieldRange].Value, "new field added")
|
||||
}
|
||||
|
||||
func TestDatasetTime(t *testing.T) {
|
||||
ref := time.Date(2026, 5, 31, 8, 0, 0, 0, time.UTC)
|
||||
|
||||
tc := []struct {
|
||||
d dataset
|
||||
expected time.Time
|
||||
err bool
|
||||
}{
|
||||
{dataset{Name: "20260531080000_WVWZZZ_no_content_found.zip"}, ref, false}, // real portal format
|
||||
{dataset{Name: "20260531080000_WVWZZZ.zip"}, ref, false}, // content file
|
||||
{dataset{CreatedOn: "2026-05-31T08:00:00Z"}, ref, false}, // createdOn fallback
|
||||
{dataset{CreatedOn: "2026-05-31T08:00:00Z", Name: "no-stamp.zip"}, ref, false}, // name unparseable, createdOn used
|
||||
{dataset{Name: "no-timestamp.zip"}, time.Time{}, true}, // nothing parseable
|
||||
}
|
||||
|
||||
for _, tc := range tc {
|
||||
got, err := tc.d.time()
|
||||
if tc.err {
|
||||
assert.Error(t, err, "dataset %+v", tc.d)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err, "dataset %+v", tc.d)
|
||||
assert.Equal(t, tc.expected, got, "dataset %+v", tc.d)
|
||||
}
|
||||
}
|
||||
|
||||
// TestResetDelay verifies the cache reset is scheduled for when the portal is
|
||||
// expected to deliver the dataset following the one just read.
|
||||
func TestResetDelay(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -64,13 +64,13 @@ func resetDelay(ts, now time.Time) time.Duration {
|
|||
}
|
||||
|
||||
// lookup returns the first present, non-empty value among the given field names
|
||||
func lookup(data map[string]point, fields ...string) (string, bool) {
|
||||
func lookup(data map[string]point, fields ...string) *point {
|
||||
for _, f := range fields {
|
||||
if v, ok := data[f]; ok && v.Value != "" {
|
||||
return v.Value, true
|
||||
if v, ok := data[f]; ok {
|
||||
return new(v)
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ api.Battery = (*Provider)(nil)
|
||||
|
|
@ -82,8 +82,8 @@ func (v *Provider) Soc() (float64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
if s, ok := lookup(data, FieldSoc, FieldHvSoc); ok {
|
||||
return strconv.ParseFloat(s, 64)
|
||||
if p := lookup(data, FieldSoc, FieldHvSoc); p != nil {
|
||||
return strconv.ParseFloat(p.Value, 64)
|
||||
}
|
||||
|
||||
return 0, api.ErrNotAvailable
|
||||
|
|
@ -98,14 +98,32 @@ func (v *Provider) Range() (int64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
if s, ok := lookup(data, FieldRange, FieldRangePrimary); ok {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if p := lookup(data, FieldRange, FieldRangePrimary); p != nil {
|
||||
f, err := strconv.ParseFloat(p.Value, 64)
|
||||
return int64(f), err
|
||||
}
|
||||
|
||||
return 0, api.ErrNotAvailable
|
||||
}
|
||||
|
||||
var _ api.VehicleFinishTimer = (*Provider)(nil)
|
||||
|
||||
// FinishTime implements the api.VehicleFinishTimer interface
|
||||
func (v *Provider) FinishTime() (time.Time, error) {
|
||||
data, err := v.statusG()
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
if p := lookup(data, FieldRemainingTime); p != nil && p.Value != "65535" {
|
||||
if v, err := strconv.ParseInt(p.Value, 0, 64); err == nil {
|
||||
return p.Timestamp.Add(time.Duration(v) * time.Minute), nil
|
||||
}
|
||||
}
|
||||
|
||||
return time.Time{}, api.ErrNotAvailable
|
||||
}
|
||||
|
||||
var _ api.VehicleOdometer = (*Provider)(nil)
|
||||
|
||||
// Odometer implements the api.VehicleOdometer interface
|
||||
|
|
@ -115,8 +133,8 @@ func (v *Provider) Odometer() (float64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
if s, ok := lookup(data, FieldOdometer); ok {
|
||||
return strconv.ParseFloat(s, 64)
|
||||
if p := lookup(data, FieldOdometer); p != nil {
|
||||
return strconv.ParseFloat(p.Value, 64)
|
||||
}
|
||||
|
||||
return 0, api.ErrNotAvailable
|
||||
|
|
@ -133,11 +151,11 @@ func (v *Provider) Status() (api.ChargeStatus, error) {
|
|||
return status, err
|
||||
}
|
||||
|
||||
if s, ok := lookup(data, FieldPlugState); ok && strings.EqualFold(s, "connected") {
|
||||
if p := lookup(data, FieldPlugState); p != nil && strings.EqualFold(p.Value, "connected") {
|
||||
status = api.StatusB
|
||||
}
|
||||
|
||||
if s, ok := lookup(data, FieldChargingState); ok && strings.EqualFold(s, "charging") {
|
||||
if p := lookup(data, FieldChargingState); p != nil && strings.EqualFold(p.Value, "charging") {
|
||||
status = api.StatusC
|
||||
}
|
||||
|
||||
|
|
@ -153,8 +171,8 @@ func (v *Provider) GetLimitSoc() (int64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
if s, ok := lookup(data, FieldTargetSoc); ok {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if p := lookup(data, FieldTargetSoc); p != nil {
|
||||
f, err := strconv.ParseFloat(p.Value, 64)
|
||||
return int64(f), err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,12 +66,8 @@ func (s *store) update() (time.Time, error) {
|
|||
|
||||
var newest time.Time
|
||||
for _, d := range list {
|
||||
t, err := d.time()
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
if t.After(newest) {
|
||||
newest = t
|
||||
if d.CreatedOn.After(newest) {
|
||||
newest = d.CreatedOn
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,8 +90,8 @@ func (s *store) update() (time.Time, error) {
|
|||
merge(s.data, data)
|
||||
|
||||
// advance the high-water mark so this dataset is never downloaded again
|
||||
if d.Timestamp.After(s.after) {
|
||||
s.after = d.Timestamp
|
||||
if d.CreatedOn.After(s.after) {
|
||||
s.after = d.CreatedOn
|
||||
}
|
||||
|
||||
if !initial {
|
||||
|
|
@ -146,7 +142,7 @@ func pending(content []dataset, after time.Time) []dataset {
|
|||
|
||||
res := make([]dataset, 0, len(content))
|
||||
for _, d := range content {
|
||||
if d.Timestamp.After(after) {
|
||||
if d.CreatedOn.After(after) {
|
||||
res = append(res, d)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
|
|
@ -78,26 +77,7 @@ func (v Vehicle) Name() string {
|
|||
// createdOn field.
|
||||
type dataset struct {
|
||||
Name string `json:"name"`
|
||||
CreatedOn string `json:"createdOn"`
|
||||
Timestamp time.Time `json:"-"`
|
||||
}
|
||||
|
||||
// nameTime parses the compact timestamp the portal prefixes to a dataset file
|
||||
// name, e.g. 20260531102941_WAUZZZ..._no_content_found.zip.
|
||||
func nameTime(name string) (time.Time, error) {
|
||||
prefix, _, _ := strings.Cut(name, "_")
|
||||
return time.Parse("20060102150405", prefix)
|
||||
}
|
||||
|
||||
// time parses the delivery time the dataset carries. The portal embeds it in the
|
||||
// file name and also delivers it as the createdOn field; the file name is
|
||||
// preferred and createdOn is the fallback. An error is returned when neither
|
||||
// carries a parseable timestamp.
|
||||
func (d dataset) time() (time.Time, error) {
|
||||
if t, err := nameTime(d.Name); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
return time.Parse(time.RFC3339, d.CreatedOn)
|
||||
CreatedOn time.Time `json:"createdOn"`
|
||||
}
|
||||
|
||||
// dataPoint is a single data point as delivered in the dataset JSON document
|
||||
|
|
@ -129,6 +109,7 @@ const (
|
|||
FieldChargingState = "charging_state"
|
||||
FieldPlugState = "plug_state"
|
||||
FieldTargetSoc = "settings.target_soc"
|
||||
FieldRemainingTime = "remaining_charging_time"
|
||||
)
|
||||
|
||||
// contentDatasets returns the datasets that actually carry content, with their
|
||||
|
|
@ -143,17 +124,11 @@ func contentDatasets(list []dataset) ([]dataset, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
t, err := d.time()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dataset %q: %w", d.Name, err)
|
||||
}
|
||||
d.Timestamp = t
|
||||
|
||||
content = append(content, d)
|
||||
}
|
||||
|
||||
slices.SortStableFunc(content, func(a, b dataset) int {
|
||||
return a.Timestamp.Compare(b.Timestamp)
|
||||
return a.CreatedOn.Compare(b.CreatedOn)
|
||||
})
|
||||
|
||||
return content, nil
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue