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