Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 36 additions & 22 deletions archive/archive.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package archive

import (
"bufio"
"bytes"
"errors"
"os"
"io"
)

// Format represents the archive format.
Expand All @@ -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
Expand All @@ -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
}
17 changes: 15 additions & 2 deletions archive/pack.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package archive

import (
"errors"
"io"
"os"
)

// Pack creates an archive from File struct.
Expand All @@ -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
}
}

Expand All @@ -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
}
4 changes: 2 additions & 2 deletions archive/tar.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
31 changes: 24 additions & 7 deletions archive/unpack.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package archive

import (
"bytes"
"fmt"
"io"
"os"
Expand All @@ -9,17 +10,33 @@ 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
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()
}

_, format := IsArchive(b)
rr := asReader(r)
_, format := isArchive(rr)
switch format {
case ZIP:
return unpackZip(b)
if ra != nil {
return unpackZip(ra, size)
}
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
}
Expand Down
6 changes: 3 additions & 3 deletions archive/zip.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
2 changes: 1 addition & 1 deletion cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]{}
}

Expand Down
2 changes: 2 additions & 0 deletions cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cache
import (
"runtime"
"testing"
"time"
"weak"
)

Expand Down Expand Up @@ -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")
}
Expand Down
31 changes: 13 additions & 18 deletions cache/renew.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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
}

Expand All @@ -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 {
Expand Down
Loading