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