This repository has been archived by the owner on Jul 31, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 21
/
binaries.coffee
283 lines (235 loc) · 9.17 KB
/
binaries.coffee
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
multiparty = require 'multiparty'
log = require('printit')
date: true
prefix: 'binaries'
db = require('../helpers/db_connect_helper').db_connect()
binaryManagement = require '../lib/binary'
dbHelper = require '../lib/db_remove_helper'
downloader = require '../lib/downloader'
async = require 'async'
https = require 'https'
fs = require 'fs'
## Actions
# API to manage attachments separately from the CouchDB API. Cozy term for such
# kind of attachements is binary.
# POST /data/:id/binaries
# Allow to create a binary and to link it to given document.
module.exports.add = (req, res, next) ->
# Parse given form to extract image blobs.
form = new multiparty.Form
autoFields: false
autoFiles: false
form.parse req
# Dirty hack to end request if no file were sent when form is fully parsed.
nofile = true
fields = {}
# We read part one by one to avoid writing the full file to the disk
# and send it directly as a stream.
form.on 'part', (part) ->
# It's a field, we store it in case of the file name is set in the
# form
unless part.filename?
fields[part.name] = ''
part.on 'data', (buffer) ->
fields[part.name] = buffer.toString()
part.resume()
# It's a file, we pipe it directly to Couch to avoid too much memory
# consumption.
# The 'file' event from the multiparty form stores automatically
# the file to the disk and we don't want that.
else
nofile = false
if fields.name?
name = fields.name
else
name = part.filename
# Build file data
fileData =
name: name
"content-type": part.headers['content-type']
binaryManagement.addBinary req.doc, fileData, part, (err)->
if err
log.error "#{JSON.stringify err}"
form.emit 'error', err
else
res.status(201).send success: true
form.on 'progress', (bytesReceived, bytesExpected) ->
form.on 'error', (err) ->
next err
form.on 'close', ->
if fields.fromURL?
downloadFromUrl(form, res, req.doc, fields.fromURL, fields.name)
else
# If no file was found, returns a client error.
res.status(400).send error: 'No file sent' if nofile
next()
downloadFromUrl = (form, res, doc, url, name) ->
followRedirect = (url1, callback, maxRedirect)->
https.get(url1, (res1)->
if res1.statusCode >= 300 and res1.statusCode < 400 \
and res1.headers.location and maxRedirect > 0
followRedirect res1.headers.location, callback, maxRedirect - 1
else
callback res1
).on('error', (err)->
log.error "#{JSON.stringify err}"
form.emit 'error', err
)
attach = (response) ->
name ?= 'file'
# Build file data
fileData =
name: name
"content-type": response.headers['content-type']
file =
size: response.headers['content-length']
lastModification: new Date(response.headers['last-modified'])
creationDate: new Date(response.headers['date'])
db.merge doc.id, file, (err) ->
if err
log.error "addFileData #{doc.id}/#{doc.name}: #{err}"
form.emit 'error', err
else
log.info "addFileData #{doc.id}/#{doc.name}: Added file data."
log.info "Attaching Binary to #{doc.id}/#{doc.name}"
binaryManagement.addBinary doc, fileData, response,
(err)->
if err
log.error "#{JSON.stringify err}"
form.emit 'error', err
else
res.status(201).send success: true
followRedirect(url, attach, 5)
# GET /data/:id/binaries/:name/
# Download a the file attached to the binary object.
module.exports.get = (req, res, next) ->
name = req.params.name
binary = req.doc.binary
if binary and binary[name]
# Build stream for fetching file from the database. Use a custom lib
# instead of cradle to avoid too high memory consumption.
id = binary[name].id
# Run the download with Node low level api.
downloadRequest = downloader.download id, name, (err, stream) ->
if err
next err
else
# Set response header from attachment infos
res.setHeader 'Content-Length', stream.headers['content-length']
res.setHeader 'Content-Type', stream.headers['content-type']
req.once 'close', -> downloadRequest.abort()
#@TODO forward other cache-control header
# Use streaming to avoid high memory consumption.
stream.pipe res
# No binary found, error is returned.
else
err = new Error "not found"
err.status = 404
next err
# DELETE /data/:id/binaries/:name
# Remove binary object and remove link set on given document.
module.exports.remove = (req, res, next) ->
name = req.params.name
if req.doc.binary and req.doc.binary[name]
id = req.doc.binary[name].id
# Remove reference to binary from doc
delete req.doc.binary[name]
delete req.doc.binary if req.doc.binary.length is 0
# Save updated doc
db.save req.doc, (err) ->
# Check if binary is used by another document
db.view 'binary/byDoc', {key: id}, (err, result) ->
if result.length isnt 0
res.status(204).send success: true
return next()
# Then delete binary document.
db.get id, (err, binary) ->
unless binary?
err = new Error('Binary not found')
err.status = 404
return next err
dbHelper.remove binary, (err) ->
if err
console.log "[Attachment] err: " + \
JSON.stringify err
next err
else
res.status(204).send success: true
next()
# No binary given, error is returned.
else
err = new Error "no binary ID is provided"
err.status = 400
next err
module.exports.convert = (req, res, next) ->
binaries = {}
name = req.params.name
removeOldAttach = (attach, binaryId, callback) ->
db.get req.doc.id, (err, doc) ->
if err
callback err
else
db.removeAttachment doc, attach, (err) ->
if err
callback err
else
db.get binaryId, (err, doc) ->
if err
callback err
else
callback null, doc
createBinary = (keyData, callback) ->
return callback() unless keyData?
# Create binary
binary =
docType: "Binary"
db.save binary, (err, binDoc) ->
# Get attachment
readStream = db.getAttachment req.doc.id, keyData.oldKey, (err) ->
console.log err if err
data =
name: keyData.newBinaryKey
body: ''
# Attach document to binary
writeStream = db.saveAttachment binDoc, data, (err, res) ->
return callback err if err
# Remove attachment from documents
removeOldAttach keyData.oldKey, binDoc._id, (err, doc) ->
if err
callback err
else
# Store binaries information
binaries[keyData.newFileKey] =
id: doc._id
rev: doc._rev
callback()
readStream.pipe(writeStream)
attachments = req.doc._attachments
keys2 = []
if attachments?
keys = Object.keys attachments
keys2.push key for key in keys
datas = []
#for key, val of req.doc_attachments
for key in keys2
datas.push {
oldKey: key
newFileKey: if keys.length = 1 and name? then name else key
newBinaryKey: if name? then name else key
}
async.eachSeries datas, createBinary, (err) ->
if err
next err
else
# Store binaries
db.get req.doc.id, (err, doc) ->
doc.binary = binaries
db.save doc, (err, doc) ->
if err
next err
else
res.status(200).send success: true
next()
else
res.status(200).send success: true
next()