-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathremotefile.go
127 lines (101 loc) · 2.3 KB
/
remotefile.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
package remotefile
import (
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"net/url"
"time"
)
var ErrOffset = errors.New("seek: invalid offset")
var errWhence = errors.New("seek: invalid whence")
// RemoteFile implements `fs.File`, `fs.FileInfo`, `io.ReadSeekCloser`, `io.ReaderAt`
type RemoteFile struct {
FileName string
URL url.URL
Length int64
LastModified time.Time
offset int64
}
func (rf *RemoteFile) Read(p []byte) (n int, err error) {
start, end := rf.calcRange(rf.offset, int64(len(p)))
n, err = rf.readInto(p, start, end)
rf.offset += int64(n)
if rf.offset >= rf.Length-1 {
err = io.EOF
}
return n, err
}
func (rf RemoteFile) ReadAt(p []byte, off int64) (n int, err error) {
start, end := rf.calcRange(off, int64(len(p)))
n, err = rf.readInto(p, start, end)
if off+end-start >= rf.Length-1 {
err = io.EOF
}
return n, err
}
func (rf *RemoteFile) Seek(offset int64, whence int) (int64, error) {
switch whence {
default:
return 0, errWhence
case io.SeekStart:
// do nothing
case io.SeekCurrent:
offset += rf.offset
case io.SeekEnd:
offset = rf.Length - offset
}
if offset < 0 || offset >= rf.Length-1 {
return rf.offset, ErrOffset
}
rf.offset = offset
return rf.offset, nil
}
func (rf *RemoteFile) Close() error {
rf.offset = 0
return nil
}
func (rf RemoteFile) Stat() (fs.FileInfo, error) {
return rf, nil
}
func (rf RemoteFile) Name() string {
return rf.FileName
}
func (rf RemoteFile) Size() int64 {
return rf.Length
}
func (rf RemoteFile) Mode() fs.FileMode {
return fs.ModeSymlink
}
func (rf RemoteFile) ModTime() time.Time {
return rf.LastModified
}
func (rf RemoteFile) IsDir() bool {
return false
}
func (rf RemoteFile) Sys() any {
return nil
}
func (rf *RemoteFile) calcRange(offset int64, length int64) (int64, int64) {
start := offset
end := offset + length
if end >= rf.Length-1 {
end = rf.Length - 1
}
return start, end
}
func (rf *RemoteFile) readInto(buf []byte, start int64, end int64) (n int, err error) {
req, err := http.NewRequest("GET", rf.URL.String(), nil)
if err != nil {
return 0, err
}
req.Header.Set("Range", fmt.Sprintf("bytes=%v-%v", start, end))
r, err := http.DefaultClient.Do(req)
if err != nil {
return 0, err
}
defer r.Body.Close()
n, err = io.ReadAtLeast(r.Body, buf, int(end-start))
return n, err
}