github.com/TeaOSLab/EdgeNode@v1.3.8/internal/caches/reader_partial_file.go (about) 1 package caches 2 3 import ( 4 "encoding/binary" 5 "errors" 6 "fmt" 7 fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs" 8 rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges" 9 "github.com/iwind/TeaGo/types" 10 "io" 11 ) 12 13 type PartialFileReader struct { 14 *FileReader 15 16 ranges *PartialRanges 17 rangePath string 18 } 19 20 func NewPartialFileReader(fp *fsutils.File) *PartialFileReader { 21 return &PartialFileReader{ 22 FileReader: NewFileReader(fp), 23 rangePath: PartialRangesFilePath(fp.Name()), 24 } 25 } 26 27 func (this *PartialFileReader) Init() error { 28 return this.InitAutoDiscard(true) 29 } 30 31 func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error { 32 if this.openFile != nil { 33 this.meta = this.openFile.meta 34 this.header = this.openFile.header 35 } 36 37 var isOk = false 38 39 if autoDiscard { 40 defer func() { 41 if !isOk { 42 _ = this.discard() 43 } 44 }() 45 } 46 47 // 读取Range 48 ranges, err := NewPartialRangesFromFile(this.rangePath) 49 if err != nil { 50 return fmt.Errorf("read ranges failed: %w", err) 51 } 52 this.ranges = ranges 53 54 var buf = this.meta 55 if len(buf) == 0 { 56 buf = make([]byte, SizeMeta) 57 ok, readErr := this.readToBuff(this.fp, buf) 58 if readErr != nil { 59 return readErr 60 } 61 if !ok { 62 return ErrNotFound 63 } 64 this.meta = buf 65 } 66 67 this.expiresAt = int64(binary.BigEndian.Uint32(buf[:SizeExpiresAt])) 68 69 status := types.Int(string(buf[SizeExpiresAt : SizeExpiresAt+SizeStatus])) 70 if status < 100 || status > 999 { 71 return errors.New("invalid status") 72 } 73 this.status = status 74 75 // URL 76 var urlLength = binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus : SizeExpiresAt+SizeStatus+SizeURLLength]) 77 78 // header 79 var headerSize = int(binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus+SizeURLLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength])) 80 if headerSize == 0 { 81 return nil 82 } 83 this.headerSize = headerSize 84 this.headerOffset = int64(SizeMeta) + int64(urlLength) 85 86 // body 87 this.bodyOffset = this.headerOffset + int64(headerSize) 88 bodySize := int(binary.BigEndian.Uint64(buf[SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength+SizeBodyLength])) 89 if bodySize == 0 { 90 isOk = true 91 return nil 92 } 93 this.bodySize = int64(bodySize) 94 95 // read header 96 if this.openFileCache != nil && len(this.header) == 0 { 97 if headerSize > 0 && headerSize <= 512 { 98 this.header = make([]byte, headerSize) 99 _, err = this.fp.Seek(this.headerOffset, io.SeekStart) 100 if err != nil { 101 return err 102 } 103 _, err = this.readToBuff(this.fp, this.header) 104 if err != nil { 105 return err 106 } 107 } 108 } 109 110 isOk = true 111 112 return nil 113 } 114 115 // ContainsRange 是否包含某些区间内容 116 // 这里的 r 是已经经过格式化的 117 func (this *PartialFileReader) ContainsRange(r rangeutils.Range) (r2 rangeutils.Range, ok bool) { 118 r2, ok = this.ranges.Nearest(r.Start(), r.End()) 119 if ok && this.bodySize > 0 { 120 // 考虑可配置 121 const minSpan = 128 << 10 122 123 // 这里限制返回的最小缓存,防止因为返回的内容过小而导致请求过多 124 if r2.Length() < r.Length() && r2.Length() < minSpan { 125 ok = false 126 } 127 } 128 return 129 } 130 131 // MaxLength 获取区间最大长度 132 func (this *PartialFileReader) MaxLength() int64 { 133 if this.bodySize > 0 { 134 return this.bodySize 135 } 136 return this.ranges.Max() + 1 137 } 138 139 func (this *PartialFileReader) Ranges() *PartialRanges { 140 return this.ranges 141 } 142 143 func (this *PartialFileReader) IsCompleted() bool { 144 return this.ranges != nil && this.ranges.IsCompleted() 145 } 146 147 func (this *PartialFileReader) discard() error { 148 SharedPartialRangesQueue.Delete(this.rangePath) 149 _ = fsutils.Remove(this.rangePath) 150 151 return this.FileReader.discard() 152 }