422 lines
7.5 KiB
Go
422 lines
7.5 KiB
Go
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
|
//go:build plus
|
|
|
|
package caches
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
|
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
|
"github.com/TeaOSLab/EdgeNode/internal/utils/mmap"
|
|
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
|
"github.com/iwind/TeaGo/types"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
// 设置最大能映射的文件尺寸
|
|
var maxMMAPFileSize int64 = 8 << 20
|
|
|
|
func init() {
|
|
var estimatedSize = int64((memutils.SystemMemoryGB() * 4) << 20)
|
|
if estimatedSize > maxMMAPFileSize {
|
|
maxMMAPFileSize = estimatedSize
|
|
}
|
|
}
|
|
|
|
// MMAPFileReader 通过MMAP读取文件
|
|
type MMAPFileReader struct {
|
|
BaseReader
|
|
|
|
rawReader *mmap.SharedMMAP
|
|
path string
|
|
modifiedAt int64
|
|
|
|
meta []byte
|
|
header []byte
|
|
|
|
expiresAt int64
|
|
status int
|
|
headerOffset int64
|
|
headerSize int
|
|
bodySize int64
|
|
bodyOffset int64
|
|
|
|
currentOffset int64
|
|
|
|
isClosed bool
|
|
|
|
once sync.Once
|
|
}
|
|
|
|
func NewMMAPFileReaderFromPath(filename string) (*MMAPFileReader, error) {
|
|
reader, err := mmap.OpenSharedMMAP(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NewMMAPFileReader(reader)
|
|
}
|
|
|
|
func NewMMAPFileReader(mmapReader *mmap.SharedMMAP) (*MMAPFileReader, error) {
|
|
return &MMAPFileReader{
|
|
path: mmapReader.Name(),
|
|
rawReader: mmapReader,
|
|
modifiedAt: mmapReader.Stat().ModTime().Unix(),
|
|
}, nil
|
|
}
|
|
|
|
func (this *MMAPFileReader) Init() error {
|
|
return this.InitAutoDiscard(true)
|
|
}
|
|
|
|
func (this *MMAPFileReader) InitAutoDiscard(autoDiscard bool) error {
|
|
var isOk = false
|
|
|
|
if autoDiscard {
|
|
defer func() {
|
|
if !isOk {
|
|
_ = this.discard()
|
|
}
|
|
}()
|
|
}
|
|
|
|
var buf = this.meta
|
|
if len(buf) == 0 {
|
|
buf = make([]byte, SizeMeta)
|
|
ok, err := this.readNext(buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
return ErrNotFound
|
|
}
|
|
this.meta = buf
|
|
}
|
|
|
|
this.expiresAt = int64(binary.BigEndian.Uint32(buf[:SizeExpiresAt]))
|
|
|
|
var status = types.Int(string(buf[OffsetStatus : OffsetStatus+SizeStatus]))
|
|
if status < 100 || status > 999 {
|
|
return errors.New("invalid status")
|
|
}
|
|
this.status = status
|
|
|
|
// URL
|
|
var urlLength = binary.BigEndian.Uint32(buf[OffsetURLLength : OffsetURLLength+SizeURLLength])
|
|
|
|
// header
|
|
var headerSize = int(binary.BigEndian.Uint32(buf[OffsetHeaderLength : OffsetHeaderLength+SizeHeaderLength]))
|
|
if headerSize == 0 {
|
|
return nil
|
|
}
|
|
this.headerSize = headerSize
|
|
this.headerOffset = int64(SizeMeta) + int64(urlLength)
|
|
|
|
// body
|
|
this.bodyOffset = this.headerOffset + int64(headerSize)
|
|
var bodySize = int(binary.BigEndian.Uint64(buf[OffsetBodyLength : OffsetBodyLength+SizeBodyLength]))
|
|
if bodySize == 0 {
|
|
isOk = true
|
|
return nil
|
|
}
|
|
this.bodySize = int64(bodySize)
|
|
|
|
isOk = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func (this *MMAPFileReader) TypeName() string {
|
|
return "disk"
|
|
}
|
|
|
|
func (this *MMAPFileReader) ExpiresAt() int64 {
|
|
return this.expiresAt
|
|
}
|
|
|
|
func (this *MMAPFileReader) Status() int {
|
|
return this.status
|
|
}
|
|
|
|
func (this *MMAPFileReader) LastModified() int64 {
|
|
if this.modifiedAt > 0 {
|
|
return this.modifiedAt
|
|
}
|
|
stat, err := os.Stat(this.path)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return stat.ModTime().Unix()
|
|
}
|
|
|
|
func (this *MMAPFileReader) HeaderSize() int64 {
|
|
return int64(this.headerSize)
|
|
}
|
|
|
|
func (this *MMAPFileReader) BodySize() int64 {
|
|
return this.bodySize
|
|
}
|
|
|
|
func (this *MMAPFileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
|
|
// 使用缓存
|
|
if len(this.header) > 0 && len(buf) >= len(this.header) {
|
|
copy(buf, this.header)
|
|
_, err := callback(len(this.header))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 移动到Body位置
|
|
this.moveTo(this.bodyOffset)
|
|
return nil
|
|
}
|
|
|
|
var isOk = false
|
|
|
|
defer func() {
|
|
if !isOk {
|
|
_ = this.discard()
|
|
}
|
|
}()
|
|
|
|
this.moveTo(this.headerOffset)
|
|
|
|
var headerSize = this.headerSize
|
|
|
|
if len(buf) > headerSize {
|
|
n, err := this.rawReader.ReadAt(buf[:headerSize], this.headerOffset)
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
return err
|
|
}
|
|
}
|
|
|
|
_, err = callback(n)
|
|
if err != nil {
|
|
isOk = true
|
|
return err
|
|
}
|
|
} else {
|
|
for {
|
|
n, err := this.read(buf)
|
|
if n > 0 {
|
|
if n < headerSize {
|
|
goNext, e := callback(n)
|
|
if e != nil {
|
|
isOk = true
|
|
return e
|
|
}
|
|
if !goNext {
|
|
break
|
|
}
|
|
headerSize -= n
|
|
} else {
|
|
_, e := callback(headerSize)
|
|
if e != nil {
|
|
isOk = true
|
|
return e
|
|
}
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
return err
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
isOk = true
|
|
|
|
// 移动到Body位置
|
|
this.moveTo(this.bodyOffset)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (this *MMAPFileReader) ReadBody(buf []byte, callback ReaderFunc) error {
|
|
if this.bodySize == 0 {
|
|
return nil
|
|
}
|
|
|
|
var isOk = false
|
|
|
|
defer func() {
|
|
if !isOk {
|
|
_ = this.discard()
|
|
}
|
|
}()
|
|
|
|
var offset = this.bodyOffset
|
|
|
|
// 开始读Body部分
|
|
this.moveTo(offset)
|
|
|
|
for {
|
|
n, err := this.read(buf)
|
|
if n > 0 {
|
|
goNext, e := callback(n)
|
|
if e != nil {
|
|
isOk = true
|
|
return e
|
|
}
|
|
if !goNext {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
return err
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
isOk = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func (this *MMAPFileReader) Read(buf []byte) (n int, err error) {
|
|
if this.bodySize == 0 {
|
|
n = 0
|
|
err = io.EOF
|
|
return
|
|
}
|
|
|
|
n, err = this.read(buf)
|
|
if err != nil && err != io.EOF {
|
|
_ = this.discard()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (this *MMAPFileReader) ReadBodyRange(buf []byte, start int64, end int64, callback ReaderFunc) error {
|
|
var isOk = false
|
|
|
|
defer func() {
|
|
if !isOk {
|
|
_ = this.discard()
|
|
}
|
|
}()
|
|
|
|
var offset = start
|
|
if start < 0 {
|
|
offset = this.bodyOffset + this.bodySize + end
|
|
end = this.bodyOffset + this.bodySize - 1
|
|
} else if end < 0 {
|
|
offset = this.bodyOffset + start
|
|
end = this.bodyOffset + this.bodySize - 1
|
|
} else {
|
|
offset = this.bodyOffset + start
|
|
end = this.bodyOffset + end
|
|
}
|
|
if offset < 0 || end < 0 || offset > end {
|
|
isOk = true
|
|
return ErrInvalidRange
|
|
}
|
|
|
|
this.moveTo(offset)
|
|
|
|
for {
|
|
n, err := this.read(buf)
|
|
if n > 0 {
|
|
var n2 = int(end-offset) + 1
|
|
if n2 <= n {
|
|
_, e := callback(n2)
|
|
if e != nil {
|
|
isOk = true
|
|
return e
|
|
}
|
|
break
|
|
} else {
|
|
goNext, e := callback(n)
|
|
if e != nil {
|
|
isOk = true
|
|
return e
|
|
}
|
|
if !goNext {
|
|
break
|
|
}
|
|
}
|
|
|
|
offset += int64(n)
|
|
if offset > end {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
return err
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
isOk = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// ContainsRange 是否包含某些区间内容
|
|
func (this *MMAPFileReader) ContainsRange(r rangeutils.Range) (r2 rangeutils.Range, ok bool) {
|
|
return r, true
|
|
}
|
|
|
|
func (this *MMAPFileReader) CopyBodyTo(writer io.Writer) (int, error) {
|
|
return this.rawReader.Write(writer, int(this.bodyOffset))
|
|
}
|
|
|
|
func (this *MMAPFileReader) Close() error {
|
|
if this.isClosed {
|
|
return nil
|
|
}
|
|
this.isClosed = true
|
|
|
|
var err error
|
|
this.once.Do(func() {
|
|
err = this.rawReader.Close()
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (this *MMAPFileReader) moveTo(offset int64) {
|
|
this.currentOffset = offset
|
|
}
|
|
|
|
func (this *MMAPFileReader) readNext(buf []byte) (ok bool, err error) {
|
|
n, err := this.rawReader.ReadAt(buf, this.currentOffset)
|
|
if n > 0 {
|
|
this.currentOffset += int64(n)
|
|
}
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
ok = n == len(buf)
|
|
return
|
|
}
|
|
|
|
func (this *MMAPFileReader) read(p []byte) (n int, err error) {
|
|
n, err = this.rawReader.ReadAt(p, this.currentOffset)
|
|
if n > 0 {
|
|
this.currentOffset += int64(n)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (this *MMAPFileReader) discard() error {
|
|
this.once.Do(func() {
|
|
_ = this.rawReader.Close()
|
|
})
|
|
|
|
this.isClosed = true
|
|
|
|
// remove file
|
|
return fsutils.Remove(this.path)
|
|
}
|