| /**
 * Uploader.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */
/**
 * Upload blobs or blob infos to the specified URL or handler.
 *
 * @private
 * @class tinymce.file.Uploader
 * @example
 * var uploader = new Uploader({
 *     url: '/upload.php',
 *     basePath: '/base/path',
 *     credentials: true,
 *     handler: function(data, success, failure) {
 *         ...
 *     }
 * });
 *
 * uploader.upload(blobInfos).then(function(result) {
 *     ...
 * });
 */
define(
  'tinymce.core.file.Uploader',
  [
    'ephox.sand.api.XMLHttpRequest',
    'global!window',
    'tinymce.core.util.Fun',
    'tinymce.core.util.Promise',
    'tinymce.core.util.Tools'
  ],
  function (XMLHttpRequest, window, Fun, Promise, Tools) {
    return function (uploadStatus, settings) {
      var pendingPromises = {};
      var pathJoin = function (path1, path2) {
        if (path1) {
          return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, '');
        }
        return path2;
      };
      var defaultHandler = function (blobInfo, success, failure, progress) {
        var xhr, formData;
        xhr = new XMLHttpRequest();
        xhr.open('POST', settings.url);
        xhr.withCredentials = settings.credentials;
        xhr.upload.onprogress = function (e) {
          progress(e.loaded / e.total * 100);
        };
        xhr.onerror = function () {
          failure("Image upload failed due to a XHR Transport error. Code: " + xhr.status);
        };
        xhr.onload = function () {
          var json;
          if (xhr.status < 200 || xhr.status >= 300) {
            failure("HTTP Error: " + xhr.status);
            return;
          }
          json = JSON.parse(xhr.responseText);
          if (!json || typeof json.location != "string") {
            failure("Invalid JSON: " + xhr.responseText);
            return;
          }
          success(pathJoin(settings.basePath, json.location));
        };
        formData = new window.FormData(); // TODO: Stick this in sand
        formData.append('file', blobInfo.blob(), blobInfo.filename());
        xhr.send(formData);
      };
      var noUpload = function () {
        return new Promise(function (resolve) {
          resolve([]);
        });
      };
      var handlerSuccess = function (blobInfo, url) {
        return {
          url: url,
          blobInfo: blobInfo,
          status: true
        };
      };
      var handlerFailure = function (blobInfo, error) {
        return {
          url: '',
          blobInfo: blobInfo,
          status: false,
          error: error
        };
      };
      var resolvePending = function (blobUri, result) {
        Tools.each(pendingPromises[blobUri], function (resolve) {
          resolve(result);
        });
        delete pendingPromises[blobUri];
      };
      var uploadBlobInfo = function (blobInfo, handler, openNotification) {
        uploadStatus.markPending(blobInfo.blobUri());
        return new Promise(function (resolve) {
          var notification, progress;
          var noop = function () {
          };
          try {
            var closeNotification = function () {
              if (notification) {
                notification.close();
                progress = noop; // Once it's closed it's closed
              }
            };
            var success = function (url) {
              closeNotification();
              uploadStatus.markUploaded(blobInfo.blobUri(), url);
              resolvePending(blobInfo.blobUri(), handlerSuccess(blobInfo, url));
              resolve(handlerSuccess(blobInfo, url));
            };
            var failure = function (error) {
              closeNotification();
              uploadStatus.removeFailed(blobInfo.blobUri());
              resolvePending(blobInfo.blobUri(), handlerFailure(blobInfo, error));
              resolve(handlerFailure(blobInfo, error));
            };
            progress = function (percent) {
              if (percent < 0 || percent > 100) {
                return;
              }
              if (!notification) {
                notification = openNotification();
              }
              notification.progressBar.value(percent);
            };
            handler(blobInfo, success, failure, progress);
          } catch (ex) {
            resolve(handlerFailure(blobInfo, ex.message));
          }
        });
      };
      var isDefaultHandler = function (handler) {
        return handler === defaultHandler;
      };
      var pendingUploadBlobInfo = function (blobInfo) {
        var blobUri = blobInfo.blobUri();
        return new Promise(function (resolve) {
          pendingPromises[blobUri] = pendingPromises[blobUri] || [];
          pendingPromises[blobUri].push(resolve);
        });
      };
      var uploadBlobs = function (blobInfos, openNotification) {
        blobInfos = Tools.grep(blobInfos, function (blobInfo) {
          return !uploadStatus.isUploaded(blobInfo.blobUri());
        });
        return Promise.all(Tools.map(blobInfos, function (blobInfo) {
          return uploadStatus.isPending(blobInfo.blobUri()) ?
            pendingUploadBlobInfo(blobInfo) : uploadBlobInfo(blobInfo, settings.handler, openNotification);
        }));
      };
      var upload = function (blobInfos, openNotification) {
        return (!settings.url && isDefaultHandler(settings.handler)) ? noUpload() : uploadBlobs(blobInfos, openNotification);
      };
      settings = Tools.extend({
        credentials: false,
        // We are adding a notify argument to this (at the moment, until it doesn't work)
        handler: defaultHandler
      }, settings);
      return {
        upload: upload
      };
    };
  }
);
 |