/
upload.go
130 lines (113 loc) · 4.4 KB
/
upload.go
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
package renter
// upload.go performs basic preprocessing on upload requests and then adds the
// requested files into the repair heap.
//
// TODO: Currently the minimum contracts check is not enforced while testing,
// which means that code is not covered at all. Enabling enforcement during
// testing will probably break a ton of existing tests, which means they will
// all need to be fixed when we do enable it, but we should enable it.
import (
"fmt"
"os"
"gitlab.com/NebulousLabs/errors"
"go.sia.tech/siad/build"
"go.sia.tech/siad/crypto"
"go.sia.tech/siad/modules"
"go.sia.tech/siad/modules/renter/filesystem"
)
var (
// ErrUploadDirectory is returned if the user tries to upload a directory.
ErrUploadDirectory = errors.New("cannot upload directory")
)
// Upload instructs the renter to start tracking a file. The renter will
// automatically upload and repair tracked files using a background loop.
func (r *Renter) Upload(up modules.FileUploadParams) error {
if err := r.tg.Add(); err != nil {
return err
}
defer r.tg.Done()
// Check if the file is a directory.
sourceInfo, err := os.Stat(up.Source)
if err != nil {
return errors.AddContext(err, "unable to stat input file")
}
if sourceInfo.IsDir() {
return ErrUploadDirectory
}
// Check for read access.
file, err := os.Open(up.Source)
if err != nil {
return errors.AddContext(err, "unable to open the source file")
}
err = file.Close()
if err != nil {
return errors.AddContext(err, "unable to close file after checking permissions")
}
// Delete existing file if overwrite flag is set. Ignore ErrUnknownPath.
if up.Force {
err := r.DeleteFile(up.SiaPath)
if err != nil && !errors.Contains(err, filesystem.ErrNotExist) {
return errors.AddContext(err, "unable to delete existing file")
}
}
// Fill in any missing upload params with sensible defaults.
if up.ErasureCode == nil {
up.ErasureCode = modules.NewRSSubCodeDefault()
}
// Check that we have contracts to upload to. We need at least data +
// parity/2 contracts. NumPieces is equal to data+parity, and min pieces is
// equal to parity. Therefore (NumPieces+MinPieces)/2 = (data+data+parity)/2
// = data+parity/2.
numContracts := len(r.hostContractor.Contracts())
requiredContracts := (up.ErasureCode.NumPieces() + up.ErasureCode.MinPieces()) / 2
if numContracts < requiredContracts && build.Release != "testing" {
return fmt.Errorf("not enough contracts to upload file: got %v, needed %v", numContracts, (up.ErasureCode.NumPieces()+up.ErasureCode.MinPieces())/2)
}
// Create the directory path on disk. Renter directory is already present so
// only files not in top level directory need to have directories created
dirSiaPath, err := up.SiaPath.Dir()
if err != nil {
return err
}
// Determine what type of encryption key to use. If no cipher type has been
// set, the default renter type will be used.
var ct crypto.CipherType
if up.CipherType == ct {
up.CipherType = crypto.TypeDefaultRenter
}
// Generate a key using the cipher type.
cipherKey := crypto.GenerateSiaKey(up.CipherType)
// Create the Siafile and add to renter
err = r.staticFileSystem.NewSiaFile(up.SiaPath, up.Source, up.ErasureCode, cipherKey, uint64(sourceInfo.Size()), sourceInfo.Mode(), up.DisablePartialChunk)
if err != nil {
return errors.AddContext(err, "could not create a new sia file")
}
entry, err := r.staticFileSystem.OpenSiaFile(up.SiaPath)
if err != nil {
return errors.AddContext(err, "could not open the new sia file")
}
// No need to upload zero-byte files.
if sourceInfo.Size() == 0 {
return nil
}
// Bubble the health of the SiaFile directory to ensure the health is
// updated with the new file
//
// Queue a bubble to bubble the directory, ignore the return channel as we do
// not want to block on this update.
_ = r.staticBubbleScheduler.callQueueBubble(dirSiaPath)
// Create nil maps for offline and goodForRenew to pass in to
// callBuildAndPushChunks. These maps are used to determine the health of
// the file and its chunks. Nil maps will result in the file and its chunks
// having the worst possible health which is accurate since the file hasn't
// been uploaded yet
nilMap := make(map[string]bool)
// Send the upload to the repair loop.
hosts := r.managedRefreshHostsAndWorkers()
r.callBuildAndPushChunks([]*filesystem.FileNode{entry}, hosts, targetUnstuckChunks, nilMap, nilMap)
select {
case r.uploadHeap.newUploads <- struct{}{}:
default:
}
return nil
}