diff options
author | Bjørn Erik Pedersen <[email protected]> | 2024-05-17 17:06:47 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2024-06-04 16:07:39 +0200 |
commit | 447108fed2842e264897659856e9fd9cdc32ca23 (patch) | |
tree | 53687693e04496919dd84266cc1edc16746101b0 /common | |
parent | c71e24af5172e230baa5f7dfa2078721cda38df4 (diff) | |
download | hugo-447108fed2842e264897659856e9fd9cdc32ca23.tar.gz hugo-447108fed2842e264897659856e9fd9cdc32ca23.zip |
Add a HTTP cache for remote resources.
Fixes #12502
Closes #11891
Diffstat (limited to 'common')
-rw-r--r-- | common/maps/maps.go | 8 | ||||
-rw-r--r-- | common/maps/maps_test.go | 6 | ||||
-rw-r--r-- | common/predicate/predicate.go | 6 | ||||
-rw-r--r-- | common/tasks/tasks.go | 153 | ||||
-rw-r--r-- | common/types/closer.go | 47 |
5 files changed, 214 insertions, 6 deletions
diff --git a/common/maps/maps.go b/common/maps/maps.go index 2686baad6..f9171ebf2 100644 --- a/common/maps/maps.go +++ b/common/maps/maps.go @@ -112,17 +112,17 @@ func ToSliceStringMap(in any) ([]map[string]any, error) { } // LookupEqualFold finds key in m with case insensitive equality checks. -func LookupEqualFold[T any | string](m map[string]T, key string) (T, bool) { +func LookupEqualFold[T any | string](m map[string]T, key string) (T, string, bool) { if v, found := m[key]; found { - return v, true + return v, key, true } for k, v := range m { if strings.EqualFold(k, key) { - return v, true + return v, k, true } } var s T - return s, false + return s, "", false } // MergeShallow merges src into dst, but only if the key does not already exist in dst. diff --git a/common/maps/maps_test.go b/common/maps/maps_test.go index 098098388..b4f9c5a3d 100644 --- a/common/maps/maps_test.go +++ b/common/maps/maps_test.go @@ -180,16 +180,18 @@ func TestLookupEqualFold(t *testing.T) { "B": "bv", } - v, found := LookupEqualFold(m1, "b") + v, k, found := LookupEqualFold(m1, "b") c.Assert(found, qt.IsTrue) c.Assert(v, qt.Equals, "bv") + c.Assert(k, qt.Equals, "B") m2 := map[string]string{ "a": "av", "B": "bv", } - v, found = LookupEqualFold(m2, "b") + v, k, found = LookupEqualFold(m2, "b") c.Assert(found, qt.IsTrue) + c.Assert(k, qt.Equals, "B") c.Assert(v, qt.Equals, "bv") } diff --git a/common/predicate/predicate.go b/common/predicate/predicate.go index f9cb1bb2b..f71536474 100644 --- a/common/predicate/predicate.go +++ b/common/predicate/predicate.go @@ -24,6 +24,9 @@ func (p P[T]) And(ps ...P[T]) P[T] { return false } } + if p == nil { + return true + } return p(v) } } @@ -36,6 +39,9 @@ func (p P[T]) Or(ps ...P[T]) P[T] { return true } } + if p == nil { + return false + } return p(v) } } diff --git a/common/tasks/tasks.go b/common/tasks/tasks.go new file mode 100644 index 000000000..1f7e061f9 --- /dev/null +++ b/common/tasks/tasks.go @@ -0,0 +1,153 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "sync" + "time" +) + +// RunEvery runs a function at intervals defined by the function itself. +// Functions can be added and removed while running. +type RunEvery struct { + // Any error returned from the function will be passed to this function. + HandleError func(string, error) + + // If set, the function will be run immediately. + RunImmediately bool + + // The named functions to run. + funcs map[string]*Func + + mu sync.Mutex + started bool + closed bool + quit chan struct{} +} + +type Func struct { + // The shortest interval between each run. + IntervalLow time.Duration + + // The longest interval between each run. + IntervalHigh time.Duration + + // The function to run. + F func(interval time.Duration) (time.Duration, error) + + interval time.Duration + last time.Time +} + +func (r *RunEvery) Start() error { + if r.started { + return nil + } + + r.started = true + r.quit = make(chan struct{}) + + go func() { + if r.RunImmediately { + r.run() + } + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + for { + select { + case <-r.quit: + return + case <-ticker.C: + r.run() + } + } + }() + + return nil +} + +// Close stops the RunEvery from running. +func (r *RunEvery) Close() error { + if r.closed { + return nil + } + r.closed = true + if r.quit != nil { + close(r.quit) + } + return nil +} + +// Add adds a function to the RunEvery. +func (r *RunEvery) Add(name string, f Func) { + r.mu.Lock() + defer r.mu.Unlock() + if r.funcs == nil { + r.funcs = make(map[string]*Func) + } + if f.IntervalLow == 0 { + f.IntervalLow = 500 * time.Millisecond + } + if f.IntervalHigh <= f.IntervalLow { + f.IntervalHigh = 20 * time.Second + } + + start := f.IntervalHigh / 3 + if start < f.IntervalLow { + start = f.IntervalLow + } + f.interval = start + f.last = time.Now() + + r.funcs[name] = &f +} + +// Remove removes a function from the RunEvery. +func (r *RunEvery) Remove(name string) { + r.mu.Lock() + defer r.mu.Unlock() + delete(r.funcs, name) +} + +// Has returns whether the RunEvery has a function with the given name. +func (r *RunEvery) Has(name string) bool { + r.mu.Lock() + defer r.mu.Unlock() + _, found := r.funcs[name] + return found +} + +func (r *RunEvery) run() { + r.mu.Lock() + defer r.mu.Unlock() + for name, f := range r.funcs { + if time.Now().Before(f.last.Add(f.interval)) { + continue + } + f.last = time.Now() + interval, err := f.F(f.interval) + if err != nil && r.HandleError != nil { + r.HandleError(name, err) + } + + if interval < f.IntervalLow { + interval = f.IntervalLow + } + + if interval > f.IntervalHigh { + interval = f.IntervalHigh + } + f.interval = interval + } +} diff --git a/common/types/closer.go b/common/types/closer.go new file mode 100644 index 000000000..2844b1986 --- /dev/null +++ b/common/types/closer.go @@ -0,0 +1,47 @@ +// Copyright 2024 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import "sync" + +type Closer interface { + Close() error +} + +type CloseAdder interface { + Add(Closer) +} + +type Closers struct { + mu sync.Mutex + cs []Closer +} + +func (cs *Closers) Add(c Closer) { + cs.mu.Lock() + defer cs.mu.Unlock() + cs.cs = append(cs.cs, c) +} + +func (cs *Closers) Close() error { + cs.mu.Lock() + defer cs.mu.Unlock() + for _, c := range cs.cs { + c.Close() + } + + cs.cs = cs.cs[:0] + + return nil +} |