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.
Files changed (89) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +5 -0
  4. data/README.md +83 -0
  5. data/Rakefile +44 -0
  6. data/berkshelf-store.gemspec +26 -0
  7. data/bin/berkshelf-store +49 -0
  8. data/lib/berkshelf-store/backends/filesystem.rb +138 -0
  9. data/lib/berkshelf-store/backends.rb +5 -0
  10. data/lib/berkshelf-store/webservice.rb +100 -0
  11. data/lib/berkshelf-store.rb +5 -0
  12. data/test/data/arbo/cookbooks/apache2/1.10.4/data.json +1 -0
  13. data/test/data/arbo/cookbooks/couchbase/1.1.0/data.json +1 -0
  14. data/test/data/arbo/cookbooks/couchbase/1.2.0/data.json +1 -0
  15. data/test/data/catalog.json +69 -0
  16. data/test/data/tarballs/apache2-v1.10.4.tar.gz +0 -0
  17. data/test/data/tarballs/couchbase-v1.1.0.tar.gz +0 -0
  18. data/test/data/tarballs/couchbase-v1.2.0.tar.gz +0 -0
  19. data/test/data/tarballs/not_a_cookbook.tgz +0 -0
  20. data/test/test_berkshelf-store_backends_filesystem.rb +76 -0
  21. data/ui/static/bootstrap/css/bootstrap-theme.css +347 -0
  22. data/ui/static/bootstrap/css/bootstrap-theme.css.map +1 -0
  23. data/ui/static/bootstrap/css/bootstrap-theme.min.css +7 -0
  24. data/ui/static/bootstrap/css/bootstrap.css +5785 -0
  25. data/ui/static/bootstrap/css/bootstrap.css.map +1 -0
  26. data/ui/static/bootstrap/css/bootstrap.min.css +7 -0
  27. data/ui/static/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  28. data/ui/static/bootstrap/fonts/glyphicons-halflings-regular.svg +229 -0
  29. data/ui/static/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  30. data/ui/static/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  31. data/ui/static/bootstrap/js/bootstrap.js +1951 -0
  32. data/ui/static/bootstrap/js/bootstrap.min.js +6 -0
  33. data/ui/static/jquery/jquery.min.js +4 -0
  34. data/ui/static/jquery_file_upload/CONTRIBUTING.md +42 -0
  35. data/ui/static/jquery_file_upload/Gruntfile.js +37 -0
  36. data/ui/static/jquery_file_upload/README.md +123 -0
  37. data/ui/static/jquery_file_upload/angularjs.html +211 -0
  38. data/ui/static/jquery_file_upload/basic-plus.html +226 -0
  39. data/ui/static/jquery_file_upload/basic.html +136 -0
  40. data/ui/static/jquery_file_upload/blueimp-file-upload.jquery.json +50 -0
  41. data/ui/static/jquery_file_upload/bower.json +85 -0
  42. data/ui/static/jquery_file_upload/cors/postmessage.html +75 -0
  43. data/ui/static/jquery_file_upload/cors/result.html +24 -0
  44. data/ui/static/jquery_file_upload/css/demo-ie8.css +21 -0
  45. data/ui/static/jquery_file_upload/css/demo.css +67 -0
  46. data/ui/static/jquery_file_upload/css/jquery.fileupload-noscript.css +22 -0
  47. data/ui/static/jquery_file_upload/css/jquery.fileupload-ui-noscript.css +17 -0
  48. data/ui/static/jquery_file_upload/css/jquery.fileupload-ui.css +57 -0
  49. data/ui/static/jquery_file_upload/css/jquery.fileupload.css +36 -0
  50. data/ui/static/jquery_file_upload/css/style.css +15 -0
  51. data/ui/static/jquery_file_upload/img/loading.gif +0 -0
  52. data/ui/static/jquery_file_upload/img/progressbar.gif +0 -0
  53. data/ui/static/jquery_file_upload/index.html +255 -0
  54. data/ui/static/jquery_file_upload/jquery-ui.html +250 -0
  55. data/ui/static/jquery_file_upload/js/app.js +101 -0
  56. data/ui/static/jquery_file_upload/js/cors/jquery.postmessage-transport.js +117 -0
  57. data/ui/static/jquery_file_upload/js/cors/jquery.xdr-transport.js +86 -0
  58. data/ui/static/jquery_file_upload/js/jquery.fileupload-angular.js +429 -0
  59. data/ui/static/jquery_file_upload/js/jquery.fileupload-audio.js +106 -0
  60. data/ui/static/jquery_file_upload/js/jquery.fileupload-image.js +315 -0
  61. data/ui/static/jquery_file_upload/js/jquery.fileupload-jquery-ui.js +152 -0
  62. data/ui/static/jquery_file_upload/js/jquery.fileupload-process.js +172 -0
  63. data/ui/static/jquery_file_upload/js/jquery.fileupload-ui.js +699 -0
  64. data/ui/static/jquery_file_upload/js/jquery.fileupload-validate.js +119 -0
  65. data/ui/static/jquery_file_upload/js/jquery.fileupload-video.js +106 -0
  66. data/ui/static/jquery_file_upload/js/jquery.fileupload.js +1426 -0
  67. data/ui/static/jquery_file_upload/js/jquery.iframe-transport.js +214 -0
  68. data/ui/static/jquery_file_upload/js/main.js +75 -0
  69. data/ui/static/jquery_file_upload/js/vendor/jquery.ui.widget.js +530 -0
  70. data/ui/static/jquery_file_upload/package.json +54 -0
  71. data/ui/static/jquery_file_upload/server/gae-go/app/main.go +296 -0
  72. data/ui/static/jquery_file_upload/server/gae-go/app.yaml +12 -0
  73. data/ui/static/jquery_file_upload/server/gae-go/static/favicon.ico +0 -0
  74. data/ui/static/jquery_file_upload/server/gae-go/static/robots.txt +2 -0
  75. data/ui/static/jquery_file_upload/server/gae-python/app.yaml +16 -0
  76. data/ui/static/jquery_file_upload/server/gae-python/main.py +170 -0
  77. data/ui/static/jquery_file_upload/server/gae-python/static/favicon.ico +0 -0
  78. data/ui/static/jquery_file_upload/server/gae-python/static/robots.txt +2 -0
  79. data/ui/static/jquery_file_upload/server/node/package.json +41 -0
  80. data/ui/static/jquery_file_upload/server/node/server.js +292 -0
  81. data/ui/static/jquery_file_upload/server/php/UploadHandler.php +1330 -0
  82. data/ui/static/jquery_file_upload/server/php/index.php +15 -0
  83. data/ui/static/jquery_file_upload/test/index.html +166 -0
  84. data/ui/static/jquery_file_upload/test/test.js +1288 -0
  85. data/ui/views/catalog.erb +20 -0
  86. data/ui/views/doc.erb +23 -0
  87. data/ui/views/layout.erb +47 -0
  88. data/ui/views/upload.erb +46 -0
  89. 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
+ }
@@ -0,0 +1,12 @@
1
+ application: jquery-file-upload
2
+ version: 2
3
+ runtime: go
4
+ api_version: go1
5
+
6
+ handlers:
7
+ - url: /(favicon\.ico|robots\.txt)
8
+ static_files: static/\1
9
+ upload: static/(.*)
10
+ expiration: '1d'
11
+ - url: /.*
12
+ script: _go_app
@@ -0,0 +1,2 @@
1
+ User-agent: *
2
+ Disallow:
@@ -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
+ )
@@ -0,0 +1,2 @@
1
+ User-agent: *
2
+ Disallow:
@@ -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
+ }