text: /**
   * @fileOverview 负责文件上传相关。
   */
  define([
      '../base',
      '../uploader',
      '../file',
      '../lib/transport',
      './widget'
  ], function( Base, Uploader, WUFile, Transport ) {
  
      var $ = Base.$,
          isPromise = Base.isPromise,
          Status = WUFile.Status;
  
      // 添加默认配置项
      $.extend( Uploader.options, {
  
  
          /**
           * @property {Boolean} [prepareNextFile=false]
           * @namespace options
           * @for Uploader
           * @description 是否允许在文件传输时提前把下一个文件准备好。
           * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。
           * 如果能提前在当前文件传输期处理，可以节省总体耗时。
           */
          prepareNextFile: false,
  
          /**
           * @property {Boolean} [chunked=false]
           * @namespace options
           * @for Uploader
           * @description 是否要分片处理大文件上传。
           */
          chunked: false,
  
          /**
           * @property {Boolean} [chunkSize=5242880]
           * @namespace options
           * @for Uploader
           * @description 如果要分片，分多大一片？ 默认大小为5M.
           */
          chunkSize: 5 * 1024 * 1024,
  
          /**
           * @property {Boolean} [chunkRetry=2]
           * @namespace options
           * @for Uploader
           * @description 如果某个分片由于网络问题出错，允许自动重传多少次？
           */
          chunkRetry: 2,
  
          /**
           * @property {Boolean} [threads=3]
           * @namespace options
           * @for Uploader
           * @description 上传并发数。允许同时最大上传进程数。
           */
          threads: 3,
  
  
          /**
           * @property {Object} [formData={}]
           * @namespace options
           * @for Uploader
           * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。
           */
          formData: {}
  
          /**
           * @property {Object} [fileVal='file']
           * @namespace options
           * @for Uploader
           * @description 设置文件上传域的name。
           */
  
          /**
           * @property {Object} [sendAsBinary=false]
           * @namespace options
           * @for Uploader
           * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，
           * 其他参数在$_GET数组中。
           */
      });
  
      // 负责将文件切片。
      function CuteFile( file, chunkSize ) {
          var pending = [],
              blob = file.source,
              total = blob.size,
              chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,
              start = 0,
              index = 0,
              len, api;
  
          api = {
              file: file,
  
              has: function() {
                  return !!pending.length;
              },
  
              shift: function() {
                  return pending.shift();
              },
  
              unshift: function( block ) {
                  pending.unshift( block );
              }
          };
  
          while ( index < chunks ) {
              len = Math.min( chunkSize, total - start );
  
              pending.push({
                  file: file,
                  start: start,
                  end: chunkSize ? (start + len) : total,
                  total: total,
                  chunks: chunks,
                  chunk: index++,
                  cuted: api
              });
              start += len;
          }
  
          file.blocks = pending.concat();
          file.remaning = pending.length;
  
          return api;
      }
  
      Uploader.register({
          name: 'upload',
  
          init: function() {
              var owner = this.owner,
                  me = this;
  
              this.runing = false;
              this.progress = false;
  
              owner
                  .on( 'startUpload', function() {
                      me.progress = true;
                  })
                  .on( 'uploadFinished', function() {
                      me.progress = false;
                  });
  
              // 记录当前正在传的数据，跟threads相关
              this.pool = [];
  
              // 缓存分好片的文件。
              this.stack = [];
  
              // 缓存即将上传的文件。
              this.pending = [];
  
              // 跟踪还有多少分片在上传中但是没有完成上传。
              this.remaning = 0;
              this.__tick = Base.bindFn( this._tick, this );
  
              // 销毁上传相关的属性。
              owner.on( 'uploadComplete', function( file ) {
  
                  // 把其他块取消了。
                  file.blocks && $.each( file.blocks, function( _, v ) {
                      v.transport && (v.transport.abort(), v.transport.destroy());
                      delete v.transport;
                  });
  
                  delete file.blocks;
                  delete file.remaning;
              });
          },
  
          reset: function() {
              this.request( 'stop-upload', true );
              this.runing = false;
              this.pool = [];
              this.stack = [];
              this.pending = [];
              this.remaning = 0;
              this._trigged = false;
              this._promise = null;
          },
  
          /**
           * @event startUpload
           * @description 当开始上传流程时触发。
           * @for  Uploader
           */
  
          /**
           * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。
           *
           * 可以指定开始某一个文件。
           * @grammar upload() => undefined
           * @grammar upload( file | fileId) => undefined
           * @method upload
           * @for  Uploader
           */
          startUpload: function(file) {
              var me = this;
  
              // 移出invalid的文件
              $.each( me.request( 'get-files', Status.INVALID ), function() {
                  me.request( 'remove-file', this );
              });
  
              // 如果指定了开始某个文件，则只开始指定的文件。
              if ( file ) {
                  file = file.id ? file : me.request( 'get-file', file );
  
                  if (file.getStatus() === Status.INTERRUPT) {
                      file.setStatus( Status.QUEUED );
  
                      $.each( me.pool, function( _, v ) {
  
                          // 之前暂停过。
                          if (v.file !== file) {
                              return;
                          }
  
                          v.transport && v.transport.send();
                          file.setStatus( Status.PROGRESS );
                      });
  
                      
                  } else if (file.getStatus() !== Status.PROGRESS) {
                      file.setStatus( Status.QUEUED );
                  }
              } else {
                  $.each( me.request( 'get-files', [ Status.INITED ] ), function() {
                      this.setStatus( Status.QUEUED );
                  });
              }
  
              if ( me.runing ) {
                  return Base.nextTick( me.__tick );
              }
  
              me.runing = true;
              var files = [];
  
              // 如果有暂停的，则续传
              file || $.each( me.pool, function( _, v ) {
                  var file = v.file;
  
                  if ( file.getStatus() === Status.INTERRUPT ) {
                      me._trigged = false;
                      files.push(file);
                      v.transport && v.transport.send();
                  }
              });
  
              $.each(files, function() {
                  this.setStatus( Status.PROGRESS );
              });
  
              file || $.each( me.request( 'get-files',
                      Status.INTERRUPT ), function() {
                  this.setStatus( Status.PROGRESS );
              });
  
              me._trigged = false;
              Base.nextTick( me.__tick );
              me.owner.trigger('startUpload');
          },
  
          /**
           * @event stopUpload
           * @description 当开始上传流程暂停时触发。
           * @for  Uploader
           */
  
          /**
           * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。
           *
           * 如果第一个参数是文件，则只暂停指定文件。
           * @grammar stop() => undefined
           * @grammar stop( true ) => undefined
           * @grammar stop( file ) => undefined
           * @method stop
           * @for  Uploader
           */
          stopUpload: function( file, interrupt ) {
              var me = this,
                  block;
  
              if (file === true) {
                  interrupt = file;
                  file = null;
              }
  
              if ( me.runing === false ) {
                  return;
              }
  
              // 如果只是暂停某个文件。
              if ( file ) {
                  file = file.id ? file : me.request( 'get-file', file );
  
                  if ( file.getStatus() !== Status.PROGRESS &&
                          file.getStatus() !== Status.QUEUED ) {
                      return;
                  }
  
                  file.setStatus( Status.INTERRUPT );
  
  
                  $.each( me.pool, function( _, v ) {
  
                      // 只 abort 指定的文件。
                      if (v.file === file) {
                          block = v;
                          return false;
                      }
                  });
  
                  block.transport && block.transport.abort();
  
                  if (interrupt) {
                      me._putback(block);
                      me._popBlock(block);
                  }
  
                  return Base.nextTick( me.__tick );
              }
  
              me.runing = false;
  
              // 正在准备中的文件。
              if (this._promise && this._promise.file) {
                  this._promise.file.setStatus( Status.INTERRUPT );
              }
  
              interrupt && $.each( me.pool, function( _, v ) {
                  v.transport && v.transport.abort();
                  v.file.setStatus( Status.INTERRUPT );
              });
  
              me.owner.trigger('stopUpload');
          },
  
          /**
           * @method cancelFile
           * @grammar cancelFile( file ) => undefined
           * @grammar cancelFile( id ) => undefined
           * @param {File|id} file File对象或这File对象的id
           * @description 标记文件状态为已取消, 同时将中断文件传输。
           * @for  Uploader
           * @example
           *
           * $li.on('click', '.remove-this', function() {
           *     uploader.cancelFile( file );
           * })
           */
          cancelFile: function( file ) {
              file = file.id ? file : this.request( 'get-file', file );
  
              // 如果正在上传。
              file.blocks && $.each( file.blocks, function( _, v ) {
                  var _tr = v.transport;
  
                  if ( _tr ) {
                      _tr.abort();
                      _tr.destroy();
                      delete v.transport;
                  }
              });
  
              file.setStatus( Status.CANCELLED );
              this.owner.trigger( 'fileDequeued', file );
          },
  
          /**
           * 判断`Uplaode`r是否正在上传中。
           * @grammar isInProgress() => Boolean
           * @method isInProgress
           * @for  Uploader
           */
          isInProgress: function() {
              return !!this.progress;
          },
  
          _getStats: function() {
              return this.request('get-stats');
          },
  
          /**
           * 掉过一个文件上传，直接标记指定文件为已上传状态。
           * @grammar skipFile( file ) => undefined
           * @method skipFile
           * @for  Uploader
           */
          skipFile: function( file, status ) {
              file = file.id ? file : this.request( 'get-file', file );
  
              file.setStatus( status || Status.COMPLETE );
              file.skipped = true;
  
              // 如果正在上传。
              file.blocks && $.each( file.blocks, function( _, v ) {
                  var _tr = v.transport;
  
                  if ( _tr ) {
                      _tr.abort();
                      _tr.destroy();
                      delete v.transport;
                  }
              });
  
              this.owner.trigger( 'uploadSkip', file );
          },
  
          /**
           * @event uploadFinished
           * @description 当所有文件上传结束时触发。
           * @for  Uploader
           */
          _tick: function() {
              var me = this,
                  opts = me.options,
                  fn, val;
  
              // 上一个promise还没有结束，则等待完成后再执行。
              if ( me._promise ) {
                  return me._promise.always( me.__tick );
              }
  
              // 还有位置，且还有文件要处理的话。
              if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {
                  me._trigged = false;
  
                  fn = function( val ) {
                      me._promise = null;
  
                      // 有可能是reject过来的，所以要检测val的类型。
                      val && val.file && me._startSend( val );
                      Base.nextTick( me.__tick );
                  };
  
                  me._promise = isPromise( val ) ? val.always( fn ) : fn( val );
  
              // 没有要上传的了，且没有正在传输的了。
              } else if ( !me.remaning && !me._getStats().numOfQueue &&
                  !me._getStats().numofInterrupt ) {
                  me.runing = false;
  
                  me._trigged || Base.nextTick(function() {
                      me.owner.trigger('uploadFinished');
                  });
                  me._trigged = true;
              }
          },
  
          _putback: function(block) {
              var idx;
  
              block.cuted.unshift(block);
              idx = this.stack.indexOf(block.cuted);
  
              if (!~idx) {
                  this.stack.unshift(block.cuted);
              }
          },
  
          _getStack: function() {
              var i = 0,
                  act;
  
              while ( (act = this.stack[ i++ ]) ) {
                  if ( act.has() && act.file.getStatus() === Status.PROGRESS ) {
                      return act;
                  } else if (!act.has() ||
                          act.file.getStatus() !== Status.PROGRESS &&
                          act.file.getStatus() !== Status.INTERRUPT ) {
  
                      // 把已经处理完了的，或者，状态为非 progress（上传中）、
                      // interupt（暂停中） 的移除。
                      this.stack.splice( --i, 1 );
                  }
              }
  
              return null;
          },
  
          _nextBlock: function() {
              var me = this,
                  opts = me.options,
                  act, next, done, preparing;
  
              // 如果当前文件还有没有需要传输的，则直接返回剩下的。
              if ( (act = this._getStack()) ) {
  
                  // 是否提前准备下一个文件
                  if ( opts.prepareNextFile && !me.pending.length ) {
                      me._prepareNextFile();
                  }
  
                  return act.shift();
  
              // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。
              } else if ( me.runing ) {
  
                  // 如果缓存中有，则直接在缓存中取，没有则去queue中取。
                  if ( !me.pending.length && me._getStats().numOfQueue ) {
                      me._prepareNextFile();
                  }
  
                  next = me.pending.shift();
                  done = function( file ) {
                      if ( !file ) {
                          return null;
                      }
  
                      act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );
                      me.stack.push(act);
                      return act.shift();
                  };
  
                  // 文件可能还在prepare中，也有可能已经完全准备好了。
                  if ( isPromise( next) ) {
                      preparing = next.file;
                      next = next[ next.pipe ? 'pipe' : 'then' ]( done );
                      next.file = preparing;
                      return next;
                  }
  
                  return done( next );
              }
          },
  
  
          /**
           * @event uploadStart
           * @param {File} file File对象
           * @description 某个文件开始上传前触发，一个文件只会触发一次。
           * @for  Uploader
           */
          _prepareNextFile: function() {
              var me = this,
                  file = me.request('fetch-file'),
                  pending = me.pending,
                  promise;
  
              if ( file ) {
                  promise = me.request( 'before-send-file', file, function() {
  
                      // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.
                      if ( file.getStatus() === Status.PROGRESS ||
                          file.getStatus() === Status.INTERRUPT ) {
                          return file;
                      }
  
                      return me._finishFile( file );
                  });
  
                  me.owner.trigger( 'uploadStart', file );
                  file.setStatus( Status.PROGRESS );
  
                  promise.file = file;
  
                  // 如果还在pending中，则替换成文件本身。
                  promise.done(function() {
                      var idx = $.inArray( promise, pending );
  
                      ~idx && pending.splice( idx, 1, file );
                  });
  
                  // befeore-send-file的钩子就有错误发生。
                  promise.fail(function( reason ) {
                      file.setStatus( Status.ERROR, reason );
                      me.owner.trigger( 'uploadError', file, reason );
                      me.owner.trigger( 'uploadComplete', file );
                  });
  
                  pending.push( promise );
              }
          },
  
          // 让出位置了，可以让其他分片开始上传
          _popBlock: function( block ) {
              var idx = $.inArray( block, this.pool );
  
              this.pool.splice( idx, 1 );
              block.file.remaning--;
              this.remaning--;
          },
  
          // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。
          _startSend: function( block ) {
              var me = this,
                  file = block.file,
                  promise;
  
              // 有可能在 before-send-file 的 promise 期间改变了文件状态。
              // 如：暂停，取消
              // 我们不能中断 promise, 但是可以在 promise 完后，不做上传操作。
              if ( file.getStatus() !== Status.PROGRESS ) {
  
                  // 如果是中断，则还需要放回去。
                  if (file.getStatus() === Status.INTERRUPT) {
                      me._putback(block);
                  }
  
                  return;
              }
  
              me.pool.push( block );
              me.remaning++;
  
              // 如果没有分片，则直接使用原始的。
              // 不会丢失content-type信息。
              block.blob = block.chunks === 1 ? file.source :
                      file.source.slice( block.start, block.end );
  
              // hook, 每个分片发送之前可能要做些异步的事情。
              promise = me.request( 'before-send', block, function() {
  
                  // 有可能文件已经上传出错了，所以不需要再传输了。
                  if ( file.getStatus() === Status.PROGRESS ) {
                      me._doSend( block );
                  } else {
                      me._popBlock( block );
                      Base.nextTick( me.__tick );
                  }
              });
  
              // 如果为fail了，则跳过此分片。
              promise.fail(function() {
                  if ( file.remaning === 1 ) {
                      me._finishFile( file ).always(function() {
                          block.percentage = 1;
                          me._popBlock( block );
                          me.owner.trigger( 'uploadComplete', file );
                          Base.nextTick( me.__tick );
                      });
                  } else {
                      block.percentage = 1;
                      me.updateFileProgress( file );
                      me._popBlock( block );
                      Base.nextTick( me.__tick );
                  }
              });
          },
  
  
          /**
           * @event uploadBeforeSend
           * @param {Object} object
           * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。
           * @param {Object} headers 可以扩展此对象来控制上传头部。
           * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。
           * @for  Uploader
           */
  
          /**
           * @event uploadAccept
           * @param {Object} object
           * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。
           * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。
           * @for  Uploader
           */
  
          /**
           * @event uploadProgress
           * @param {File} file File对象
           * @param {Number} percentage 上传进度
           * @description 上传过程中触发，携带上传进度。
           * @for  Uploader
           */
  
  
          /**
           * @event uploadError
           * @param {File} file File对象
           * @param {String} reason 出错的code
           * @description 当文件上传出错时触发。
           * @for  Uploader
           */
  
          /**
           * @event uploadSuccess
           * @param {File} file File对象
           * @param {Object} response 服务端返回的数据
           * @description 当文件上传成功时触发。
           * @for  Uploader
           */
  
          /**
           * @event uploadComplete
           * @param {File} [file] File对象
           * @description 不管成功或者失败，文件上传完成时触发。
           * @for  Uploader
           */
  
          // 做上传操作。
          _doSend: function( block ) {
              var me = this,
                  owner = me.owner,
                  opts = me.options,
                  file = block.file,
                  tr = new Transport( opts ),
                  data = $.extend({}, opts.formData ),
                  headers = $.extend({}, opts.headers ),
                  requestAccept, ret;
  
              block.transport = tr;
  
              tr.on( 'destroy', function() {
                  delete block.transport;
                  me._popBlock( block );
                  Base.nextTick( me.__tick );
              });
  
              // 广播上传进度。以文件为单位。
              tr.on( 'progress', function( percentage ) {
                  block.percentage = percentage;
                  me.updateFileProgress( file );
              });
  
              // 用来询问，是否返回的结果是有错误的。
              requestAccept = function( reject ) {
                  var fn;
  
                  ret = tr.getResponseAsJson() || {};
                  ret._raw = tr.getResponse();
                  fn = function( value ) {
                      reject = value;
                  };
  
                  // 服务端响应了，不代表成功了，询问是否响应正确。
                  if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {
                      reject = reject || 'server';
                  }
  
                  return reject;
              };
  
              // 尝试重试，然后广播文件上传出错。
              tr.on( 'error', function( type, flag ) {
                  block.retried = block.retried || 0;
  
                  // 自动重试
                  if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&
                          block.retried < opts.chunkRetry ) {
  
                      block.retried++;
                      tr.send();
  
                  } else {
  
                      // http status 500 ~ 600
                      if ( !flag && type === 'server' ) {
                          type = requestAccept( type );
                      }
  
                      file.setStatus( Status.ERROR, type );
                      owner.trigger( 'uploadError', file, type );
                      owner.trigger( 'uploadComplete', file );
                  }
              });
  
              // 上传成功
              tr.on( 'load', function() {
                  var reason;
  
                  // 如果非预期，转向上传出错。
                  if ( (reason = requestAccept()) ) {
                      tr.trigger( 'error', reason, true );
                      return;
                  }
  
                  // 全部上传完成。
                  if ( file.remaning === 1 ) {
                      me._finishFile( file, ret );
                  } else {
                      tr.destroy();
                  }
              });
  
              // 配置默认的上传字段。
              data = $.extend( data, {
                  id: file.id,
                  name: file.name,
                  type: file.type,
                  lastModifiedDate: file.lastModifiedDate,
                  size: file.size
              });
  
              block.chunks > 1 && $.extend( data, {
                  chunks: block.chunks,
                  chunk: block.chunk
              });
  
              // 在发送之间可以添加字段什么的。。。
              // 如果默认的字段不够使用，可以通过监听此事件来扩展
              owner.trigger( 'uploadBeforeSend', block, data, headers );
  
              // 开始发送。
              tr.appendBlob( opts.fileVal, block.blob, file.name );
              tr.append( data );
              tr.setRequestHeader( headers );
              tr.send();
          },
  
          // 完成上传。
          _finishFile: function( file, ret, hds ) {
              var owner = this.owner;
  
              return owner
                      .request( 'after-send-file', arguments, function() {
                          file.setStatus( Status.COMPLETE );
                          owner.trigger( 'uploadSuccess', file, ret, hds );
                      })
                      .fail(function( reason ) {
  
                          // 如果外部已经标记为invalid什么的，不再改状态。
                          if ( file.getStatus() === Status.PROGRESS ) {
                              file.setStatus( Status.ERROR, reason );
                          }
  
                          owner.trigger( 'uploadError', file, reason );
                      })
                      .always(function() {
                          owner.trigger( 'uploadComplete', file );
                      });
          },
  
          updateFileProgress: function(file) {
              var totalPercent = 0,
                  uploaded = 0;
  
              if (!file.blocks) {
                  return;
              }
  
              $.each( file.blocks, function( _, v ) {
                  uploaded += (v.percentage || 0) * (v.end - v.start);
              });
  
              totalPercent = uploaded / file.size;
              this.owner.trigger( 'uploadProgress', file, totalPercent || 0 );
          }
  
      });
  });
  