github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/util/filebuffer/file_backed_buffer.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package filebuffer 7 8 import ( 9 "bytes" 10 "errors" 11 "io" 12 "os" 13 ) 14 15 const maxInt = int(^uint(0) >> 1) // taken from bytes.Buffer 16 17 var ( 18 // ErrInvalidMemorySize occurs if the memory size is not in a valid range 19 ErrInvalidMemorySize = errors.New("Memory size must be greater 0 and lower math.MaxInt32") 20 // ErrWriteAfterRead occurs if Write is called after a read operation 21 ErrWriteAfterRead = errors.New("Write is unsupported after a read operation") 22 ) 23 24 type readAtSeeker interface { 25 io.ReadSeeker 26 io.ReaderAt 27 } 28 29 // FileBackedBuffer uses a memory buffer with a fixed size. 30 // If more data is written a temporary file is used instead. 31 // It implements io.ReadWriteCloser, io.ReadSeekCloser and io.ReaderAt 32 type FileBackedBuffer struct { 33 maxMemorySize int64 34 size int64 35 buffer bytes.Buffer 36 file *os.File 37 reader readAtSeeker 38 } 39 40 // New creates a file backed buffer with a specific maximum memory size 41 func New(maxMemorySize int) (*FileBackedBuffer, error) { 42 if maxMemorySize < 0 || maxMemorySize > maxInt { 43 return nil, ErrInvalidMemorySize 44 } 45 46 return &FileBackedBuffer{ 47 maxMemorySize: int64(maxMemorySize), 48 }, nil 49 } 50 51 // CreateFromReader creates a file backed buffer and copies the provided reader data into it. 52 func CreateFromReader(r io.Reader, maxMemorySize int) (*FileBackedBuffer, error) { 53 b, err := New(maxMemorySize) 54 if err != nil { 55 return nil, err 56 } 57 58 _, err = io.Copy(b, r) 59 if err != nil { 60 return nil, err 61 } 62 63 return b, nil 64 } 65 66 // Write implements io.Writer 67 func (b *FileBackedBuffer) Write(p []byte) (int, error) { 68 if b.reader != nil { 69 return 0, ErrWriteAfterRead 70 } 71 72 var n int 73 var err error 74 75 if b.file != nil { 76 n, err = b.file.Write(p) 77 } else { 78 if b.size+int64(len(p)) > b.maxMemorySize { 79 b.file, err = os.CreateTemp("", "gitbundle-buffer-") 80 if err != nil { 81 return 0, err 82 } 83 84 _, err = io.Copy(b.file, &b.buffer) 85 if err != nil { 86 return 0, err 87 } 88 89 return b.Write(p) 90 } 91 92 n, err = b.buffer.Write(p) 93 } 94 95 if err != nil { 96 return n, err 97 } 98 b.size += int64(n) 99 return n, nil 100 } 101 102 // Size returns the byte size of the buffered data 103 func (b *FileBackedBuffer) Size() int64 { 104 return b.size 105 } 106 107 func (b *FileBackedBuffer) switchToReader() error { 108 if b.reader != nil { 109 return nil 110 } 111 112 if b.file != nil { 113 if _, err := b.file.Seek(0, io.SeekStart); err != nil { 114 return err 115 } 116 b.reader = b.file 117 } else { 118 b.reader = bytes.NewReader(b.buffer.Bytes()) 119 } 120 return nil 121 } 122 123 // Read implements io.Reader 124 func (b *FileBackedBuffer) Read(p []byte) (int, error) { 125 if err := b.switchToReader(); err != nil { 126 return 0, err 127 } 128 129 return b.reader.Read(p) 130 } 131 132 // ReadAt implements io.ReaderAt 133 func (b *FileBackedBuffer) ReadAt(p []byte, off int64) (int, error) { 134 if err := b.switchToReader(); err != nil { 135 return 0, err 136 } 137 138 return b.reader.ReadAt(p, off) 139 } 140 141 // Seek implements io.Seeker 142 func (b *FileBackedBuffer) Seek(offset int64, whence int) (int64, error) { 143 if err := b.switchToReader(); err != nil { 144 return 0, err 145 } 146 147 return b.reader.Seek(offset, whence) 148 } 149 150 // Close implements io.Closer 151 func (b *FileBackedBuffer) Close() error { 152 if b.file != nil { 153 err := b.file.Close() 154 os.Remove(b.file.Name()) 155 return err 156 } 157 return nil 158 }