(function (exports) { var styleOptionRe = /(\d+)x(\d+)/i; function parseStyleOption(option) { var m = option.match(styleOptionRe); return { width: m && m[1] || 150, height: m && m[2] || 150 }; } /* * Edits (size, quality, parameters) image file for S3 server uploading. */ function generateThumbnail(origFile, type, max_width, max_height, cb) { var fileRequest = $.Deferred(); var img = new Image; var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); // todo allow for different x/y ratio canvas.width = max_width; canvas.height = max_height; img.onload = function () { var size; var offsetX = 0; var offsetY = 0; if (this.width > this.height) { size = this.height; offsetX = (this.width - this.height) / 2; } else { size = this.width; offsetY = (this.height - this.width) / 2; } if (type === "image/jpeg") { type = "image/jpg"; } ctx.drawImage(this, offsetX, offsetY, size, size, 0, 0, canvas.width, canvas.height); canvas.toBlob(function (blob) { fileRequest.resolve(cb(blob)); }, type, 0.8); }; // Must be after onload, otherwise it could load before we would // capture the event img.src = URL.createObjectURL(origFile); return fileRequest.promise(); } /* * The server checks if files are OK (presence, size and spoofing) * and only then generates posts for S3 server file uploading * (each post for different size/style of the same file). */ function fetchUploadSignature(ev, fileInput, file, signUrl) { var formData = new FormData(); formData.append("file", file); return $.ajax({ url: signUrl, type: 'POST', data: formData, processData: false, contentType: false, error: function (xhr) { try { // File error var jsonData = $.parseJSON(xhr.responseText); var errMsg = jsonToValuesArray(jsonData.errors); } catch(err) { // Connection error var errMsg = I18n.t("general.file.upload_failure"); } renderFormError(ev, fileInput, errMsg); } }); } /* * Upload file to S3 server. */ function uploadFile(postData, ev, fileInput) { var url = postData.url; var fields = postData.fields; var formData = new FormData(); for (var k in fields) { formData.append(k, fields[k]); } formData.append("file", postData.file, postData.fileName); return $.ajax({ url: url, type: 'POST', data: formData, processData: false, contentType: false, error: function (xhr) { try { // File error var $xmlData = $(xhr.responseText); var errMsg = $xmlData.find("Message").text().strToErrorFormat(); } catch(err) { // Connection error var errMsg = I18n.t("general.file.upload_failure"); } renderFormError(ev, fileInput, errMsg); } }); } /* * Proccesses file's posts and uploads them. */ function processPosts(ev, fileInput, posts, fileRequests) { var file = fileInput.files[0]; _.each(posts, function (postData) { postData.fileName = file.name; if (postData.style_option) { // Picture file var styleSize = parseStyleOption(postData.style_option); var fileRequest = generateThumbnail(file, postData.mime_type, styleSize.width, styleSize.height, function (blob) { postData.file = blob; uploadFile(postData, ev, fileInput); }); } else { // Other file postData.file = file; var fileRequest = uploadFile(postData, ev, fileInput); } fileRequests.push(fileRequest); }); } /* * Spoof checks files on server and uploads them to S3 server. * * First we asyncronously spoof check files on server and generate post * requests (fetchUploadSignature), if OK the post requests are used to uplaod * files asyncronously to S3 (uploadFile), and if successful the form is * submitted, otherwise no file is saved and errors are shown. * If any post fails, the user is allowed to leave the page, but other files * are still being uploaded because of asynchronous behaviour, so that errors * for other files can still show afterwards. * * TODO On S3 server upload error the other files that were already * asynchronously uploaded remain on the server, but should be deleted - this * should generally not happen, because all files should fail to upload in * such cases (like S3 server connection error), except if user cancels the * upload while still in progress. But this can be abused! One way to solve * it is to make request to our server for each file seperatelly, and not for * all together as it is now, despite being less efficient. To make it * bulletproof, post requests should be issued on server-side. */ exports.directUpload = function (ev, signUrl) { $form = $(ev.target.form); $form.clearFormErrors(); $form.removeBlankFileForms(); $fileInputs = $form.find("input[type=file]"); var signRequests = []; if ($fileInputs.length) { // Before file processing and uploading animateSpinner(); preventLeavingPage(true, I18n.t("general.file.uploading")); ev.preventDefault(); // Spoof checks files and, if OK, gets upload post requests _.each($fileInputs, function (fileInput) { var file = fileInput.files[0]; if (!_.isUndefined(file)) { signRequests.push( fetchUploadSignature(ev, fileInput, file, signUrl) ); } }); $.when.apply($, signRequests).then(function () { // After successful file spoof check and upload post requests fetching if (signRequests.length) { var fileRequests = []; if (signRequests.length == 1) { arguments = [arguments]; } $.each(arguments, function (fileIdx, responseData) { $fileInput = $fileInputs[fileIdx]; var jqXHR = responseData[2]; var data = JSON.parse(jqXHR.responseText); processPosts(ev, $fileInput, data.posts, fileRequests); }); $.when.apply($, fileRequests).then(function () { // After successful posts processing and file uploading $form.onAjaxComplete(function () { animateSpinner(null, false); preventLeavingPage(false); }); $form.submit(); }, function() { // After unsuccessful posts processing and file uploading animateSpinner(null, false); preventLeavingPage(false); }); } }, function () { // After unsuccessful file spoof check and posts fetching animateSpinner(null, false); preventLeavingPage(false); }); } }; }(this));