berkshelf-store 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +2 -0
- data/Gemfile +5 -0
- data/README.md +83 -0
- data/Rakefile +44 -0
- data/berkshelf-store.gemspec +26 -0
- data/bin/berkshelf-store +49 -0
- data/lib/berkshelf-store/backends/filesystem.rb +138 -0
- data/lib/berkshelf-store/backends.rb +5 -0
- data/lib/berkshelf-store/webservice.rb +100 -0
- data/lib/berkshelf-store.rb +5 -0
- data/test/data/arbo/cookbooks/apache2/1.10.4/data.json +1 -0
- data/test/data/arbo/cookbooks/couchbase/1.1.0/data.json +1 -0
- data/test/data/arbo/cookbooks/couchbase/1.2.0/data.json +1 -0
- data/test/data/catalog.json +69 -0
- data/test/data/tarballs/apache2-v1.10.4.tar.gz +0 -0
- data/test/data/tarballs/couchbase-v1.1.0.tar.gz +0 -0
- data/test/data/tarballs/couchbase-v1.2.0.tar.gz +0 -0
- data/test/data/tarballs/not_a_cookbook.tgz +0 -0
- data/test/test_berkshelf-store_backends_filesystem.rb +76 -0
- data/ui/static/bootstrap/css/bootstrap-theme.css +347 -0
- data/ui/static/bootstrap/css/bootstrap-theme.css.map +1 -0
- data/ui/static/bootstrap/css/bootstrap-theme.min.css +7 -0
- data/ui/static/bootstrap/css/bootstrap.css +5785 -0
- data/ui/static/bootstrap/css/bootstrap.css.map +1 -0
- data/ui/static/bootstrap/css/bootstrap.min.css +7 -0
- data/ui/static/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- data/ui/static/bootstrap/fonts/glyphicons-halflings-regular.svg +229 -0
- data/ui/static/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/ui/static/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- data/ui/static/bootstrap/js/bootstrap.js +1951 -0
- data/ui/static/bootstrap/js/bootstrap.min.js +6 -0
- data/ui/static/jquery/jquery.min.js +4 -0
- data/ui/static/jquery_file_upload/CONTRIBUTING.md +42 -0
- data/ui/static/jquery_file_upload/Gruntfile.js +37 -0
- data/ui/static/jquery_file_upload/README.md +123 -0
- data/ui/static/jquery_file_upload/angularjs.html +211 -0
- data/ui/static/jquery_file_upload/basic-plus.html +226 -0
- data/ui/static/jquery_file_upload/basic.html +136 -0
- data/ui/static/jquery_file_upload/blueimp-file-upload.jquery.json +50 -0
- data/ui/static/jquery_file_upload/bower.json +85 -0
- data/ui/static/jquery_file_upload/cors/postmessage.html +75 -0
- data/ui/static/jquery_file_upload/cors/result.html +24 -0
- data/ui/static/jquery_file_upload/css/demo-ie8.css +21 -0
- data/ui/static/jquery_file_upload/css/demo.css +67 -0
- data/ui/static/jquery_file_upload/css/jquery.fileupload-noscript.css +22 -0
- data/ui/static/jquery_file_upload/css/jquery.fileupload-ui-noscript.css +17 -0
- data/ui/static/jquery_file_upload/css/jquery.fileupload-ui.css +57 -0
- data/ui/static/jquery_file_upload/css/jquery.fileupload.css +36 -0
- data/ui/static/jquery_file_upload/css/style.css +15 -0
- data/ui/static/jquery_file_upload/img/loading.gif +0 -0
- data/ui/static/jquery_file_upload/img/progressbar.gif +0 -0
- data/ui/static/jquery_file_upload/index.html +255 -0
- data/ui/static/jquery_file_upload/jquery-ui.html +250 -0
- data/ui/static/jquery_file_upload/js/app.js +101 -0
- data/ui/static/jquery_file_upload/js/cors/jquery.postmessage-transport.js +117 -0
- data/ui/static/jquery_file_upload/js/cors/jquery.xdr-transport.js +86 -0
- data/ui/static/jquery_file_upload/js/jquery.fileupload-angular.js +429 -0
- data/ui/static/jquery_file_upload/js/jquery.fileupload-audio.js +106 -0
- data/ui/static/jquery_file_upload/js/jquery.fileupload-image.js +315 -0
- data/ui/static/jquery_file_upload/js/jquery.fileupload-jquery-ui.js +152 -0
- data/ui/static/jquery_file_upload/js/jquery.fileupload-process.js +172 -0
- data/ui/static/jquery_file_upload/js/jquery.fileupload-ui.js +699 -0
- data/ui/static/jquery_file_upload/js/jquery.fileupload-validate.js +119 -0
- data/ui/static/jquery_file_upload/js/jquery.fileupload-video.js +106 -0
- data/ui/static/jquery_file_upload/js/jquery.fileupload.js +1426 -0
- data/ui/static/jquery_file_upload/js/jquery.iframe-transport.js +214 -0
- data/ui/static/jquery_file_upload/js/main.js +75 -0
- data/ui/static/jquery_file_upload/js/vendor/jquery.ui.widget.js +530 -0
- data/ui/static/jquery_file_upload/package.json +54 -0
- data/ui/static/jquery_file_upload/server/gae-go/app/main.go +296 -0
- data/ui/static/jquery_file_upload/server/gae-go/app.yaml +12 -0
- data/ui/static/jquery_file_upload/server/gae-go/static/favicon.ico +0 -0
- data/ui/static/jquery_file_upload/server/gae-go/static/robots.txt +2 -0
- data/ui/static/jquery_file_upload/server/gae-python/app.yaml +16 -0
- data/ui/static/jquery_file_upload/server/gae-python/main.py +170 -0
- data/ui/static/jquery_file_upload/server/gae-python/static/favicon.ico +0 -0
- data/ui/static/jquery_file_upload/server/gae-python/static/robots.txt +2 -0
- data/ui/static/jquery_file_upload/server/node/package.json +41 -0
- data/ui/static/jquery_file_upload/server/node/server.js +292 -0
- data/ui/static/jquery_file_upload/server/php/UploadHandler.php +1330 -0
- data/ui/static/jquery_file_upload/server/php/index.php +15 -0
- data/ui/static/jquery_file_upload/test/index.html +166 -0
- data/ui/static/jquery_file_upload/test/test.js +1288 -0
- data/ui/views/catalog.erb +20 -0
- data/ui/views/doc.erb +23 -0
- data/ui/views/layout.erb +47 -0
- data/ui/views/upload.erb +46 -0
- metadata +210 -0
@@ -0,0 +1,296 @@
|
|
1
|
+
/*
|
2
|
+
* jQuery File Upload Plugin GAE Go Example 3.1.1
|
3
|
+
* https://github.com/blueimp/jQuery-File-Upload
|
4
|
+
*
|
5
|
+
* Copyright 2011, Sebastian Tschan
|
6
|
+
* https://blueimp.net
|
7
|
+
*
|
8
|
+
* Licensed under the MIT license:
|
9
|
+
* http://www.opensource.org/licenses/MIT
|
10
|
+
*/
|
11
|
+
|
12
|
+
package app
|
13
|
+
|
14
|
+
import (
|
15
|
+
"appengine"
|
16
|
+
"appengine/blobstore"
|
17
|
+
"appengine/image"
|
18
|
+
"appengine/taskqueue"
|
19
|
+
"bytes"
|
20
|
+
"encoding/json"
|
21
|
+
"fmt"
|
22
|
+
"io"
|
23
|
+
"log"
|
24
|
+
"mime/multipart"
|
25
|
+
"net/http"
|
26
|
+
"net/url"
|
27
|
+
"regexp"
|
28
|
+
"strings"
|
29
|
+
"time"
|
30
|
+
)
|
31
|
+
|
32
|
+
const (
|
33
|
+
WEBSITE = "http://blueimp.github.io/jQuery-File-Upload/"
|
34
|
+
MIN_FILE_SIZE = 1 // bytes
|
35
|
+
MAX_FILE_SIZE = 5000000 // bytes
|
36
|
+
IMAGE_TYPES = "image/(gif|p?jpeg|(x-)?png)"
|
37
|
+
ACCEPT_FILE_TYPES = IMAGE_TYPES
|
38
|
+
EXPIRATION_TIME = 300 // seconds
|
39
|
+
THUMBNAIL_PARAM = "=s80"
|
40
|
+
)
|
41
|
+
|
42
|
+
var (
|
43
|
+
imageTypes = regexp.MustCompile(IMAGE_TYPES)
|
44
|
+
acceptFileTypes = regexp.MustCompile(ACCEPT_FILE_TYPES)
|
45
|
+
)
|
46
|
+
|
47
|
+
type FileInfo struct {
|
48
|
+
Key appengine.BlobKey `json:"-"`
|
49
|
+
Url string `json:"url,omitempty"`
|
50
|
+
ThumbnailUrl string `json:"thumbnailUrl,omitempty"`
|
51
|
+
Name string `json:"name"`
|
52
|
+
Type string `json:"type"`
|
53
|
+
Size int64 `json:"size"`
|
54
|
+
Error string `json:"error,omitempty"`
|
55
|
+
DeleteUrl string `json:"deleteUrl,omitempty"`
|
56
|
+
DeleteType string `json:"deleteType,omitempty"`
|
57
|
+
}
|
58
|
+
|
59
|
+
func (fi *FileInfo) ValidateType() (valid bool) {
|
60
|
+
if acceptFileTypes.MatchString(fi.Type) {
|
61
|
+
return true
|
62
|
+
}
|
63
|
+
fi.Error = "Filetype not allowed"
|
64
|
+
return false
|
65
|
+
}
|
66
|
+
|
67
|
+
func (fi *FileInfo) ValidateSize() (valid bool) {
|
68
|
+
if fi.Size < MIN_FILE_SIZE {
|
69
|
+
fi.Error = "File is too small"
|
70
|
+
} else if fi.Size > MAX_FILE_SIZE {
|
71
|
+
fi.Error = "File is too big"
|
72
|
+
} else {
|
73
|
+
return true
|
74
|
+
}
|
75
|
+
return false
|
76
|
+
}
|
77
|
+
|
78
|
+
func (fi *FileInfo) CreateUrls(r *http.Request, c appengine.Context) {
|
79
|
+
u := &url.URL{
|
80
|
+
Scheme: r.URL.Scheme,
|
81
|
+
Host: appengine.DefaultVersionHostname(c),
|
82
|
+
Path: "/",
|
83
|
+
}
|
84
|
+
uString := u.String()
|
85
|
+
fi.Url = uString + escape(string(fi.Key)) + "/" +
|
86
|
+
escape(string(fi.Name))
|
87
|
+
fi.DeleteUrl = fi.Url + "?delete=true"
|
88
|
+
fi.DeleteType = "DELETE"
|
89
|
+
if imageTypes.MatchString(fi.Type) {
|
90
|
+
servingUrl, err := image.ServingURL(
|
91
|
+
c,
|
92
|
+
fi.Key,
|
93
|
+
&image.ServingURLOptions{
|
94
|
+
Secure: strings.HasSuffix(u.Scheme, "s"),
|
95
|
+
Size: 0,
|
96
|
+
Crop: false,
|
97
|
+
},
|
98
|
+
)
|
99
|
+
check(err)
|
100
|
+
fi.ThumbnailUrl = servingUrl.String() + THUMBNAIL_PARAM
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
func check(err error) {
|
105
|
+
if err != nil {
|
106
|
+
panic(err)
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
func escape(s string) string {
|
111
|
+
return strings.Replace(url.QueryEscape(s), "+", "%20", -1)
|
112
|
+
}
|
113
|
+
|
114
|
+
func delayedDelete(c appengine.Context, fi *FileInfo) {
|
115
|
+
if key := string(fi.Key); key != "" {
|
116
|
+
task := &taskqueue.Task{
|
117
|
+
Path: "/" + escape(key) + "/-",
|
118
|
+
Method: "DELETE",
|
119
|
+
Delay: time.Duration(EXPIRATION_TIME) * time.Second,
|
120
|
+
}
|
121
|
+
taskqueue.Add(c, task, "")
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
func handleUpload(r *http.Request, p *multipart.Part) (fi *FileInfo) {
|
126
|
+
fi = &FileInfo{
|
127
|
+
Name: p.FileName(),
|
128
|
+
Type: p.Header.Get("Content-Type"),
|
129
|
+
}
|
130
|
+
if !fi.ValidateType() {
|
131
|
+
return
|
132
|
+
}
|
133
|
+
defer func() {
|
134
|
+
if rec := recover(); rec != nil {
|
135
|
+
log.Println(rec)
|
136
|
+
fi.Error = rec.(error).Error()
|
137
|
+
}
|
138
|
+
}()
|
139
|
+
lr := &io.LimitedReader{R: p, N: MAX_FILE_SIZE + 1}
|
140
|
+
context := appengine.NewContext(r)
|
141
|
+
w, err := blobstore.Create(context, fi.Type)
|
142
|
+
defer func() {
|
143
|
+
w.Close()
|
144
|
+
fi.Size = MAX_FILE_SIZE + 1 - lr.N
|
145
|
+
fi.Key, err = w.Key()
|
146
|
+
check(err)
|
147
|
+
if !fi.ValidateSize() {
|
148
|
+
err := blobstore.Delete(context, fi.Key)
|
149
|
+
check(err)
|
150
|
+
return
|
151
|
+
}
|
152
|
+
delayedDelete(context, fi)
|
153
|
+
fi.CreateUrls(r, context)
|
154
|
+
}()
|
155
|
+
check(err)
|
156
|
+
_, err = io.Copy(w, lr)
|
157
|
+
return
|
158
|
+
}
|
159
|
+
|
160
|
+
func getFormValue(p *multipart.Part) string {
|
161
|
+
var b bytes.Buffer
|
162
|
+
io.CopyN(&b, p, int64(1<<20)) // Copy max: 1 MiB
|
163
|
+
return b.String()
|
164
|
+
}
|
165
|
+
|
166
|
+
func handleUploads(r *http.Request) (fileInfos []*FileInfo) {
|
167
|
+
fileInfos = make([]*FileInfo, 0)
|
168
|
+
mr, err := r.MultipartReader()
|
169
|
+
check(err)
|
170
|
+
r.Form, err = url.ParseQuery(r.URL.RawQuery)
|
171
|
+
check(err)
|
172
|
+
part, err := mr.NextPart()
|
173
|
+
for err == nil {
|
174
|
+
if name := part.FormName(); name != "" {
|
175
|
+
if part.FileName() != "" {
|
176
|
+
fileInfos = append(fileInfos, handleUpload(r, part))
|
177
|
+
} else {
|
178
|
+
r.Form[name] = append(r.Form[name], getFormValue(part))
|
179
|
+
}
|
180
|
+
}
|
181
|
+
part, err = mr.NextPart()
|
182
|
+
}
|
183
|
+
return
|
184
|
+
}
|
185
|
+
|
186
|
+
func get(w http.ResponseWriter, r *http.Request) {
|
187
|
+
if r.URL.Path == "/" {
|
188
|
+
http.Redirect(w, r, WEBSITE, http.StatusFound)
|
189
|
+
return
|
190
|
+
}
|
191
|
+
parts := strings.Split(r.URL.Path, "/")
|
192
|
+
if len(parts) == 3 {
|
193
|
+
if key := parts[1]; key != "" {
|
194
|
+
blobKey := appengine.BlobKey(key)
|
195
|
+
bi, err := blobstore.Stat(appengine.NewContext(r), blobKey)
|
196
|
+
if err == nil {
|
197
|
+
w.Header().Add("X-Content-Type-Options", "nosniff")
|
198
|
+
if !imageTypes.MatchString(bi.ContentType) {
|
199
|
+
w.Header().Add("Content-Type", "application/octet-stream")
|
200
|
+
w.Header().Add(
|
201
|
+
"Content-Disposition",
|
202
|
+
fmt.Sprintf("attachment; filename=\"%s\"", parts[2]),
|
203
|
+
)
|
204
|
+
}
|
205
|
+
w.Header().Add(
|
206
|
+
"Cache-Control",
|
207
|
+
fmt.Sprintf("public,max-age=%d", EXPIRATION_TIME),
|
208
|
+
)
|
209
|
+
blobstore.Send(w, blobKey)
|
210
|
+
return
|
211
|
+
}
|
212
|
+
}
|
213
|
+
}
|
214
|
+
http.Error(w, "404 Not Found", http.StatusNotFound)
|
215
|
+
}
|
216
|
+
|
217
|
+
func post(w http.ResponseWriter, r *http.Request) {
|
218
|
+
result := make(map[string][]*FileInfo, 1)
|
219
|
+
result["files"] = handleUploads(r)
|
220
|
+
b, err := json.Marshal(result)
|
221
|
+
check(err)
|
222
|
+
if redirect := r.FormValue("redirect"); redirect != "" {
|
223
|
+
if strings.Contains(redirect, "%s") {
|
224
|
+
redirect = fmt.Sprintf(
|
225
|
+
redirect,
|
226
|
+
escape(string(b)),
|
227
|
+
)
|
228
|
+
}
|
229
|
+
http.Redirect(w, r, redirect, http.StatusFound)
|
230
|
+
return
|
231
|
+
}
|
232
|
+
w.Header().Set("Cache-Control", "no-cache")
|
233
|
+
jsonType := "application/json"
|
234
|
+
if strings.Index(r.Header.Get("Accept"), jsonType) != -1 {
|
235
|
+
w.Header().Set("Content-Type", jsonType)
|
236
|
+
}
|
237
|
+
fmt.Fprintln(w, string(b))
|
238
|
+
}
|
239
|
+
|
240
|
+
func delete(w http.ResponseWriter, r *http.Request) {
|
241
|
+
parts := strings.Split(r.URL.Path, "/")
|
242
|
+
if len(parts) != 3 {
|
243
|
+
return
|
244
|
+
}
|
245
|
+
result := make(map[string]bool, 1)
|
246
|
+
if key := parts[1]; key != "" {
|
247
|
+
c := appengine.NewContext(r)
|
248
|
+
blobKey := appengine.BlobKey(key)
|
249
|
+
err := blobstore.Delete(c, blobKey)
|
250
|
+
check(err)
|
251
|
+
err = image.DeleteServingURL(c, blobKey)
|
252
|
+
check(err)
|
253
|
+
result[key] = true
|
254
|
+
}
|
255
|
+
jsonType := "application/json"
|
256
|
+
if strings.Index(r.Header.Get("Accept"), jsonType) != -1 {
|
257
|
+
w.Header().Set("Content-Type", jsonType)
|
258
|
+
}
|
259
|
+
b, err := json.Marshal(result)
|
260
|
+
check(err)
|
261
|
+
fmt.Fprintln(w, string(b))
|
262
|
+
}
|
263
|
+
|
264
|
+
func handle(w http.ResponseWriter, r *http.Request) {
|
265
|
+
params, err := url.ParseQuery(r.URL.RawQuery)
|
266
|
+
check(err)
|
267
|
+
w.Header().Add("Access-Control-Allow-Origin", "*")
|
268
|
+
w.Header().Add(
|
269
|
+
"Access-Control-Allow-Methods",
|
270
|
+
"OPTIONS, HEAD, GET, POST, PUT, DELETE",
|
271
|
+
)
|
272
|
+
w.Header().Add(
|
273
|
+
"Access-Control-Allow-Headers",
|
274
|
+
"Content-Type, Content-Range, Content-Disposition",
|
275
|
+
)
|
276
|
+
switch r.Method {
|
277
|
+
case "OPTIONS":
|
278
|
+
case "HEAD":
|
279
|
+
case "GET":
|
280
|
+
get(w, r)
|
281
|
+
case "POST":
|
282
|
+
if len(params["_method"]) > 0 && params["_method"][0] == "DELETE" {
|
283
|
+
delete(w, r)
|
284
|
+
} else {
|
285
|
+
post(w, r)
|
286
|
+
}
|
287
|
+
case "DELETE":
|
288
|
+
delete(w, r)
|
289
|
+
default:
|
290
|
+
http.Error(w, "501 Not Implemented", http.StatusNotImplemented)
|
291
|
+
}
|
292
|
+
}
|
293
|
+
|
294
|
+
func init() {
|
295
|
+
http.HandleFunc("/", handle)
|
296
|
+
}
|
Binary file
|
@@ -0,0 +1,16 @@
|
|
1
|
+
application: jquery-file-upload
|
2
|
+
version: 1
|
3
|
+
runtime: python27
|
4
|
+
api_version: 1
|
5
|
+
threadsafe: true
|
6
|
+
|
7
|
+
builtins:
|
8
|
+
- deferred: on
|
9
|
+
|
10
|
+
handlers:
|
11
|
+
- url: /(favicon\.ico|robots\.txt)
|
12
|
+
static_files: static/\1
|
13
|
+
upload: static/(.*)
|
14
|
+
expiration: '1d'
|
15
|
+
- url: /.*
|
16
|
+
script: main.app
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# jQuery File Upload Plugin GAE Python Example 2.1.1
|
4
|
+
# https://github.com/blueimp/jQuery-File-Upload
|
5
|
+
#
|
6
|
+
# Copyright 2011, Sebastian Tschan
|
7
|
+
# https://blueimp.net
|
8
|
+
#
|
9
|
+
# Licensed under the MIT license:
|
10
|
+
# http://www.opensource.org/licenses/MIT
|
11
|
+
#
|
12
|
+
|
13
|
+
from __future__ import with_statement
|
14
|
+
from google.appengine.api import files, images
|
15
|
+
from google.appengine.ext import blobstore, deferred
|
16
|
+
from google.appengine.ext.webapp import blobstore_handlers
|
17
|
+
import json
|
18
|
+
import re
|
19
|
+
import urllib
|
20
|
+
import webapp2
|
21
|
+
|
22
|
+
WEBSITE = 'http://blueimp.github.io/jQuery-File-Upload/'
|
23
|
+
MIN_FILE_SIZE = 1 # bytes
|
24
|
+
MAX_FILE_SIZE = 5000000 # bytes
|
25
|
+
IMAGE_TYPES = re.compile('image/(gif|p?jpeg|(x-)?png)')
|
26
|
+
ACCEPT_FILE_TYPES = IMAGE_TYPES
|
27
|
+
THUMBNAIL_MODIFICATOR = '=s80' # max width / height
|
28
|
+
EXPIRATION_TIME = 300 # seconds
|
29
|
+
|
30
|
+
|
31
|
+
def cleanup(blob_keys):
|
32
|
+
blobstore.delete(blob_keys)
|
33
|
+
|
34
|
+
|
35
|
+
class UploadHandler(webapp2.RequestHandler):
|
36
|
+
|
37
|
+
def initialize(self, request, response):
|
38
|
+
super(UploadHandler, self).initialize(request, response)
|
39
|
+
self.response.headers['Access-Control-Allow-Origin'] = '*'
|
40
|
+
self.response.headers[
|
41
|
+
'Access-Control-Allow-Methods'
|
42
|
+
] = 'OPTIONS, HEAD, GET, POST, PUT, DELETE'
|
43
|
+
self.response.headers[
|
44
|
+
'Access-Control-Allow-Headers'
|
45
|
+
] = 'Content-Type, Content-Range, Content-Disposition'
|
46
|
+
|
47
|
+
def validate(self, file):
|
48
|
+
if file['size'] < MIN_FILE_SIZE:
|
49
|
+
file['error'] = 'File is too small'
|
50
|
+
elif file['size'] > MAX_FILE_SIZE:
|
51
|
+
file['error'] = 'File is too big'
|
52
|
+
elif not ACCEPT_FILE_TYPES.match(file['type']):
|
53
|
+
file['error'] = 'Filetype not allowed'
|
54
|
+
else:
|
55
|
+
return True
|
56
|
+
return False
|
57
|
+
|
58
|
+
def get_file_size(self, file):
|
59
|
+
file.seek(0, 2) # Seek to the end of the file
|
60
|
+
size = file.tell() # Get the position of EOF
|
61
|
+
file.seek(0) # Reset the file position to the beginning
|
62
|
+
return size
|
63
|
+
|
64
|
+
def write_blob(self, data, info):
|
65
|
+
blob = files.blobstore.create(
|
66
|
+
mime_type=info['type'],
|
67
|
+
_blobinfo_uploaded_filename=info['name']
|
68
|
+
)
|
69
|
+
with files.open(blob, 'a') as f:
|
70
|
+
f.write(data)
|
71
|
+
files.finalize(blob)
|
72
|
+
return files.blobstore.get_blob_key(blob)
|
73
|
+
|
74
|
+
def handle_upload(self):
|
75
|
+
results = []
|
76
|
+
blob_keys = []
|
77
|
+
for name, fieldStorage in self.request.POST.items():
|
78
|
+
if type(fieldStorage) is unicode:
|
79
|
+
continue
|
80
|
+
result = {}
|
81
|
+
result['name'] = re.sub(
|
82
|
+
r'^.*\\',
|
83
|
+
'',
|
84
|
+
fieldStorage.filename
|
85
|
+
)
|
86
|
+
result['type'] = fieldStorage.type
|
87
|
+
result['size'] = self.get_file_size(fieldStorage.file)
|
88
|
+
if self.validate(result):
|
89
|
+
blob_key = str(
|
90
|
+
self.write_blob(fieldStorage.value, result)
|
91
|
+
)
|
92
|
+
blob_keys.append(blob_key)
|
93
|
+
result['deleteType'] = 'DELETE'
|
94
|
+
result['deleteUrl'] = self.request.host_url +\
|
95
|
+
'/?key=' + urllib.quote(blob_key, '')
|
96
|
+
if (IMAGE_TYPES.match(result['type'])):
|
97
|
+
try:
|
98
|
+
result['url'] = images.get_serving_url(
|
99
|
+
blob_key,
|
100
|
+
secure_url=self.request.host_url.startswith(
|
101
|
+
'https'
|
102
|
+
)
|
103
|
+
)
|
104
|
+
result['thumbnailUrl'] = result['url'] +\
|
105
|
+
THUMBNAIL_MODIFICATOR
|
106
|
+
except: # Could not get an image serving url
|
107
|
+
pass
|
108
|
+
if not 'url' in result:
|
109
|
+
result['url'] = self.request.host_url +\
|
110
|
+
'/' + blob_key + '/' + urllib.quote(
|
111
|
+
result['name'].encode('utf-8'), '')
|
112
|
+
results.append(result)
|
113
|
+
deferred.defer(
|
114
|
+
cleanup,
|
115
|
+
blob_keys,
|
116
|
+
_countdown=EXPIRATION_TIME
|
117
|
+
)
|
118
|
+
return results
|
119
|
+
|
120
|
+
def options(self):
|
121
|
+
pass
|
122
|
+
|
123
|
+
def head(self):
|
124
|
+
pass
|
125
|
+
|
126
|
+
def get(self):
|
127
|
+
self.redirect(WEBSITE)
|
128
|
+
|
129
|
+
def post(self):
|
130
|
+
if (self.request.get('_method') == 'DELETE'):
|
131
|
+
return self.delete()
|
132
|
+
result = {'files': self.handle_upload()}
|
133
|
+
s = json.dumps(result, separators=(',', ':'))
|
134
|
+
redirect = self.request.get('redirect')
|
135
|
+
if redirect:
|
136
|
+
return self.redirect(str(
|
137
|
+
redirect.replace('%s', urllib.quote(s, ''), 1)
|
138
|
+
))
|
139
|
+
if 'application/json' in self.request.headers.get('Accept'):
|
140
|
+
self.response.headers['Content-Type'] = 'application/json'
|
141
|
+
self.response.write(s)
|
142
|
+
|
143
|
+
def delete(self):
|
144
|
+
key = self.request.get('key') or ''
|
145
|
+
blobstore.delete(key)
|
146
|
+
s = json.dumps({key: True}, separators=(',', ':'))
|
147
|
+
if 'application/json' in self.request.headers.get('Accept'):
|
148
|
+
self.response.headers['Content-Type'] = 'application/json'
|
149
|
+
self.response.write(s)
|
150
|
+
|
151
|
+
|
152
|
+
class DownloadHandler(blobstore_handlers.BlobstoreDownloadHandler):
|
153
|
+
def get(self, key, filename):
|
154
|
+
if not blobstore.get(key):
|
155
|
+
self.error(404)
|
156
|
+
else:
|
157
|
+
# Prevent browsers from MIME-sniffing the content-type:
|
158
|
+
self.response.headers['X-Content-Type-Options'] = 'nosniff'
|
159
|
+
# Cache for the expiration time:
|
160
|
+
self.response.headers['Cache-Control'] = 'public,max-age=%d' % EXPIRATION_TIME
|
161
|
+
# Send the file forcing a download dialog:
|
162
|
+
self.send_blob(key, save_as=filename, content_type='application/octet-stream')
|
163
|
+
|
164
|
+
app = webapp2.WSGIApplication(
|
165
|
+
[
|
166
|
+
('/', UploadHandler),
|
167
|
+
('/([^/]+)/([^/]+)', DownloadHandler)
|
168
|
+
],
|
169
|
+
debug=True
|
170
|
+
)
|
Binary file
|
@@ -0,0 +1,41 @@
|
|
1
|
+
{
|
2
|
+
"name": "blueimp-file-upload-node",
|
3
|
+
"version": "2.1.0",
|
4
|
+
"title": "jQuery File Upload Node.js example",
|
5
|
+
"description": "Node.js implementation example of a file upload handler for jQuery File Upload.",
|
6
|
+
"keywords": [
|
7
|
+
"file",
|
8
|
+
"upload",
|
9
|
+
"cross-domain",
|
10
|
+
"cross-site",
|
11
|
+
"node"
|
12
|
+
],
|
13
|
+
"homepage": "https://github.com/blueimp/jQuery-File-Upload",
|
14
|
+
"author": {
|
15
|
+
"name": "Sebastian Tschan",
|
16
|
+
"url": "https://blueimp.net"
|
17
|
+
},
|
18
|
+
"maintainers": [
|
19
|
+
{
|
20
|
+
"name": "Sebastian Tschan",
|
21
|
+
"url": "https://blueimp.net"
|
22
|
+
}
|
23
|
+
],
|
24
|
+
"repository": {
|
25
|
+
"type": "git",
|
26
|
+
"url": "git://github.com/blueimp/jQuery-File-Upload.git"
|
27
|
+
},
|
28
|
+
"bugs": "https://github.com/blueimp/jQuery-File-Upload/issues",
|
29
|
+
"licenses": [
|
30
|
+
{
|
31
|
+
"type": "MIT",
|
32
|
+
"url": "http://www.opensource.org/licenses/MIT"
|
33
|
+
}
|
34
|
+
],
|
35
|
+
"dependencies": {
|
36
|
+
"formidable": ">=1.0.11",
|
37
|
+
"node-static": ">=0.6.5",
|
38
|
+
"imagemagick": ">=0.1.3"
|
39
|
+
},
|
40
|
+
"main": "server.js"
|
41
|
+
}
|