-
Notifications
You must be signed in to change notification settings - Fork 2
/
resource.go
100 lines (90 loc) · 2.67 KB
/
resource.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
package asset
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
)
// The Resource class wraps a streamable file or remote Resource.
type Resource struct {
io.ReadCloser
url *url.URL
}
// Returns the path to this resource.
func (r *Resource) Path() string {
return r.url.String()
}
// Return the remote path to this resource. If this is a remote resource then
// this method returns the base path (without leading /) of the remote URL.
// Otherwise, this method returns the same value as Path().
func (r *Resource) RemotePath() string {
if r.IsRemote() {
return filepath.Base(r.url.Path)
}
return r.Path()
}
// Returns true if the Resource is streamed over http/https.
func (r *Resource) IsRemote() bool {
return r.url.Scheme != ""
}
// Create a new Resource data stream. If relTo is specified and pathToResource
// does not define a scheme, then the path to the new Resource will be generated
// by concatenating the base path of relTo and pathToResource.
//
// This function can handle http/https URLs by delegating to the net/http package.
// The caller must make sure to close the returned io.ReadCloser to prevent mem leaks.
func NewResource(pathToResource string, relTo *Resource) (*Resource, error) {
// Replace forward slashes with backslaces and try parsing as a URL
url, err := url.Parse(strings.Replace(pathToResource, `\`, `/`, -1))
if err != nil {
return nil, err
}
// If this is a relative url, clone parent url and adjust its path
if url.Scheme == "" && relTo != nil {
path := url.Path
url, _ = url.Parse(relTo.url.String())
prefix := url.Path
if url.Scheme == "" {
prefix, err = filepath.Abs(relTo.url.String())
if err != nil {
return nil, fmt.Errorf("resource: could not detect abs path for %s; %s", relTo.url.String(), err.Error())
}
}
url.Path = filepath.Dir(prefix) + "/" + path
}
var reader io.ReadCloser
switch url.Scheme {
case "":
reader, err = os.Open(filepath.Clean(url.Path))
if err != nil {
return nil, err
}
case "http", "https":
resp, err := http.Get(url.String())
if err != nil {
return nil, fmt.Errorf("resource: could not fetch '%s': %s", url.String(), err)
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("resource: could not fetch '%s': status %d", url.String(), resp.StatusCode)
}
reader = resp.Body
default:
return nil, fmt.Errorf("resource: unsupported scheme '%s'", url.Scheme)
}
return &Resource{
ReadCloser: reader,
url: url,
}, nil
}
// Create a resource from a reader.
func NewResourceFromStream(name string, source io.Reader) *Resource {
url, _ := url.Parse(name)
return &Resource{
ReadCloser: ioutil.NopCloser(source),
url: url,
}
}