|
1 | 1 | const Service = require('../service.js');
|
2 | 2 | const {{spec.title | caseUcfirst}}Exception = require('../exception.js');
|
| 3 | +const InputFile = require('../inputFile.js'); |
3 | 4 | const client = require('../client.js');
|
| 5 | +const Stream = require('stream'); |
4 | 6 | const { promisify } = require('util');
|
5 | 7 | const fs = require('fs');
|
6 | 8 |
|
@@ -34,95 +36,146 @@ class {{ service.name | caseUcfirst }} extends Service {
|
34 | 36 | {% for parameter in method.parameters.query %}
|
35 | 37 |
|
36 | 38 | if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
|
37 |
| - payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword }}{% if method.consumes[0] == "multipart/form-data" and ( parameter.type != "string" and parameter.type != "array" ) %}.toString(){% endif %}; |
| 39 | + payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword }}{% if method.consumes[0] == "multipart/form-data" and ( parameter.type != "string" and parameter.type != "array" and parameter.type != "file" ) %}.toString(){% endif %}; |
38 | 40 | }
|
39 | 41 | {% endfor %}
|
40 | 42 | {% for parameter in method.parameters.body %}
|
41 | 43 |
|
42 | 44 | if (typeof {{ parameter.name | caseCamel | escapeKeyword }} !== 'undefined') {
|
43 |
| - payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword}}{% if method.consumes[0] == "multipart/form-data" and ( parameter.type != "string" and parameter.type != "array" ) %}.toString(){% endif %}; |
| 45 | + payload['{{ parameter.name }}'] = {{ parameter.name | caseCamel | escapeKeyword}}{% if method.consumes[0] == "multipart/form-data" and ( parameter.type != "string" and parameter.type != "array" and parameter.type != "file" ) %}.toString(){% endif %}; |
44 | 46 | }
|
45 | 47 | {% endfor %}
|
46 | 48 |
|
47 | 49 | {% if 'multipart/form-data' in method.consumes %}
|
48 | 50 | {% for parameter in method.parameters.all %}
|
49 | 51 | {% if parameter.type == 'file' %}
|
50 |
| - const { size: size } = await promisify(fs.stat)({{ parameter.name | caseCamel | escapeKeyword }}); |
| 52 | + const size = {{ parameter.name | caseCamel | escapeKeyword }}.size; |
51 | 53 |
|
52 |
| - if (size <= client.CHUNK_SIZE) { |
53 |
| - payload['{{ parameter.name }}'] = fs.createReadStream({{ parameter.name | caseCamel | escapeKeyword }}); |
54 |
| - |
55 |
| - return await this.client.call('{{ method.method | caseLower }}', path, { |
| 54 | + const headers = { |
56 | 55 | {% for parameter in method.parameters.header %}
|
57 |
| - '{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }}, |
| 56 | + '{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }}, |
58 | 57 | {% endfor %}
|
59 | 58 | {% for key, header in method.headers %}
|
60 |
| - '{{ key }}': '{{ header }}', |
| 59 | + '{{ key }}': '{{ header }}', |
61 | 60 | {% endfor %}
|
62 |
| - }, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %}); |
63 |
| - } else { |
64 |
| - let id = undefined; |
65 |
| - let response = undefined; |
| 61 | + }; |
66 | 62 |
|
67 |
| - let counter = 0; |
68 |
| - const totalCounters = Math.ceil(size / client.CHUNK_SIZE); |
| 63 | + let id = undefined; |
| 64 | + let response = undefined; |
69 | 65 |
|
70 |
| - const headers = { |
71 |
| -{% for parameter in method.parameters.header %} |
72 |
| - '{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }}, |
73 |
| -{% endfor %} |
74 |
| -{% for key, header in method.headers %} |
75 |
| - '{{ key }}': '{{ header }}', |
76 |
| -{% endfor %} |
77 |
| - }; |
| 66 | + let chunksUploaded = 0; |
78 | 67 |
|
79 | 68 | {% for parameter in method.parameters.all %}
|
80 | 69 | {% if parameter.isUploadID %}
|
81 |
| - if({{ parameter.name | caseCamel | escapeKeyword }} != 'unique()') { |
82 |
| - try { |
83 |
| - response = await this.client.call('get', path + '/' + {{ parameter.name }}, headers); |
84 |
| - counter = response.chunksUploaded; |
85 |
| - } catch(e) { |
86 |
| - } |
| 70 | + if({{ parameter.name | caseCamel | escapeKeyword }} != 'unique()') { |
| 71 | + try { |
| 72 | + response = await this.client.call('get', path + '/' + {{ parameter.name }}, headers); |
| 73 | + chunksUploaded = response.chunksUploaded; |
| 74 | + } catch(e) { |
87 | 75 | }
|
| 76 | + } |
88 | 77 | {% endif %}
|
89 | 78 | {% endfor %}
|
90 | 79 |
|
91 |
| - for (counter; counter < totalCounters; counter++) { |
92 |
| - const start = (counter * client.CHUNK_SIZE); |
93 |
| - const end = Math.min((((counter * client.CHUNK_SIZE) + client.CHUNK_SIZE) - 1), size); |
| 80 | + let currentChunk = Buffer.from(''); |
| 81 | + let currentChunkSize = 0; |
| 82 | + let currentChunkStart = 0; |
| 83 | + |
| 84 | + const selfClient = this.client; |
94 | 85 |
|
| 86 | + async function uploadChunk(lastUpload = false) { |
| 87 | + if(chunksUploaded - 1 >= currentChunkStart / client.CHUNK_SIZE) { |
| 88 | + return; |
| 89 | + } |
| 90 | + |
| 91 | + const start = currentChunkStart; |
| 92 | + const end = Math.min(((start + client.CHUNK_SIZE) - 1), size); |
| 93 | + |
| 94 | + if(!lastUpload || currentChunkStart !== 0) { |
95 | 95 | headers['content-range'] = 'bytes ' + start + '-' + end + '/' + size;
|
| 96 | + } |
96 | 97 |
|
97 |
| - if (id) { |
98 |
| - headers['x-{{spec.title | caseLower }}-id'] = id; |
99 |
| - } |
| 98 | + if (id) { |
| 99 | + headers['x-{{spec.title | caseLower }}-id'] = id; |
| 100 | + } |
| 101 | + |
| 102 | + const stream = Stream.Readable.from(currentChunk); |
| 103 | + payload['{{ parameter.name }}'] = { type: 'file', file: stream, filename: file.name }; |
| 104 | + |
| 105 | + response = await selfClient.call('{{ method.method | caseLower }}', path, headers, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %}); |
100 | 106 |
|
101 |
| - const stream = fs.createReadStream({{ parameter.name | caseCamel | escapeKeyword }}, { |
102 |
| - start, |
103 |
| - end |
| 107 | + if (!id) { |
| 108 | + id = response['$id']; |
| 109 | + } |
| 110 | + |
| 111 | + if (onProgress !== null) { |
| 112 | + onProgress({ |
| 113 | + $id: response['$id'], |
| 114 | + progress: Math.min((start+client.CHUNK_SIZE) * client.CHUNK_SIZE, size) / size * 100, |
| 115 | + sizeUploaded: end+1, |
| 116 | + chunksTotal: response['chunksTotal'], |
| 117 | + chunksUploaded: response['chunksUploaded'] |
104 | 118 | });
|
105 |
| - payload['{{ parameter.name }}'] = stream; |
| 119 | + } |
106 | 120 |
|
107 |
| - response = await this.client.call('{{ method.method | caseLower }}', path, headers, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %}); |
| 121 | + currentChunkStart += client.CHUNK_SIZE; |
| 122 | + } |
108 | 123 |
|
109 |
| - if (!id) { |
110 |
| - id = response['$id']; |
| 124 | + return await new Promise((resolve, reject) => { |
| 125 | + const writeStream = new Stream.Writable(); |
| 126 | + writeStream._write = async (mainChunk, encoding, next) => { |
| 127 | + // Segment incoming chunk into up to 5MB chunks |
| 128 | + const mainChunkSize = Buffer.byteLength(mainChunk); |
| 129 | + const chunksCount = Math.ceil(mainChunkSize / client.CHUNK_SIZE); |
| 130 | + const chunks = []; |
| 131 | + |
| 132 | + for(let i = 0; i < chunksCount; i++) { |
| 133 | + const chunk = mainChunk.slice(i * client.CHUNK_SIZE, client.CHUNK_SIZE); |
| 134 | + chunks.push(chunk); |
111 | 135 | }
|
112 |
| - |
113 |
| - if (onProgress !== null) { |
114 |
| - onProgress({ |
115 |
| - $id: response['$id'], |
116 |
| - progress: Math.min((counter+1) * client.CHUNK_SIZE, size) / size * 100, |
117 |
| - sizeUploaded: end+1, |
118 |
| - chunksTotal: response['chunksTotal'], |
119 |
| - chunksUploaded: response['chunksUploaded'] |
120 |
| - }); |
| 136 | + |
| 137 | + for (const chunk of chunks) { |
| 138 | + const chunkSize = Buffer.byteLength(chunk); |
| 139 | + |
| 140 | + if(chunkSize + currentChunkSize == client.CHUNK_SIZE) { |
| 141 | + // Upload chunk |
| 142 | + currentChunk = Buffer.concat([currentChunk, chunk]); |
| 143 | + await uploadChunk(); |
| 144 | + currentChunk = Buffer.from(''); |
| 145 | + currentChunkSize = 0; |
| 146 | + } else if(chunkSize + currentChunkSize > client.CHUNK_SIZE) { |
| 147 | + // Upload chunk, put rest into next chunk |
| 148 | + const bytesToUpload = client.CHUNK_SIZE - currentChunkSize; |
| 149 | + const newChunkSection = chunk.slice(0, bytesToUpload); |
| 150 | + currentChunk = Buffer.concat([currentChunk, newChunkSection]); |
| 151 | + currentChunkSize = Buffer.byteLength(currentChunk); |
| 152 | + await uploadChunk(); |
| 153 | + currentChunk = chunk.slice(bytesToUpload, undefined); |
| 154 | + currentChunkSize = chunkSize - bytesToUpload; |
| 155 | + } else { |
| 156 | + // Append into current chunk |
| 157 | + currentChunk = Buffer.concat([currentChunk, chunk]); |
| 158 | + currentChunkSize = chunkSize + currentChunkSize; |
| 159 | + } |
121 | 160 | }
|
| 161 | + |
| 162 | + next(); |
122 | 163 | }
|
123 | 164 |
|
124 |
| - return response; |
125 |
| - } |
| 165 | + writeStream.on("finish", async () => { |
| 166 | + if(currentChunkSize > 0) { |
| 167 | + await uploadChunk(true); |
| 168 | + } |
| 169 | + |
| 170 | + resolve(response); |
| 171 | + }); |
| 172 | + |
| 173 | + writeStream.on("error", (err) => { |
| 174 | + reject(err); |
| 175 | + }); |
| 176 | + |
| 177 | + {{ parameter.name | caseCamel | escapeKeyword }}.stream.pipe(writeStream); |
| 178 | + }); |
126 | 179 | {% endif %}
|
127 | 180 | {% endfor %}
|
128 | 181 | {% else %}
|
|
0 commit comments