|
| 1 | +const conf = require('../conf'); |
| 2 | +const zone = require('../zone'); |
| 3 | +const util = require('../util'); |
| 4 | +const rpc = require('../rpc'); |
| 5 | +const path = require('path'); |
| 6 | +const mime = require('mime'); |
| 7 | +const fs = require('fs'); |
| 8 | + |
| 9 | +exports.ResumeUploader = ResumeUploader; |
| 10 | +exports.PutExtra = PutExtra; |
| 11 | + |
| 12 | +function ResumeUploader(config) { |
| 13 | + this.config = config || new conf.Config(); |
| 14 | +} |
| 15 | + |
| 16 | +// 上传可选参数 |
| 17 | +// @params fname 请求体中的文件的名称 |
| 18 | +// @params params 额外参数设置,参数名称必须以x:开头 |
| 19 | +// @param mimeType 指定文件的mimeType |
| 20 | +// @param progressCallback(BlkputRet) 上传进度回调 |
| 21 | +// @param ctxList 断点续传的已上传的文件ctx列表, |
| 22 | +// 在上传的过程中可以在progressCallback中写入本地文件 |
| 23 | +function PutExtra(fname, params, mimeType, blkputRets, progressCallback) { |
| 24 | + this.fname = fname || ''; |
| 25 | + this.params = params || {}; |
| 26 | + this.mimeType = mimeType || null; |
| 27 | + this.blkputRets = blkputRets || []; |
| 28 | + this.progressCallback = progressCallback || null; |
| 29 | +} |
| 30 | + |
| 31 | +function BlkputRet(options) { |
| 32 | + this.ctx = options.ctx || null; |
| 33 | + this.checksum = options.checksum || null; |
| 34 | + this.crc32 = options.crc32 || null; |
| 35 | + this.offset = options.offset || null; |
| 36 | + this.host = options.host || null; |
| 37 | + this.expired_at = options.expired_at || null; |
| 38 | +} |
| 39 | + |
| 40 | +ResumeUploader.prototype.putStream = function(uploadToken, key, rsStream, |
| 41 | + rsStreamLen, putExtra, callbackFunc) { |
| 42 | + putExtra = putExtra || new PutExtra(); |
| 43 | + if (!putExtra.mimeType) { |
| 44 | + putExtra.mimeType = 'application/octet-stream'; |
| 45 | + } |
| 46 | + |
| 47 | + if (!putExtra.fname) { |
| 48 | + putExtra.fname = key ? key : '?'; |
| 49 | + } |
| 50 | + |
| 51 | + rsStream.on("error", function(err) { |
| 52 | + //callbackFunc |
| 53 | + callbackFunc(err, null, null); |
| 54 | + return; |
| 55 | + }); |
| 56 | + |
| 57 | + var useCache = false; |
| 58 | + var that = this; |
| 59 | + if (this.config.zone) { |
| 60 | + if (this.config.zoneExpire == -1) { |
| 61 | + useCache = true; |
| 62 | + } else { |
| 63 | + if (!util.isTimestampExpired(this.config.zoneExpire)) { |
| 64 | + useCache = true; |
| 65 | + } |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + var accessKey = util.getAKFromUptoken(uploadToken); |
| 70 | + var bucket = util.getBucketFromUptoken(uploadToken); |
| 71 | + if (useCache) { |
| 72 | + putReq(this.config, uploadToken, key, rsStream, rsStreamLen, putExtra, |
| 73 | + callbackFunc); |
| 74 | + } else { |
| 75 | + zone.getZoneInfo(accessKey, bucket, function(err, cZoneInfo, |
| 76 | + cZoneExpire) { |
| 77 | + if (err) { |
| 78 | + callbackFunc(err, null, null); |
| 79 | + return; |
| 80 | + } |
| 81 | + |
| 82 | + //update object |
| 83 | + that.config.zone = cZoneInfo; |
| 84 | + that.config.zoneExpire = cZoneExpire; |
| 85 | + |
| 86 | + //req |
| 87 | + putReq(that.config, uploadToken, key, rsStream, rsStreamLen, |
| 88 | + putExtra, |
| 89 | + callbackFunc); |
| 90 | + }); |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +function putReq(config, uploadToken, key, rsStream, rsStreamLen, putExtra, |
| 95 | + callbackFunc) { |
| 96 | + //set up hosts order |
| 97 | + var upHosts = []; |
| 98 | + |
| 99 | + if (config.useCdnDomain) { |
| 100 | + if (config.zone.cdnUpHosts) { |
| 101 | + config.zone.cdnUpHosts.forEach(function(host) { |
| 102 | + upHosts.push(host); |
| 103 | + }); |
| 104 | + } |
| 105 | + config.zone.srcUpHosts.forEach(function(host) { |
| 106 | + upHosts.push(host); |
| 107 | + }); |
| 108 | + } else { |
| 109 | + config.zone.srcUpHosts.forEach(function(host) { |
| 110 | + upHosts.push(host); |
| 111 | + }); |
| 112 | + config.zone.cdnUpHosts.forEach(function(host) { |
| 113 | + upHosts.push(host); |
| 114 | + }); |
| 115 | + } |
| 116 | + |
| 117 | + var scheme = config.useHttpsDomain ? "https://" : "http://"; |
| 118 | + var upDomain = scheme + upHosts[0]; |
| 119 | + // block upload |
| 120 | + |
| 121 | + var fileSize = rsStreamLen; |
| 122 | + //console.log("file size:" + fileSize); |
| 123 | + var blockCnt = fileSize / conf.BLOCK_SIZE |
| 124 | + var totalBlockNum = (fileSize % conf.BLOCK_SIZE == 0) ? blockCnt : (blockCnt + |
| 125 | + 1); |
| 126 | + var finishedBlock = 0; |
| 127 | + var curBlock = 0; |
| 128 | + var readLen = 0; |
| 129 | + var readBuffers = []; |
| 130 | + var finishedCtxList = []; |
| 131 | + |
| 132 | + //check putExtra.blkputRets |
| 133 | + if (putExtra.blkputRets) { |
| 134 | + for (var index = 0; index < putExtra.blkputRets.length; index++) { |
| 135 | + //check ctx expired or not |
| 136 | + var blkputRet = putExtra.blkputRets[index]; |
| 137 | + var expiredAt = blkputRet.expired_at; |
| 138 | + //make sure the ctx at least has one day expiration |
| 139 | + expiredAt += 3600 * 24; |
| 140 | + if (util.isTimestampExpired(expiredAt)) { |
| 141 | + //discard these ctxs |
| 142 | + break; |
| 143 | + } |
| 144 | + |
| 145 | + finishedBlock += 1; |
| 146 | + finishedCtxList.push(blkputRet.ctx); |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + //check when to mkblk |
| 151 | + rsStream.on('data', function(chunk) { |
| 152 | + readLen += chunk.length; |
| 153 | + readBuffers.push(chunk); |
| 154 | + |
| 155 | + if (readLen % conf.BLOCK_SIZE == 0 || readLen == fileSize) { |
| 156 | + //console.log(readLen); |
| 157 | + var readData = Buffer.concat(readBuffers); |
| 158 | + readBuffers = []; //reset read buffer |
| 159 | + curBlock += 1; //set current block |
| 160 | + if (curBlock > finishedBlock) { |
| 161 | + rsStream.pause(); |
| 162 | + mkblkReq(upDomain, uploadToken, readData, function(respErr, |
| 163 | + respBody, |
| 164 | + respInfo) { |
| 165 | + if (respInfo.statusCode != 200) { |
| 166 | + callbackFunc(respErr, respBody, respInfo); |
| 167 | + return; |
| 168 | + } else { |
| 169 | + finishedBlock += 1; |
| 170 | + rsStream.resume(); |
| 171 | + var blkputRet = respBody; |
| 172 | + finishedCtxList.push(blkputRet.ctx); |
| 173 | + if (putExtra.progressCallback) { |
| 174 | + putExtra.progressCallback(blkputRet); |
| 175 | + } |
| 176 | + } |
| 177 | + }); |
| 178 | + } |
| 179 | + } |
| 180 | + }); |
| 181 | + |
| 182 | + //check when to mkfile |
| 183 | + rsStream.on('end', function() { |
| 184 | + //console.log("end"); |
| 185 | + mkfileReq(upDomain, uploadToken, fileSize, finishedCtxList, key, |
| 186 | + putExtra, callbackFunc); |
| 187 | + }); |
| 188 | +} |
| 189 | + |
| 190 | +function mkblkReq(upDomain, uploadToken, blkData, callbackFunc) { |
| 191 | + //console.log("mkblk"); |
| 192 | + var requestURI = upDomain + "/mkblk/" + blkData.length; |
| 193 | + var auth = 'UpToken ' + uploadToken; |
| 194 | + var headers = { |
| 195 | + 'Authorization': auth, |
| 196 | + 'Content-Type': 'application/octet-stream' |
| 197 | + } |
| 198 | + rpc.post(requestURI, blkData, headers, callbackFunc); |
| 199 | +} |
| 200 | + |
| 201 | +function mkfileReq(upDomain, uploadToken, fileSize, ctxList, key, putExtra, |
| 202 | + callbackFunc) { |
| 203 | + //console.log("mkfile"); |
| 204 | + var requestURI = upDomain + "/mkfile/" + fileSize; |
| 205 | + if (key) { |
| 206 | + requestURI += "/key/" + util.urlsafeBase64Encode(key); |
| 207 | + } |
| 208 | + if (putExtra.mimeType) { |
| 209 | + requestURI += "/mimeType/" + util.urlsafeBase64Encode(putExtra.mimeType); |
| 210 | + } |
| 211 | + if (putExtra.fname) { |
| 212 | + requestURI += "/fname/" + util.urlsafeBase64Encode(putExtra.fname); |
| 213 | + } |
| 214 | + if (putExtra.params) { |
| 215 | + //putExtra params |
| 216 | + for (var k in putExtra.params) { |
| 217 | + if (k.startsWith("x:") && putExtra.params[k]) { |
| 218 | + requestURI += "/" + k + "/" + util.urlsafeBase64Encode(putExtra.params[ |
| 219 | + k].toString()); |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + var auth = 'UpToken ' + uploadToken; |
| 224 | + var headers = { |
| 225 | + 'Authorization': auth, |
| 226 | + 'Content-Type': 'application/octet-stream' |
| 227 | + } |
| 228 | + var postBody = ctxList.join(","); |
| 229 | + rpc.post(requestURI, postBody, headers, callbackFunc); |
| 230 | +} |
| 231 | + |
| 232 | +ResumeUploader.prototype.putFile = function(uploadToken, key, localFile, |
| 233 | + putExtra, callbackFunc) { |
| 234 | + putExtra = putExtra || new PutExtra(); |
| 235 | + var rsStream = fs.createReadStream(localFile); |
| 236 | + var rsStreamLen = fs.statSync(localFile).size; |
| 237 | + if (!putExtra.mimeType) { |
| 238 | + putExtra.mimeType = mime.lookup(localFile); |
| 239 | + } |
| 240 | + |
| 241 | + if (!putExtra.fname) { |
| 242 | + putExtra.fname = path.basename(localFile); |
| 243 | + } |
| 244 | + |
| 245 | + return this.putStream(uploadToken, key, rsStream, rsStreamLen, putExtra, |
| 246 | + callbackFunc); |
| 247 | +} |
| 248 | + |
| 249 | +ResumeUploader.prototype.putFileWithoutKey = function(uploadToken, localFile, |
| 250 | + putExtra, callbackFunc) { |
| 251 | + return this.putFile(uploadToken, null, localFile, putExtra, callbackFunc); |
| 252 | +} |
0 commit comments