From 95f79a617fa13b50737c13958a8184801a823d6b Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Fri, 12 Sep 2025 14:10:58 +0800 Subject: [PATCH 1/3] archive --- archive/archive.go | 58 ++++++++++++++++++++++++++++------------------ archive/pack.go | 17 ++++++++++++-- archive/tar.go | 4 ++-- archive/unpack.go | 17 +++++++------- archive/zip.go | 6 ++--- 5 files changed, 65 insertions(+), 37 deletions(-) diff --git a/archive/archive.go b/archive/archive.go index bececa3..516d951 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -1,8 +1,10 @@ package archive import ( + "bufio" + "bytes" "errors" - "os" + "io" ) // Format represents the archive format. @@ -15,9 +17,31 @@ const ( TAR ) +type format struct { + format Format + magic string +} + +var formats = []format{ + {ZIP, zipMagic}, + {TAR, tarMagic}, +} + // ErrFormat indicates that encountered an unknown format. var ErrFormat = errors.New("unknown format") +type reader interface { + io.Reader + Peek(int) ([]byte, error) +} + +func asReader(r io.Reader) reader { + if rr, ok := r.(reader); ok { + return rr + } + return bufio.NewReader(r) +} + func match(magic string, b []byte) bool { if len(magic) != len(b) { return false @@ -30,34 +54,24 @@ func match(magic string, b []byte) bool { return true } -// IsArchive tests b is an archive file or not, if ok also return its format. -func IsArchive(b []byte) (bool, Format) { - l := len(b) - if zip := len(zipMagic); l >= zip && match(zipMagic, b[:zip]) { - return true, ZIP - } else if tar := len(tarMagic); l >= tar && match(tarMagic, b[:tar]) { - return true, TAR +func isArchive(r reader) (bool, Format) { + for _, f := range formats { + b, err := r.Peek(len(f.magic)) + if err == nil && match(f.magic, b) { + return true, f.format + } } - return false, -1 } +// IsArchive tests b is an archive file or not, if ok also return its format. +func IsArchive(b []byte) (bool, Format) { + return isArchive(asReader(bytes.NewReader(b))) +} + // File struct contains bytes body and the provided name field. type File struct { Name string Body []byte IsDir bool } - -func readFiles(files ...string) (fs []File, err error) { - for _, f := range files { - var file File - file.Name = f - file.Body, err = os.ReadFile(f) - if err != nil { - return - } - fs = append(fs, file) - } - return -} diff --git a/archive/pack.go b/archive/pack.go index d7ef63d..00d0625 100644 --- a/archive/pack.go +++ b/archive/pack.go @@ -1,8 +1,8 @@ package archive import ( - "errors" "io" + "os" ) // Pack creates an archive from File struct. @@ -13,7 +13,7 @@ func Pack(w io.Writer, format Format, files ...File) error { case TAR: return packTar(w, files...) default: - return errors.New("unknown format") + return ErrFormat } } @@ -25,3 +25,16 @@ func PackFromFiles(w io.Writer, format Format, files ...string) error { } return Pack(w, format, fs...) } + +func readFiles(files ...string) (fs []File, err error) { + for _, f := range files { + var file File + file.Name = f + file.Body, err = os.ReadFile(f) + if err != nil { + return + } + fs = append(fs, file) + } + return +} diff --git a/archive/tar.go b/archive/tar.go index 8e0cdfd..8a647f0 100644 --- a/archive/tar.go +++ b/archive/tar.go @@ -35,8 +35,8 @@ func packTar(w io.Writer, files ...File) error { return gw.Close() } -func unpackTar(b []byte) ([]File, error) { - gr, err := gzip.NewReader(bytes.NewReader(b)) +func unpackTar(r io.Reader) ([]File, error) { + gr, err := gzip.NewReader(r) if err != nil { return nil, err } diff --git a/archive/unpack.go b/archive/unpack.go index d947f13..c6505c9 100644 --- a/archive/unpack.go +++ b/archive/unpack.go @@ -1,6 +1,7 @@ package archive import ( + "bytes" "fmt" "io" "os" @@ -9,17 +10,17 @@ import ( // Unpack decompresses an archive to File struct. func Unpack(r io.Reader) ([]File, error) { - b, err := io.ReadAll(r) - if err != nil { - return nil, err - } - - _, format := IsArchive(b) + rr := asReader(r) + _, format := isArchive(rr) switch format { case ZIP: - return unpackZip(b) + b, err := io.ReadAll(rr) + if err != nil { + return nil, err + } + return unpackZip(bytes.NewReader(b), int64(len(b))) case TAR: - return unpackTar(b) + return unpackTar(rr) default: return nil, ErrFormat } diff --git a/archive/zip.go b/archive/zip.go index ff235d7..08fc878 100644 --- a/archive/zip.go +++ b/archive/zip.go @@ -25,14 +25,14 @@ func packZip(w io.Writer, files ...File) error { return zw.Close() } -func unpackZip(b []byte) ([]File, error) { - r, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) +func unpackZip(r io.ReaderAt, size int64) ([]File, error) { + zr, err := zip.NewReader(r, size) if err != nil { return nil, err } var fs []File - for _, f := range r.File { + for _, f := range zr.File { switch { case f.FileInfo().IsDir(): fs = append(fs, File{Name: f.Name, IsDir: true}) From 1bc7935a52399e29fe5856edd8fd45343d6ff588 Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Fri, 12 Sep 2025 15:10:41 +0800 Subject: [PATCH 2/3] cache --- cache/cache.go | 2 +- cache/cache_test.go | 2 ++ cache/renew.go | 31 +++++++++++++------------------ 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/cache/cache.go b/cache/cache.go index b14a99d..1637bee 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -13,7 +13,7 @@ type Cache[Key any, Value any] struct { } // New creates a new cache with auto clean or not. -func New[Key comparable, Value any]() *Cache[Key, Value] { +func New[Key any, Value any]() *Cache[Key, Value] { return &Cache[Key, Value]{} } diff --git a/cache/cache_test.go b/cache/cache_test.go index 13a43cf..d1075e8 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -3,6 +3,7 @@ package cache import ( "runtime" "testing" + "time" "weak" ) @@ -34,6 +35,7 @@ func TestCache(t *testing.T) { t.Fatalf("expected %q, got %q", value, v) } runtime.GC() + time.Sleep(time.Second) if _, ok := cache.m.Load(p); ok { t.Fatal("expected not cached, got cached") } diff --git a/cache/renew.go b/cache/renew.go index 81ff3e9..8d318ae 100644 --- a/cache/renew.go +++ b/cache/renew.go @@ -9,24 +9,21 @@ import ( "github.com/sunshineplan/utils/container" ) -var valueKey int - type item[T any] struct { sync.Mutex ctx context.Context cancel context.CancelFunc lifecycle time.Duration + value T fn func() (T, error) } -func (i *item[T]) set(value *T) { - if i.ctx = context.WithValue(context.Background(), &valueKey, value); i.lifecycle > 0 { +func (i *item[T]) set(value T) { + i.ctx = context.Background() + if i.lifecycle > 0 { i.ctx, i.cancel = context.WithTimeout(i.ctx, i.lifecycle) } -} - -func (i *item[T]) value() T { - return *i.ctx.Value(&valueKey).(*T) + i.value = value } func (i *item[T]) renew() T { @@ -35,9 +32,9 @@ func (i *item[T]) renew() T { defer i.Unlock() if err != nil { log.Print(err) - v = i.value() + v = i.value } - i.set(&v) + i.set(v) return v } @@ -55,9 +52,7 @@ func NewWithRenew[Key comparable, Value any](autoRenew bool) *CacheWithRenew[Key // Set sets cache value for a key, if fn is presented, this value will regenerate when expired. func (c *CacheWithRenew[Key, Value]) Set(key Key, value Value, lifecycle time.Duration, fn func() (Value, error)) { i := &item[Value]{lifecycle: lifecycle, fn: fn} - i.Lock() - defer i.Unlock() - i.set(&value) + i.set(value) if c.autoRenew && lifecycle > 0 { go func() { for { @@ -99,7 +94,7 @@ func (c *CacheWithRenew[Key, Value]) Get(key Key) (value Value, ok bool) { } i.Lock() defer i.Unlock() - value = i.value() + value = i.value return } @@ -120,16 +115,16 @@ func (c *CacheWithRenew[Key, Value]) Swap(key Key, value Value) (previous Value, if i, loaded = c.get(key); loaded { i.Lock() defer i.Unlock() - previous = i.value() - i.set(&value) + previous = i.value + i.set(value) } return } // Clear deletes all values in cache. func (c *CacheWithRenew[Key, Value]) Clear() { - c.m.Range(func(k Key, i *item[Value]) bool { - c.m.Delete(k) + c.m.Range(func(key Key, i *item[Value]) bool { + c.m.Delete(key) i.Lock() defer i.Unlock() if i.cancel != nil { From 4f889db960d7d15fd1cff70be7a3c5a01e02b2d2 Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Mon, 15 Sep 2025 09:11:45 +0800 Subject: [PATCH 3/3] archive: Update --- archive/unpack.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/archive/unpack.go b/archive/unpack.go index c6505c9..614d333 100644 --- a/archive/unpack.go +++ b/archive/unpack.go @@ -10,10 +10,26 @@ import ( // Unpack decompresses an archive to File struct. func Unpack(r io.Reader) ([]File, error) { + var ra io.ReaderAt + var size int64 + if f, ok := r.(*os.File); ok { + stat, err := f.Stat() + if err != nil { + return nil, err + } + ra = f + size = stat.Size() + } else if r, ok := r.(*bytes.Reader); ok { + ra = r + size = r.Size() + } rr := asReader(r) _, format := isArchive(rr) switch format { case ZIP: + if ra != nil { + return unpackZip(ra, size) + } b, err := io.ReadAll(rr) if err != nil { return nil, err