-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfile.go
158 lines (139 loc) · 3.98 KB
/
file.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package networkfile
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"os"
)
// file is the base file for the remote file handles
type file struct {
client *http.Client
ctx context.Context
baseURL string
sharedSecret string
fileID FileID
offset int64
logger *slog.Logger
}
// SetLogger sets a new structured logger, replacing the default slog logger
func (f *file) SetLogger(logger *slog.Logger) {
f.logger = logger
}
// Seek seeks to the given offset from the given mode
func (f *file) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekStart:
f.offset = offset
case io.SeekCurrent:
f.offset += offset
case io.SeekEnd:
fi, err := f.stat()
if err != nil {
return 0, err
}
f.offset = fi.Size() + offset
default:
return 0, ErrUnsupportedOperation
}
return f.offset, nil
}
// FileID returns the fileID
func (f *file) FileID() FileID {
return f.fileID
}
// SharedSecret returns the shared secret
func (f *file) SharedSecret() string {
return f.sharedSecret
}
// Stat returns the remote file information
func (f *file) Stat() (os.FileInfo, error) {
info, err := f.stat()
if err != nil {
return nil, err
}
return &info, nil
}
// Close tells the server to close the remote file
func (f *file) Close() error {
return f.close()
}
// prepareRequest prepares a new HTTP request
func (f *file) prepareRequest(method, url string, body io.Reader) (*http.Request, error) {
var req *http.Request
var err error
if f.ctx == nil {
req, err = http.NewRequest(method, url, body)
} else {
req, err = http.NewRequestWithContext(f.ctx, method, url, body)
}
if err != nil {
return nil, err
}
req.Header.Set(HeaderSharedSecret, f.sharedSecret)
return req, nil
}
// stat returns the remote file information
func (f *file) stat() (FileInfo, error) {
fi := FileInfo{}
url := fmt.Sprintf("%s/%s", f.baseURL, f.fileID)
req, err := f.prepareRequest(http.MethodOptions, url, nil) // nolint:noctx
if err != nil {
f.logger.Error("networkfile.File.stat: Error creating request", "fileID", f.fileID, "error", err)
return fi, err
}
resp, err := f.client.Do(req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
f.logger.Info("networkfile.File.stat: Context expired", "fileID", f.fileID, "error", err)
} else {
f.logger.Error("networkfile.File.stat: Error executing request", "fileID", f.fileID, "error", err)
}
return fi, err
}
defer func() {
_ = resp.Body.Close()
}()
err = responseCodeToError(resp, http.StatusOK)
if err != nil {
f.logger.Info("networkfile.File.stat: A remote error occurred", "fileID", f.fileID, "error", err)
return fi, err
}
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(&fi)
if err != nil {
f.logger.Error("networkfile.File.stat: Error decoding file info", "fileID", f.fileID, "error", err)
return fi, err
}
f.logger.Debug("networkfile.File.stat: File info", "fileID", f.fileID, "fileInfo", fi)
return fi, nil
}
// close tells the remote server to close the file
func (f *file) close() error {
url := fmt.Sprintf("%s/%s", f.baseURL, f.fileID)
req, err := f.prepareRequest(http.MethodDelete, url, nil) //nolint: noctx
if err != nil {
f.logger.Error("networkfile.File.close: Error creating request", "error", err)
return err
}
resp, err := f.client.Do(req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
f.logger.Info("networkfile.File.close: Context expired", "fileID", f.fileID, "error", err)
} else {
f.logger.Error("networkfile.File.close: Error executing request", "fileID", f.fileID, "error", err)
}
return err
}
_ = resp.Body.Close()
err = responseCodeToError(resp, http.StatusNoContent)
if err != nil {
f.logger.Info("networkfile.File.close: A remote error occurred", "fileID", f.fileID, "error", err)
return err
}
f.logger.Debug("networkfile.File.close: The remote file was closed", "fileID", f.fileID)
return nil
}