/
memory.go
143 lines (121 loc) · 3.83 KB
/
memory.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
package storage
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"path"
"path/filepath"
"github.com/pkg/errors"
"github.com/spf13/afero"
)
// Memory is a storage backend that uses an in memory filesystem. It is intended only
// for use in development to avoid dependency on an external service.
type Memory struct {
root string
webRoot string
fs *afero.Afero
tempFs *afero.Afero
}
// MemoryParams contains parameter for instantiating a Memory storage backend
type MemoryParams struct {
root string
webRoot string
}
// NewMemoryParams returns default values for MemoryParams
func NewMemoryParams(localStorageRoot string, localStorageWebRoot string) MemoryParams {
absTmpPath, err := filepath.Abs(localStorageRoot)
if err != nil {
log.Fatalln(fmt.Errorf("could not get absolute path for %s", localStorageRoot))
}
storagePath := path.Join(absTmpPath, localStorageWebRoot)
webRoot := "/" + localStorageWebRoot
return MemoryParams{
root: storagePath,
webRoot: webRoot,
}
}
// NewMemory creates a new Memory struct using the provided MemoryParams
func NewMemory(params MemoryParams) *Memory {
var fs = afero.NewMemMapFs()
var tempFs = afero.NewMemMapFs()
return &Memory{
root: params.root,
webRoot: params.webRoot,
fs: &afero.Afero{Fs: fs},
tempFs: &afero.Afero{Fs: tempFs},
}
}
// Store stores the content from an io.ReadSeeker at the specified key.
func (fs *Memory) Store(key string, data io.ReadSeeker, _ string, _ *string) (*StoreResult, error) {
if key == "" {
return nil, errors.New("A valid StorageKey must be set before data can be uploaded")
}
joined := filepath.Join(fs.root, key)
dir := filepath.Dir(joined)
err := fs.fs.MkdirAll(dir, 0755)
if err != nil {
return nil, errors.Wrap(err, "could not create parent directory")
}
file, err := fs.fs.Create(joined)
if err != nil {
return nil, errors.Wrap(err, "could not open file")
}
defer func() {
if closeErr := file.Close(); closeErr != nil {
fmt.Println("Failed to close file")
}
}()
_, err = io.Copy(file, data)
if err != nil {
return nil, errors.Wrap(err, "write to disk failed")
}
return &StoreResult{}, nil
}
// Delete deletes the file at the specified key
func (fs *Memory) Delete(key string) error {
joined := filepath.Join(fs.root, key)
return errors.Wrap(fs.fs.Remove(joined), "could not remove file")
}
// PresignedURL returns a URL that provides access to a file for 15 mintes.
func (fs *Memory) PresignedURL(key, contentType string) (string, error) {
values := url.Values{}
values.Add("contentType", contentType)
url := fs.webRoot + "/" + key + "?" + values.Encode()
return url, nil
}
// Fetch retrieves a copy of a file and stores it in a tempfile. The path to this
// file is returned.
//
// It is the caller's responsibility to delete the tempfile.
func (fs *Memory) Fetch(key string) (io.ReadCloser, error) {
sourcePath := filepath.Join(fs.root, key)
f, err := fs.fs.Open(sourcePath)
return f, errors.Wrap(err, "could not open file")
}
// Tags returns the tags for a specified key
func (fs *Memory) Tags(_ string) (map[string]string, error) {
tags := make(map[string]string)
return tags, nil
}
// FileSystem returns the underlying afero filesystem
func (fs *Memory) FileSystem() *afero.Afero {
return fs.fs
}
// TempFileSystem returns the temporary afero filesystem
func (fs *Memory) TempFileSystem() *afero.Afero {
return fs.tempFs
}
// NewMemoryHandler returns an Handler that adds a Content-Type header so that
// files are handled properly by the browser.
func NewMemoryHandler(root string) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
contentType := r.URL.Query().Get("contentType")
if contentType != "" {
w.Header().Add("Content-Type", contentType)
}
input := filepath.Join(root, filepath.FromSlash(path.Clean("/"+r.URL.Path)))
http.ServeFile(w, r, input)
})
}