-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package container
import (
"context"
"errors"
"io"
"os"
"path/filepath"
"strings"
packages_model "code.gitea.io/gitea/models/packages"
packages_module "code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
var (
// errWriteAfterRead occurs if Write is called after a read operation
errWriteAfterRead = errors.New("write is unsupported after a read operation")
// errOffsetMissmatch occurs if the file offset is different than the model
errOffsetMissmatch = errors.New("offset mismatch between file and model")
)
// BlobUploader handles chunked blob uploads
type BlobUploader struct {
*packages_model.PackageBlobUpload
*packages_module.MultiHasher
file *os.File
reading bool
}
func buildFilePath(id string) string {
return filepath.Join(setting.Packages.ChunkedUploadPath, util.CleanPath(strings.ReplaceAll(id, "\\", "/")))
}
// NewBlobUploader creates a new blob uploader for the given id
func NewBlobUploader(ctx context.Context, id string) (*BlobUploader, error) {
model, err := packages_model.GetBlobUploadByID(ctx, id)
if err != nil {
return nil, err
}
hash := packages_module.NewMultiHasher()
if len(model.HashStateBytes) != 0 {
if err := hash.UnmarshalBinary(model.HashStateBytes); err != nil {
return nil, err
}
}
f, err := os.OpenFile(buildFilePath(model.ID), os.O_RDWR|os.O_CREATE, 0o666)
if err != nil {
return nil, err
}
return &BlobUploader{
model,
hash,
f,
false,
}, nil
}
// Close implements io.Closer
func (u *BlobUploader) Close() error {
return u.file.Close()
}
// Append appends a chunk of data and updates the model
func (u *BlobUploader) Append(ctx context.Context, r io.Reader) error {
if u.reading {
return errWriteAfterRead
}
offset, err := u.file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if offset != u.BytesReceived {
return errOffsetMissmatch
}
n, err := io.Copy(io.MultiWriter(u.file, u.MultiHasher), r)
if err != nil {
return err
}
// fast path if nothing was written
if n == 0 {
return nil
}
u.BytesReceived += n
u.HashStateBytes, err = u.MultiHasher.MarshalBinary()
if err != nil {
return err
}
return packages_model.UpdateBlobUpload(ctx, u.PackageBlobUpload)
}
func (u *BlobUploader) Size() int64 {
return u.BytesReceived
}
// Read implements io.Reader
func (u *BlobUploader) Read(p []byte) (int, error) {
if !u.reading {
_, err := u.file.Seek(0, io.SeekStart)
if err != nil {
return 0, err
}
u.reading = true
}
return u.file.Read(p)
}
// Remove deletes the data and the model of a blob upload
func RemoveBlobUploadByID(ctx context.Context, id string) error {
if err := packages_model.DeleteBlobUploadByID(ctx, id); err != nil {
return err
}
err := os.Remove(buildFilePath(id))
if err != nil && !os.IsNotExist(err) {
return err
}
return nil
}