diff --git a/.gitignore b/.gitignore index aa2e6ef..ca1ca27 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ .idea .gradle /classes/ -.clover \ No newline at end of file +.clover +src/main/webapp/upload/ diff --git a/README.md b/README.md index 4a71332..e5031df 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,17 @@ # FileUpload.Java [![Build Status](https://travis-ci.org/izhangzhihao/FileUpload.Java.svg?branch=master)](https://travis-ci.org/izhangzhihao/FileUpload.Java) - 文件上传,图片上传(后缀名验证,文件类型验证),大文件分片上传,“秒传”,断点续传,传输失败自动重试,手动重试 +# 文件上传,图片上传(后缀名验证,文件类型验证),大文件分片上传,“秒传”,断点续传,传输失败自动重试,手动重试 + +1.主要功能经测试支持IE9以上,Chrome,FireFox;其他浏览器未测试; + +2.文件上传部分:主要实现了文件的上传,进度条,多文件一起上传,上传前删除,上传失败后手动删除,上传失败自动重试,上传失败手动重试(retry按钮),自动上传; + +3.大文件上传部分:重磅功能:大文件“秒传”;在文件上传部分已有功能的基础上实现了按10MB分为多个块,异步上传,服务端合并,MD5验证,文件秒传,断点续传,网络问题自动重试,手动重试; + +4.图片上传部分:在文件上传部分已有功能的基础上实现了上传前缩略图预览,前台js文件后缀验证,后台代码文件后缀验证和文件类型验证(就算修改后缀名也无法成功上传),支持图片上传前压缩; + +5.多选择器多文件上传:通过不同的文件选择器选择不同的文件,最后同时上传,Controller只是简单示意,并没有详细写实现,具体怎么做可参照上面的其它上穿方法。 + +# 文件上传这里好多方法可以抽象出来,当然这个项目只是一个示例,所以我偷了点懒,应用到生产环境时还要根据环境选择保存到不同的文件路径等等,大家根据自己的情况自己封装方法吧。 + diff --git a/build.gradle b/build.gradle index f00ca6b..0961c5e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,20 +1,19 @@ -group 'zhangzhihao' -version '1.0-SNAPSHOT' +group "com.github.izhangzhihao" +version "1.0-SNAPSHOT" apply plugin: "java" apply plugin: "war" sourceCompatibility = 1.8 targetCompatibility = 1.8 -def springVersion = "4.3.0.RELEASE" -def HibernateVersion = "5.2.0.Final" +def springVersion = "4.3.4.RELEASE" +def HibernateVersion = "5.2.4.Final" +def JacksonVersion = "2.8.4" repositories { - maven { url "http://maven.oschina.net/content/groups/public/" } mavenLocal() + maven { url "http://maven.aliyun.com/nexus/content/groups/public/" } mavenCentral() - maven { url "http://repo.spring.io/release" } - maven { url "https://repo.spring.io/libs-snapshot" } } dependencies { @@ -33,7 +32,7 @@ dependencies { "org.springframework:spring-orm:${springVersion}", // MySQL - "mysql:mysql-connector-java:6.0.2", + "mysql:mysql-connector-java:6.0.4", //servlet "javax.servlet:javax.servlet-api:3.1.0", @@ -42,26 +41,28 @@ dependencies { "commons-fileupload:commons-fileupload:1.3.1", // JSON - // "com.alibaba:fastjson:1.2.6", - "com.fasterxml.jackson.core:jackson-core:2.7.4", - "com.fasterxml.jackson.core:jackson-databind:2.7.4", - "com.fasterxml.jackson.core:jackson-annotations:2.7.4", + "com.fasterxml.jackson.core:jackson-core:${JacksonVersion}", + "com.fasterxml.jackson.core:jackson-databind:${JacksonVersion}", + "com.fasterxml.jackson.core:jackson-annotations:${JacksonVersion}", - //Hibernate 5.2.0.Final有一个bug + //Hibernate "org.hibernate:hibernate-core:${HibernateVersion}", + "org.hibernate:hibernate-validator:5.2.4.Final", //C3P0 - "c3p0:c3p0:0.9.1.2", + "com.mchange:c3p0:0.9.5.2", "org.hibernate:hibernate-c3p0:${HibernateVersion}", //ehcache - "net.sf.ehcache:ehcache-core:2.6.11", + "org.ehcache:ehcache:3.1.2", "org.hibernate:hibernate-ehcache:${HibernateVersion}", //jUnit "junit:junit:4.12", - + + //@NotNull + "org.jetbrains:annotations:15.0", ) testCompile( diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java index ee822c9..044ab7f 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java @@ -1,25 +1,103 @@ package com.zhangzhihao.FileUpload.Java.Controller; import com.zhangzhihao.FileUpload.Java.Service.FileService; +import com.zhangzhihao.FileUpload.Java.Utils.SaveFile; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +import static com.zhangzhihao.FileUpload.Java.Utils.IsAllUploaded.Uploaded; @Controller @RequestMapping("/BigFileUpload") -public class BigFileUploadController { - @Autowired - private FileService fileService; - - /** - * 转向操作页面 - * - * @return 操作页面 - */ - @RequestMapping(value = "/Index", method = RequestMethod.GET) - public String Index() { - return "BigFileUpload/Index"; - } +public class BigFileUploadController extends SaveFile { + @Autowired + private FileService fileService; + + /** + * 转向操作页面 + * + * @return 操作页面 + */ + @RequestMapping(value = "/Index", method = RequestMethod.GET) + public String Index() { + return "BigFileUpload/Index"; + } + + @ResponseBody + @RequestMapping(value = "/IsMD5Exist", method = RequestMethod.POST) + public String bigFileUpload(String fileMd5, String fileName, String fileID) { + + try { + boolean md5Exist = fileService.isMd5Exist(fileMd5); + if (md5Exist) { + return "this file is exist"; + } else { + return "this file is not exist"; + } + } catch (Exception e) { + e.printStackTrace(); + return "this file is not exist"; + } + } + + /** + * @param guid 临时文件名 + * @param md5value 客户端生成md5值 + * @param chunks 分块数 + * @param chunk 分块序号 + * @param id 文件id便于区分 + * @param name 上传文件名 + * @param type 文件类型 + * @param lastModifiedDate 上次修改时间 + * @param size 文件大小 + * @param file 文件本身 + * @return + */ + @ResponseBody + @RequestMapping(value = "/BigFileUp") + public String fileUpload(String guid, + String md5value, + String chunks, + String chunk, + String id, + String name, + String type, + String lastModifiedDate, + int size, + MultipartFile file) { + String fileName; + try { + int index; + String uploadFolderPath = getRealPath(); + + String mergePath =uploadFolderPath + guid + "/"; + String ext = name.substring(name.lastIndexOf(".")); + + //判断文件是否分块 + if (chunks != null && chunk != null) { + index = Integer.parseInt(chunk); + fileName = String.valueOf(index) + ext; + // 将文件分块保存到临时文件夹里,便于之后的合并文件 + saveFile(mergePath, fileName, file); + // 验证所有分块是否上传成功,成功的话进行合并 + Uploaded(md5value, guid, chunk, chunks, uploadFolderPath, fileName, ext, fileService); + } else { + fileName = guid + ext; + //上传文件没有分块的话就直接保存 + saveFile(uploadFolderPath, fileName, file); + } + + } catch (Exception ex) { + ex.printStackTrace(); + return "{\"error\":true}"; + } + + return "{jsonrpc = \"2.0\",id = id,filePath = \"/Upload/\" + fileFullName}"; + } + } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java index 912ced8..6138bef 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java @@ -1,7 +1,57 @@ package com.zhangzhihao.FileUpload.Java.Controller; -/** - * Created by izhangzhihao on 2016/6/22 0022. - */ +import com.zhangzhihao.FileUpload.Java.Model.File; +import com.zhangzhihao.FileUpload.Java.Service.FileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Date; +import java.util.UUID; + +import static com.zhangzhihao.FileUpload.Java.Utils.CreateMd5.createMd5; +import static com.zhangzhihao.FileUpload.Java.Utils.SaveFile.getRealPath; +import static com.zhangzhihao.FileUpload.Java.Utils.SaveFile.saveFile; + + +@Controller +@RequestMapping("/FileUpload") public class FileUploadController { + @Autowired + private FileService fileService; + + @RequestMapping(value = "/Index", method = RequestMethod.GET) + public String Index() { + return "FileUpload/Index"; + } + + @ResponseBody + @RequestMapping(value = "/FileUp", method = RequestMethod.POST) + public String fileUpload(@RequestParam("id") String id, + @RequestParam("name") String name, + @RequestParam("type") String type, + @RequestParam("lastModifiedDate") String lastModifiedDate, + @RequestParam("size") int size, + @RequestParam("file") MultipartFile file) { + String fileName; + + try { + String ext = name.substring(name.lastIndexOf(".")); + fileName = UUID.randomUUID().toString() + ext; + saveFile(getRealPath(), fileName, file); + } catch (Exception ex) { + return "{\"error\":true}"; + } + try { + fileService.save(new File(fileName, createMd5(file).toString(), new Date())); + } catch (Exception e) { + return "{\"error\":true}"; + } + + return "{jsonrpc = \"2.0\",id = id,filePath = \"/Upload/\" + fileFullName}"; + } } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java new file mode 100644 index 0000000..1a3fce1 --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java @@ -0,0 +1,67 @@ +package com.zhangzhihao.FileUpload.Java.Controller; + +import com.zhangzhihao.FileUpload.Java.Model.File; +import com.zhangzhihao.FileUpload.Java.Service.FileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Date; +import java.util.UUID; + +import static com.zhangzhihao.FileUpload.Java.Utils.CreateMd5.createMd5; +import static com.zhangzhihao.FileUpload.Java.Utils.DeepCopy.deepClone; +import static com.zhangzhihao.FileUpload.Java.Utils.IsImag.isImage; +import static com.zhangzhihao.FileUpload.Java.Utils.SaveFile.getRealPath; +import static com.zhangzhihao.FileUpload.Java.Utils.SaveFile.saveFile; + + +@Controller +@RequestMapping("/ImageUpload") +public class ImageUploadController { + @Autowired + private FileService fileService; + + @RequestMapping(value = "/Index", method = RequestMethod.GET) + public String Upload() { + return "ImageUpload/Upload"; + } + + @ResponseBody + @RequestMapping(value = "/ImageUp", method = RequestMethod.POST) + public String fileUpload(@RequestParam("id") String id, + @RequestParam("name") String name, + @RequestParam("type") String type, + @RequestParam("lastModifiedDate") String lastModifiedDate, + @RequestParam("size") int size, + @RequestParam("file") MultipartFile file) { + String fileName = ""; + + MultipartFile saveFile = null; + + try { + saveFile = (MultipartFile) deepClone(file); + java.io.File tempFile = new java.io.File(UUID.randomUUID().toString()); + file.transferTo(tempFile); + if (!isImage(tempFile)) + return "{\"error\":true}"; + + String realpath = getRealPath(); + String ext = name.substring(name.lastIndexOf(".")); + fileName = UUID.randomUUID().toString() + ext; + saveFile(realpath, fileName, saveFile); + + fileService.save(new File(fileName, createMd5(file).toString(), new Date())); + + } catch (Exception ex) { + return "{\"error\":true}"; + } + + return "{jsonrpc = \"2.0\",id = id,filePath = \"/Upload/\" + fileFullName}"; + } +} + diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/MultiPickerUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/MultiPickerUploadController.java new file mode 100644 index 0000000..3408c3b --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/MultiPickerUploadController.java @@ -0,0 +1,46 @@ +package com.zhangzhihao.FileUpload.Java.Controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +/** + * Created by 张志豪 on 2016/12/5 0005. + * 多个文件选择器上传文件,一个选择器对应一个文件 + */ +@Controller +@RequestMapping("/MultiPickerUpload") +public class MultiPickerUploadController { + + @GetMapping(value = "/Index") + public String Index() { + return "MultiPicker/Index"; + } + + @PostMapping("/") + public ResponseEntity fileUpload(@RequestParam("type") String type, + @RequestParam("name") String name, + @RequestParam("file") MultipartFile file) throws Exception { + + switch (type) { + case "researchReport": //研究报告 + //save file + break; + case "researchReportStuff": //研究报告支撑材料(限PDF) + //save file + break; + case "applyReport": //应用报告 + //save file + break; + case "applyReportStuff": //应用报告支撑材料(限PDF) + //save file + break; + default: + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } + return new ResponseEntity<>(HttpStatus.CREATED); + } + +} diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/BaseDao.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/BaseDao.java index 42be9ac..d06013a 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/BaseDao.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/BaseDao.java @@ -2,19 +2,14 @@ import com.zhangzhihao.FileUpload.Java.Utils.PageResults; -import org.hibernate.Criteria; -import org.hibernate.SQLQuery; -import org.hibernate.Session; -import org.hibernate.criterion.Criterion; -import org.hibernate.criterion.Order; -import org.hibernate.criterion.Projection; -import org.hibernate.criterion.Projections; -import org.springframework.beans.factory.annotation.Autowired; +import org.jetbrains.annotations.NotNull; import org.springframework.context.annotation.Primary; -import org.springframework.orm.hibernate5.HibernateTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.TypedQuery; import java.io.Serializable; import java.util.List; @@ -24,51 +19,55 @@ * * @param 实体类型 */ -@SuppressWarnings({"rawtypes", "unchecked"}) -@Transactional(timeout = 1) +@SuppressWarnings({"unchecked"}) +@Transactional(timeout = 5) @Repository @Primary public class BaseDao { - @Autowired - private HibernateTemplate hibernateTemplate; + //获取到和当前事务关联的 EntityManager 对象 + //实际上是获得EntityManager的代理对象,是线程安全的 + @PersistenceContext + private EntityManager entityManager; /** - * 保存对象 + * 这个实体是否存在在数据库 * - * @param model 需要添加的对象 - * @return 是否添加成功 + * @param model 实体 + * @return 是否存在 */ - public Boolean save(final T model) { - Serializable save = hibernateTemplate.save(model); - if (save != null) { - return true; - } else { - return false; - } + public boolean contains(@NotNull final T model) { + return entityManager.contains(model); } /** - * 添加并且返回Integer类型的ID + * 使实体变为不受管理的状态 * - * @param model 需要添加的对象 - * @return Integer类型的ID + * @param model 实体 */ - public Integer saveAndGetIntegerID(final T model) { - return (Integer) hibernateTemplate.save(model); + public void detach(@NotNull final T model) { + entityManager.detach(model); } /** - * 添加并且返回String类型的ID + * 保存对象 * * @param model 需要添加的对象 - * @return String类型的ID */ - public String saveAndGetStringID(final T model) { - return (String) hibernateTemplate.save(model); + public void save(@NotNull final T model) { + entityManager.persist(model); } + /** + * 批量保存对象 + * + * @param modelList 需要增加的对象的集合 + * 失败会抛异常 + */ + public void saveAll(@NotNull final List modelList) { + modelList.stream().forEach(entityManager::persist); + } /** * 删除对象 @@ -76,8 +75,8 @@ public String saveAndGetStringID(final T model) { * @param model 需要删除的对象 * 失败会抛异常 */ - public void delete(final T model) { - hibernateTemplate.delete(model); + public void delete(@NotNull final T model) { + entityManager.remove(entityManager.contains(model) ? model : entityManager.merge(model)); } /** @@ -86,8 +85,8 @@ public void delete(final T model) { * @param modelList 需要删除的对象的集合 * 失败会抛异常 */ - public void deleteAll(final List modelList) { - modelList.stream().forEach(hibernateTemplate::delete); + public void deleteAll(@NotNull final List modelList) { + modelList.stream().forEach(this::delete); } /** @@ -97,29 +96,28 @@ public void deleteAll(final List modelList) { * @param id 需要删除的对象的id * 失败抛出异常 */ - public void deleteById(final Class modelClass, Serializable id) { - hibernateTemplate.delete(this.getById(modelClass, id)); + public void deleteById(final Class modelClass, @NotNull final Serializable id) { + this.delete(this.getById(modelClass, id)); } /** - * 更新对象 + * 更新或保存对象 * * @param model 需要更新的对象 * 失败会抛出异常 */ - public void update(final T model) { - hibernateTemplate.update(model); + public void saveOrUpdate(@NotNull final T model) { + entityManager.merge(model); } - /** - * 添加或者更新 + * 批量更新或保存对象 * - * @param model 需要更新或添加的对象 - * 失败会抛出异常 + * @param modelList 需要更新或保存的对象 + * 失败会抛出异常 */ - public void saveOrUpdate(final T model) { - hibernateTemplate.saveOrUpdate(model); + public void saveOrUpdateAll(@NotNull final List modelList) { + modelList.stream().forEach(entityManager::merge); } /** @@ -130,8 +128,8 @@ public void saveOrUpdate(final T model) { * @return model */ @Transactional(readOnly = true) - public T getById(Class modelClass, final Serializable id) { - return hibernateTemplate.get(modelClass, id); + public T getById(Class modelClass, @NotNull final Serializable id) { + return entityManager.find(modelClass, id); } /** @@ -141,8 +139,9 @@ public T getById(Class modelClass, final Serializable id) { * @return List */ @Transactional(readOnly = true) - public List loadAll(Class modelClass) { - return hibernateTemplate.loadAll(modelClass); + public List getAll(Class modelClass) { + Query query = new Query(modelClass, entityManager); + return entityManager.createQuery(query.createCriteriaQuery()).getResultList(); } @@ -155,120 +154,187 @@ public List loadAll(Class modelClass) { * @return 查询结果 */ @Transactional(readOnly = true) - public List getListByPage(Class modelClass,final Integer currentPageNumber,final Integer pageSize) { + public List getListByPage(Class modelClass, + @NotNull final Integer currentPageNumber, + @NotNull final Integer pageSize) { if (currentPageNumber <= 0 || pageSize <= 0) { return null; } - Criteria criteria = hibernateTemplate.getSessionFactory().getCurrentSession().createCriteria(modelClass); - criteria.setFirstResult((currentPageNumber - 1) * pageSize); - criteria.setMaxResults(pageSize); - return criteria.list(); + Query query = new Query(modelClass, entityManager); + return entityManager.createQuery(query.createCriteriaQuery()) + .setFirstResult((currentPageNumber - 1) * pageSize) + .setMaxResults(pageSize) + .getResultList(); } - /** - * 按条件分页,条件以可变参形式传入,类型为Criterion [URL]http://zzk.cnblogs.com/s?t=b&w=Criteria + * 按条件分页 * - * @param modelClass 类型,比如User.class * @param currentPageNumber 页码 * @param pageSize 每页数量 - * @param criterions 查询条件数组,由Restrictions对象生成,如Restrictions.like("name","%x%")等; - * @param orders 查询后记录的排序条件,由Order对象生成 - * @param projections 分组和聚合查询条件 + * @param query 封装的查询条件 * @return 查询结果 */ @Transactional(readOnly = true) - public PageResults getListByPageAndRule(Class modelClass, Integer currentPageNumber, Integer pageSize, final Criterion[] criterions, final Order[] orders, - final Projection[] projections) { - Criteria criteria = hibernateTemplate.getSessionFactory().getCurrentSession().createCriteria(modelClass); - //添加条件 - if (criterions != null && criterions.length > 0) { - for (int i = 0; i < criterions.length; i++) { - criteria.add(criterions[i]); - } - } - //添加排序 - if (orders != null && orders.length > 0) { - for (int i = 0; i < orders.length; i++) { - criteria.addOrder(orders[i]); - } - } - //添加分组统计 - if (projections != null && projections.length > 0) { - for (int i = 0; i < projections.length; i++) { - criteria.setProjection(projections[i]); - } - } - - //参数验证 - int totalCount = getCountByRule(modelClass, criterions); + public PageResults getListByPageAndQuery(@NotNull Integer currentPageNumber, + @NotNull Integer pageSize, + @NotNull Query query) + throws Exception { + //获得符合条件的总数目 + int totalCount = getCountByQuery((Query) query.deepClone()); int pageCount = totalCount % pageSize == 0 ? totalCount / pageSize : totalCount / pageSize + 1; + if (currentPageNumber > pageCount && pageCount != 0) { currentPageNumber = pageCount; } - + TypedQuery typedQuery = query.createTypedQuery(); //查看是否要分页 - if (currentPageNumber >= 0 && pageSize >= 0) { - criteria.setFirstResult((currentPageNumber - 1) * pageSize); - criteria.setMaxResults(pageSize); + if (currentPageNumber > 0 && pageSize > 0) { + typedQuery + .setFirstResult((currentPageNumber - 1) * pageSize) + .setMaxResults(pageSize); } - return new PageResults(currentPageNumber + 1, currentPageNumber, pageSize, totalCount, pageCount, criteria.list()); + List list = typedQuery.getResultList(); + return new PageResults<>(currentPageNumber + 1, currentPageNumber, pageSize, totalCount, pageCount, list); } + /** + * 获得数量 利用Count(*)实现 + * + * @param modelClass 类型,比如User.class + * @return 数量 + */ + @Transactional(readOnly = true) + public int getCount(Class modelClass) { + Query query = new Query(modelClass, entityManager); + return getCountByQuery(query); + } /** * 获得符合对应条件的数量 利用Count(*)实现 * - * @param modelClass 类型,比如User.class - * @param criterions 查询条件数组,由Restrictions对象生成,如Restrictions.like("name","%x%")等; + * @param query 查询条件 * @return 数量 */ @Transactional(readOnly = true) - public int getCountByRule(Class modelClass, final Criterion[] criterions) { - Criteria criteria = hibernateTemplate.getSessionFactory().getCurrentSession().createCriteria(modelClass); - //添加条件 - if (criterions != null && criterions.length > 0) { - for (int i = 0; i < criterions.length; i++) { - criteria.add(criterions[i]); - } + public int getCountByQuery(@NotNull final Query query) { + query.selectCount(); + return Integer.parseInt( + query.createTypedQuery() + .getSingleResult() + .toString() + ); + } + + /** + * 执行Sql语句 + * + * @param sql sql + * @param values 不定参数数组 + * @return 受影响的行数 + */ + public int executeSql(@NotNull String sql, @NotNull final Object... values) { + javax.persistence.Query nativeQuery = entityManager.createNativeQuery(sql); + for (int i = 0; i < values.length; i++) { + nativeQuery.setParameter(i, values[i]); + } + return nativeQuery.executeUpdate(); + } + + /** + * 通过jpql查询 + * + * @param jpql + * @param values + * @return + */ + @Transactional(readOnly = true) + public Object queryByJpql(@NotNull final String jpql, @NotNull final Object... values) { + javax.persistence.Query query = entityManager.createQuery(jpql); + for (int i = 0; i < values.length; i++) { + query.setParameter(i, values[i]); } - criteria.setProjection(Projections.rowCount()); - long uniqueResult = 0; - try { - uniqueResult = (long) criteria.uniqueResult(); - } catch (Exception ex) { - uniqueResult = 0; + return query.getResultList(); + } + + /** + * 获得符合对应条件的数量 利用Count(*)实现 + * + * @param jpql jpql查询条件 + * @return 数量 + */ + @Transactional(readOnly = true) + public int getCountByJpql(@NotNull final String jpql, @NotNull final Object... values) { + javax.persistence.Query query = entityManager.createQuery(jpql); + for (int i = 0; i < values.length; i++) { + query.setParameter(i, values[i]); } - return (int) uniqueResult; + return query.getResultList().size(); } + /** + * 通过Jpql分页查询 + * + * @param currentPageNumber 当前页 + * @param pageSize 每页数量 + * @param jpql jpql语句 + * @param values jpql参数 + * @return 查询结果 + */ + @Transactional(readOnly = true) + public PageResults getListByPageAndJpql(@NotNull Integer currentPageNumber, + @NotNull Integer pageSize, + @NotNull final String jpql, + @NotNull final Object... values) { + //参数验证 + int totalCount = getCountByJpql(jpql, values); + int pageCount = totalCount % pageSize == 0 ? totalCount / pageSize + : totalCount / pageSize + 1; + + if (currentPageNumber > pageCount && pageCount != 0) { + currentPageNumber = pageCount; + } + + javax.persistence.Query query = entityManager.createQuery(jpql); + for (int i = 0; i < values.length; i++) { + query.setParameter(i, values[i]); + } + + //查看是否要分页 + if (currentPageNumber > 0 && pageSize > 0) { + query + .setFirstResult((currentPageNumber - 1) * pageSize) + .setMaxResults(pageSize); + } + List list = query.getResultList(); + return new PageResults<>(currentPageNumber + 1, currentPageNumber, pageSize, totalCount, pageCount, list); + } /** - * 执行Sql语句 + * 执行jpql语句 * - * @param sqlString sql - * @param values 不定参数数组 - * @return 受影响的行数 + * @param jpql + * @param values + * @return */ - public int executeSql(String sqlString, Object... values) { - Session session = hibernateTemplate.getSessionFactory().getCurrentSession(); - SQLQuery sqlQuery = session.createSQLQuery(sqlString); - if (values != null) { - for (int i = 0; i < values.length; i++) { - sqlQuery.setParameter(i, values[i]); - } + public int executeJpql(@NotNull final String jpql, @NotNull final Object... values) { + javax.persistence.Query query = entityManager.createQuery(jpql); + for (int i = 0; i < values.length; i++) { + query.setParameter(i, values[i]); } - return sqlQuery.executeUpdate(); + return query.executeUpdate(); } /** * refresh 刷新实体状态 * - * @param t 实体 + * @param model 实体 */ - public void refresh(T t) { - hibernateTemplate.refresh(t); + public void refresh(@NotNull T model) { + entityManager.refresh(model); } + + } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/Query.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/Query.java new file mode 100644 index 0000000..e2b62a4 --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/Query.java @@ -0,0 +1,857 @@ +package com.zhangzhihao.FileUpload.Java.Dao; + + +import org.jetbrains.annotations.NotNull; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.*; +import javax.persistence.criteria.CriteriaBuilder.In; +import java.io.*; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Query + * 封装JPA CriteriaBuilder查询条件 + */ +@SuppressWarnings({"unused", "unchecked", "rawtypes", "null", "hiding"}) +public class Query implements Serializable { + + private static final long serialVersionUID = 3366932251068926942L; + + private EntityManager entityManager; + + /** + * 要查询的实体类型 + */ + private Class clazz; + + /** + * 查询根,定义查询的From子句中能出现的类型。 + * Criteria查询的查询根定义了实体类型,能为将来导航获得想要的结果,它与SQL查询中的FROM子句类似。 + * Root实例也是类型化的,且定义了查询的FROM子句中能够出现的类型。 + * 查询根实例能通过传入一个实体类型给 AbstractQuery.from方法获得。 + * Criteria查询,可以有多个查询根。 + */ + private From from; + + /** + * 谓词,也就是过滤条件,用CriteriaBuilder生成。 + * 过滤条件应用到SQL语句的FROM子句中, + * 在Criteria 查询中,查询条件通过Predicate 或Expression 实例应用到CriteriaQuery 对象上。 + */ + private List predicates; + + /** + * 安全查询主语句 + */ + private CriteriaQuery criteriaQuery; + + /** + * 安全查询创建工厂。 + * CriteriaBuilder是一个工厂对象,安全查询的开始, + * 用于构建JPA安全查询,创建CriteriaQuery,创建查询具体具体条件Predicate 等。 + */ + private CriteriaBuilder criteriaBuilder; + + /** + * select条件,用于处理平均值之类的查询 + */ + private Selection selection; + + /** + * 排序方式列表 + */ + private List orders; + + /** + * 查询参数 + */ + private Map parameters; + + /** + * 子查询 + */ + private Map subQuery; + + /** + * 关联查询 + */ + private Map linkQuery; + + /** + * 子查询字段 + */ + private String projection; + + /** + * 或条件 + */ + private List orQuery; + + /** + * 分组条件 + */ + private String groupBy; + + private Query() { + } + + /** + * 创建查询条件 + * + * @param clazz 要查询的类型 + * @param entityManager EntityManager + */ + public Query(@NotNull Class clazz, @NotNull EntityManager entityManager) { + this.clazz = clazz; + this.entityManager = entityManager; + this.criteriaBuilder = this.entityManager.getCriteriaBuilder(); + this.criteriaQuery = criteriaBuilder.createQuery(this.clazz); + this.from = criteriaQuery.from(this.clazz); + this.predicates = new ArrayList(); + this.orders = new ArrayList(); + } + + /** + * 构造函数 + * + * @param entityManager EntityManager + */ + public Query(@NotNull EntityManager entityManager) { + this.entityManager = entityManager; + this.criteriaBuilder = this.entityManager.getCriteriaBuilder(); + this.predicates = new ArrayList(); + this.orders = new ArrayList(); + } + + /** + * FluentAPI 式构造函数 + * + * @param clazz 要查询的类型 + * @return 返回Query对象 + */ + public Query from(@NotNull Class clazz) { + this.clazz = clazz; + this.criteriaQuery = criteriaBuilder.createQuery(this.clazz); + this.from = criteriaQuery.from(this.clazz); + return this; + } + + /** + * 创建强类型查询,并且自动注入参数!!! + * 如果使用参数化自动注入的方法,必须调用此方法创建强类型查询 + * + * @return 返回强类型查询的实例 + */ + public TypedQuery createTypedQuery() { + TypedQuery typedQuery = entityManager.createQuery(this.createCriteriaQuery()); + if (parameters != null) { + for (ParameterExpression parameter : parameters.keySet()) { + typedQuery.setParameter(parameter, parameters.get(parameter)); + } + } + return typedQuery; + } + + /** + * 将ParameterExpression和对应的值添加进一个map里,最后在createTypedQuery时放入TypedQuery里面,完成参数化查询 + * + * @param parameter ParameterExpression参数 + * @param object 参数值 + */ + private void setParameter(@NotNull final ParameterExpression parameter, @NotNull final Object object) { + if (parameters == null) { + parameters = new HashMap<>(); + } + parameters.put(parameter, object); + } + + /** + * 自动创建ParameterExpression参数 + * + * @param value 参数值 + * @return 返回ParameterExpression参数 + */ + private ParameterExpression makeParameter(@NotNull final Object value) { + Class aClass = value.getClass(); + ParameterExpression parameter = this.createParameter(aClass); + setParameter(parameter, value); + return parameter; + } + + + /** + * 创建查询条件 + * + * @return JPA标准查询 + */ + public CriteriaQuery createCriteriaQuery() { + criteriaQuery.where(predicates.toArray(new Predicate[0])); + if (!isNullOrEmpty(groupBy)) { + criteriaQuery.groupBy(from.get(groupBy)); + } + if (orders != null) { + criteriaQuery.orderBy(orders); + } + if (selection != null) { + criteriaQuery.select(selection); + } + addLinkCondition(this); + return criteriaQuery; + } + + /** + * 参数化查询 + */ + public ParameterExpression createParameter(@NotNull Class clazz) { + return this.getCriteriaBuilder().parameter(clazz); + } + + /** + * 添加连接查询,在最后创建CriteriaQuery的时候调用 + * + * @param query query对象 + */ + private void addLinkCondition(@NotNull Query query) { + Map linkQuery = query.linkQuery; + if (linkQuery == null) + return; + for (Object o : linkQuery.keySet()) { + String key = o.toString(); + Query sub = (Query) linkQuery.get(key); + from.join(key); + criteriaQuery.where(sub.predicates.toArray(new Predicate[0])); + addLinkCondition(sub); + } + } + + /** + * 增关联查询 + */ + public void addLinkQuery(@NotNull final String propertyName, @NotNull Query query) { + if (this.linkQuery == null) + this.linkQuery = new HashMap(); + + this.linkQuery.put(propertyName, query); + } + + /** + * 增加子查询,必须设置子查询字段 projection + */ + private void addSubQuery(@NotNull final String propertyName, @NotNull Query query) { + if (this.subQuery == null) + this.subQuery = new HashMap(); + + if (query.projection == null) + throw new RuntimeException("子查询字段未设置"); + + this.subQuery.put(propertyName, query); + } + + /** + * 直接添加JPA内部的查询条件, + * 用于应付一些复杂查询的情况,例如 "或" + */ + public Query addCriterions(@NotNull Predicate predicate) { + this.predicates.add(predicate); + return this; + } + + + /** + * 增加升序还是降序排序,可以同时包含多个排序规则 + * + * @param propertyName 排序的属性名 + * @param order 排序方式 asc或desc + * @return 返回query实例 + */ + public Query addOrder(@NotNull final String propertyName, @NotNull final String order) { + if (this.orders == null) + this.orders = new ArrayList(); + + if (order.equalsIgnoreCase("asc")) + this.orders.add(criteriaBuilder.asc(from.get(propertyName))); + else if (order.equalsIgnoreCase("desc")) + this.orders.add(criteriaBuilder.desc(from.get(propertyName))); + return this; + } + + /** + * 设置升序还是降序排序,只能有一个排序规则 + * + * @param propertyName 排序的属性名 + * @param order 排序方式 asc或desc + * @return 返回query实例 + */ + public Query setOrder(@NotNull final String propertyName, @NotNull final String order) { + this.orders = null; + addOrder(propertyName, order); + return this; + } + + + /** + * 设置查询,此方法为高级用法,用于应付比较复杂的查询规则 + * 比如 Or Some Any + * + * @param selection 查询规则 + * @return query实例 + */ + public Query setSelect(Selection selection) { + this.selection = selection; + return this; + } + + /** + * 此方法只是为了读起来更有语义感,实际上不做任何工作,可以不用 + * + * @return query实例 + */ + public Query select() { + return this; + } + + /** + * 查询总数 + * + * @return query实例 + */ + public Query selectCount() { + Expression count = criteriaBuilder.count(from); + setSelect(count); + return this; + } + + /** + * 查询总数并去重 + * + * @return query实例 + */ + public Query selectCountDistinct() { + Expression count = criteriaBuilder.countDistinct(from); + setSelect(count); + return this; + } + + /** + * 查询特定属性的平均值 + * + * @param propertyName 要计算平均值的属性名 + * @return query实例 + */ + public Query selectAvg(@NotNull final String propertyName) { + Expression avg = criteriaBuilder.avg(from.get(propertyName)); + setSelect(avg); + return this; + } + + /** + * 查询特定属性的值之和 + * + * @param propertyName 要计算和的属性名 + * @return query实例 + */ + public Query selectSum(@NotNull final String propertyName) { + setSelect(criteriaBuilder.sum(from.get(propertyName))); + return this; + } + + /** + * 查询特定属性值的和,并设置查询结果类型为Long + * + * @param propertyName 要计算和的属性名 + * @return query实例 + */ + public Query selectSumAsLong(@NotNull final String propertyName) { + setSelect(criteriaBuilder.sumAsLong(from.get(propertyName))); + return this; + } + + /** + * 查询特定属性值的和,并设置查询结果类型为Double + * + * @param propertyName 要计算和的属性名 + * @return query实例 + */ + public Query selectSumAsDouble(@NotNull final String propertyName) { + setSelect(criteriaBuilder.sumAsDouble(from.get(propertyName))); + return this; + } + + /** + * 查询特定属性的最大值 + * + * @param propertyName 要查询最大值的属性名 + * @return query实例 + */ + public Query selectMax(@NotNull final String propertyName) { + setSelect(criteriaBuilder.max(from.get(propertyName))); + return this; + } + + /** + * 查询特定属性的最小值 + * + * @param propertyName 要查询最小值的属性名 + * @return query实例 + */ + public Query selectMin(@NotNull final String propertyName) { + setSelect(criteriaBuilder.min(from.get(propertyName))); + return this; + } + + + /** + * 查找属性值等于特定值的实体 + * + * @param propertyName 要查询属性的属性名 + * @param parameter 参数表达式 + * @return query实例 + */ + public Query whereEqual(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.equal(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值等于特定值的实体 + * + * @param propertyName 要查询属性的属性名 + * @param value 属性值 + * @return query实例 + */ + public Query whereEqual(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.equal(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 查找属性值不等于特定值的实体 + * + * @param propertyName 要查询属性的属性名 + * @param parameter 参数表达式 + * @return query实例 + */ + public Query whereNotEqual(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.notEqual(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值不等于特定值的实体 + * + * @param propertyName 要查询属性的属性名 + * @param value 属性值 + * @return query实例 + */ + public Query whereNotEqual(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.notEqual(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 查找属性值小于等于特定值的实体 + * + * @param propertyName 属性名称 + * @param parameter 参数表达式 + */ + public Query whereLessThanOrEqual(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.le(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值小于等于特定值的实体 + * + * @param propertyName 属性名称 + * @param value 属性值 + */ + public Query whereLessThanOrEqual(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.le(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 查找属性值小于特定值的实体 + * + * @param propertyName 属性名称 + * @param parameter 参数表达式 + */ + public Query whereLessThan(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.lt(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值小于特定值的实体 + * + * @param propertyName 属性名称 + * @param value 属性值 + */ + public Query whereLessThan(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.lt(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 查找属性值大于等于特定值的实体 + * + * @param propertyName 属性名称 + * @param parameter 参数表达式 + */ + public Query whereGreaterThanOrEqual(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.ge(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值大于等于特定值的实体 + * + * @param propertyName 属性名称 + * @param value 属性值 + */ + public Query whereGreaterThanOrEqual(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.ge(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 查找属性值大于特定值的实体 + * + * @param propertyName 属性名称 + * @param parameter 参数表达式 + */ + public Query whereGreaterThan(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.gt(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值大于特定值的实体 + * + * @param propertyName 属性名称 + * @param value 属性值 + */ + public Query whereGreaterThan(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.gt(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 或者特定属性名等于特定值的实体 + * + * @param propertyName 要查询的属性的属性名 + * @param parameter 参数表达式 + * @return query实例 + */ + public Query whereOr(@NotNull final List propertyName, @NotNull final ParameterExpression parameter) { + Predicate predicate = criteriaBuilder.or(criteriaBuilder.equal(from.get(propertyName.get(0)), parameter)); + for (int i = 1; i < propertyName.size(); i++) + predicate = criteriaBuilder.or(predicate, criteriaBuilder.equal(from.get(propertyName.get(i)), parameter)); + this.predicates.add(predicate); + return this; + } + + /** + * 或者特定属性名等于特定值的实体 + * + * @param propertyName 要查询的属性的属性名 + * @param value 属性值 + * @return query实例 + */ + public Query whereOr(@NotNull final List propertyName, @NotNull final Object value) { + Predicate predicate = criteriaBuilder.or(criteriaBuilder.equal(from.get(propertyName.get(0)), makeParameter(value))); + for (int i = 1; i < propertyName.size(); i++) + predicate = criteriaBuilder.or(predicate, criteriaBuilder.equal(from.get(propertyName.get(i)), makeParameter(value))); + this.predicates.add(predicate); + return this; + } + + /** + * 模糊匹配 + * + * @param propertyName 属性名称 + * @param parameter 参数表达式 + */ + public Query whereLike(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.like(from.get(propertyName), parameter)); + return this; + } + + /** + * 模糊匹配 + * + * @param propertyName 属性名称 + * @param value 属性值 + */ + public Query whereLike(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.like(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 模糊查询,或者包含 + * + * @param propertyName 要查询的特定属性的属性名 + * @param parameter 参数表达式 + * @return query实例 + */ + public Query whereOrLike(@NotNull final List propertyName, @NotNull final ParameterExpression parameter) { + Predicate predicate = criteriaBuilder.or(criteriaBuilder.like(from.get(propertyName.get(0)), parameter)); + for (int i = 1; i < propertyName.size(); i++) + predicate = criteriaBuilder.or(predicate, criteriaBuilder.like(from.get(propertyName.get(i)), parameter)); + this.predicates.add(predicate); + return this; + } + + /** + * 模糊查询,或者包含 + * + * @param propertyName 要查询的特定属性的属性名 + * @param value 参数值 + * @return query实例 + */ + public Query whereOrLike(@NotNull final List propertyName, @NotNull final Object value) { + Predicate predicate = criteriaBuilder.or(criteriaBuilder.like(from.get(propertyName.get(0)), makeParameter(value))); + for (int i = 1; i < propertyName.size(); i++) + predicate = criteriaBuilder.or(predicate, criteriaBuilder.like(from.get(propertyName.get(i)), makeParameter(value))); + this.predicates.add(predicate); + return this; + } + + /** + * 查找特定字段为空的实体 + * + * @param propertyName 特定属性的属性名 + * @return query实例 + */ + public Query whereIsNull(@NotNull final String propertyName) { + this.predicates.add(criteriaBuilder.isNull(from.get(propertyName))); + return this; + } + + /** + * 查找特定字段非空的实体 + * + * @param propertyName 特定属性的属性名 + * @return query实例 + */ + public Query whereIsNotNull(@NotNull final String propertyName) { + this.predicates.add(criteriaBuilder.isNotNull(from.get(propertyName))); + return this; + } + + + /** + * 时间区间查询 + * + * @param propertyName 属性名称 + * @param lo 属性起始值 + * @param go 属性结束值 + */ + public Query whereBetween(@NotNull final String propertyName, @NotNull final Date lo, @NotNull final Date go) { + this.predicates.add(criteriaBuilder.between(from.get(propertyName), lo, go)); + return this; + } + + /** + * 数字区间查询 + * + * @param propertyName 属性名称 + * @param lo 数字起始值 + * @param go 数字结束值 + */ + public Query whereBetween(@NotNull final String propertyName, @NotNull final ParameterExpression lo, @NotNull final ParameterExpression go) { + whereGreaterThanOrEqual(propertyName, lo) + .whereLessThanOrEqual(propertyName, go); + return this; + } + + /** + * 数字区间查询 + * + * @param propertyName 属性名称 + * @param lo 数字起始值 + * @param go 数字结束值 + */ + public Query whereBetween(@NotNull final String propertyName, @NotNull final Number lo, @NotNull final Number go) { + whereGreaterThanOrEqual(propertyName, lo) + .whereLessThanOrEqual(propertyName, go); + return this; + } + + + /** + * 包含 + * + * @param propertyName 属性名称 + * @param values 参数集合 + */ + public Query whereIn(@NotNull final String propertyName, @NotNull final List values) { + In in = criteriaBuilder.in(from.get(propertyName)); + values.stream().forEach(in::value); + this.predicates.add(in); + return this; + } + + /** + * 包含 + * + * @param propertyName 属性名称 + * @param values 参数值集合 + */ + public Query whereValueIn(@NotNull final String propertyName, @NotNull final List values) { + In in = criteriaBuilder.in(from.get(propertyName)); + values.stream() + .map(this::makeParameter) + .collect(Collectors.toList()) + .stream() + .forEach(in::value); + this.predicates.add(in); + return this; + } + + /** + * 不包含 + * + * @param propertyName 属性名称 + * @param values 参数集合 + */ + public Query whereNotIn(@NotNull final String propertyName, @NotNull final List values) { + In in = criteriaBuilder.in(from.get(propertyName)); + values.stream().forEach(in::value); + this.predicates.add(criteriaBuilder.not(in)); + return this; + } + + /** + * 不包含 + * + * @param propertyName 属性名称 + * @param values 参数集合 + */ + public Query whereValueNotIn(@NotNull final String propertyName, @NotNull final List values) { + In in = criteriaBuilder.in(from.get(propertyName)); + values.stream() + .map(this::makeParameter) + .collect(Collectors.toList()) + .stream() + .forEach(in::value); + this.predicates.add(criteriaBuilder.not(in)); + return this; + } + + /** + * 设置分组方式 + * + * @param groupBy 分组查询的属性名 + */ + public Query groupBy(@NotNull final String groupBy) { + this.groupBy = groupBy; + return this; + } + + /** + * 工具方法 + */ + private boolean isNullOrEmpty(Object value) { + if (value instanceof String) { + return "".equals(value); + } + return value == null; + } + + /** + * 深拷贝 + * + * @return + */ + public Object deepClone() throws Exception { + // 序列化 + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + + oos.writeObject(this); + + // 反序列化 + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + + return ois.readObject(); + } + + public Class getModelClass() { + return this.clazz; + } + + public String getProjection() { + return this.projection; + } + + public void setProjection(String projection) { + this.projection = projection; + } + + public Class getClazz() { + return this.clazz; + } + + public List getOrders() { + return orders; + } + + public void setOrders(List orders) { + this.orders = orders; + } + + public EntityManager getEntityManager() { + return this.entityManager; + } + + public void setEntityManager(EntityManager em) { + this.entityManager = em; + } + + public From getFrom() { + return from; + } + + public List getPredicates() { + return predicates; + } + + public void setPredicates(List predicates) { + this.predicates = predicates; + } + + public CriteriaQuery getCriteriaQuery() { + return criteriaQuery; + } + + public CriteriaBuilder getCriteriaBuilder() { + return criteriaBuilder; + } + + public Selection getSelection() { + return selection; + } + + public void setSelection(Selection selection) { + this.selection = selection; + } + + public Map getParameters() { + return parameters; + } + + public void setParameters(Map parameters) { + this.parameters = parameters; + } + + public void setFetchModes(List fetchField, List fetchMode) { + + } + +} \ No newline at end of file diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Model/UploadInfo.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Model/UploadInfo.java new file mode 100644 index 0000000..00da1ca --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Model/UploadInfo.java @@ -0,0 +1,83 @@ +package com.zhangzhihao.FileUpload.Java.Model; + + +public class UploadInfo { + private String md5; + private String chunks; + private String chunk; + private String path; + private String fileName; + private String ext; + + public UploadInfo() { + } + + public UploadInfo(String md5, String chunks, String chunk, String path, String fileName, String ext) { + this.md5 = md5; + this.chunks = chunks; + this.chunk = chunk; + this.path = path; + this.fileName = fileName; + this.ext = ext; + } + + @Override + public String toString() { + return "UploadInfo{" + + "md5='" + md5 + '\'' + + ", chunks='" + chunks + '\'' + + ", chunk='" + chunk + '\'' + + ", path='" + path + '\'' + + ", fileName='" + fileName + '\'' + + ", ext='" + ext + '\'' + + '}'; + } + + public String getMd5() { + return md5; + } + + public void setMd5(String md5) { + this.md5 = md5; + } + + public String getChunks() { + return chunks; + } + + public void setChunks(String chunks) { + this.chunks = chunks; + } + + public String getChunk() { + return chunk; + } + + public void setChunk(String chunk) { + this.chunk = chunk; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getExt() { + return ext; + } + + public void setExt(String ext) { + this.ext = ext; + } +} diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Service/BaseService.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Service/BaseService.java index 2f46a49..07dff54 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Service/BaseService.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Service/BaseService.java @@ -2,12 +2,10 @@ import com.zhangzhihao.FileUpload.Java.Dao.BaseDao; +import com.zhangzhihao.FileUpload.Java.Dao.Query; import com.zhangzhihao.FileUpload.Java.Utils.PageResults; -import org.hibernate.criterion.Criterion; -import org.hibernate.criterion.Order; -import org.hibernate.criterion.Projection; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; import java.io.Serializable; import java.lang.reflect.ParameterizedType; @@ -32,40 +30,28 @@ public BaseService() { * 保存对象 * * @param model 需要添加的对象 - * @return 是否添加成功 */ - public Boolean save(T model) { - return baseDao.save(model); + public void save(@NotNull final T model) throws Exception { + baseDao.save(model); } /** - * 添加并且返回Integer类型的ID + * 批量保存对象 * - * @param model 需要添加的对象 - * @return Integer类型的ID - */ - public Integer saveAndGetIntegerID(T model) { - return (Integer) baseDao.saveAndGetIntegerID(model); - } - - /** - * 添加并且返回String类型的ID - * - * @param model 需要添加的对象 - * @return String类型的ID + * @param modelList 需要增加的对象的集合 + * 失败会抛异常 */ - public String saveAndGetStringID(T model) { - return (String) baseDao.saveAndGetStringID(model); + public void saveAll(@NotNull final List modelList) throws Exception { + baseDao.saveAll(modelList); } - /** * 删除对象 * * @param model 需要删除的对象 * 失败会抛异常 */ - public void delete(T model) { + public void delete(@NotNull final T model) throws Exception { baseDao.delete(model); } @@ -75,49 +61,47 @@ public void delete(T model) { * @param modelList 需要删除的对象的集合 * 失败会抛异常 */ - public void deleteAll(List modelList) { - modelList.stream().forEach(baseDao::delete); + public void deleteAll(@NotNull final List modelList) throws Exception { + baseDao.deleteAll(modelList); } /** * 按照id删除对象 * - * @param id 需要删除的对象的id - * 失败抛出异常 + * @param id 需要删除的对象的id + * 失败抛出异常 */ - public void deleteById(Serializable id) { - baseDao.delete(this.getById(id)); + public void deleteById(@NotNull final Serializable id) throws Exception { + baseDao.deleteById(modelClass, id); } /** - * 更新对象 + * 更新或保存对象 * * @param model 需要更新的对象 * 失败会抛出异常 */ - public void update(T model) { - baseDao.update(model); + public void saveOrUpdate(@NotNull final T model) throws Exception { + baseDao.saveOrUpdate(model); } - /** - * 添加或者更新 + * 批量更新或保存对象 * - * @param model 需要更新或添加的对象 - * 失败会抛出异常 + * @param modelList 需要更新或保存的对象 + * 失败会抛出异常 */ - public void saveOrUpdate(T model) { - baseDao.saveOrUpdate(model); + public void saveOrUpdateAll(@NotNull final List modelList) throws Exception { + baseDao.saveOrUpdateAll(modelList); } /** * 通过主键, 查询对象 * - * @param id 主键(Serializable) + * @param id 主键(Serializable) * @return model */ - @Transactional(readOnly = true) - public T getById(Serializable id) { + public T getById(@NotNull final Serializable id) throws Exception { return baseDao.getById(modelClass, id); } @@ -126,9 +110,8 @@ public T getById(Serializable id) { * * @return List */ - @Transactional(readOnly = true) - public List loadAll() { - return baseDao.loadAll(modelClass); + public List getAll() throws Exception { + return baseDao.getAll(modelClass); } @@ -139,58 +122,113 @@ public List loadAll() { * @param pageSize 每页数量 * @return 查询结果 */ - @Transactional(readOnly = true) - public List getListByPage(Integer currentPageNumber, Integer pageSize) { + public List getListByPage(@NotNull final Integer currentPageNumber, + @NotNull final Integer pageSize) + throws Exception { return baseDao.getListByPage(modelClass, currentPageNumber, pageSize); } - /** - * 按条件分页,条件以可变参形式传入,类型为Criterion [URL]http://zzk.cnblogs.com/s?t=b&w=Criteria + * 按条件分页 * * @param currentPageNumber 页码 * @param pageSize 每页数量 - * @param criterions 查询条件数组,由Restrictions对象生成,如Restrictions.like("name","%x%")等; - * @param orders 查询后记录的排序条件,由Order对象生成 - * @param projections 分组和聚合查询条件 + * @param query 封装的查询条件 * @return 查询结果 */ - @Transactional(readOnly = true) - public PageResults getListByPageAndRule(Integer currentPageNumber, Integer pageSize, final Criterion[] criterions, final Order[] orders, - final Projection[] projections) { - return baseDao.getListByPageAndRule(modelClass, currentPageNumber, pageSize, criterions, orders, projections); + public PageResults getListByPageAndQuery(@NotNull Integer currentPageNumber, + @NotNull Integer pageSize, + @NotNull Query query) + throws Exception { + return baseDao.getListByPageAndQuery(currentPageNumber, pageSize, query); } + /** + * 获得数量 利用Count(*)实现 + * + * @return 数量 + */ + public int getCount() throws Exception { + return baseDao.getCount(modelClass); + } /** * 获得符合对应条件的数量 利用Count(*)实现 * - * @param criterions 查询条件数组,由Restrictions对象生成,如Restrictions.like("name","%x%")等; + * @param query 查询条件 * @return 数量 */ - @Transactional(readOnly = true) - public int getCountByRule(final Criterion[] criterions) { - return baseDao.getCountByRule(modelClass, criterions); + public int getCountByQuery(@NotNull final Query query) throws Exception { + return baseDao.getCountByQuery(query); } - /** * 执行Sql语句 * - * @param sqlString sql - * @param values 不定参数数组 + * @param sql sql + * @param values 不定参数数组 + * @return 受影响的行数 + */ + public int executeSql(@NotNull final String sql, @NotNull final Object... values) + throws Exception { + return baseDao.executeSql(sql, values); + } + + /** + * 通过jpql查询 + * + * @param jpql jpql语句 + * @param values 参数列表 + * @return 受影响的行数 + */ + public Object queryByJpql(@NotNull final String jpql, @NotNull final Object... values) { + return baseDao.queryByJpql(jpql, values); + } + + /** + * 获得符合对应条件的数量 利用Count(*)实现 + * + * @param jpql jpql查询条件 + * @return 数量 + */ + public int getCountByJpql(@NotNull final String jpql, @NotNull final Object... values) { + return baseDao.getCountByJpql(jpql, values); + } + + + /** + * 通过Jpql分页查询 + * + * @param currentPageNumber 当前页 + * @param pageSize 每页数量 + * @param jpql jpql语句 + * @param values jpql参数 + * @return 查询结果 + */ + public PageResults getListByPageAndJpql(@NotNull Integer currentPageNumber, + @NotNull Integer pageSize, + @NotNull final String jpql, + @NotNull Object... values) { + return baseDao.getListByPageAndJpql(currentPageNumber, pageSize, jpql, values); + } + + /** + * 执行jpql语句 + * + * @param jpql jpql语句 + * @param values 参数列表 * @return 受影响的行数 */ - public int executeSql(String sqlString, Object... values) { - return baseDao.executeSql(sqlString, values); + public int executeJpql(@NotNull final String jpql, @NotNull final Object... values) { + return baseDao.executeJpql(jpql, values); } /** * refresh 刷新实体状态 * - * @param t 实体 + * @param model 实体 */ - public void refresh(T t) { - baseDao.refresh(t); + public void refresh(@NotNull T model) throws Exception { + baseDao.refresh(model); } } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java index c7d8653..edbd5f6 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java @@ -1,8 +1,27 @@ package com.zhangzhihao.FileUpload.Java.Service; +import com.zhangzhihao.FileUpload.Java.Dao.Query; import com.zhangzhihao.FileUpload.Java.Model.File; import org.springframework.stereotype.Service; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; + @Service public class FileService extends BaseService { + @PersistenceContext + private EntityManager entityManager; + + public boolean isMd5Exist(String md5) { + Query query = new Query(entityManager); + @SuppressWarnings("unchecked") List result = query.from(File.class) + .select() + .whereEqual("MD5", md5) + .createTypedQuery() + .getResultList(); + + return !result.isEmpty(); + + } } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java new file mode 100644 index 0000000..6227e0e --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java @@ -0,0 +1,26 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + +import org.jetbrains.annotations.NotNull; +import org.springframework.web.multipart.MultipartFile; + +import java.io.InputStream; +import java.security.MessageDigest; + + +public class CreateMd5 { + public static StringBuilder createMd5(@NotNull final MultipartFile file) + throws Exception { + StringBuilder sb = new StringBuilder(); + //生成MD5实例 + MessageDigest md5 = MessageDigest.getInstance("MD5"); + InputStream inputStream = file.getInputStream(); + int available = inputStream.available(); + byte[] bytes = new byte[available]; + md5.update(bytes); + for (byte by : md5.digest()) { + //将生成的字节MD5值转换成字符串 + sb.append(String.format("%02X", by)); + } + return sb; + } +} diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java new file mode 100644 index 0000000..0f79130 --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java @@ -0,0 +1,31 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + +import org.jetbrains.annotations.NotNull; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + + +public class DeepCopy { + /** + * 深拷贝 + * + * @return 深拷贝得到的新实例 + */ + public static Object deepClone(@NotNull final Object object) + throws Exception { + // 序列化 + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + + oos.writeObject(object); + + // 反序列化 + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + + return ois.readObject(); + } +} diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeleteFolder.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeleteFolder.java new file mode 100644 index 0000000..52e2945 --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeleteFolder.java @@ -0,0 +1,29 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + + +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +public class DeleteFolder { + /** + * 删除指定文件夹 + * @param folderPath 文件夹路径 + * @return 是否删除成功 + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + public static boolean deleteFolder(@NotNull final String folderPath) { + File dir = new File(folderPath); + File[] files = dir.listFiles(); + if(files!=null){ + for (File file : files) { + try { + file.delete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + return dir.delete(); + } +} diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java new file mode 100644 index 0000000..edb24e2 --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java @@ -0,0 +1,73 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + + +import com.zhangzhihao.FileUpload.Java.Model.UploadInfo; +import com.zhangzhihao.FileUpload.Java.Service.FileService; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static com.zhangzhihao.FileUpload.Java.Utils.MergeFile.mergeFile; + +public class IsAllUploaded { + + private final static List uploadInfoList = new ArrayList<>(); + + /** + * @param md5 + * @param chunks + * @return + */ + public static boolean isAllUploaded(@NotNull final String md5, + @NotNull final String chunks) { + int size = uploadInfoList.stream() + .filter(item -> item.getMd5().equals(md5)) + .distinct() + .collect(Collectors.toList()) + .size(); + boolean bool = (size == Integer.parseInt(chunks)); + if (bool) { + synchronized (uploadInfoList) { + uploadInfoList.removeIf(item -> Objects.equals(item.getMd5(), md5)); + } + } + return bool; + } + + /** + * @param md5 MD5 + * @param guid 随机生成的文件名 + * @param chunk 文件分块序号 + * @param chunks 文件分块数 + * @param fileName 文件名 + * @param ext 文件后缀名 + * @param fileService fileService + */ + public static void Uploaded(@NotNull final String md5, + @NotNull final String guid, + @NotNull final String chunk, + @NotNull final String chunks, + @NotNull final String uploadFolderPath, + @NotNull final String fileName, + @NotNull final String ext, + @NotNull final FileService fileService) + throws Exception { + synchronized (uploadInfoList) { + uploadInfoList.add(new UploadInfo(md5, chunks, chunk, uploadFolderPath, fileName, ext)); + } + boolean allUploaded = isAllUploaded(md5, chunks); + int chunksNumber = Integer.parseInt(chunks); + + if (allUploaded) { + mergeFile(chunksNumber, ext, guid, uploadFolderPath); + fileService.save(new com.zhangzhihao.FileUpload.Java.Model.File(guid + ext, md5, new Date())); + } + } +} + + + diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java new file mode 100644 index 0000000..a4dbe06 --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java @@ -0,0 +1,15 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; +import java.io.File; + +@SuppressWarnings("ConstantConditions") +public class IsImag { + public static boolean isImage(File tempFile) + throws Exception { + ImageInputStream is= ImageIO.createImageInputStream(tempFile); + return is!=null; + } +} diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java new file mode 100644 index 0000000..a2cf63a --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java @@ -0,0 +1,43 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + +import org.jetbrains.annotations.NotNull; + +import java.io.*; + +import static com.zhangzhihao.FileUpload.Java.Utils.DeleteFolder.deleteFolder; +import static com.zhangzhihao.FileUpload.Java.Utils.StreamUtil.saveStreamToFile; + + +public class MergeFile { + + /** + * @param chunksNumber + * @param ext + * @param guid + * @param uploadFolderPath + * @throws Exception + */ + public static void mergeFile(final int chunksNumber, + @NotNull final String ext, + @NotNull final String guid, + @NotNull final String uploadFolderPath) + throws Exception { + /*合并输入流*/ + String mergePath = uploadFolderPath + guid + "/"; + SequenceInputStream s ; + InputStream s1 = new FileInputStream(mergePath + 0 + ext); + InputStream s2 = new FileInputStream(mergePath + 1 + ext); + s = new SequenceInputStream(s1, s2); + for (int i = 2; i < chunksNumber; i++) { + InputStream s3 = new FileInputStream(mergePath + i + ext); + s = new SequenceInputStream(s, s3); + } + + //通过输出流向文件写入数据 + saveStreamToFile(s, uploadFolderPath + guid + ext); + + //删除保存分块文件的文件夹 + deleteFolder(mergePath); + + } +} diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java new file mode 100644 index 0000000..333639f --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java @@ -0,0 +1,130 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + +import com.zhangzhihao.FileUpload.Java.Controller.FileUploadController; +import org.jetbrains.annotations.NotNull; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + + +public class SaveFile { + private static final File uploadDirectory = new File(getRealPath()); + /** + * @param savePath + * @param fileFullName + * @param file + * @return + * @throws Exception + */ + public static boolean saveFile(@NotNull final String savePath, + @NotNull final String fileFullName, + @NotNull final MultipartFile file) + throws Exception { + byte[] data = readInputStream(file.getInputStream()); + //new一个文件对象用来保存图片,默认保存当前工程根目录 + File uploadFile = new File(savePath + fileFullName); + //判断文件夹是否存在,不存在就创建一个 + File fileDirectory = new File(savePath); + synchronized (uploadDirectory){ + if(!uploadDirectory.exists()){ + if(!uploadDirectory.mkdir()){ + throw new Exception("保存文件的父文件夹创建失败!路径为:" + savePath); + } + } + if (!fileDirectory.exists()) { + if (!fileDirectory.mkdir()) { + throw new Exception("文件夹创建失败!路径为:" + savePath); + } + } + } + + //创建输出流 + try (FileOutputStream outStream = new FileOutputStream(uploadFile)) {//写入数据 + outStream.write(data); + outStream.flush(); + + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + return uploadFile.exists(); + } + + public static byte[] readInputStream(InputStream inStream) throws Exception { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + //创建一个Buffer字符串 + byte[] buffer = new byte[1024]; + //每次读取的字符串长度,如果为-1,代表全部读取完毕 + int len; + //使用一个输入流从buffer里把数据读取出来 + while ((len = inStream.read(buffer)) != -1) { + //用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度 + outStream.write(buffer, 0, len); + } + //关闭输入流 + inStream.close(); + //把outStream里的数据写入内存 + return outStream.toByteArray(); + } + + /** + * 获得绝对路径(不带文件名) + * + * @return + */ + public static String getRealPath() { + String realPath; + String path = FileUploadController.class.getResource("/").getFile(); + int index = path.indexOf("build"); + realPath = path.substring(0, index) + "/src/main/webapp/upload/"; + realPath = realPath.replaceFirst("/", ""); + return realPath; + } + + + /** + * 根据文件路径获取File + * + * @param filePath + * @return + * @throws IOException + */ + public static java.io.File getFileByPath(String filePath) throws IOException { + Path path = Paths.get(getRealPath() + filePath); + if (Files.exists(path)) { + return new java.io.File(path.toUri()); + } + return null; + } + + /** + * 压缩文件 + * + * @param srcFileList + * @param zipFile + * @throws IOException + */ + public static void zipFiles(List srcFileList, java.io.File zipFile) throws IOException { + byte[] buf = new byte[1024]; + //ZipOutputStream类:完成文件或文件夹的压缩 + ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile)); + + for (java.io.File aSrcFileList : srcFileList) { + FileInputStream in = new FileInputStream(aSrcFileList); + out.putNextEntry(new ZipEntry(aSrcFileList.getName())); + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.closeEntry(); + in.close(); + } + out.close(); + } +} diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java new file mode 100644 index 0000000..c6a3dd1 --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java @@ -0,0 +1,38 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + + +import org.jetbrains.annotations.NotNull; + +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +public class StreamUtil { + /** + * 从stream中保存文件 + * + * @param inputStream inputStream + * @param filePath 保存路径 + * @throws Exception 异常 抛异常代表失败了 + */ + public static void saveStreamToFile(@NotNull final InputStream inputStream, + @NotNull final String filePath) + throws Exception { + /*创建输出流,写入数据,合并分块*/ + OutputStream outputStream = new FileOutputStream(filePath); + byte[] buffer = new byte[1024]; + int len = 0; + try { + while ((len = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + outputStream.flush(); + } + } catch (Exception e) { + e.printStackTrace(); + throw e; + } finally { + outputStream.close(); + inputStream.close(); + } + } +} diff --git a/src/main/resources/Hibernate.cfg.xml b/src/main/resources/Hibernate.cfg.xml deleted file mode 100644 index 9806501..0000000 --- a/src/main/resources/Hibernate.cfg.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - org.hibernate.dialect.MySQL5Dialect - - - - true - - true - - update - - - - 2 - - - true - - - - - - - true - - - org.hibernate.cache.ehcache.EhCacheRegionFactory - - - - true - - - - \ No newline at end of file diff --git a/src/main/resources/Spring.xml b/src/main/resources/Spring.xml index a17e0d4..d916dd0 100644 --- a/src/main/resources/Spring.xml +++ b/src/main/resources/Spring.xml @@ -2,15 +2,13 @@ + http://www.springframework.org/schema/tx/spring-tx.xsd"> + - - - + + + + - - - - + - org.hibernate.dialect.MySQL5InnoDBDialect + org.hibernate.dialect.MySQL5Dialect + true true update + true + org.hibernate.cache.SingletonEhCacheRegionFactory + + true + 2 + true + 20 + 1 + 2 + + 2000 + 2000 + + 10 + + 100 + + 30 - --> - - - - - - com.zhangzhihao.FileUpload.Java.Model - + + + - - - + + + - - - - - - - - - - - - - + + - - - \ No newline at end of file diff --git a/src/main/resources/SpringMVC.xml b/src/main/resources/SpringMVC.xml index 6df61b6..8786cdf 100644 --- a/src/main/resources/SpringMVC.xml +++ b/src/main/resources/SpringMVC.xml @@ -5,7 +5,7 @@ xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> - + @@ -32,7 +32,4 @@ - - - \ No newline at end of file diff --git a/src/main/resources/db.properties b/src/main/resources/db.properties index d931eba..065aab9 100644 --- a/src/main/resources/db.properties +++ b/src/main/resources/db.properties @@ -1,7 +1,7 @@ # ݿӲ jdbc.driverClass=com.mysql.jdbc.Driver -jdbc.connectionURL=jdbc:mysql://10.16.155.241:8081/FileUpload?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC +jdbc.connectionURL=jdbc:mysql:///FileUpload?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC jdbc.userName=root -jdbc.password=root +jdbc.password= jdbc.initPoolSize=5 jdbc.maxPoolSize=10 diff --git a/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp b/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp index 04edb43..d2d9209 100644 --- a/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp +++ b/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp @@ -7,10 +7,9 @@ - + - <%-- diff --git a/src/main/webapp/WEB-INF/views/FileUpload/Index.jsp b/src/main/webapp/WEB-INF/views/FileUpload/Index.jsp new file mode 100644 index 0000000..6577041 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/FileUpload/Index.jsp @@ -0,0 +1,170 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + 文件上传 + + + + + + + + + + + + +
+
+
+
+
+
+
+
+ 选择文件 + +
+
+ +
开始上传
+
+
+
+ + + diff --git a/src/main/webapp/WEB-INF/views/ImageUpload/Upload.jsp b/src/main/webapp/WEB-INF/views/ImageUpload/Upload.jsp new file mode 100644 index 0000000..a88d43d --- /dev/null +++ b/src/main/webapp/WEB-INF/views/ImageUpload/Upload.jsp @@ -0,0 +1,194 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + + 图片上传 + + + + + + + + + + +
+
+
+
+
+
+
+
+ 选择图片 + +
+
+ +
开始上传
+
+
+
+ + +<%--
--%> + <%--Username:--%> + <%----%> + <%----%> +<%--
--%> + + + + diff --git a/src/main/webapp/WEB-INF/views/MultiPicker/Index.jsp b/src/main/webapp/WEB-INF/views/MultiPicker/Index.jsp new file mode 100644 index 0000000..22fb050 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/MultiPicker/Index.jsp @@ -0,0 +1,233 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + + 多选择器多文件上传 + + + + + + + + + + +
+ +
+ +

提示

+

只能上传四个文件,只支持以下后缀名的文件doc,docx,pdf,zip,rar,7z。如果需要上传其他文件(如图片)请压缩后上传。请依次点击按钮上传指定文件,如果添加了错误的文件可以删除。

+
+ +
+
+
+ +
+
+
+
+
+
+ 选择研究报告 + +
+
+
+
+ +
+
+
+
+ 选择研究报告支撑材料(限PDF) + +
+
+
+
+ +
+
+
+
+ 选择应用报告 + +
+
+
+
+ +
+
+
+
+ 选择应用报告支撑材料(限PDF) + +
+
+
+
+ +
+
+
开始上传所选文件
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 1e2d979..9ee82f8 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -23,4 +23,21 @@ dispatcher / + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/app/BigFileUpload/Index/BigFileUpload.js b/src/main/webapp/app/BigFileUpload/Index/BigFileUpload.js deleted file mode 100644 index 58c8ea5..0000000 --- a/src/main/webapp/app/BigFileUpload/Index/BigFileUpload.js +++ /dev/null @@ -1,189 +0,0 @@ -$(function () { - $list = $('#fileList'); - var uploader = WebUploader.create({ - - - //设置选完文件后是否自动上传 - auto: false, - - - //swf文件路径 - //swf: BASE_URL + '~/FileUpload/Uploader.swf', - swf: '/assets/Uploader.swf', - - // 文件接收服务端。 - server: '/BigFileUpload/BigFileUp', - - // 选择文件的按钮。可选。 - // 内部根据当前运行是创建,可能是input元素,也可能是flash. - pick: '#picker', - - chunked: true, //开启分块上传 - chunkSize: 10 * 1024 * 1024, - chunkRetry: 3,//网络问题上传失败后重试次数 - threads: 5, //上传并发数 - //formData: { guid: WebUploader.guid() }, //一个文件有一个guid,在服务器端合并成一个文件 这里有个问题,多个文件或者上传一个文件成功后不刷新直接添加文件上传生成的guid不变!!! 暂时只能传一个大文件(已解决) - //fileNumLimit :1, - fileSizeLimit: 2000 * 1024 * 1024,//最大2GB - fileSingleSizeLImit: 2000 * 1024 * 1024, - - - resize: false//不压缩 - - //选择文件类型 - //accept: { - // title: 'Images', - // extensions: 'gif,jpg,jpeg,bmp,png', - // mimeTypes: 'image/*' - //} - }); - - - - - // 当有文件被添加进队列的时候 - uploader.on('fileQueued', function (file) { - $list.append('
' + - '

' + file.name + '

' + - '

等待上传...

' + - '
');//id="' + file.id + 'btn" - //删除要上传的文件 - //每次添加文件都给btn-delete绑定删除方法 - $(".btn-delete").click(function () { - //console.log($(this).attr("fileId"));//拿到文件id - uploader.removeFile(uploader.getFile($(this).attr("fileId"), true)); - $(this).parent().parent().fadeOut();//视觉上消失了 - $(this).parent().parent().remove();//DOM上删除了 - }); - - uploader.options.formData.guid = WebUploader.guid();//每个文件都附带一个guid,以在服务端确定哪些文件块本来是一个 - - uploader.md5File(file) - .then(function (fileMd5) { // 完成 - //var end = +new Date(); - // console.log("before-send-file preupload: file.size="+file.size+" file.md5="+fileMd5); - //insertLog("
" + moment().format("YYYY-MM-DD HH:mm:ss") + " before-send-file preupload:计算文件(" + file.name + ")MD5完成. 耗时 " + (end - start) + '毫秒 fileMd5: ' + fileMd5); - file.wholeMd5 = fileMd5;//获取到了md5 - uploader.options.formData.md5value = file.wholeMd5;//每个文件都附带一个md5,便于实现秒传 - $.ajax({//向服务端发送请求 - cache: false, - type: "post", - dataType: "json", - url: "/BigFileUpload/IsMD5Exist",//baseUrl + - data: { - fileMd5: fileMd5, - fileName: file.name, - fileID: file.id, - }, - success: function (result) { - console.log(result); - if (result == "this file is exist") { - console.log("服务器上已经有同样的文件了,开始秒传!"); - - uploader.removeFile(file, true); - - $('#' + file.id).find('p.state').text('已上传'); - $('#' + file.id).find(".progress").find(".progress-bar").attr("class", "progress-bar progress-bar-success"); - $('#' + file.id).find(".info").find('.btn').fadeOut('slow');//上传完后删除"删除"按钮 - $("#StopBtn").fadeOut('slow'); - } else { - console.log("服务器上没有同样的文件,秒传失败!"); - } - } - }); - }); - - }); - - // 文件上传过程中创建进度条实时显示。 - uploader.on('uploadProgress', function (file, percentage) { - var $li = $('#' + file.id), - $percent = $li.find('.progress .progress-bar'); - - // 避免重复创建 - if (!$percent.length) { - $percent = $('
' + - '
' + - '
' + - '
').appendTo($li).find('.progress-bar'); - } - - $li.find('p.state').text('上传中'); - - $percent.css('width', percentage * 100 + '%'); - }); - - uploader.on('uploadSuccess', function (file) { - $('#' + file.id).find('p.state').text('已上传'); - $('#' + file.id).find(".progress").find(".progress-bar").attr("class", "progress-bar progress-bar-success"); - $('#' + file.id).find(".info").find('.btn').fadeOut('slow');//上传完后删除"删除"按钮 - $('#StopBtn').fadeOut('slow'); - }); - - uploader.on('uploadError', function (file) { - $('#' + file.id).find('p.state').text('上传出错'); - //上传出错后进度条爆红 - $('#' + file.id).find(".progress").find(".progress-bar").attr("class", "progress-bar progress-bar-danger"); - if ($('#' + file.id).find(".btn-retry").length < 1) { - var btn = $(''); - $('#' + file.id).find(".info").append(btn); - } - - - - $(".btn-retry").click(function () { - uploader.retry(uploader.getFile($(this).attr("fileId"))); - - }); - }); - - uploader.on('uploadComplete', function (file) {//上传完成后回调 - }); - - uploader.on('uploadFinished', function () { - //上传完后的回调方法 - //alert("所有文件上传完毕"); - //提交表单 - }); - - $("#UploadBtn").click(function () { - uploader.upload();//上传 - }); - - $("#StopBtn").click(function () { - console.log($('#StopBtn').attr("status")); - var status = $('#StopBtn').attr("status"); - if (status == "suspend") { - console.log("当前按钮是暂停,即将变为继续"); - $("#StopBtn").html("继续上传"); - $("#StopBtn").attr("status", "continuous"); - console.log("__________________当前所有的文件_______________________"); - console.log(uploader.getFiles()); - console.log("__________________暂停上传_____________________________"); - uploader.stop(true); - console.log("__________________所有当前暂停的文件___________________"); - console.log(uploader.getFiles("interrupt")); - } else { - console.log("当前按钮是继续,即将变为暂停"); - $("#StopBtn").html("暂停上传"); - $("#StopBtn").attr("status", "suspend"); - - console.log("__________________所有当前暂停的文件___________________"); - console.log(uploader.getFiles("interrupt")); - uploader.upload(uploader.getFiles("interrupt")); - } - }); - - - - - uploader.on('uploadAccept', function (file, response) { - if (response._raw == '{"error":true}') { - return false; - } - - }); -}); - - - diff --git a/src/main/webapp/assets/webuploader.nolog.js b/src/main/webapp/assets/webuploader.nolog.js deleted file mode 100644 index 294e9e0..0000000 --- a/src/main/webapp/assets/webuploader.nolog.js +++ /dev/null @@ -1,8012 +0,0 @@ -/*! WebUploader 0.1.5 */ - - -/** - * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 - * - * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 - */ -(function( root, factory ) { - var modules = {}, - - // 内部require, 简单不完全实现。 - // https://github.com/amdjs/amdjs-api/wiki/require - _require = function( deps, callback ) { - var args, len, i; - - // 如果deps不是数组,则直接返回指定module - if ( typeof deps === 'string' ) { - return getModule( deps ); - } else { - args = []; - for( len = deps.length, i = 0; i < len; i++ ) { - args.push( getModule( deps[ i ] ) ); - } - - return callback.apply( null, args ); - } - }, - - // 内部define,暂时不支持不指定id. - _define = function( id, deps, factory ) { - if ( arguments.length === 2 ) { - factory = deps; - deps = null; - } - - _require( deps || [], function() { - setModule( id, factory, arguments ); - }); - }, - - // 设置module, 兼容CommonJs写法。 - setModule = function( id, factory, args ) { - var module = { - exports: factory - }, - returned; - - if ( typeof factory === 'function' ) { - args.length || (args = [ _require, module.exports, module ]); - returned = factory.apply( null, args ); - returned !== undefined && (module.exports = returned); - } - - modules[ id ] = module.exports; - }, - - // 根据id获取module - getModule = function( id ) { - var module = modules[ id ] || root[ id ]; - - if ( !module ) { - throw new Error( '`' + id + '` is undefined' ); - } - - return module; - }, - - // 将所有modules,将路径ids装换成对象。 - exportsTo = function( obj ) { - var key, host, parts, part, last, ucFirst; - - // make the first character upper case. - ucFirst = function( str ) { - return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); - }; - - for ( key in modules ) { - host = obj; - - if ( !modules.hasOwnProperty( key ) ) { - continue; - } - - parts = key.split('/'); - last = ucFirst( parts.pop() ); - - while( (part = ucFirst( parts.shift() )) ) { - host[ part ] = host[ part ] || {}; - host = host[ part ]; - } - - host[ last ] = modules[ key ]; - } - - return obj; - }, - - makeExport = function( dollar ) { - root.__dollar = dollar; - - // exports every module. - return exportsTo( factory( root, _define, _require ) ); - }, - - origin; - - if ( typeof module === 'object' && typeof module.exports === 'object' ) { - - // For CommonJS and CommonJS-like environments where a proper window is present, - module.exports = makeExport(); - } else if ( typeof define === 'function' && define.amd ) { - - // Allow using this built library as an AMD module - // in another project. That other project will only - // see this AMD call, not the internal modules in - // the closure below. - define([ 'jquery' ], makeExport ); - } else { - - // Browser globals case. Just assign the - // result to a property on the global. - origin = root.WebUploader; - root.WebUploader = makeExport(); - root.WebUploader.noConflict = function() { - root.WebUploader = origin; - }; - } -})( window, function( window, define, require ) { - - - /** - * @fileOverview jQuery or Zepto - */ - define('dollar-third',[],function() { - var $ = window.__dollar || window.jQuery || window.Zepto; - - if ( !$ ) { - throw new Error('jQuery or Zepto not found!'); - } - - return $; - }); - /** - * @fileOverview Dom 操作相关 - */ - define('dollar',[ - 'dollar-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 使用jQuery的Promise - */ - define('promise-third',[ - 'dollar' - ], function( $ ) { - return { - Deferred: $.Deferred, - when: $.when, - - isPromise: function( anything ) { - return anything && typeof anything.then === 'function'; - } - }; - }); - /** - * @fileOverview Promise/A+ - */ - define('promise',[ - 'promise-third' - ], function( _ ) { - return _; - }); - /** - * @fileOverview 基础类方法。 - */ - - /** - * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 - * - * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. - * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: - * - * * module `base`:WebUploader.Base - * * module `file`: WebUploader.File - * * module `lib/dnd`: WebUploader.Lib.Dnd - * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd - * - * - * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 - * @module WebUploader - * @title WebUploader API文档 - */ - define('base',[ - 'dollar', - 'promise' - ], function( $, promise ) { - - var noop = function() {}, - call = Function.call; - - // http://jsperf.com/uncurrythis - // 反科里化 - function uncurryThis( fn ) { - return function() { - return call.apply( fn, arguments ); - }; - } - - function bindFn( fn, context ) { - return function() { - return fn.apply( context, arguments ); - }; - } - - function createObject( proto ) { - var f; - - if ( Object.create ) { - return Object.create( proto ); - } else { - f = function() {}; - f.prototype = proto; - return new f(); - } - } - - - /** - * 基础类,提供一些简单常用的方法。 - * @class Base - */ - return { - - /** - * @property {String} version 当前版本号。 - */ - version: '0.1.5', - - /** - * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 - */ - $: $, - - Deferred: promise.Deferred, - - isPromise: promise.isPromise, - - when: promise.when, - - /** - * @description 简单的浏览器检查结果。 - * - * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 - * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 - * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** - * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 - * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 - * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 - * - * @property {Object} [browser] - */ - browser: (function( ua ) { - var ret = {}, - webkit = ua.match( /WebKit\/([\d.]+)/ ), - chrome = ua.match( /Chrome\/([\d.]+)/ ) || - ua.match( /CriOS\/([\d.]+)/ ), - - ie = ua.match( /MSIE\s([\d\.]+)/ ) || - ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), - firefox = ua.match( /Firefox\/([\d.]+)/ ), - safari = ua.match( /Safari\/([\d.]+)/ ), - opera = ua.match( /OPR\/([\d.]+)/ ); - - webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); - chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); - ie && (ret.ie = parseFloat( ie[ 1 ] )); - firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); - safari && (ret.safari = parseFloat( safari[ 1 ] )); - opera && (ret.opera = parseFloat( opera[ 1 ] )); - - return ret; - })( navigator.userAgent ), - - /** - * @description 操作系统检查结果。 - * - * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 - * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 - * @property {Object} [os] - */ - os: (function( ua ) { - var ret = {}, - - // osx = !!ua.match( /\(Macintosh\; Intel / ), - android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), - ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); - - // osx && (ret.osx = true); - android && (ret.android = parseFloat( android[ 1 ] )); - ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); - - return ret; - })( navigator.userAgent ), - - /** - * 实现类与类之间的继承。 - * @method inherits - * @grammar Base.inherits( super ) => child - * @grammar Base.inherits( super, protos ) => child - * @grammar Base.inherits( super, protos, statics ) => child - * @param {Class} super 父类 - * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 - * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 - * @param {Object} [statics] 静态属性或方法。 - * @return {Class} 返回子类。 - * @example - * function Person() { - * console.log( 'Super' ); - * } - * Person.prototype.hello = function() { - * console.log( 'hello' ); - * }; - * - * var Manager = Base.inherits( Person, { - * world: function() { - * console.log( 'World' ); - * } - * }); - * - * // 因为没有指定构造器,父类的构造器将会执行。 - * var instance = new Manager(); // => Super - * - * // 继承子父类的方法 - * instance.hello(); // => hello - * instance.world(); // => World - * - * // 子类的__super__属性指向父类 - * console.log( Manager.__super__ === Person ); // => true - */ - inherits: function( Super, protos, staticProtos ) { - var child; - - if ( typeof protos === 'function' ) { - child = protos; - protos = null; - } else if ( protos && protos.hasOwnProperty('constructor') ) { - child = protos.constructor; - } else { - child = function() { - return Super.apply( this, arguments ); - }; - } - - // 复制静态方法 - $.extend( true, child, Super, staticProtos || {} ); - - /* jshint camelcase: false */ - - // 让子类的__super__属性指向父类。 - child.__super__ = Super.prototype; - - // 构建原型,添加原型方法或属性。 - // 暂时用Object.create实现。 - child.prototype = createObject( Super.prototype ); - protos && $.extend( true, child.prototype, protos ); - - return child; - }, - - /** - * 一个不做任何事情的方法。可以用来赋值给默认的callback. - * @method noop - */ - noop: noop, - - /** - * 返回一个新的方法,此方法将已指定的`context`来执行。 - * @grammar Base.bindFn( fn, context ) => Function - * @method bindFn - * @example - * var doSomething = function() { - * console.log( this.name ); - * }, - * obj = { - * name: 'Object Name' - * }, - * aliasFn = Base.bind( doSomething, obj ); - * - * aliasFn(); // => Object Name - * - */ - bindFn: bindFn, - - /** - * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 - * @grammar Base.log( args... ) => undefined - * @method log - */ - log: (function() { - if ( window.console ) { - return bindFn( console.log, console ); - } - return noop; - })(), - - nextTick: (function() { - - return function( cb ) { - setTimeout( cb, 1 ); - }; - - // @bug 当浏览器不在当前窗口时就停了。 - // var next = window.requestAnimationFrame || - // window.webkitRequestAnimationFrame || - // window.mozRequestAnimationFrame || - // function( cb ) { - // window.setTimeout( cb, 1000 / 60 ); - // }; - - // // fix: Uncaught TypeError: Illegal invocation - // return bindFn( next, window ); - })(), - - /** - * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 - * 将用来将非数组对象转化成数组对象。 - * @grammar Base.slice( target, start[, end] ) => Array - * @method slice - * @example - * function doSomthing() { - * var args = Base.slice( arguments, 1 ); - * console.log( args ); - * } - * - * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] - */ - slice: uncurryThis( [].slice ), - - /** - * 生成唯一的ID - * @method guid - * @grammar Base.guid() => String - * @grammar Base.guid( prefx ) => String - */ - guid: (function() { - var counter = 0; - - return function( prefix ) { - var guid = (+new Date()).toString( 32 ), - i = 0; - - for ( ; i < 5; i++ ) { - guid += Math.floor( Math.random() * 65535 ).toString( 32 ); - } - - return (prefix || 'wu_') + guid + (counter++).toString( 32 ); - }; - })(), - - /** - * 格式化文件大小, 输出成带单位的字符串 - * @method formatSize - * @grammar Base.formatSize( size ) => String - * @grammar Base.formatSize( size, pointLength ) => String - * @grammar Base.formatSize( size, pointLength, units ) => String - * @param {Number} size 文件大小 - * @param {Number} [pointLength=2] 精确到的小数点数。 - * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. - * @example - * console.log( Base.formatSize( 100 ) ); // => 100B - * console.log( Base.formatSize( 1024 ) ); // => 1.00K - * console.log( Base.formatSize( 1024, 0 ) ); // => 1K - * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M - * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G - * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB - */ - formatSize: function( size, pointLength, units ) { - var unit; - - units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; - - while ( (unit = units.shift()) && size > 1024 ) { - size = size / 1024; - } - - return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + - unit; - } - }; - }); - /** - * 事件处理类,可以独立使用,也可以扩展给对象使用。 - * @fileOverview Mediator - */ - define('mediator',[ - 'base' - ], function( Base ) { - var $ = Base.$, - slice = [].slice, - separator = /\s+/, - protos; - - // 根据条件过滤出事件handlers. - function findHandlers( arr, name, callback, context ) { - return $.grep( arr, function( handler ) { - return handler && - (!name || handler.e === name) && - (!callback || handler.cb === callback || - handler.cb._cb === callback) && - (!context || handler.ctx === context); - }); - } - - function eachEvent( events, callback, iterator ) { - // 不支持对象,只支持多个event用空格隔开 - $.each( (events || '').split( separator ), function( _, key ) { - iterator( key, callback ); - }); - } - - function triggerHanders( events, args ) { - var stoped = false, - i = -1, - len = events.length, - handler; - - while ( ++i < len ) { - handler = events[ i ]; - - if ( handler.cb.apply( handler.ctx2, args ) === false ) { - stoped = true; - break; - } - } - - return !stoped; - } - - protos = { - - /** - * 绑定事件。 - * - * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 - * ```javascript - * var obj = {}; - * - * // 使得obj有事件行为 - * Mediator.installTo( obj ); - * - * obj.on( 'testa', function( arg1, arg2 ) { - * console.log( arg1, arg2 ); // => 'arg1', 'arg2' - * }); - * - * obj.trigger( 'testa', 'arg1', 'arg2' ); - * ``` - * - * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 - * 切会影响到`trigger`方法的返回值,为`false`。 - * - * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, - * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 - * ```javascript - * obj.on( 'all', function( type, arg1, arg2 ) { - * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' - * }); - * ``` - * - * @method on - * @grammar on( name, callback[, context] ) => self - * @param {String} name 事件名,支持多个事件用空格隔开 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - * @class Mediator - */ - on: function( name, callback, context ) { - var me = this, - set; - - if ( !callback ) { - return this; - } - - set = this._events || (this._events = []); - - eachEvent( name, callback, function( name, callback ) { - var handler = { e: name }; - - handler.cb = callback; - handler.ctx = context; - handler.ctx2 = context || me; - handler.id = set.length; - - set.push( handler ); - }); - - return this; - }, - - /** - * 绑定事件,且当handler执行完后,自动解除绑定。 - * @method once - * @grammar once( name, callback[, context] ) => self - * @param {String} name 事件名 - * @param {Function} callback 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - once: function( name, callback, context ) { - var me = this; - - if ( !callback ) { - return me; - } - - eachEvent( name, callback, function( name, callback ) { - var once = function() { - me.off( name, once ); - return callback.apply( context || me, arguments ); - }; - - once._cb = callback; - me.on( name, once, context ); - }); - - return me; - }, - - /** - * 解除事件绑定 - * @method off - * @grammar off( [name[, callback[, context] ] ] ) => self - * @param {String} [name] 事件名 - * @param {Function} [callback] 事件处理器 - * @param {Object} [context] 事件处理器的上下文。 - * @return {self} 返回自身,方便链式 - * @chainable - */ - off: function( name, cb, ctx ) { - var events = this._events; - - if ( !events ) { - return this; - } - - if ( !name && !cb && !ctx ) { - this._events = []; - return this; - } - - eachEvent( name, cb, function( name, cb ) { - $.each( findHandlers( events, name, cb, ctx ), function() { - delete events[ this.id ]; - }); - }); - - return this; - }, - - /** - * 触发事件 - * @method trigger - * @grammar trigger( name[, args...] ) => self - * @param {String} type 事件名 - * @param {*} [...] 任意参数 - * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true - */ - trigger: function( type ) { - var args, events, allEvents; - - if ( !this._events || !type ) { - return this; - } - - args = slice.call( arguments, 1 ); - events = findHandlers( this._events, type ); - allEvents = findHandlers( this._events, 'all' ); - - return triggerHanders( events, args ) && - triggerHanders( allEvents, arguments ); - } - }; - - /** - * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 - * 主要目的是负责模块与模块之间的合作,降低耦合度。 - * - * @class Mediator - */ - return $.extend({ - - /** - * 可以通过这个接口,使任何对象具备事件功能。 - * @method installTo - * @param {Object} obj 需要具备事件行为的对象。 - * @return {Object} 返回obj. - */ - installTo: function( obj ) { - return $.extend( obj, protos ); - } - - }, protos ); - }); - /** - * @fileOverview Uploader上传类 - */ - define('uploader',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$; - - /** - * 上传入口类。 - * @class Uploader - * @constructor - * @grammar new Uploader( opts ) => Uploader - * @example - * var uploader = WebUploader.Uploader({ - * swf: 'path_of_swf/Uploader.swf', - * - * // 开起分片上传。 - * chunked: true - * }); - */ - function Uploader( opts ) { - this.options = $.extend( true, {}, Uploader.options, opts ); - this._init( this.options ); - } - - // default Options - // widgets中有相应扩展 - Uploader.options = {}; - Mediator.installTo( Uploader.prototype ); - - // 批量添加纯命令式方法。 - $.each({ - upload: 'start-upload', - stop: 'stop-upload', - getFile: 'get-file', - getFiles: 'get-files', - addFile: 'add-file', - addFiles: 'add-file', - sort: 'sort-files', - removeFile: 'remove-file', - cancelFile: 'cancel-file', - skipFile: 'skip-file', - retry: 'retry', - isInProgress: 'is-in-progress', - makeThumb: 'make-thumb', - md5File: 'md5-file', - getDimension: 'get-dimension', - addButton: 'add-btn', - predictRuntimeType: 'predict-runtime-type', - refresh: 'refresh', - disable: 'disable', - enable: 'enable', - reset: 'reset' - }, function( fn, command ) { - Uploader.prototype[ fn ] = function() { - return this.request( command, arguments ); - }; - }); - - $.extend( Uploader.prototype, { - state: 'pending', - - _init: function( opts ) { - var me = this; - - me.request( 'init', opts, function() { - me.state = 'ready'; - me.trigger('ready'); - }); - }, - - /** - * 获取或者设置Uploader配置项。 - * @method option - * @grammar option( key ) => * - * @grammar option( key, val ) => self - * @example - * - * // 初始状态图片上传前不会压缩 - * var uploader = new WebUploader.Uploader({ - * compress: null; - * }); - * - * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 - * uploader.option( 'compress', { - * width: 1600, - * height: 1600 - * }); - */ - option: function( key, val ) { - var opts = this.options; - - // setter - if ( arguments.length > 1 ) { - - if ( $.isPlainObject( val ) && - $.isPlainObject( opts[ key ] ) ) { - $.extend( opts[ key ], val ); - } else { - opts[ key ] = val; - } - - } else { // getter - return key ? opts[ key ] : opts; - } - }, - - /** - * 获取文件统计信息。返回一个包含一下信息的对象。 - * * `successNum` 上传成功的文件数 - * * `progressNum` 上传中的文件数 - * * `cancelNum` 被删除的文件数 - * * `invalidNum` 无效的文件数 - * * `uploadFailNum` 上传失败的文件数 - * * `queueNum` 还在队列中的文件数 - * * `interruptNum` 被暂停的文件数 - * @method getStats - * @grammar getStats() => Object - */ - getStats: function() { - // return this._mgr.getStats.apply( this._mgr, arguments ); - var stats = this.request('get-stats'); - - return stats ? { - successNum: stats.numOfSuccess, - progressNum: stats.numOfProgress, - - // who care? - // queueFailNum: 0, - cancelNum: stats.numOfCancel, - invalidNum: stats.numOfInvalid, - uploadFailNum: stats.numOfUploadFailed, - queueNum: stats.numOfQueue, - interruptNum: stats.numofInterrupt - } : {}; - }, - - // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 - trigger: function( type/*, args...*/ ) { - var args = [].slice.call( arguments, 1 ), - opts = this.options, - name = 'on' + type.substring( 0, 1 ).toUpperCase() + - type.substring( 1 ); - - if ( - // 调用通过on方法注册的handler. - Mediator.trigger.apply( this, arguments ) === false || - - // 调用opts.onEvent - $.isFunction( opts[ name ] ) && - opts[ name ].apply( this, args ) === false || - - // 调用this.onEvent - $.isFunction( this[ name ] ) && - this[ name ].apply( this, args ) === false || - - // 广播所有uploader的事件。 - Mediator.trigger.apply( Mediator, - [ this, type ].concat( args ) ) === false ) { - - return false; - } - - return true; - }, - - /** - * 销毁 webuploader 实例 - * @method destroy - * @grammar destroy() => undefined - */ - destroy: function() { - this.request( 'destroy', arguments ); - this.off(); - }, - - // widgets/widget.js将补充此方法的详细文档。 - request: Base.noop - }); - - /** - * 创建Uploader实例,等同于new Uploader( opts ); - * @method create - * @class Base - * @static - * @grammar Base.create( opts ) => Uploader - */ - Base.create = Uploader.create = function( opts ) { - return new Uploader( opts ); - }; - - // 暴露Uploader,可以通过它来扩展业务逻辑。 - Base.Uploader = Uploader; - - return Uploader; - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/runtime',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - factories = {}, - - // 获取对象的第一个key - getFirstKey = function( obj ) { - for ( var key in obj ) { - if ( obj.hasOwnProperty( key ) ) { - return key; - } - } - return null; - }; - - // 接口类。 - function Runtime( options ) { - this.options = $.extend({ - container: document.body - }, options ); - this.uid = Base.guid('rt_'); - } - - $.extend( Runtime.prototype, { - - getContainer: function() { - var opts = this.options, - parent, container; - - if ( this._container ) { - return this._container; - } - - parent = $( opts.container || document.body ); - container = $( document.createElement('div') ); - - container.attr( 'id', 'rt_' + this.uid ); - container.css({ - position: 'absolute', - top: '0px', - left: '0px', - width: '1px', - height: '1px', - overflow: 'hidden' - }); - - parent.append( container ); - parent.addClass('webuploader-container'); - this._container = container; - this._parent = parent; - return container; - }, - - init: Base.noop, - exec: Base.noop, - - destroy: function() { - this._container && this._container.remove(); - this._parent && this._parent.removeClass('webuploader-container'); - this.off(); - } - }); - - Runtime.orders = 'html5,flash'; - - - /** - * 添加Runtime实现。 - * @param {String} type 类型 - * @param {Runtime} factory 具体Runtime实现。 - */ - Runtime.addRuntime = function( type, factory ) { - factories[ type ] = factory; - }; - - Runtime.hasRuntime = function( type ) { - return !!(type ? factories[ type ] : getFirstKey( factories )); - }; - - Runtime.create = function( opts, orders ) { - var type, runtime; - - orders = orders || Runtime.orders; - $.each( orders.split( /\s*,\s*/g ), function() { - if ( factories[ this ] ) { - type = this; - return false; - } - }); - - type = type || getFirstKey( factories ); - - if ( !type ) { - throw new Error('Runtime Error'); - } - - runtime = new factories[ type ]( opts ); - return runtime; - }; - - Mediator.installTo( Runtime.prototype ); - return Runtime; - }); - - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/client',[ - 'base', - 'mediator', - 'runtime/runtime' - ], function( Base, Mediator, Runtime ) { - - var cache; - - cache = (function() { - var obj = {}; - - return { - add: function( runtime ) { - obj[ runtime.uid ] = runtime; - }, - - get: function( ruid, standalone ) { - var i; - - if ( ruid ) { - return obj[ ruid ]; - } - - for ( i in obj ) { - // 有些类型不能重用,比如filepicker. - if ( standalone && obj[ i ].__standalone ) { - continue; - } - - return obj[ i ]; - } - - return null; - }, - - remove: function( runtime ) { - delete obj[ runtime.uid ]; - } - }; - })(); - - function RuntimeClient( component, standalone ) { - var deferred = Base.Deferred(), - runtime; - - this.uid = Base.guid('client_'); - - // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 - this.runtimeReady = function( cb ) { - return deferred.done( cb ); - }; - - this.connectRuntime = function( opts, cb ) { - - // already connected. - if ( runtime ) { - throw new Error('already connected!'); - } - - deferred.done( cb ); - - if ( typeof opts === 'string' && cache.get( opts ) ) { - runtime = cache.get( opts ); - } - - // 像filePicker只能独立存在,不能公用。 - runtime = runtime || cache.get( null, standalone ); - - // 需要创建 - if ( !runtime ) { - runtime = Runtime.create( opts, opts.runtimeOrder ); - runtime.__promise = deferred.promise(); - runtime.once( 'ready', deferred.resolve ); - runtime.init(); - cache.add( runtime ); - runtime.__client = 1; - } else { - // 来自cache - Base.$.extend( runtime.options, opts ); - runtime.__promise.then( deferred.resolve ); - runtime.__client++; - } - - standalone && (runtime.__standalone = standalone); - return runtime; - }; - - this.getRuntime = function() { - return runtime; - }; - - this.disconnectRuntime = function() { - if ( !runtime ) { - return; - } - - runtime.__client--; - - if ( runtime.__client <= 0 ) { - cache.remove( runtime ); - delete runtime.__promise; - runtime.destroy(); - } - - runtime = null; - }; - - this.exec = function() { - if ( !runtime ) { - return; - } - - var args = Base.slice( arguments ); - component && args.unshift( component ); - - return runtime.exec.apply( this, args ); - }; - - this.getRuid = function() { - return runtime && runtime.uid; - }; - - this.destroy = (function( destroy ) { - return function() { - destroy && destroy.apply( this, arguments ); - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }; - })( this.destroy ); - } - - Mediator.installTo( RuntimeClient.prototype ); - return RuntimeClient; - }); - /** - * @fileOverview 错误信息 - */ - define('lib/dnd',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function DragAndDrop( opts ) { - opts = this.options = $.extend({}, DragAndDrop.options, opts ); - - opts.container = $( opts.container ); - - if ( !opts.container.length ) { - return; - } - - RuntimeClent.call( this, 'DragAndDrop' ); - } - - DragAndDrop.options = { - accept: null, - disableGlobalDnd: false - }; - - Base.inherits( RuntimeClent, { - constructor: DragAndDrop, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( DragAndDrop.prototype ); - - return DragAndDrop; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/widget',[ - 'base', - 'uploader' - ], function( Base, Uploader ) { - - var $ = Base.$, - _init = Uploader.prototype._init, - _destroy = Uploader.prototype.destroy, - IGNORE = {}, - widgetClass = []; - - function isArrayLike( obj ) { - if ( !obj ) { - return false; - } - - var length = obj.length, - type = $.type( obj ); - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === 'array' || type !== 'function' && type !== 'string' && - (length === 0 || typeof length === 'number' && length > 0 && - (length - 1) in obj); - } - - function Widget( uploader ) { - this.owner = uploader; - this.options = uploader.options; - } - - $.extend( Widget.prototype, { - - init: Base.noop, - - // 类Backbone的事件监听声明,监听uploader实例上的事件 - // widget直接无法监听事件,事件只能通过uploader来传递 - invoke: function( apiName, args ) { - - /* - { - 'make-thumb': 'makeThumb' - } - */ - var map = this.responseMap; - - // 如果无API响应声明则忽略 - if ( !map || !(apiName in map) || !(map[ apiName ] in this) || - !$.isFunction( this[ map[ apiName ] ] ) ) { - - return IGNORE; - } - - return this[ map[ apiName ] ].apply( this, args ); - - }, - - /** - * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 - * @method request - * @grammar request( command, args ) => * | Promise - * @grammar request( command, args, callback ) => Promise - * @for Uploader - */ - request: function() { - return this.owner.request.apply( this.owner, arguments ); - } - }); - - // 扩展Uploader. - $.extend( Uploader.prototype, { - - /** - * @property {String | Array} [disableWidgets=undefined] - * @namespace options - * @for Uploader - * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 - */ - - // 覆写_init用来初始化widgets - _init: function() { - var me = this, - widgets = me._widgets = [], - deactives = me.options.disableWidgets || ''; - - $.each( widgetClass, function( _, klass ) { - (!deactives || !~deactives.indexOf( klass._name )) && - widgets.push( new klass( me ) ); - }); - - return _init.apply( me, arguments ); - }, - - request: function( apiName, args, callback ) { - var i = 0, - widgets = this._widgets, - len = widgets && widgets.length, - rlts = [], - dfds = [], - widget, rlt, promise, key; - - args = isArrayLike( args ) ? args : [ args ]; - - for ( ; i < len; i++ ) { - widget = widgets[ i ]; - rlt = widget.invoke( apiName, args ); - - if ( rlt !== IGNORE ) { - - // Deferred对象 - if ( Base.isPromise( rlt ) ) { - dfds.push( rlt ); - } else { - rlts.push( rlt ); - } - } - } - - // 如果有callback,则用异步方式。 - if ( callback || dfds.length ) { - promise = Base.when.apply( Base, dfds ); - key = promise.pipe ? 'pipe' : 'then'; - - // 很重要不能删除。删除了会死循环。 - // 保证执行顺序。让callback总是在下一个 tick 中执行。 - return promise[ key ](function() { - var deferred = Base.Deferred(), - args = arguments; - - if ( args.length === 1 ) { - args = args[ 0 ]; - } - - setTimeout(function() { - deferred.resolve( args ); - }, 1 ); - - return deferred.promise(); - })[ callback ? key : 'done' ]( callback || Base.noop ); - } else { - return rlts[ 0 ]; - } - }, - - destroy: function() { - _destroy.apply( this, arguments ); - this._widgets = null; - } - }); - - /** - * 添加组件 - * @grammar Uploader.register(proto); - * @grammar Uploader.register(map, proto); - * @param {object} responseMap API 名称与函数实现的映射 - * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 - * @method Uploader.register - * @for Uploader - * @example - * Uploader.register({ - * 'make-thumb': 'makeThumb' - * }, { - * init: function( options ) {}, - * makeThumb: function() {} - * }); - * - * Uploader.register({ - * 'make-thumb': function() { - * - * } - * }); - */ - Uploader.register = Widget.register = function( responseMap, widgetProto ) { - var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, - klass; - - if ( arguments.length === 1 ) { - widgetProto = responseMap; - - // 自动生成 map 表。 - $.each(widgetProto, function(key) { - if ( key[0] === '_' || key === 'name' ) { - key === 'name' && (map.name = widgetProto.name); - return; - } - - map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; - }); - - } else { - map = $.extend( map, responseMap ); - } - - widgetProto.responseMap = map; - klass = Base.inherits( Widget, widgetProto ); - klass._name = map.name; - widgetClass.push( klass ); - - return klass; - }; - - /** - * 删除插件,只有在注册时指定了名字的才能被删除。 - * @grammar Uploader.unRegister(name); - * @param {string} name 组件名字 - * @method Uploader.unRegister - * @for Uploader - * @example - * - * Uploader.register({ - * name: 'custom', - * - * 'make-thumb': function() { - * - * } - * }); - * - * Uploader.unRegister('custom'); - */ - Uploader.unRegister = Widget.unRegister = function( name ) { - if ( !name || name === 'anonymous' ) { - return; - } - - // 删除指定的插件。 - for ( var i = widgetClass.length; i--; ) { - if ( widgetClass[i]._name === name ) { - widgetClass.splice(i, 1) - } - } - }; - - return Widget; - }); - /** - * @fileOverview DragAndDrop Widget。 - */ - define('widgets/filednd',[ - 'base', - 'uploader', - 'lib/dnd', - 'widgets/widget' - ], function( Base, Uploader, Dnd ) { - var $ = Base.$; - - Uploader.options.dnd = ''; - - /** - * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 - * @namespace options - * @for Uploader - */ - - /** - * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 - * @namespace options - * @for Uploader - */ - - /** - * @event dndAccept - * @param {DataTransferItemList} items DataTransferItem - * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 - * @for Uploader - */ - return Uploader.register({ - name: 'dnd', - - init: function( opts ) { - - if ( !opts.dnd || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - disableGlobalDnd: opts.disableGlobalDnd, - container: opts.dnd, - accept: opts.accept - }), - dnd; - - this.dnd = dnd = new Dnd( options ); - - dnd.once( 'ready', deferred.resolve ); - dnd.on( 'drop', function( files ) { - me.request( 'add-file', [ files ]); - }); - - // 检测文件是否全部允许添加。 - dnd.on( 'accept', function( items ) { - return me.owner.trigger( 'dndAccept', items ); - }); - - dnd.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.dnd && this.dnd.destroy(); - } - }); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepaste',[ - 'base', - 'mediator', - 'runtime/client' - ], function( Base, Mediator, RuntimeClent ) { - - var $ = Base.$; - - function FilePaste( opts ) { - opts = this.options = $.extend({}, opts ); - opts.container = $( opts.container || document.body ); - RuntimeClent.call( this, 'FilePaste' ); - } - - Base.inherits( RuntimeClent, { - constructor: FilePaste, - - init: function() { - var me = this; - - me.connectRuntime( me.options, function() { - me.exec('init'); - me.trigger('ready'); - }); - } - }); - - Mediator.installTo( FilePaste.prototype ); - - return FilePaste; - }); - /** - * @fileOverview 组件基类。 - */ - define('widgets/filepaste',[ - 'base', - 'uploader', - 'lib/filepaste', - 'widgets/widget' - ], function( Base, Uploader, FilePaste ) { - var $ = Base.$; - - /** - * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. - * @namespace options - * @for Uploader - */ - return Uploader.register({ - name: 'paste', - - init: function( opts ) { - - if ( !opts.paste || - this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - var me = this, - deferred = Base.Deferred(), - options = $.extend({}, { - container: opts.paste, - accept: opts.accept - }), - paste; - - this.paste = paste = new FilePaste( options ); - - paste.once( 'ready', deferred.resolve ); - paste.on( 'paste', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - paste.init(); - - return deferred.promise(); - }, - - destroy: function() { - this.paste && this.paste.destroy(); - } - }); - }); - /** - * @fileOverview Blob - */ - define('lib/blob',[ - 'base', - 'runtime/client' - ], function( Base, RuntimeClient ) { - - function Blob( ruid, source ) { - var me = this; - - me.source = source; - me.ruid = ruid; - this.size = source.size || 0; - - // 如果没有指定 mimetype, 但是知道文件后缀。 - if ( !source.type && this.ext && - ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { - this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); - } else { - this.type = source.type || 'application/octet-stream'; - } - - RuntimeClient.call( me, 'Blob' ); - this.uid = source.uid || this.uid; - - if ( ruid ) { - me.connectRuntime( ruid ); - } - } - - Base.inherits( RuntimeClient, { - constructor: Blob, - - slice: function( start, end ) { - return this.exec( 'slice', start, end ); - }, - - getSource: function() { - return this.source; - } - }); - - return Blob; - }); - /** - * 为了统一化Flash的File和HTML5的File而存在。 - * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 - * @fileOverview File - */ - define('lib/file',[ - 'base', - 'lib/blob' - ], function( Base, Blob ) { - - var uid = 1, - rExt = /\.([^.]+)$/; - - function File( ruid, file ) { - var ext; - - this.name = file.name || ('untitled' + uid++); - ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; - - // todo 支持其他类型文件的转换。 - // 如果有 mimetype, 但是文件名里面没有找出后缀规律 - if ( !ext && file.type ) { - ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? - RegExp.$1.toLowerCase() : ''; - this.name += '.' + ext; - } - - this.ext = ext; - this.lastModifiedDate = file.lastModifiedDate || - (new Date()).toLocaleString(); - - Blob.apply( this, arguments ); - } - - return Base.inherits( Blob, File ); - }); - - /** - * @fileOverview 错误信息 - */ - define('lib/filepicker',[ - 'base', - 'runtime/client', - 'lib/file' - ], function( Base, RuntimeClent, File ) { - - var $ = Base.$; - - function FilePicker( opts ) { - opts = this.options = $.extend({}, FilePicker.options, opts ); - - opts.container = $( opts.id ); - - if ( !opts.container.length ) { - throw new Error('按钮指定错误'); - } - - opts.innerHTML = opts.innerHTML || opts.label || - opts.container.html() || ''; - - opts.button = $( opts.button || document.createElement('div') ); - opts.button.html( opts.innerHTML ); - opts.container.html( opts.button ); - - RuntimeClent.call( this, 'FilePicker', true ); - } - - FilePicker.options = { - button: null, - container: null, - label: null, - innerHTML: null, - multiple: true, - accept: null, - name: 'file' - }; - - Base.inherits( RuntimeClent, { - constructor: FilePicker, - - init: function() { - var me = this, - opts = me.options, - button = opts.button; - - button.addClass('webuploader-pick'); - - me.on( 'all', function( type ) { - var files; - - switch ( type ) { - case 'mouseenter': - button.addClass('webuploader-pick-hover'); - break; - - case 'mouseleave': - button.removeClass('webuploader-pick-hover'); - break; - - case 'change': - files = me.exec('getFiles'); - me.trigger( 'select', $.map( files, function( file ) { - file = new File( me.getRuid(), file ); - - // 记录来源。 - file._refer = opts.container; - return file; - }), opts.container ); - break; - } - }); - - me.connectRuntime( opts, function() { - me.refresh(); - me.exec( 'init', opts ); - me.trigger('ready'); - }); - - this._resizeHandler = Base.bindFn( this.refresh, this ); - $( window ).on( 'resize', this._resizeHandler ); - }, - - refresh: function() { - var shimContainer = this.getRuntime().getContainer(), - button = this.options.button, - width = button.outerWidth ? - button.outerWidth() : button.width(), - - height = button.outerHeight ? - button.outerHeight() : button.height(), - - pos = button.offset(); - - width && height && shimContainer.css({ - bottom: 'auto', - right: 'auto', - width: width + 'px', - height: height + 'px' - }).offset( pos ); - }, - - enable: function() { - var btn = this.options.button; - - btn.removeClass('webuploader-pick-disable'); - this.refresh(); - }, - - disable: function() { - var btn = this.options.button; - - this.getRuntime().getContainer().css({ - top: '-99999px' - }); - - btn.addClass('webuploader-pick-disable'); - }, - - destroy: function() { - var btn = this.options.button; - $( window ).off( 'resize', this._resizeHandler ); - btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + - 'webuploader-pick'); - } - }); - - return FilePicker; - }); - - /** - * @fileOverview 文件选择相关 - */ - define('widgets/filepicker',[ - 'base', - 'uploader', - 'lib/filepicker', - 'widgets/widget' - ], function( Base, Uploader, FilePicker ) { - var $ = Base.$; - - $.extend( Uploader.options, { - - /** - * @property {Selector | Object} [pick=undefined] - * @namespace options - * @for Uploader - * @description 指定选择文件的按钮容器,不指定则不创建按钮。 - * - * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 - * * `label` {String} 请采用 `innerHTML` 代替 - * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 - * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 - */ - pick: null, - - /** - * @property {Arroy} [accept=null] - * @namespace options - * @for Uploader - * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 - * - * * `title` {String} 文字描述 - * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 - * * `mimeTypes` {String} 多个用逗号分割。 - * - * 如: - * - * ``` - * { - * title: 'Images', - * extensions: 'gif,jpg,jpeg,bmp,png', - * mimeTypes: 'image/*' - * } - * ``` - */ - accept: null/*{ - title: 'Images', - extensions: 'gif,jpg,jpeg,bmp,png', - mimeTypes: 'image/*' - }*/ - }); - - return Uploader.register({ - name: 'picker', - - init: function( opts ) { - this.pickers = []; - return opts.pick && this.addBtn( opts.pick ); - }, - - refresh: function() { - $.each( this.pickers, function() { - this.refresh(); - }); - }, - - /** - * @method addButton - * @for Uploader - * @grammar addButton( pick ) => Promise - * @description - * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 - * @example - * uploader.addButton({ - * id: '#btnContainer', - * innerHTML: '选择文件' - * }); - */ - addBtn: function( pick ) { - var me = this, - opts = me.options, - accept = opts.accept, - promises = []; - - if ( !pick ) { - return; - } - - $.isPlainObject( pick ) || (pick = { - id: pick - }); - - $( pick.id ).each(function() { - var options, picker, deferred; - - deferred = Base.Deferred(); - - options = $.extend({}, pick, { - accept: $.isPlainObject( accept ) ? [ accept ] : accept, - swf: opts.swf, - runtimeOrder: opts.runtimeOrder, - id: this - }); - - picker = new FilePicker( options ); - - picker.once( 'ready', deferred.resolve ); - picker.on( 'select', function( files ) { - me.owner.request( 'add-file', [ files ]); - }); - picker.init(); - - me.pickers.push( picker ); - - promises.push( deferred.promise() ); - }); - - return Base.when.apply( Base, promises ); - }, - - disable: function() { - $.each( this.pickers, function() { - this.disable(); - }); - }, - - enable: function() { - $.each( this.pickers, function() { - this.enable(); - }); - }, - - destroy: function() { - $.each( this.pickers, function() { - this.destroy(); - }); - this.pickers = null; - } - }); - }); - /** - * @fileOverview Image - */ - define('lib/image',[ - 'base', - 'runtime/client', - 'lib/blob' - ], function( Base, RuntimeClient, Blob ) { - var $ = Base.$; - - // 构造器。 - function Image( opts ) { - this.options = $.extend({}, Image.options, opts ); - RuntimeClient.call( this, 'Image' ); - - this.on( 'load', function() { - this._info = this.exec('info'); - this._meta = this.exec('meta'); - }); - } - - // 默认选项。 - Image.options = { - - // 默认的图片处理质量 - quality: 90, - - // 是否裁剪 - crop: false, - - // 是否保留头部信息 - preserveHeaders: false, - - // 是否允许放大。 - allowMagnify: false - }; - - // 继承RuntimeClient. - Base.inherits( RuntimeClient, { - constructor: Image, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - loadFromBlob: function( blob ) { - var me = this, - ruid = blob.getRuid(); - - this.connectRuntime( ruid, function() { - me.exec( 'init', me.options ); - me.exec( 'loadFromBlob', blob ); - }); - }, - - resize: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'resize' ].concat( args ) ); - }, - - crop: function() { - var args = Base.slice( arguments ); - return this.exec.apply( this, [ 'crop' ].concat( args ) ); - }, - - getAsDataUrl: function( type ) { - return this.exec( 'getAsDataUrl', type ); - }, - - getAsBlob: function( type ) { - var blob = this.exec( 'getAsBlob', type ); - - return new Blob( this.getRuid(), blob ); - } - }); - - return Image; - }); - /** - * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 - */ - define('widgets/image',[ - 'base', - 'uploader', - 'lib/image', - 'widgets/widget' - ], function( Base, Uploader, Image ) { - - var $ = Base.$, - throttle; - - // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 - throttle = (function( max ) { - var occupied = 0, - waiting = [], - tick = function() { - var item; - - while ( waiting.length && occupied < max ) { - item = waiting.shift(); - occupied += item[ 0 ]; - item[ 1 ](); - } - }; - - return function( emiter, size, cb ) { - waiting.push([ size, cb ]); - emiter.once( 'destroy', function() { - occupied -= size; - setTimeout( tick, 1 ); - }); - setTimeout( tick, 1 ); - }; - })( 5 * 1024 * 1024 ); - - $.extend( Uploader.options, { - - /** - * @property {Object} [thumb] - * @namespace options - * @for Uploader - * @description 配置生成缩略图的选项。 - * - * 默认为: - * - * ```javascript - * { - * width: 110, - * height: 110, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 70, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: true, - * - * // 是否允许裁剪。 - * crop: true, - * - * // 为空的话则保留原有图片格式。 - * // 否则强制转换成指定的类型。 - * type: 'image/jpeg' - * } - * ``` - */ - thumb: { - width: 110, - height: 110, - quality: 70, - allowMagnify: true, - crop: true, - preserveHeaders: false, - - // 为空的话则保留原有图片格式。 - // 否则强制转换成指定的类型。 - // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 - // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg - type: 'image/jpeg' - }, - - /** - * @property {Object} [compress] - * @namespace options - * @for Uploader - * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 - * - * 默认为: - * - * ```javascript - * { - * width: 1600, - * height: 1600, - * - * // 图片质量,只有type为`image/jpeg`的时候才有效。 - * quality: 90, - * - * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. - * allowMagnify: false, - * - * // 是否允许裁剪。 - * crop: false, - * - * // 是否保留头部meta信息。 - * preserveHeaders: true, - * - * // 如果发现压缩后文件大小比原来还大,则使用原来图片 - * // 此属性可能会影响图片自动纠正功能 - * noCompressIfLarger: false, - * - * // 单位字节,如果图片大小小于此值,不会采用压缩。 - * compressSize: 0 - * } - * ``` - */ - compress: { - width: 1600, - height: 1600, - quality: 90, - allowMagnify: false, - crop: false, - preserveHeaders: true - } - }); - - return Uploader.register({ - - name: 'image', - - - /** - * 生成缩略图,此过程为异步,所以需要传入`callback`。 - * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 - * - * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 - * - * `callback`中可以接收到两个参数。 - * * 第一个为error,如果生成缩略图有错误,此error将为真。 - * * 第二个为ret, 缩略图的Data URL值。 - * - * **注意** - * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 - * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 - * - * @method makeThumb - * @grammar makeThumb( file, callback ) => undefined - * @grammar makeThumb( file, callback, width, height ) => undefined - * @for Uploader - * @example - * - * uploader.on( 'fileQueued', function( file ) { - * var $li = ...; - * - * uploader.makeThumb( file, function( error, ret ) { - * if ( error ) { - * $li.text('预览错误'); - * } else { - * $li.append(''); - * } - * }); - * - * }); - */ - makeThumb: function( file, cb, width, height ) { - var opts, image; - - file = this.request( 'get-file', file ); - - // 只预览图片格式。 - if ( !file.type.match( /^image/ ) ) { - cb( true ); - return; - } - - opts = $.extend({}, this.options.thumb ); - - // 如果传入的是object. - if ( $.isPlainObject( width ) ) { - opts = $.extend( opts, width ); - width = null; - } - - width = width || opts.width; - height = height || opts.height; - - image = new Image( opts ); - - image.once( 'load', function() { - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - // 当 resize 完后 - image.once( 'complete', function() { - cb( false, image.getAsDataUrl( opts.type ) ); - image.destroy(); - }); - - image.once( 'error', function( reason ) { - cb( reason || true ); - image.destroy(); - }); - - throttle( image, file.source.size, function() { - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - image.loadFromBlob( file.source ); - }); - }, - - beforeSendFile: function( file ) { - var opts = this.options.compress || this.options.resize, - compressSize = opts && opts.compressSize || 0, - noCompressIfLarger = opts && opts.noCompressIfLarger || false, - image, deferred; - - file = this.request( 'get-file', file ); - - // 只压缩 jpeg 图片格式。 - // gif 可能会丢失针 - // bmp png 基本上尺寸都不大,且压缩比比较小。 - if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || - file.size < compressSize || - file._compressed ) { - return; - } - - opts = $.extend({}, opts ); - deferred = Base.Deferred(); - - image = new Image( opts ); - - deferred.always(function() { - image.destroy(); - image = null; - }); - image.once( 'error', deferred.reject ); - image.once( 'load', function() { - var width = opts.width, - height = opts.height; - - file._info = file._info || image.info(); - file._meta = file._meta || image.meta(); - - // 如果 width 的值介于 0 - 1 - // 说明设置的是百分比。 - if ( width <= 1 && width > 0 ) { - width = file._info.width * width; - } - - // 同样的规则应用于 height - if ( height <= 1 && height > 0 ) { - height = file._info.height * height; - } - - image.resize( width, height ); - }); - - image.once( 'complete', function() { - var blob, size; - - // 移动端 UC / qq 浏览器的无图模式下 - // ctx.getImageData 处理大图的时候会报 Exception - // INDEX_SIZE_ERR: DOM Exception 1 - try { - blob = image.getAsBlob( opts.type ); - - size = file.size; - - // 如果压缩后,比原来还大则不用压缩后的。 - if ( !noCompressIfLarger || blob.size < size ) { - // file.source.destroy && file.source.destroy(); - file.source = blob; - file.size = blob.size; - - file.trigger( 'resize', blob.size, size ); - } - - // 标记,避免重复压缩。 - file._compressed = true; - deferred.resolve(); - } catch ( e ) { - // 出错了直接继续,让其上传原始图片 - deferred.resolve(); - } - }); - - file._info && image.info( file._info ); - file._meta && image.meta( file._meta ); - - image.loadFromBlob( file.source ); - return deferred.promise(); - } - }); - }); - /** - * @fileOverview 文件属性封装 - */ - define('file',[ - 'base', - 'mediator' - ], function( Base, Mediator ) { - - var $ = Base.$, - idPrefix = 'WU_FILE_', - idSuffix = 0, - rExt = /\.([^.]+)$/, - statusMap = {}; - - function gid() { - return idPrefix + idSuffix++; - } - - /** - * 文件类 - * @class File - * @constructor 构造函数 - * @grammar new File( source ) => File - * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 - */ - function WUFile( source ) { - - /** - * 文件名,包括扩展名(后缀) - * @property name - * @type {string} - */ - this.name = source.name || 'Untitled'; - - /** - * 文件体积(字节) - * @property size - * @type {uint} - * @default 0 - */ - this.size = source.size || 0; - - /** - * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) - * @property type - * @type {string} - * @default 'application/octet-stream' - */ - this.type = source.type || 'application/octet-stream'; - - /** - * 文件最后修改日期 - * @property lastModifiedDate - * @type {int} - * @default 当前时间戳 - */ - this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); - - /** - * 文件ID,每个对象具有唯一ID,与文件名无关 - * @property id - * @type {string} - */ - this.id = gid(); - - /** - * 文件扩展名,通过文件名获取,例如test.png的扩展名为png - * @property ext - * @type {string} - */ - this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; - - - /** - * 状态文字说明。在不同的status语境下有不同的用途。 - * @property statusText - * @type {string} - */ - this.statusText = ''; - - // 存储文件状态,防止通过属性直接修改 - statusMap[ this.id ] = WUFile.Status.INITED; - - this.source = source; - this.loaded = 0; - - this.on( 'error', function( msg ) { - this.setStatus( WUFile.Status.ERROR, msg ); - }); - } - - $.extend( WUFile.prototype, { - - /** - * 设置状态,状态变化时会触发`change`事件。 - * @method setStatus - * @grammar setStatus( status[, statusText] ); - * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) - * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 - */ - setStatus: function( status, text ) { - - var prevStatus = statusMap[ this.id ]; - - typeof text !== 'undefined' && (this.statusText = text); - - if ( status !== prevStatus ) { - statusMap[ this.id ] = status; - /** - * 文件状态变化 - * @event statuschange - */ - this.trigger( 'statuschange', status, prevStatus ); - } - - }, - - /** - * 获取文件状态 - * @return {File.Status} - * @example - 文件状态具体包括以下几种类型: - { - // 初始化 - INITED: 0, - // 已入队列 - QUEUED: 1, - // 正在上传 - PROGRESS: 2, - // 上传出错 - ERROR: 3, - // 上传成功 - COMPLETE: 4, - // 上传取消 - CANCELLED: 5 - } - */ - getStatus: function() { - return statusMap[ this.id ]; - }, - - /** - * 获取文件原始信息。 - * @return {*} - */ - getSource: function() { - return this.source; - }, - - destroy: function() { - this.off(); - delete statusMap[ this.id ]; - } - }); - - Mediator.installTo( WUFile.prototype ); - - /** - * 文件状态值,具体包括以下几种类型: - * * `inited` 初始状态 - * * `queued` 已经进入队列, 等待上传 - * * `progress` 上传中 - * * `complete` 上传完成。 - * * `error` 上传出错,可重试 - * * `interrupt` 上传中断,可续传。 - * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 - * * `cancelled` 文件被移除。 - * @property {Object} Status - * @namespace File - * @class File - * @static - */ - WUFile.Status = { - INITED: 'inited', // 初始状态 - QUEUED: 'queued', // 已经进入队列, 等待上传 - PROGRESS: 'progress', // 上传中 - ERROR: 'error', // 上传出错,可重试 - COMPLETE: 'complete', // 上传完成。 - CANCELLED: 'cancelled', // 上传取消。 - INTERRUPT: 'interrupt', // 上传中断,可续传。 - INVALID: 'invalid' // 文件不合格,不能重试上传。 - }; - - return WUFile; - }); - - /** - * @fileOverview 文件队列 - */ - define('queue',[ - 'base', - 'mediator', - 'file' - ], function( Base, Mediator, WUFile ) { - - var $ = Base.$, - STATUS = WUFile.Status; - - /** - * 文件队列, 用来存储各个状态中的文件。 - * @class Queue - * @extends Mediator - */ - function Queue() { - - /** - * 统计文件数。 - * * `numOfQueue` 队列中的文件数。 - * * `numOfSuccess` 上传成功的文件数 - * * `numOfCancel` 被取消的文件数 - * * `numOfProgress` 正在上传中的文件数 - * * `numOfUploadFailed` 上传错误的文件数。 - * * `numOfInvalid` 无效的文件数。 - * * `numofDeleted` 被移除的文件数。 - * @property {Object} stats - */ - this.stats = { - numOfQueue: 0, - numOfSuccess: 0, - numOfCancel: 0, - numOfProgress: 0, - numOfUploadFailed: 0, - numOfInvalid: 0, - numofDeleted: 0, - numofInterrupt: 0 - }; - - // 上传队列,仅包括等待上传的文件 - this._queue = []; - - // 存储所有文件 - this._map = {}; - } - - $.extend( Queue.prototype, { - - /** - * 将新文件加入对队列尾部 - * - * @method append - * @param {File} file 文件对象 - */ - append: function( file ) { - this._queue.push( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 将新文件加入对队列头部 - * - * @method prepend - * @param {File} file 文件对象 - */ - prepend: function( file ) { - this._queue.unshift( file ); - this._fileAdded( file ); - return this; - }, - - /** - * 获取文件对象 - * - * @method getFile - * @param {String} fileId 文件ID - * @return {File} - */ - getFile: function( fileId ) { - if ( typeof fileId !== 'string' ) { - return fileId; - } - return this._map[ fileId ]; - }, - - /** - * 从队列中取出一个指定状态的文件。 - * @grammar fetch( status ) => File - * @method fetch - * @param {String} status [文件状态值](#WebUploader:File:File.Status) - * @return {File} [File](#WebUploader:File) - */ - fetch: function( status ) { - var len = this._queue.length, - i, file; - - status = status || STATUS.QUEUED; - - for ( i = 0; i < len; i++ ) { - file = this._queue[ i ]; - - if ( status === file.getStatus() ) { - return file; - } - } - - return null; - }, - - /** - * 对队列进行排序,能够控制文件上传顺序。 - * @grammar sort( fn ) => undefined - * @method sort - * @param {Function} fn 排序方法 - */ - sort: function( fn ) { - if ( typeof fn === 'function' ) { - this._queue.sort( fn ); - } - }, - - /** - * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 - * @grammar getFiles( [status1[, status2 ...]] ) => Array - * @method getFiles - * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) - */ - getFiles: function() { - var sts = [].slice.call( arguments, 0 ), - ret = [], - i = 0, - len = this._queue.length, - file; - - for ( ; i < len; i++ ) { - file = this._queue[ i ]; - - if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { - continue; - } - - ret.push( file ); - } - - return ret; - }, - - /** - * 在队列中删除文件。 - * @grammar removeFile( file ) => Array - * @method removeFile - * @param {File} 文件对象。 - */ - removeFile: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( existing ) { - delete this._map[ file.id ]; - file.destroy(); - this.stats.numofDeleted++; - } - }, - - _fileAdded: function( file ) { - var me = this, - existing = this._map[ file.id ]; - - if ( !existing ) { - this._map[ file.id ] = file; - - file.on( 'statuschange', function( cur, pre ) { - me._onFileStatusChange( cur, pre ); - }); - } - }, - - _onFileStatusChange: function( curStatus, preStatus ) { - var stats = this.stats; - - switch ( preStatus ) { - case STATUS.PROGRESS: - stats.numOfProgress--; - break; - - case STATUS.QUEUED: - stats.numOfQueue --; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed--; - break; - - case STATUS.INVALID: - stats.numOfInvalid--; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt--; - break; - } - - switch ( curStatus ) { - case STATUS.QUEUED: - stats.numOfQueue++; - break; - - case STATUS.PROGRESS: - stats.numOfProgress++; - break; - - case STATUS.ERROR: - stats.numOfUploadFailed++; - break; - - case STATUS.COMPLETE: - stats.numOfSuccess++; - break; - - case STATUS.CANCELLED: - stats.numOfCancel++; - break; - - - case STATUS.INVALID: - stats.numOfInvalid++; - break; - - case STATUS.INTERRUPT: - stats.numofInterrupt++; - break; - } - } - - }); - - Mediator.installTo( Queue.prototype ); - - return Queue; - }); - /** - * @fileOverview 队列 - */ - define('widgets/queue',[ - 'base', - 'uploader', - 'queue', - 'file', - 'lib/file', - 'runtime/client', - 'widgets/widget' - ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { - - var $ = Base.$, - rExt = /\.\w+$/, - Status = WUFile.Status; - - return Uploader.register({ - name: 'queue', - - init: function( opts ) { - var me = this, - deferred, len, i, item, arr, accept, runtime; - - if ( $.isPlainObject( opts.accept ) ) { - opts.accept = [ opts.accept ]; - } - - // accept中的中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].extensions; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = '\\.' + arr.join(',') - .replace( /,/g, '$|\\.' ) - .replace( /\*/g, '.*' ) + '$'; - } - - me.accept = new RegExp( accept, 'i' ); - } - - me.queue = new Queue(); - me.stats = me.queue.stats; - - // 如果当前不是html5运行时,那就算了。 - // 不执行后续操作 - if ( this.request('predict-runtime-type') !== 'html5' ) { - return; - } - - // 创建一个 html5 运行时的 placeholder - // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 - deferred = Base.Deferred(); - this.placeholder = runtime = new RuntimeClient('Placeholder'); - runtime.connectRuntime({ - runtimeOrder: 'html5' - }, function() { - me._ruid = runtime.getRuid(); - deferred.resolve(); - }); - return deferred.promise(); - }, - - - // 为了支持外部直接添加一个原生File对象。 - _wrapFile: function( file ) { - if ( !(file instanceof WUFile) ) { - - if ( !(file instanceof File) ) { - if ( !this._ruid ) { - throw new Error('Can\'t add external files.'); - } - file = new File( this._ruid, file ); - } - - file = new WUFile( file ); - } - - return file; - }, - - // 判断文件是否可以被加入队列 - acceptFile: function( file ) { - var invalid = !file || !file.size || this.accept && - - // 如果名字中有后缀,才做后缀白名单处理。 - rExt.exec( file.name ) && !this.accept.test( file.name ); - - return !invalid; - }, - - - /** - * @event beforeFileQueued - * @param {File} file File对象 - * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 - * @for Uploader - */ - - /** - * @event fileQueued - * @param {File} file File对象 - * @description 当文件被加入队列以后触发。 - * @for Uploader - */ - - _addFile: function( file ) { - var me = this; - - file = me._wrapFile( file ); - - // 不过类型判断允许不允许,先派送 `beforeFileQueued` - if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { - return; - } - - // 类型不匹配,则派送错误事件,并返回。 - if ( !me.acceptFile( file ) ) { - me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); - return; - } - - me.queue.append( file ); - me.owner.trigger( 'fileQueued', file ); - return file; - }, - - getFile: function( fileId ) { - return this.queue.getFile( fileId ); - }, - - /** - * @event filesQueued - * @param {File} files 数组,内容为原始File(lib/File)对象。 - * @description 当一批文件添加进队列以后触发。 - * @for Uploader - */ - - /** - * @property {Boolean} [auto=false] - * @namespace options - * @for Uploader - * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 - * - */ - - /** - * @method addFiles - * @grammar addFiles( file ) => undefined - * @grammar addFiles( [file1, file2 ...] ) => undefined - * @param {Array of File or File} [files] Files 对象 数组 - * @description 添加文件到队列 - * @for Uploader - */ - addFile: function( files ) { - var me = this; - - if ( !files.length ) { - files = [ files ]; - } - - files = $.map( files, function( file ) { - return me._addFile( file ); - }); - - me.owner.trigger( 'filesQueued', files ); - - if ( me.options.auto ) { - setTimeout(function() { - me.request('start-upload'); - }, 20 ); - } - }, - - getStats: function() { - return this.stats; - }, - - /** - * @event fileDequeued - * @param {File} file File对象 - * @description 当文件被移除队列后触发。 - * @for Uploader - */ - - /** - * @method removeFile - * @grammar removeFile( file ) => undefined - * @grammar removeFile( id ) => undefined - * @grammar removeFile( file, true ) => undefined - * @grammar removeFile( id, true ) => undefined - * @param {File|id} file File对象或这File对象的id - * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 - * @for Uploader - * @example - * - * $li.on('click', '.remove-this', function() { - * uploader.removeFile( file ); - * }) - */ - removeFile: function( file, remove ) { - var me = this; - - file = file.id ? file : me.queue.getFile( file ); - - this.request( 'cancel-file', file ); - - if ( remove ) { - this.queue.removeFile( file ); - } - }, - - /** - * @method getFiles - * @grammar getFiles() => Array - * @grammar getFiles( status1, status2, status... ) => Array - * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 - * @for Uploader - * @example - * console.log( uploader.getFiles() ); // => all files - * console.log( uploader.getFiles('error') ) // => all error files. - */ - getFiles: function() { - return this.queue.getFiles.apply( this.queue, arguments ); - }, - - fetchFile: function() { - return this.queue.fetch.apply( this.queue, arguments ); - }, - - /** - * @method retry - * @grammar retry() => undefined - * @grammar retry( file ) => undefined - * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 - * @for Uploader - * @example - * function retry() { - * uploader.retry(); - * } - */ - retry: function( file, noForceStart ) { - var me = this, - files, i, len; - - if ( file ) { - file = file.id ? file : me.queue.getFile( file ); - file.setStatus( Status.QUEUED ); - noForceStart || me.request('start-upload'); - return; - } - - files = me.queue.getFiles( Status.ERROR ); - i = 0; - len = files.length; - - for ( ; i < len; i++ ) { - file = files[ i ]; - file.setStatus( Status.QUEUED ); - } - - me.request('start-upload'); - }, - - /** - * @method sort - * @grammar sort( fn ) => undefined - * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 - * @for Uploader - */ - sortFiles: function() { - return this.queue.sort.apply( this.queue, arguments ); - }, - - /** - * @event reset - * @description 当 uploader 被重置的时候触发。 - * @for Uploader - */ - - /** - * @method reset - * @grammar reset() => undefined - * @description 重置uploader。目前只重置了队列。 - * @for Uploader - * @example - * uploader.reset(); - */ - reset: function() { - this.owner.trigger('reset'); - this.queue = new Queue(); - this.stats = this.queue.stats; - }, - - destroy: function() { - this.reset(); - this.placeholder && this.placeholder.destroy(); - } - }); - - }); - /** - * @fileOverview 添加获取Runtime相关信息的方法。 - */ - define('widgets/runtime',[ - 'uploader', - 'runtime/runtime', - 'widgets/widget' - ], function( Uploader, Runtime ) { - - Uploader.support = function() { - return Runtime.hasRuntime.apply( Runtime, arguments ); - }; - - /** - * @property {Object} [runtimeOrder=html5,flash] - * @namespace options - * @for Uploader - * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. - * - * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 - */ - - return Uploader.register({ - name: 'runtime', - - init: function() { - if ( !this.predictRuntimeType() ) { - throw Error('Runtime Error'); - } - }, - - /** - * 预测Uploader将采用哪个`Runtime` - * @grammar predictRuntimeType() => String - * @method predictRuntimeType - * @for Uploader - */ - predictRuntimeType: function() { - var orders = this.options.runtimeOrder || Runtime.orders, - type = this.type, - i, len; - - if ( !type ) { - orders = orders.split( /\s*,\s*/g ); - - for ( i = 0, len = orders.length; i < len; i++ ) { - if ( Runtime.hasRuntime( orders[ i ] ) ) { - this.type = type = orders[ i ]; - break; - } - } - } - - return type; - } - }); - }); - /** - * @fileOverview Transport - */ - define('lib/transport',[ - 'base', - 'runtime/client', - 'mediator' - ], function( Base, RuntimeClient, Mediator ) { - - var $ = Base.$; - - function Transport( opts ) { - var me = this; - - opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); - RuntimeClient.call( this, 'Transport' ); - - this._blob = null; - this._formData = opts.formData || {}; - this._headers = opts.headers || {}; - - this.on( 'progress', this._timeout ); - this.on( 'load error', function() { - me.trigger( 'progress', 1 ); - clearTimeout( me._timer ); - }); - } - - Transport.options = { - server: '', - method: 'POST', - - // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 - withCredentials: false, - fileVal: 'file', - timeout: 2 * 60 * 1000, // 2分钟 - formData: {}, - headers: {}, - sendAsBinary: false - }; - - $.extend( Transport.prototype, { - - // 添加Blob, 只能添加一次,最后一次有效。 - appendBlob: function( key, blob, filename ) { - var me = this, - opts = me.options; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - }); - - me._blob = blob; - opts.fileVal = key || opts.fileVal; - opts.filename = filename || opts.filename; - }, - - // 添加其他字段 - append: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._formData, key ); - } else { - this._formData[ key ] = value; - } - }, - - setRequestHeader: function( key, value ) { - if ( typeof key === 'object' ) { - $.extend( this._headers, key ); - } else { - this._headers[ key ] = value; - } - }, - - send: function( method ) { - this.exec( 'send', method ); - this._timeout(); - }, - - abort: function() { - clearTimeout( this._timer ); - return this.exec('abort'); - }, - - destroy: function() { - this.trigger('destroy'); - this.off(); - this.exec('destroy'); - this.disconnectRuntime(); - }, - - getResponse: function() { - return this.exec('getResponse'); - }, - - getResponseAsJson: function() { - return this.exec('getResponseAsJson'); - }, - - getStatus: function() { - return this.exec('getStatus'); - }, - - _timeout: function() { - var me = this, - duration = me.options.timeout; - - if ( !duration ) { - return; - } - - clearTimeout( me._timer ); - me._timer = setTimeout(function() { - me.abort(); - me.trigger( 'error', 'timeout' ); - }, duration ); - } - - }); - - // 让Transport具备事件功能。 - Mediator.installTo( Transport.prototype ); - - return Transport; - }); - /** - * @fileOverview 负责文件上传相关。 - */ - define('widgets/upload',[ - 'base', - 'uploader', - 'file', - 'lib/transport', - 'widgets/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} [method='POST'] - * @namespace options - * @for Uploader - * @description 文件上传方式,`POST`或者`GET`。 - */ - - /** - * @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) { - $.each( me.pool, function( _, v ) { - - // 之前暂停过。 - if (v.file !== file) { - return; - } - - v.transport && v.transport.send(); - }); - - file.setStatus( Status.QUEUED ); - } else if (file.getStatus() === Status.PROGRESS) { - return; - } else { - file.setStatus( Status.QUEUED ); - } - } else { - $.each( me.request( 'get-files', [ Status.INITED ] ), function() { - this.setStatus( Status.QUEUED ); - }); - } - - if ( me.runing ) { - return; - } - - me.runing = true; - - var files = []; - - // 如果有暂停的,则续传 - $.each( me.pool, function( _, v ) { - var file = v.file; - - if ( file.getStatus() === Status.INTERRUPT ) { - files.push(file); - me._trigged = false; - v.transport && v.transport.send(); - } - }); - - var file; - while ( (file = files.shift()) ) { - file.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; - - 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) { - return; - } - - v.transport && v.transport.abort(); - me._putback(v); - me._popBlock(v); - }); - - 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 ); - } - - }); - }); - /** - * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 - */ - - define('widgets/validator',[ - 'base', - 'uploader', - 'file', - 'widgets/widget' - ], function( Base, Uploader, WUFile ) { - - var $ = Base.$, - validators = {}, - api; - - /** - * @event error - * @param {String} type 错误类型。 - * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 - * - * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 - * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 - * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 - * @for Uploader - */ - - // 暴露给外面的api - api = { - - // 添加验证器 - addValidator: function( type, cb ) { - validators[ type ] = cb; - }, - - // 移除验证器 - removeValidator: function( type ) { - delete validators[ type ]; - } - }; - - // 在Uploader初始化的时候启动Validators的初始化 - Uploader.register({ - name: 'validator', - - init: function() { - var me = this; - Base.nextTick(function() { - $.each( validators, function() { - this.call( me.owner ); - }); - }); - } - }); - - /** - * @property {int} [fileNumLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总数量, 超出则不允许加入队列。 - */ - api.addValidator( 'fileNumLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileNumLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( count >= max && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return count >= max ? false : true; - }); - - uploader.on( 'fileQueued', function() { - count++; - }); - - uploader.on( 'fileDequeued', function() { - count--; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - - /** - * @property {int} [fileSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSizeLimit', function() { - var uploader = this, - opts = uploader.options, - count = 0, - max = parseInt( opts.fileSizeLimit, 10 ), - flag = true; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var invalid = count + file.size > max; - - if ( invalid && flag ) { - flag = false; - this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); - setTimeout(function() { - flag = true; - }, 1 ); - } - - return invalid ? false : true; - }); - - uploader.on( 'fileQueued', function( file ) { - count += file.size; - }); - - uploader.on( 'fileDequeued', function( file ) { - count -= file.size; - }); - - uploader.on( 'reset', function() { - count = 0; - }); - }); - - /** - * @property {int} [fileSingleSizeLimit=undefined] - * @namespace options - * @for Uploader - * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 - */ - api.addValidator( 'fileSingleSizeLimit', function() { - var uploader = this, - opts = uploader.options, - max = opts.fileSingleSizeLimit; - - if ( !max ) { - return; - } - - uploader.on( 'beforeFileQueued', function( file ) { - - if ( file.size > max ) { - file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); - this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); - return false; - } - - }); - - }); - - /** - * @property {Boolean} [duplicate=undefined] - * @namespace options - * @for Uploader - * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. - */ - api.addValidator( 'duplicate', function() { - var uploader = this, - opts = uploader.options, - mapping = {}; - - if ( opts.duplicate ) { - return; - } - - function hashString( str ) { - var hash = 0, - i = 0, - len = str.length, - _char; - - for ( ; i < len; i++ ) { - _char = str.charCodeAt( i ); - hash = _char + (hash << 6) + (hash << 16) - hash; - } - - return hash; - } - - uploader.on( 'beforeFileQueued', function( file ) { - var hash = file.__hash || (file.__hash = hashString( file.name + - file.size + file.lastModifiedDate )); - - // 已经重复了 - if ( mapping[ hash ] ) { - this.trigger( 'error', 'F_DUPLICATE', file ); - return false; - } - }); - - uploader.on( 'fileQueued', function( file ) { - var hash = file.__hash; - - hash && (mapping[ hash ] = true); - }); - - uploader.on( 'fileDequeued', function( file ) { - var hash = file.__hash; - - hash && (delete mapping[ hash ]); - }); - - uploader.on( 'reset', function() { - mapping = {}; - }); - }); - - return api; - }); - - /** - * @fileOverview Md5 - */ - define('lib/md5',[ - 'runtime/client', - 'mediator' - ], function( RuntimeClient, Mediator ) { - - function Md5() { - RuntimeClient.call( this, 'Md5' ); - } - - // 让 Md5 具备事件功能。 - Mediator.installTo( Md5.prototype ); - - Md5.prototype.loadFromBlob = function( blob ) { - var me = this; - - if ( me.getRuid() ) { - me.disconnectRuntime(); - } - - // 连接到blob归属的同一个runtime. - me.connectRuntime( blob.ruid, function() { - me.exec('init'); - me.exec( 'loadFromBlob', blob ); - }); - }; - - Md5.prototype.getResult = function() { - return this.exec('getResult'); - }; - - return Md5; - }); - /** - * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 - */ - define('widgets/md5',[ - 'base', - 'uploader', - 'lib/md5', - 'lib/blob', - 'widgets/widget' - ], function( Base, Uploader, Md5, Blob ) { - - return Uploader.register({ - name: 'md5', - - - /** - * 计算文件 md5 值,返回一个 promise 对象,可以监听 progress 进度。 - * - * - * @method md5File - * @grammar md5File( file[, start[, end]] ) => promise - * @for Uploader - * @example - * - * uploader.on( 'fileQueued', function( file ) { - * var $li = ...; - * - * uploader.md5File( file ) - * - * // 及时显示进度 - * .progress(function(percentage) { - * console.log('Percentage:', percentage); - * }) - * - * // 完成 - * .then(function(val) { - * console.log('md5 result:', val); - * }); - * - * }); - */ - md5File: function( file, start, end ) { - var md5 = new Md5(), - deferred = Base.Deferred(), - blob = (file instanceof Blob) ? file : - this.request( 'get-file', file ).source; - - md5.on( 'progress load', function( e ) { - e = e || {}; - deferred.notify( e.total ? e.loaded / e.total : 1 ); - }); - - md5.on( 'complete', function() { - deferred.resolve( md5.getResult() ); - }); - - md5.on( 'error', function( reason ) { - deferred.reject( reason ); - }); - - if ( arguments.length > 1 ) { - start = start || 0; - end = end || 0; - start < 0 && (start = blob.size + start); - end < 0 && (end = blob.size + end); - end = Math.min( end, blob.size ); - blob = blob.slice( start, end ); - } - - md5.loadFromBlob( blob ); - - return deferred.promise(); - } - }); - }); - /** - * @fileOverview Runtime管理器,负责Runtime的选择, 连接 - */ - define('runtime/compbase',[],function() { - - function CompBase( owner, runtime ) { - - this.owner = owner; - this.options = owner.options; - - this.getRuntime = function() { - return runtime; - }; - - this.getRuid = function() { - return runtime.uid; - }; - - this.trigger = function() { - return owner.trigger.apply( owner, arguments ); - }; - } - - return CompBase; - }); - /** - * @fileOverview Html5Runtime - */ - define('runtime/html5/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var type = 'html5', - components = {}; - - function Html5Runtime() { - var pool = {}, - me = this, - destroy = this.destroy; - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - if ( components[ comp ] ) { - instance = pool[ uid ] = pool[ uid ] || - new components[ comp ]( client, me ); - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - }; - - me.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - } - - Base.inherits( Runtime, { - constructor: Html5Runtime, - - // 不需要连接其他程序,直接执行callback - init: function() { - var me = this; - setTimeout(function() { - me.trigger('ready'); - }, 1 ); - } - - }); - - // 注册Components - Html5Runtime.register = function( name, component ) { - var klass = components[ name ] = Base.inherits( CompBase, component ); - return klass; - }; - - // 注册html5运行时。 - // 只有在支持的前提下注册。 - if ( window.Blob && window.FileReader && window.DataView ) { - Runtime.addRuntime( type, Html5Runtime ); - } - - return Html5Runtime; - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/html5/blob',[ - 'runtime/html5/runtime', - 'lib/blob' - ], function( Html5Runtime, Blob ) { - - return Html5Runtime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.owner.source, - slice = blob.slice || blob.webkitSlice || blob.mozSlice; - - blob = slice.call( blob, start, end ); - - return new Blob( this.getRuid(), blob ); - } - }); - }); - /** - * @fileOverview FilePaste - */ - define('runtime/html5/dnd',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - var $ = Base.$, - prefix = 'webuploader-dnd-'; - - return Html5Runtime.register( 'DragAndDrop', { - init: function() { - var elem = this.elem = this.options.container; - - this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); - this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); - this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); - this.dropHandler = Base.bindFn( this._dropHandler, this ); - this.dndOver = false; - - elem.on( 'dragenter', this.dragEnterHandler ); - elem.on( 'dragover', this.dragOverHandler ); - elem.on( 'dragleave', this.dragLeaveHandler ); - elem.on( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).on( 'dragover', this.dragOverHandler ); - $( document ).on( 'drop', this.dropHandler ); - } - }, - - _dragEnterHandler: function( e ) { - var me = this, - denied = me._denied || false, - items; - - e = e.originalEvent || e; - - if ( !me.dndOver ) { - me.dndOver = true; - - // 注意只有 chrome 支持。 - items = e.dataTransfer.items; - - if ( items && items.length ) { - me._denied = denied = !me.trigger( 'accept', items ); - } - - me.elem.addClass( prefix + 'over' ); - me.elem[ denied ? 'addClass' : - 'removeClass' ]( prefix + 'denied' ); - } - - e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; - - return false; - }, - - _dragOverHandler: function( e ) { - // 只处理框内的。 - var parentElem = this.elem.parent().get( 0 ); - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - clearTimeout( this._leaveTimer ); - this._dragEnterHandler.call( this, e ); - - return false; - }, - - _dragLeaveHandler: function() { - var me = this, - handler; - - handler = function() { - me.dndOver = false; - me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); - }; - - clearTimeout( me._leaveTimer ); - me._leaveTimer = setTimeout( handler, 100 ); - return false; - }, - - _dropHandler: function( e ) { - var me = this, - ruid = me.getRuid(), - parentElem = me.elem.parent().get( 0 ), - dataTransfer, data; - - // 只处理框内的。 - if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { - return false; - } - - e = e.originalEvent || e; - dataTransfer = e.dataTransfer; - - // 如果是页面内拖拽,还不能处理,不阻止事件。 - // 此处 ie11 下会报参数错误, - try { - data = dataTransfer.getData('text/html'); - } catch( err ) { - } - - if ( data ) { - return; - } - - me._getTansferFiles( dataTransfer, function( results ) { - me.trigger( 'drop', $.map( results, function( file ) { - return new File( ruid, file ); - }) ); - }); - - me.dndOver = false; - me.elem.removeClass( prefix + 'over' ); - return false; - }, - - // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 - _getTansferFiles: function( dataTransfer, callback ) { - var results = [], - promises = [], - items, files, file, item, i, len, canAccessFolder; - - items = dataTransfer.items; - files = dataTransfer.files; - - canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); - - for ( i = 0, len = files.length; i < len; i++ ) { - file = files[ i ]; - item = items && items[ i ]; - - if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { - - promises.push( this._traverseDirectoryTree( - item.webkitGetAsEntry(), results ) ); - } else { - results.push( file ); - } - } - - Base.when.apply( Base, promises ).done(function() { - - if ( !results.length ) { - return; - } - - callback( results ); - }); - }, - - _traverseDirectoryTree: function( entry, results ) { - var deferred = Base.Deferred(), - me = this; - - if ( entry.isFile ) { - entry.file(function( file ) { - results.push( file ); - deferred.resolve(); - }); - } else if ( entry.isDirectory ) { - entry.createReader().readEntries(function( entries ) { - var len = entries.length, - promises = [], - arr = [], // 为了保证顺序。 - i; - - for ( i = 0; i < len; i++ ) { - promises.push( me._traverseDirectoryTree( - entries[ i ], arr ) ); - } - - Base.when.apply( Base, promises ).then(function() { - results.push.apply( results, arr ); - deferred.resolve(); - }, deferred.reject ); - }); - } - - return deferred.promise(); - }, - - destroy: function() { - var elem = this.elem; - - // 还没 init 就调用 destroy - if (!elem) { - return; - } - - elem.off( 'dragenter', this.dragEnterHandler ); - elem.off( 'dragover', this.dragOverHandler ); - elem.off( 'dragleave', this.dragLeaveHandler ); - elem.off( 'drop', this.dropHandler ); - - if ( this.options.disableGlobalDnd ) { - $( document ).off( 'dragover', this.dragOverHandler ); - $( document ).off( 'drop', this.dropHandler ); - } - } - }); - }); - - /** - * @fileOverview FilePaste - */ - define('runtime/html5/filepaste',[ - 'base', - 'runtime/html5/runtime', - 'lib/file' - ], function( Base, Html5Runtime, File ) { - - return Html5Runtime.register( 'FilePaste', { - init: function() { - var opts = this.options, - elem = this.elem = opts.container, - accept = '.*', - arr, i, len, item; - - // accetp的mimeTypes中生成匹配正则。 - if ( opts.accept ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - item = opts.accept[ i ].mimeTypes; - item && arr.push( item ); - } - - if ( arr.length ) { - accept = arr.join(','); - accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); - } - } - this.accept = accept = new RegExp( accept, 'i' ); - this.hander = Base.bindFn( this._pasteHander, this ); - elem.on( 'paste', this.hander ); - }, - - _pasteHander: function( e ) { - var allowed = [], - ruid = this.getRuid(), - items, item, blob, i, len; - - e = e.originalEvent || e; - items = e.clipboardData.items; - - for ( i = 0, len = items.length; i < len; i++ ) { - item = items[ i ]; - - if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { - continue; - } - - allowed.push( new File( ruid, blob ) ); - } - - if ( allowed.length ) { - // 不阻止非文件粘贴(文字粘贴)的事件冒泡 - e.preventDefault(); - e.stopPropagation(); - this.trigger( 'paste', allowed ); - } - }, - - destroy: function() { - this.elem.off( 'paste', this.hander ); - } - }); - }); - - /** - * @fileOverview FilePicker - */ - define('runtime/html5/filepicker',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var $ = Base.$; - - return Html5Runtime.register( 'FilePicker', { - init: function() { - var container = this.getRuntime().getContainer(), - me = this, - owner = me.owner, - opts = me.options, - label = this.label = $( document.createElement('label') ), - input = this.input = $( document.createElement('input') ), - arr, i, len, mouseHandler; - - input.attr( 'type', 'file' ); - input.attr( 'name', opts.name ); - input.addClass('webuploader-element-invisible'); - - label.on( 'click', function() { - input.trigger('click'); - }); - - label.css({ - opacity: 0, - width: '100%', - height: '100%', - display: 'block', - cursor: 'pointer', - background: '#ffffff' - }); - - if ( opts.multiple ) { - input.attr( 'multiple', 'multiple' ); - } - - // @todo Firefox不支持单独指定后缀 - if ( opts.accept && opts.accept.length > 0 ) { - arr = []; - - for ( i = 0, len = opts.accept.length; i < len; i++ ) { - arr.push( opts.accept[ i ].mimeTypes ); - } - - input.attr( 'accept', arr.join(',') ); - } - - container.append( input ); - container.append( label ); - - mouseHandler = function( e ) { - owner.trigger( e.type ); - }; - - input.on( 'change', function( e ) { - var fn = arguments.callee, - clone; - - me.files = e.target.files; - - // reset input - clone = this.cloneNode( true ); - clone.value = null; - this.parentNode.replaceChild( clone, this ); - - input.off(); - input = $( clone ).on( 'change', fn ) - .on( 'mouseenter mouseleave', mouseHandler ); - - owner.trigger('change'); - }); - - label.on( 'mouseenter mouseleave', mouseHandler ); - - }, - - - getFiles: function() { - return this.files; - }, - - destroy: function() { - this.input.off(); - this.label.off(); - } - }); - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/util',[ - 'base' - ], function( Base ) { - - var urlAPI = window.createObjectURL && window || - window.URL && URL.revokeObjectURL && URL || - window.webkitURL, - createObjectURL = Base.noop, - revokeObjectURL = createObjectURL; - - if ( urlAPI ) { - - // 更安全的方式调用,比如android里面就能把context改成其他的对象。 - createObjectURL = function() { - return urlAPI.createObjectURL.apply( urlAPI, arguments ); - }; - - revokeObjectURL = function() { - return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); - }; - } - - return { - createObjectURL: createObjectURL, - revokeObjectURL: revokeObjectURL, - - dataURL2Blob: function( dataURI ) { - var byteStr, intArray, ab, i, mimetype, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - ab = new ArrayBuffer( byteStr.length ); - intArray = new Uint8Array( ab ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; - - return this.arrayBufferToBlob( ab, mimetype ); - }, - - dataURL2ArrayBuffer: function( dataURI ) { - var byteStr, intArray, i, parts; - - parts = dataURI.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - byteStr = atob( parts[ 1 ] ); - } else { - byteStr = decodeURIComponent( parts[ 1 ] ); - } - - intArray = new Uint8Array( byteStr.length ); - - for ( i = 0; i < byteStr.length; i++ ) { - intArray[ i ] = byteStr.charCodeAt( i ); - } - - return intArray.buffer; - }, - - arrayBufferToBlob: function( buffer, type ) { - var builder = window.BlobBuilder || window.WebKitBlobBuilder, - bb; - - // android不支持直接new Blob, 只能借助blobbuilder. - if ( builder ) { - bb = new builder(); - bb.append( buffer ); - return bb.getBlob( type ); - } - - return new Blob([ buffer ], type ? { type: type } : {} ); - }, - - // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. - // 你得到的结果是png. - canvasToDataUrl: function( canvas, type, quality ) { - return canvas.toDataURL( type, quality / 100 ); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - parseMeta: function( blob, callback ) { - callback( false, {}); - }, - - // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 - updateImageHead: function( data ) { - return data; - } - }; - }); - /** - * Terms: - * - * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer - * @fileOverview Image控件 - */ - define('runtime/html5/imagemeta',[ - 'runtime/html5/util' - ], function( Util ) { - - var api; - - api = { - parsers: { - 0xffe1: [] - }, - - maxMetaDataSize: 262144, - - parse: function( blob, cb ) { - var me = this, - fr = new FileReader(); - - fr.onload = function() { - cb( false, me._parse( this.result ) ); - fr = fr.onload = fr.onerror = null; - }; - - fr.onerror = function( e ) { - cb( e.message ); - fr = fr.onload = fr.onerror = null; - }; - - blob = blob.slice( 0, me.maxMetaDataSize ); - fr.readAsArrayBuffer( blob.getSource() ); - }, - - _parse: function( buffer, noParse ) { - if ( buffer.byteLength < 6 ) { - return; - } - - var dataview = new DataView( buffer ), - offset = 2, - maxOffset = dataview.byteLength - 4, - headLength = offset, - ret = {}, - markerBytes, markerLength, parsers, i; - - if ( dataview.getUint16( 0 ) === 0xffd8 ) { - - while ( offset < maxOffset ) { - markerBytes = dataview.getUint16( offset ); - - if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || - markerBytes === 0xfffe ) { - - markerLength = dataview.getUint16( offset + 2 ) + 2; - - if ( offset + markerLength > dataview.byteLength ) { - break; - } - - parsers = api.parsers[ markerBytes ]; - - if ( !noParse && parsers ) { - for ( i = 0; i < parsers.length; i += 1 ) { - parsers[ i ].call( api, dataview, offset, - markerLength, ret ); - } - } - - offset += markerLength; - headLength = offset; - } else { - break; - } - } - - if ( headLength > 6 ) { - if ( buffer.slice ) { - ret.imageHead = buffer.slice( 2, headLength ); - } else { - // Workaround for IE10, which does not yet - // support ArrayBuffer.slice: - ret.imageHead = new Uint8Array( buffer ) - .subarray( 2, headLength ); - } - } - } - - return ret; - }, - - updateImageHead: function( buffer, head ) { - var data = this._parse( buffer, true ), - buf1, buf2, bodyoffset; - - - bodyoffset = 2; - if ( data.imageHead ) { - bodyoffset = 2 + data.imageHead.byteLength; - } - - if ( buffer.slice ) { - buf2 = buffer.slice( bodyoffset ); - } else { - buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); - } - - buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); - - buf1[ 0 ] = 0xFF; - buf1[ 1 ] = 0xD8; - buf1.set( new Uint8Array( head ), 2 ); - buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); - - return buf1.buffer; - } - }; - - Util.parseMeta = function() { - return api.parse.apply( api, arguments ); - }; - - Util.updateImageHead = function() { - return api.updateImageHead.apply( api, arguments ); - }; - - return api; - }); - /** - * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image - * 暂时项目中只用了orientation. - * - * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. - * @fileOverview EXIF解析 - */ - - // Sample - // ==================================== - // Make : Apple - // Model : iPhone 4S - // Orientation : 1 - // XResolution : 72 [72/1] - // YResolution : 72 [72/1] - // ResolutionUnit : 2 - // Software : QuickTime 7.7.1 - // DateTime : 2013:09:01 22:53:55 - // ExifIFDPointer : 190 - // ExposureTime : 0.058823529411764705 [1/17] - // FNumber : 2.4 [12/5] - // ExposureProgram : Normal program - // ISOSpeedRatings : 800 - // ExifVersion : 0220 - // DateTimeOriginal : 2013:09:01 22:52:51 - // DateTimeDigitized : 2013:09:01 22:52:51 - // ComponentsConfiguration : YCbCr - // ShutterSpeedValue : 4.058893515764426 - // ApertureValue : 2.5260688216892597 [4845/1918] - // BrightnessValue : -0.3126686601998395 - // MeteringMode : Pattern - // Flash : Flash did not fire, compulsory flash mode - // FocalLength : 4.28 [107/25] - // SubjectArea : [4 values] - // FlashpixVersion : 0100 - // ColorSpace : 1 - // PixelXDimension : 2448 - // PixelYDimension : 3264 - // SensingMethod : One-chip color area sensor - // ExposureMode : 0 - // WhiteBalance : Auto white balance - // FocalLengthIn35mmFilm : 35 - // SceneCaptureType : Standard - define('runtime/html5/imagemeta/exif',[ - 'base', - 'runtime/html5/imagemeta' - ], function( Base, ImageMeta ) { - - var EXIF = {}; - - EXIF.ExifMap = function() { - return this; - }; - - EXIF.ExifMap.prototype.map = { - 'Orientation': 0x0112 - }; - - EXIF.ExifMap.prototype.get = function( id ) { - return this[ id ] || this[ this.map[ id ] ]; - }; - - EXIF.exifTagTypes = { - // byte, 8-bit unsigned int: - 1: { - getValue: function( dataView, dataOffset ) { - return dataView.getUint8( dataOffset ); - }, - size: 1 - }, - - // ascii, 8-bit byte: - 2: { - getValue: function( dataView, dataOffset ) { - return String.fromCharCode( dataView.getUint8( dataOffset ) ); - }, - size: 1, - ascii: true - }, - - // short, 16 bit int: - 3: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint16( dataOffset, littleEndian ); - }, - size: 2 - }, - - // long, 32 bit int: - 4: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // rational = two long values, - // first is numerator, second is denominator: - 5: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getUint32( dataOffset, littleEndian ) / - dataView.getUint32( dataOffset + 4, littleEndian ); - }, - size: 8 - }, - - // slong, 32 bit signed int: - 9: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ); - }, - size: 4 - }, - - // srational, two slongs, first is numerator, second is denominator: - 10: { - getValue: function( dataView, dataOffset, littleEndian ) { - return dataView.getInt32( dataOffset, littleEndian ) / - dataView.getInt32( dataOffset + 4, littleEndian ); - }, - size: 8 - } - }; - - // undefined, 8-bit byte, value depending on field: - EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; - - EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, - littleEndian ) { - - var tagType = EXIF.exifTagTypes[ type ], - tagSize, dataOffset, values, i, str, c; - - if ( !tagType ) { - Base.log('Invalid Exif data: Invalid tag type.'); - return; - } - - tagSize = tagType.size * length; - - // Determine if the value is contained in the dataOffset bytes, - // or if the value at the dataOffset is a pointer to the actual data: - dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, - littleEndian ) : (offset + 8); - - if ( dataOffset + tagSize > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid data offset.'); - return; - } - - if ( length === 1 ) { - return tagType.getValue( dataView, dataOffset, littleEndian ); - } - - values = []; - - for ( i = 0; i < length; i += 1 ) { - values[ i ] = tagType.getValue( dataView, - dataOffset + i * tagType.size, littleEndian ); - } - - if ( tagType.ascii ) { - str = ''; - - // Concatenate the chars: - for ( i = 0; i < values.length; i += 1 ) { - c = values[ i ]; - - // Ignore the terminating NULL byte(s): - if ( c === '\u0000' ) { - break; - } - str += c; - } - - return str; - } - return values; - }; - - EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, - data ) { - - var tag = dataView.getUint16( offset, littleEndian ); - data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, - dataView.getUint16( offset + 2, littleEndian ), // tag type - dataView.getUint32( offset + 4, littleEndian ), // tag length - littleEndian ); - }; - - EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, - littleEndian, data ) { - - var tagsNumber, dirEndOffset, i; - - if ( dirOffset + 6 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory offset.'); - return; - } - - tagsNumber = dataView.getUint16( dirOffset, littleEndian ); - dirEndOffset = dirOffset + 2 + 12 * tagsNumber; - - if ( dirEndOffset + 4 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid directory size.'); - return; - } - - for ( i = 0; i < tagsNumber; i += 1 ) { - this.parseExifTag( dataView, tiffOffset, - dirOffset + 2 + 12 * i, // tag offset - littleEndian, data ); - } - - // Return the offset to the next directory: - return dataView.getUint32( dirEndOffset, littleEndian ); - }; - - // EXIF.getExifThumbnail = function(dataView, offset, length) { - // var hexData, - // i, - // b; - // if (!length || offset + length > dataView.byteLength) { - // Base.log('Invalid Exif data: Invalid thumbnail data.'); - // return; - // } - // hexData = []; - // for (i = 0; i < length; i += 1) { - // b = dataView.getUint8(offset + i); - // hexData.push((b < 16 ? '0' : '') + b.toString(16)); - // } - // return 'data:image/jpeg,%' + hexData.join('%'); - // }; - - EXIF.parseExifData = function( dataView, offset, length, data ) { - - var tiffOffset = offset + 10, - littleEndian, dirOffset; - - // Check for the ASCII code for "Exif" (0x45786966): - if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { - // No Exif data, might be XMP data instead - return; - } - if ( tiffOffset + 8 > dataView.byteLength ) { - Base.log('Invalid Exif data: Invalid segment size.'); - return; - } - - // Check for the two null bytes: - if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { - Base.log('Invalid Exif data: Missing byte alignment offset.'); - return; - } - - // Check the byte alignment: - switch ( dataView.getUint16( tiffOffset ) ) { - case 0x4949: - littleEndian = true; - break; - - case 0x4D4D: - littleEndian = false; - break; - - default: - Base.log('Invalid Exif data: Invalid byte alignment marker.'); - return; - } - - // Check for the TIFF tag marker (0x002A): - if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { - Base.log('Invalid Exif data: Missing TIFF marker.'); - return; - } - - // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: - dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); - // Create the exif object to store the tags: - data.exif = new EXIF.ExifMap(); - // Parse the tags of the main image directory and retrieve the - // offset to the next directory, usually the thumbnail directory: - dirOffset = EXIF.parseExifTags( dataView, tiffOffset, - tiffOffset + dirOffset, littleEndian, data ); - - // 尝试读取缩略图 - // if ( dirOffset ) { - // thumbnailData = {exif: {}}; - // dirOffset = EXIF.parseExifTags( - // dataView, - // tiffOffset, - // tiffOffset + dirOffset, - // littleEndian, - // thumbnailData - // ); - - // // Check for JPEG Thumbnail offset: - // if (thumbnailData.exif[0x0201]) { - // data.exif.Thumbnail = EXIF.getExifThumbnail( - // dataView, - // tiffOffset + thumbnailData.exif[0x0201], - // thumbnailData.exif[0x0202] // Thumbnail data length - // ); - // } - // } - }; - - ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); - return EXIF; - }); - /** - * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug - * android里面toDataUrl('image/jpege')得到的结果却是png. - * - * 所以这里没辙,只能借助这个工具 - * @fileOverview jpeg encoder - */ - define('runtime/html5/jpegencoder',[], function( require, exports, module ) { - - /* - Copyright (c) 2008, Adobe Systems Incorporated - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of Adobe Systems Incorporated nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - /* - JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 - - Basic GUI blocking jpeg encoder - */ - - function JPEGEncoder(quality) { - var self = this; - var fround = Math.round; - var ffloor = Math.floor; - var YTable = new Array(64); - var UVTable = new Array(64); - var fdtbl_Y = new Array(64); - var fdtbl_UV = new Array(64); - var YDC_HT; - var UVDC_HT; - var YAC_HT; - var UVAC_HT; - - var bitcode = new Array(65535); - var category = new Array(65535); - var outputfDCTQuant = new Array(64); - var DU = new Array(64); - var byteout = []; - var bytenew = 0; - var bytepos = 7; - - var YDU = new Array(64); - var UDU = new Array(64); - var VDU = new Array(64); - var clt = new Array(256); - var RGB_YUV_TABLE = new Array(2048); - var currentQuality; - - var ZigZag = [ - 0, 1, 5, 6,14,15,27,28, - 2, 4, 7,13,16,26,29,42, - 3, 8,12,17,25,30,41,43, - 9,11,18,24,31,40,44,53, - 10,19,23,32,39,45,52,54, - 20,22,33,38,46,51,55,60, - 21,34,37,47,50,56,59,61, - 35,36,48,49,57,58,62,63 - ]; - - var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; - var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; - var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; - var std_ac_luminance_values = [ - 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, - 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, - 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, - 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, - 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, - 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, - 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, - 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, - 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, - 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, - 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, - 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, - 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, - 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, - 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, - 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, - 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, - 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, - 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - ]; - - var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; - var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; - var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; - var std_ac_chrominance_values = [ - 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, - 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, - 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, - 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, - 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, - 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, - 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, - 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, - 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, - 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, - 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, - 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, - 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, - 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, - 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, - 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, - 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, - 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, - 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, - 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - ]; - - function initQuantTables(sf){ - var YQT = [ - 16, 11, 10, 16, 24, 40, 51, 61, - 12, 12, 14, 19, 26, 58, 60, 55, - 14, 13, 16, 24, 40, 57, 69, 56, - 14, 17, 22, 29, 51, 87, 80, 62, - 18, 22, 37, 56, 68,109,103, 77, - 24, 35, 55, 64, 81,104,113, 92, - 49, 64, 78, 87,103,121,120,101, - 72, 92, 95, 98,112,100,103, 99 - ]; - - for (var i = 0; i < 64; i++) { - var t = ffloor((YQT[i]*sf+50)/100); - if (t < 1) { - t = 1; - } else if (t > 255) { - t = 255; - } - YTable[ZigZag[i]] = t; - } - var UVQT = [ - 17, 18, 24, 47, 99, 99, 99, 99, - 18, 21, 26, 66, 99, 99, 99, 99, - 24, 26, 56, 99, 99, 99, 99, 99, - 47, 66, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99 - ]; - for (var j = 0; j < 64; j++) { - var u = ffloor((UVQT[j]*sf+50)/100); - if (u < 1) { - u = 1; - } else if (u > 255) { - u = 255; - } - UVTable[ZigZag[j]] = u; - } - var aasf = [ - 1.0, 1.387039845, 1.306562965, 1.175875602, - 1.0, 0.785694958, 0.541196100, 0.275899379 - ]; - var k = 0; - for (var row = 0; row < 8; row++) - { - for (var col = 0; col < 8; col++) - { - fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); - fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); - k++; - } - } - } - - function computeHuffmanTbl(nrcodes, std_table){ - var codevalue = 0; - var pos_in_table = 0; - var HT = new Array(); - for (var k = 1; k <= 16; k++) { - for (var j = 1; j <= nrcodes[k]; j++) { - HT[std_table[pos_in_table]] = []; - HT[std_table[pos_in_table]][0] = codevalue; - HT[std_table[pos_in_table]][1] = k; - pos_in_table++; - codevalue++; - } - codevalue*=2; - } - return HT; - } - - function initHuffmanTbl() - { - YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); - UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); - YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); - UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); - } - - function initCategoryNumber() - { - var nrlower = 1; - var nrupper = 2; - for (var cat = 1; cat <= 15; cat++) { - //Positive numbers - for (var nr = nrlower; nr>0] = 38470 * i; - RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; - RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; - RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; - RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; - RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; - RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; - } - } - - // IO functions - function writeBits(bs) - { - var value = bs[0]; - var posval = bs[1]-1; - while ( posval >= 0 ) { - if (value & (1 << posval) ) { - bytenew |= (1 << bytepos); - } - posval--; - bytepos--; - if (bytepos < 0) { - if (bytenew == 0xFF) { - writeByte(0xFF); - writeByte(0); - } - else { - writeByte(bytenew); - } - bytepos=7; - bytenew=0; - } - } - } - - function writeByte(value) - { - byteout.push(clt[value]); // write char directly instead of converting later - } - - function writeWord(value) - { - writeByte((value>>8)&0xFF); - writeByte((value )&0xFF); - } - - // DCT & quantization core - function fDCTQuant(data, fdtbl) - { - var d0, d1, d2, d3, d4, d5, d6, d7; - /* Pass 1: process rows. */ - var dataOff=0; - var i; - var I8 = 8; - var I64 = 64; - for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); - //outputfDCTQuant[i] = fround(fDCTQuant); - - } - return outputfDCTQuant; - } - - function writeAPP0() - { - writeWord(0xFFE0); // marker - writeWord(16); // length - writeByte(0x4A); // J - writeByte(0x46); // F - writeByte(0x49); // I - writeByte(0x46); // F - writeByte(0); // = "JFIF",'\0' - writeByte(1); // versionhi - writeByte(1); // versionlo - writeByte(0); // xyunits - writeWord(1); // xdensity - writeWord(1); // ydensity - writeByte(0); // thumbnwidth - writeByte(0); // thumbnheight - } - - function writeSOF0(width, height) - { - writeWord(0xFFC0); // marker - writeWord(17); // length, truecolor YUV JPG - writeByte(8); // precision - writeWord(height); - writeWord(width); - writeByte(3); // nrofcomponents - writeByte(1); // IdY - writeByte(0x11); // HVY - writeByte(0); // QTY - writeByte(2); // IdU - writeByte(0x11); // HVU - writeByte(1); // QTU - writeByte(3); // IdV - writeByte(0x11); // HVV - writeByte(1); // QTV - } - - function writeDQT() - { - writeWord(0xFFDB); // marker - writeWord(132); // length - writeByte(0); - for (var i=0; i<64; i++) { - writeByte(YTable[i]); - } - writeByte(1); - for (var j=0; j<64; j++) { - writeByte(UVTable[j]); - } - } - - function writeDHT() - { - writeWord(0xFFC4); // marker - writeWord(0x01A2); // length - - writeByte(0); // HTYDCinfo - for (var i=0; i<16; i++) { - writeByte(std_dc_luminance_nrcodes[i+1]); - } - for (var j=0; j<=11; j++) { - writeByte(std_dc_luminance_values[j]); - } - - writeByte(0x10); // HTYACinfo - for (var k=0; k<16; k++) { - writeByte(std_ac_luminance_nrcodes[k+1]); - } - for (var l=0; l<=161; l++) { - writeByte(std_ac_luminance_values[l]); - } - - writeByte(1); // HTUDCinfo - for (var m=0; m<16; m++) { - writeByte(std_dc_chrominance_nrcodes[m+1]); - } - for (var n=0; n<=11; n++) { - writeByte(std_dc_chrominance_values[n]); - } - - writeByte(0x11); // HTUACinfo - for (var o=0; o<16; o++) { - writeByte(std_ac_chrominance_nrcodes[o+1]); - } - for (var p=0; p<=161; p++) { - writeByte(std_ac_chrominance_values[p]); - } - } - - function writeSOS() - { - writeWord(0xFFDA); // marker - writeWord(12); // length - writeByte(3); // nrofcomponents - writeByte(1); // IdY - writeByte(0); // HTY - writeByte(2); // IdU - writeByte(0x11); // HTU - writeByte(3); // IdV - writeByte(0x11); // HTV - writeByte(0); // Ss - writeByte(0x3f); // Se - writeByte(0); // Bf - } - - function processDU(CDU, fdtbl, DC, HTDC, HTAC){ - var EOB = HTAC[0x00]; - var M16zeroes = HTAC[0xF0]; - var pos; - var I16 = 16; - var I63 = 63; - var I64 = 64; - var DU_DCT = fDCTQuant(CDU, fdtbl); - //ZigZag reorder - for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; - //end0pos = first element in reverse order !=0 - if ( end0pos == 0) { - writeBits(EOB); - return DC; - } - var i = 1; - var lng; - while ( i <= end0pos ) { - var startpos = i; - for (; (DU[i]==0) && (i<=end0pos); ++i) {} - var nrzeroes = i-startpos; - if ( nrzeroes >= I16 ) { - lng = nrzeroes>>4; - for (var nrmarker=1; nrmarker <= lng; ++nrmarker) - writeBits(M16zeroes); - nrzeroes = nrzeroes&0xF; - } - pos = 32767+DU[i]; - writeBits(HTAC[(nrzeroes<<4)+category[pos]]); - writeBits(bitcode[pos]); - i++; - } - if ( end0pos != I63 ) { - writeBits(EOB); - } - return DC; - } - - function initCharLookupTable(){ - var sfcc = String.fromCharCode; - for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 - clt[i] = sfcc(i); - } - } - - this.encode = function(image,quality) // image data object - { - // var time_start = new Date().getTime(); - - if(quality) setQuality(quality); - - // Initialize bit writer - byteout = new Array(); - bytenew=0; - bytepos=7; - - // Add JPEG headers - writeWord(0xFFD8); // SOI - writeAPP0(); - writeDQT(); - writeSOF0(image.width,image.height); - writeDHT(); - writeSOS(); - - - // Encode 8x8 macroblocks - var DCY=0; - var DCU=0; - var DCV=0; - - bytenew=0; - bytepos=7; - - - this.encode.displayName = "_encode_"; - - var imageData = image.data; - var width = image.width; - var height = image.height; - - var quadWidth = width*4; - var tripleWidth = width*3; - - var x, y = 0; - var r, g, b; - var start,p, col,row,pos; - while(y < height){ - x = 0; - while(x < quadWidth){ - start = quadWidth * y + x; - p = start; - col = -1; - row = 0; - - for(pos=0; pos < 64; pos++){ - row = pos >> 3;// /8 - col = ( pos & 7 ) * 4; // %8 - p = start + ( row * quadWidth ) + col; - - if(y+row >= height){ // padding bottom - p-= (quadWidth*(y+1+row-height)); - } - - if(x+col >= quadWidth){ // padding right - p-= ((x+col) - quadWidth +4) - } - - r = imageData[ p++ ]; - g = imageData[ p++ ]; - b = imageData[ p++ ]; - - - /* // calculate YUV values dynamically - YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 - UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); - VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); - */ - - // use lookup table (slightly faster) - YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; - UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; - VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; - - } - - DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - x+=32; - } - y+=8; - } - - - //////////////////////////////////////////////////////////////// - - // Do the bit alignment of the EOI marker - if ( bytepos >= 0 ) { - var fillbits = []; - fillbits[1] = bytepos+1; - fillbits[0] = (1<<(bytepos+1))-1; - writeBits(fillbits); - } - - writeWord(0xFFD9); //EOI - - var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); - - byteout = []; - - // benchmarking - // var duration = new Date().getTime() - time_start; - // console.log('Encoding time: '+ currentQuality + 'ms'); - // - - return jpegDataUri - } - - function setQuality(quality){ - if (quality <= 0) { - quality = 1; - } - if (quality > 100) { - quality = 100; - } - - if(currentQuality == quality) return // don't recalc if unchanged - - var sf = 0; - if (quality < 50) { - sf = Math.floor(5000 / quality); - } else { - sf = Math.floor(200 - quality*2); - } - - initQuantTables(sf); - currentQuality = quality; - // console.log('Quality set to: '+quality +'%'); - } - - function init(){ - // var time_start = new Date().getTime(); - if(!quality) quality = 50; - // Create tables - initCharLookupTable() - initHuffmanTbl(); - initCategoryNumber(); - initRGBYUVTable(); - - setQuality(quality); - // var duration = new Date().getTime() - time_start; - // console.log('Initialization '+ duration + 'ms'); - } - - init(); - - }; - - JPEGEncoder.encode = function( data, quality ) { - var encoder = new JPEGEncoder( quality ); - - return encoder.encode( data ); - } - - return JPEGEncoder; - }); - /** - * @fileOverview Fix android canvas.toDataUrl bug. - */ - define('runtime/html5/androidpatch',[ - 'runtime/html5/util', - 'runtime/html5/jpegencoder', - 'base' - ], function( Util, encoder, Base ) { - var origin = Util.canvasToDataUrl, - supportJpeg; - - Util.canvasToDataUrl = function( canvas, type, quality ) { - var ctx, w, h, fragement, parts; - - // 非android手机直接跳过。 - if ( !Base.os.android ) { - return origin.apply( null, arguments ); - } - - // 检测是否canvas支持jpeg导出,根据数据格式来判断。 - // JPEG 前两位分别是:255, 216 - if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { - fragement = origin.apply( null, arguments ); - - parts = fragement.split(','); - - if ( ~parts[ 0 ].indexOf('base64') ) { - fragement = atob( parts[ 1 ] ); - } else { - fragement = decodeURIComponent( parts[ 1 ] ); - } - - fragement = fragement.substring( 0, 2 ); - - supportJpeg = fragement.charCodeAt( 0 ) === 255 && - fragement.charCodeAt( 1 ) === 216; - } - - // 只有在android环境下才修复 - if ( type === 'image/jpeg' && !supportJpeg ) { - w = canvas.width; - h = canvas.height; - ctx = canvas.getContext('2d'); - - return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); - } - - return origin.apply( null, arguments ); - }; - }); - /** - * @fileOverview Image - */ - define('runtime/html5/image',[ - 'base', - 'runtime/html5/runtime', - 'runtime/html5/util' - ], function( Base, Html5Runtime, Util ) { - - var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; - - return Html5Runtime.register( 'Image', { - - // flag: 标记是否被修改过。 - modified: false, - - init: function() { - var me = this, - img = new Image(); - - img.onload = function() { - - me._info = { - type: me.type, - width: this.width, - height: this.height - }; - - // 读取meta信息。 - if ( !me._metas && 'image/jpeg' === me.type ) { - Util.parseMeta( me._blob, function( error, ret ) { - me._metas = ret; - me.owner.trigger('load'); - }); - } else { - me.owner.trigger('load'); - } - }; - - img.onerror = function() { - me.owner.trigger('error'); - }; - - me._img = img; - }, - - loadFromBlob: function( blob ) { - var me = this, - img = me._img; - - me._blob = blob; - me.type = blob.type; - img.src = Util.createObjectURL( blob.getSource() ); - me.owner.once( 'load', function() { - Util.revokeObjectURL( img.src ); - }); - }, - - resize: function( width, height ) { - var canvas = this._canvas || - (this._canvas = document.createElement('canvas')); - - this._resize( this._img, canvas, width, height ); - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'resize' ); - }, - - crop: function( x, y, w, h, s ) { - var cvs = this._canvas || - (this._canvas = document.createElement('canvas')), - opts = this.options, - img = this._img, - iw = img.naturalWidth, - ih = img.naturalHeight, - orientation = this.getOrientation(); - - s = s || 1; - - // todo 解决 orientation 的问题。 - // values that require 90 degree rotation - // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // switch ( orientation ) { - // case 6: - // tmp = x; - // x = y; - // y = iw * s - tmp - w; - // console.log(ih * s, tmp, w) - // break; - // } - - // (w ^= h, h ^= w, w ^= h); - // } - - cvs.width = w; - cvs.height = h; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); - - this._blob = null; // 没用了,可以删掉了。 - this.modified = true; - this.owner.trigger( 'complete', 'crop' ); - }, - - getAsBlob: function( type ) { - var blob = this._blob, - opts = this.options, - canvas; - - type = type || this.type; - - // blob需要重新生成。 - if ( this.modified || this.type !== type ) { - canvas = this._canvas; - - if ( type === 'image/jpeg' ) { - - blob = Util.canvasToDataUrl( canvas, type, opts.quality ); - - if ( opts.preserveHeaders && this._metas && - this._metas.imageHead ) { - - blob = Util.dataURL2ArrayBuffer( blob ); - blob = Util.updateImageHead( blob, - this._metas.imageHead ); - blob = Util.arrayBufferToBlob( blob, type ); - return blob; - } - } else { - blob = Util.canvasToDataUrl( canvas, type ); - } - - blob = Util.dataURL2Blob( blob ); - } - - return blob; - }, - - getAsDataUrl: function( type ) { - var opts = this.options; - - type = type || this.type; - - if ( type === 'image/jpeg' ) { - return Util.canvasToDataUrl( this._canvas, type, opts.quality ); - } else { - return this._canvas.toDataURL( type ); - } - }, - - getOrientation: function() { - return this._metas && this._metas.exif && - this._metas.exif.get('Orientation') || 1; - }, - - info: function( val ) { - - // setter - if ( val ) { - this._info = val; - return this; - } - - // getter - return this._info; - }, - - meta: function( val ) { - - // setter - if ( val ) { - this._meta = val; - return this; - } - - // getter - return this._meta; - }, - - destroy: function() { - var canvas = this._canvas; - this._img.onload = null; - - if ( canvas ) { - canvas.getContext('2d') - .clearRect( 0, 0, canvas.width, canvas.height ); - canvas.width = canvas.height = 0; - this._canvas = null; - } - - // 释放内存。非常重要,否则释放不了image的内存。 - this._img.src = BLANK; - this._img = this._blob = null; - }, - - _resize: function( img, cvs, width, height ) { - var opts = this.options, - naturalWidth = img.width, - naturalHeight = img.height, - orientation = this.getOrientation(), - scale, w, h, x, y; - - // values that require 90 degree rotation - if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { - - // 交换width, height的值。 - width ^= height; - height ^= width; - width ^= height; - } - - scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, - height / naturalHeight ); - - // 不允许放大。 - opts.allowMagnify || (scale = Math.min( 1, scale )); - - w = naturalWidth * scale; - h = naturalHeight * scale; - - if ( opts.crop ) { - cvs.width = width; - cvs.height = height; - } else { - cvs.width = w; - cvs.height = h; - } - - x = (cvs.width - w) / 2; - y = (cvs.height - h) / 2; - - opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); - - this._renderImageToCanvas( cvs, img, x, y, w, h ); - }, - - _rotate2Orientaion: function( canvas, orientation ) { - var width = canvas.width, - height = canvas.height, - ctx = canvas.getContext('2d'); - - switch ( orientation ) { - case 5: - case 6: - case 7: - case 8: - canvas.width = height; - canvas.height = width; - break; - } - - switch ( orientation ) { - case 2: // horizontal flip - ctx.translate( width, 0 ); - ctx.scale( -1, 1 ); - break; - - case 3: // 180 rotate left - ctx.translate( width, height ); - ctx.rotate( Math.PI ); - break; - - case 4: // vertical flip - ctx.translate( 0, height ); - ctx.scale( 1, -1 ); - break; - - case 5: // vertical flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.scale( 1, -1 ); - break; - - case 6: // 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( 0, -height ); - break; - - case 7: // horizontal flip + 90 rotate right - ctx.rotate( 0.5 * Math.PI ); - ctx.translate( width, -height ); - ctx.scale( -1, 1 ); - break; - - case 8: // 90 rotate left - ctx.rotate( -0.5 * Math.PI ); - ctx.translate( -width, 0 ); - break; - } - }, - - // https://github.com/stomita/ios-imagefile-megapixel/ - // blob/master/src/megapix-image.js - _renderImageToCanvas: (function() { - - // 如果不是ios, 不需要这么复杂! - if ( !Base.os.ios ) { - return function( canvas ) { - var args = Base.slice( arguments, 1 ), - ctx = canvas.getContext('2d'); - - ctx.drawImage.apply( ctx, args ); - }; - } - - /** - * Detecting vertical squash in loaded image. - * Fixes a bug which squash image vertically while drawing into - * canvas for some images. - */ - function detectVerticalSquash( img, iw, ih ) { - var canvas = document.createElement('canvas'), - ctx = canvas.getContext('2d'), - sy = 0, - ey = ih, - py = ih, - data, alpha, ratio; - - - canvas.width = 1; - canvas.height = ih; - ctx.drawImage( img, 0, 0 ); - data = ctx.getImageData( 0, 0, 1, ih ).data; - - // search image edge pixel position in case - // it is squashed vertically. - while ( py > sy ) { - alpha = data[ (py - 1) * 4 + 3 ]; - - if ( alpha === 0 ) { - ey = py; - } else { - sy = py; - } - - py = (ey + sy) >> 1; - } - - ratio = (py / ih); - return (ratio === 0) ? 1 : ratio; - } - - // fix ie7 bug - // http://stackoverflow.com/questions/11929099/ - // html5-canvas-drawimage-ratio-bug-ios - if ( Base.os.ios >= 7 ) { - return function( canvas, img, x, y, w, h ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - vertSquashRatio = detectVerticalSquash( img, iw, ih ); - - return canvas.getContext('2d').drawImage( img, 0, 0, - iw * vertSquashRatio, ih * vertSquashRatio, - x, y, w, h ); - }; - } - - /** - * Detect subsampling in loaded image. - * In iOS, larger images than 2M pixels may be - * subsampled in rendering. - */ - function detectSubsampling( img ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - canvas, ctx; - - // subsampling may happen overmegapixel image - if ( iw * ih > 1024 * 1024 ) { - canvas = document.createElement('canvas'); - canvas.width = canvas.height = 1; - ctx = canvas.getContext('2d'); - ctx.drawImage( img, -iw + 1, 0 ); - - // subsampled image becomes half smaller in rendering size. - // check alpha channel value to confirm image is covering - // edge pixel or not. if alpha value is 0 - // image is not covering, hence subsampled. - return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; - } else { - return false; - } - } - - - return function( canvas, img, x, y, width, height ) { - var iw = img.naturalWidth, - ih = img.naturalHeight, - ctx = canvas.getContext('2d'), - subsampled = detectSubsampling( img ), - doSquash = this.type === 'image/jpeg', - d = 1024, - sy = 0, - dy = 0, - tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; - - if ( subsampled ) { - iw /= 2; - ih /= 2; - } - - ctx.save(); - tmpCanvas = document.createElement('canvas'); - tmpCanvas.width = tmpCanvas.height = d; - - tmpCtx = tmpCanvas.getContext('2d'); - vertSquashRatio = doSquash ? - detectVerticalSquash( img, iw, ih ) : 1; - - dw = Math.ceil( d * width / iw ); - dh = Math.ceil( d * height / ih / vertSquashRatio ); - - while ( sy < ih ) { - sx = 0; - dx = 0; - while ( sx < iw ) { - tmpCtx.clearRect( 0, 0, d, d ); - tmpCtx.drawImage( img, -sx, -sy ); - ctx.drawImage( tmpCanvas, 0, 0, d, d, - x + dx, y + dy, dw, dh ); - sx += d; - dx += dw; - } - sy += d; - dy += dh; - } - ctx.restore(); - tmpCanvas = tmpCtx = null; - }; - })() - }); - }); - /** - * @fileOverview Transport - * @todo 支持chunked传输,优势: - * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, - * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 - */ - define('runtime/html5/transport',[ - 'base', - 'runtime/html5/runtime' - ], function( Base, Html5Runtime ) { - - var noop = Base.noop, - $ = Base.$; - - return Html5Runtime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - formData, binary, fr; - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.getSource(); - } else { - formData = new FormData(); - $.each( owner._formData, function( k, v ) { - formData.append( k, v ); - }); - - formData.append( opts.fileVal, blob.getSource(), - opts.filename || owner._formData.name || '' ); - } - - if ( opts.withCredentials && 'withCredentials' in xhr ) { - xhr.open( opts.method, server, true ); - xhr.withCredentials = true; - } else { - xhr.open( opts.method, server ); - } - - this._setRequestHeader( xhr, opts.headers ); - - if ( binary ) { - // 强制设置成 content-type 为文件流。 - xhr.overrideMimeType && - xhr.overrideMimeType('application/octet-stream'); - - // android直接发送blob会导致服务端接收到的是空文件。 - // bug详情。 - // https://code.google.com/p/android/issues/detail?id=39882 - // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 - if ( Base.os.android ) { - fr = new FileReader(); - - fr.onload = function() { - xhr.send( this.result ); - fr = fr.onload = null; - }; - - fr.readAsArrayBuffer( binary ); - } else { - xhr.send( binary ); - } - } else { - xhr.send( formData ); - } - }, - - getResponse: function() { - return this._response; - }, - - getResponseAsJson: function() { - return this._parseJson( this._response ); - }, - - getStatus: function() { - return this._status; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - xhr.abort(); - - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new XMLHttpRequest(), - opts = this.options; - - if ( opts.withCredentials && !('withCredentials' in xhr) && - typeof XDomainRequest !== 'undefined' ) { - xhr = new XDomainRequest(); - } - - xhr.upload.onprogress = function( e ) { - var percentage = 0; - - if ( e.lengthComputable ) { - percentage = e.loaded / e.total; - } - - return me.trigger( 'progress', percentage ); - }; - - xhr.onreadystatechange = function() { - - if ( xhr.readyState !== 4 ) { - return; - } - - xhr.upload.onprogress = noop; - xhr.onreadystatechange = noop; - me._xhr = null; - me._status = xhr.status; - - if ( xhr.status >= 200 && xhr.status < 300 ) { - me._response = xhr.responseText; - return me.trigger('load'); - } else if ( xhr.status >= 500 && xhr.status < 600 ) { - me._response = xhr.responseText; - return me.trigger( 'error', 'server' ); - } - - - return me.trigger( 'error', me._status ? 'http' : 'abort' ); - }; - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.setRequestHeader( key, val ); - }); - }, - - _parseJson: function( str ) { - var json; - - try { - json = JSON.parse( str ); - } catch ( ex ) { - json = {}; - } - - return json; - } - }); - }); - /** - * @fileOverview Transport flash实现 - */ - define('runtime/html5/md5',[ - 'runtime/html5/runtime' - ], function( FlashRuntime ) { - - /* - * Fastest md5 implementation around (JKM md5) - * Credits: Joseph Myers - * - * @see http://www.myersdaily.org/joseph/javascript/md5-text.html - * @see http://jsperf.com/md5-shootout/7 - */ - - /* this function is much faster, - so if possible we use it. Some IEs - are the only ones I know of that - need the idiotic second function, - generated by an if clause. */ - var add32 = function (a, b) { - return (a + b) & 0xFFFFFFFF; - }, - - cmn = function (q, a, b, x, s, t) { - a = add32(add32(a, q), add32(x, t)); - return add32((a << s) | (a >>> (32 - s)), b); - }, - - ff = function (a, b, c, d, x, s, t) { - return cmn((b & c) | ((~b) & d), a, b, x, s, t); - }, - - gg = function (a, b, c, d, x, s, t) { - return cmn((b & d) | (c & (~d)), a, b, x, s, t); - }, - - hh = function (a, b, c, d, x, s, t) { - return cmn(b ^ c ^ d, a, b, x, s, t); - }, - - ii = function (a, b, c, d, x, s, t) { - return cmn(c ^ (b | (~d)), a, b, x, s, t); - }, - - md5cycle = function (x, k) { - var a = x[0], - b = x[1], - c = x[2], - d = x[3]; - - a = ff(a, b, c, d, k[0], 7, -680876936); - d = ff(d, a, b, c, k[1], 12, -389564586); - c = ff(c, d, a, b, k[2], 17, 606105819); - b = ff(b, c, d, a, k[3], 22, -1044525330); - a = ff(a, b, c, d, k[4], 7, -176418897); - d = ff(d, a, b, c, k[5], 12, 1200080426); - c = ff(c, d, a, b, k[6], 17, -1473231341); - b = ff(b, c, d, a, k[7], 22, -45705983); - a = ff(a, b, c, d, k[8], 7, 1770035416); - d = ff(d, a, b, c, k[9], 12, -1958414417); - c = ff(c, d, a, b, k[10], 17, -42063); - b = ff(b, c, d, a, k[11], 22, -1990404162); - a = ff(a, b, c, d, k[12], 7, 1804603682); - d = ff(d, a, b, c, k[13], 12, -40341101); - c = ff(c, d, a, b, k[14], 17, -1502002290); - b = ff(b, c, d, a, k[15], 22, 1236535329); - - a = gg(a, b, c, d, k[1], 5, -165796510); - d = gg(d, a, b, c, k[6], 9, -1069501632); - c = gg(c, d, a, b, k[11], 14, 643717713); - b = gg(b, c, d, a, k[0], 20, -373897302); - a = gg(a, b, c, d, k[5], 5, -701558691); - d = gg(d, a, b, c, k[10], 9, 38016083); - c = gg(c, d, a, b, k[15], 14, -660478335); - b = gg(b, c, d, a, k[4], 20, -405537848); - a = gg(a, b, c, d, k[9], 5, 568446438); - d = gg(d, a, b, c, k[14], 9, -1019803690); - c = gg(c, d, a, b, k[3], 14, -187363961); - b = gg(b, c, d, a, k[8], 20, 1163531501); - a = gg(a, b, c, d, k[13], 5, -1444681467); - d = gg(d, a, b, c, k[2], 9, -51403784); - c = gg(c, d, a, b, k[7], 14, 1735328473); - b = gg(b, c, d, a, k[12], 20, -1926607734); - - a = hh(a, b, c, d, k[5], 4, -378558); - d = hh(d, a, b, c, k[8], 11, -2022574463); - c = hh(c, d, a, b, k[11], 16, 1839030562); - b = hh(b, c, d, a, k[14], 23, -35309556); - a = hh(a, b, c, d, k[1], 4, -1530992060); - d = hh(d, a, b, c, k[4], 11, 1272893353); - c = hh(c, d, a, b, k[7], 16, -155497632); - b = hh(b, c, d, a, k[10], 23, -1094730640); - a = hh(a, b, c, d, k[13], 4, 681279174); - d = hh(d, a, b, c, k[0], 11, -358537222); - c = hh(c, d, a, b, k[3], 16, -722521979); - b = hh(b, c, d, a, k[6], 23, 76029189); - a = hh(a, b, c, d, k[9], 4, -640364487); - d = hh(d, a, b, c, k[12], 11, -421815835); - c = hh(c, d, a, b, k[15], 16, 530742520); - b = hh(b, c, d, a, k[2], 23, -995338651); - - a = ii(a, b, c, d, k[0], 6, -198630844); - d = ii(d, a, b, c, k[7], 10, 1126891415); - c = ii(c, d, a, b, k[14], 15, -1416354905); - b = ii(b, c, d, a, k[5], 21, -57434055); - a = ii(a, b, c, d, k[12], 6, 1700485571); - d = ii(d, a, b, c, k[3], 10, -1894986606); - c = ii(c, d, a, b, k[10], 15, -1051523); - b = ii(b, c, d, a, k[1], 21, -2054922799); - a = ii(a, b, c, d, k[8], 6, 1873313359); - d = ii(d, a, b, c, k[15], 10, -30611744); - c = ii(c, d, a, b, k[6], 15, -1560198380); - b = ii(b, c, d, a, k[13], 21, 1309151649); - a = ii(a, b, c, d, k[4], 6, -145523070); - d = ii(d, a, b, c, k[11], 10, -1120210379); - c = ii(c, d, a, b, k[2], 15, 718787259); - b = ii(b, c, d, a, k[9], 21, -343485551); - - x[0] = add32(a, x[0]); - x[1] = add32(b, x[1]); - x[2] = add32(c, x[2]); - x[3] = add32(d, x[3]); - }, - - /* there needs to be support for Unicode here, - * unless we pretend that we can redefine the MD-5 - * algorithm for multi-byte characters (perhaps - * by adding every four 16-bit characters and - * shortening the sum to 32 bits). Otherwise - * I suggest performing MD-5 as if every character - * was two bytes--e.g., 0040 0025 = @%--but then - * how will an ordinary MD-5 sum be matched? - * There is no way to standardize text to something - * like UTF-8 before transformation; speed cost is - * utterly prohibitive. The JavaScript standard - * itself needs to look at this: it should start - * providing access to strings as preformed UTF-8 - * 8-bit unsigned value arrays. - */ - md5blk = function (s) { - var md5blks = [], - i; /* Andy King said do it this way. */ - - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); - } - return md5blks; - }, - - md5blk_array = function (a) { - var md5blks = [], - i; /* Andy King said do it this way. */ - - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); - } - return md5blks; - }, - - md51 = function (s) { - var n = s.length, - state = [1732584193, -271733879, -1732584194, 271733878], - i, - length, - tail, - tmp, - lo, - hi; - - for (i = 64; i <= n; i += 64) { - md5cycle(state, md5blk(s.substring(i - 64, i))); - } - s = s.substring(i - 64); - length = s.length; - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); - } - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Beware that the final length might not fit in 32 bits so we take care of that - tmp = n * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - - md5cycle(state, tail); - return state; - }, - - md51_array = function (a) { - var n = a.length, - state = [1732584193, -271733879, -1732584194, 271733878], - i, - length, - tail, - tmp, - lo, - hi; - - for (i = 64; i <= n; i += 64) { - md5cycle(state, md5blk_array(a.subarray(i - 64, i))); - } - - // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 - // containing the last element of the parent array if the sub array specified starts - // beyond the length of the parent array - weird. - // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue - a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); - - length = a.length; - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= a[i] << ((i % 4) << 3); - } - - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Beware that the final length might not fit in 32 bits so we take care of that - tmp = n * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - - md5cycle(state, tail); - - return state; - }, - - hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'], - - rhex = function (n) { - var s = '', - j; - for (j = 0; j < 4; j += 1) { - s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; - } - return s; - }, - - hex = function (x) { - var i; - for (i = 0; i < x.length; i += 1) { - x[i] = rhex(x[i]); - } - return x.join(''); - }, - - md5 = function (s) { - return hex(md51(s)); - }, - - - - //////////////////////////////////////////////////////////////////////////// - - /** - * SparkMD5 OOP implementation. - * - * Use this class to perform an incremental md5, otherwise use the - * static methods instead. - */ - SparkMD5 = function () { - // call reset to init the instance - this.reset(); - }; - - - // In some cases the fast add32 function cannot be used.. - if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') { - add32 = function (x, y) { - var lsw = (x & 0xFFFF) + (y & 0xFFFF), - msw = (x >> 16) + (y >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xFFFF); - }; - } - - - /** - * Appends a string. - * A conversion will be applied if an utf8 string is detected. - * - * @param {String} str The string to be appended - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.append = function (str) { - // converts the string to utf8 bytes if necessary - if (/[\u0080-\uFFFF]/.test(str)) { - str = unescape(encodeURIComponent(str)); - } - - // then append as binary - this.appendBinary(str); - - return this; - }; - - /** - * Appends a binary string. - * - * @param {String} contents The binary string to be appended - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.appendBinary = function (contents) { - this._buff += contents; - this._length += contents.length; - - var length = this._buff.length, - i; - - for (i = 64; i <= length; i += 64) { - md5cycle(this._state, md5blk(this._buff.substring(i - 64, i))); - } - - this._buff = this._buff.substr(i - 64); - - return this; - }; - - /** - * Finishes the incremental computation, reseting the internal state and - * returning the result. - * Use the raw parameter to obtain the raw result instead of the hex one. - * - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.prototype.end = function (raw) { - var buff = this._buff, - length = buff.length, - i, - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ret; - - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); - } - - this._finish(tail, length); - ret = !!raw ? this._state : hex(this._state); - - this.reset(); - - return ret; - }; - - /** - * Finish the final calculation based on the tail. - * - * @param {Array} tail The tail (will be modified) - * @param {Number} length The length of the remaining buffer - */ - SparkMD5.prototype._finish = function (tail, length) { - var i = length, - tmp, - lo, - hi; - - tail[i >> 2] |= 0x80 << ((i % 4) << 3); - if (i > 55) { - md5cycle(this._state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; - } - } - - // Do the final computation based on the tail and length - // Beware that the final length may not fit in 32 bits so we take care of that - tmp = this._length * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - - tail[14] = lo; - tail[15] = hi; - md5cycle(this._state, tail); - }; - - /** - * Resets the internal state of the computation. - * - * @return {SparkMD5} The instance itself - */ - SparkMD5.prototype.reset = function () { - this._buff = ""; - this._length = 0; - this._state = [1732584193, -271733879, -1732584194, 271733878]; - - return this; - }; - - /** - * Releases memory used by the incremental buffer and other aditional - * resources. If you plan to use the instance again, use reset instead. - */ - SparkMD5.prototype.destroy = function () { - delete this._state; - delete this._buff; - delete this._length; - }; - - - /** - * Performs the md5 hash on a string. - * A conversion will be applied if utf8 string is detected. - * - * @param {String} str The string - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.hash = function (str, raw) { - // converts the string to utf8 bytes if necessary - if (/[\u0080-\uFFFF]/.test(str)) { - str = unescape(encodeURIComponent(str)); - } - - var hash = md51(str); - - return !!raw ? hash : hex(hash); - }; - - /** - * Performs the md5 hash on a binary string. - * - * @param {String} content The binary string - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.hashBinary = function (content, raw) { - var hash = md51(content); - - return !!raw ? hash : hex(hash); - }; - - /** - * SparkMD5 OOP implementation for array buffers. - * - * Use this class to perform an incremental md5 ONLY for array buffers. - */ - SparkMD5.ArrayBuffer = function () { - // call reset to init the instance - this.reset(); - }; - - //////////////////////////////////////////////////////////////////////////// - - /** - * Appends an array buffer. - * - * @param {ArrayBuffer} arr The array to be appended - * - * @return {SparkMD5.ArrayBuffer} The instance itself - */ - SparkMD5.ArrayBuffer.prototype.append = function (arr) { - // TODO: we could avoid the concatenation here but the algorithm would be more complex - // if you find yourself needing extra performance, please make a PR. - var buff = this._concatArrayBuffer(this._buff, arr), - length = buff.length, - i; - - this._length += arr.byteLength; - - for (i = 64; i <= length; i += 64) { - md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i))); - } - - // Avoids IE10 weirdness (documented above) - this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0); - - return this; - }; - - /** - * Finishes the incremental computation, reseting the internal state and - * returning the result. - * Use the raw parameter to obtain the raw result instead of the hex one. - * - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.ArrayBuffer.prototype.end = function (raw) { - var buff = this._buff, - length = buff.length, - tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - i, - ret; - - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= buff[i] << ((i % 4) << 3); - } - - this._finish(tail, length); - ret = !!raw ? this._state : hex(this._state); - - this.reset(); - - return ret; - }; - - SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; - - /** - * Resets the internal state of the computation. - * - * @return {SparkMD5.ArrayBuffer} The instance itself - */ - SparkMD5.ArrayBuffer.prototype.reset = function () { - this._buff = new Uint8Array(0); - this._length = 0; - this._state = [1732584193, -271733879, -1732584194, 271733878]; - - return this; - }; - - /** - * Releases memory used by the incremental buffer and other aditional - * resources. If you plan to use the instance again, use reset instead. - */ - SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; - - /** - * Concats two array buffers, returning a new one. - * - * @param {ArrayBuffer} first The first array buffer - * @param {ArrayBuffer} second The second array buffer - * - * @return {ArrayBuffer} The new array buffer - */ - SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) { - var firstLength = first.length, - result = new Uint8Array(firstLength + second.byteLength); - - result.set(first); - result.set(new Uint8Array(second), firstLength); - - return result; - }; - - /** - * Performs the md5 hash on an array buffer. - * - * @param {ArrayBuffer} arr The array buffer - * @param {Boolean} raw True to get the raw result, false to get the hex result - * - * @return {String|Array} The result - */ - SparkMD5.ArrayBuffer.hash = function (arr, raw) { - var hash = md51_array(new Uint8Array(arr)); - - return !!raw ? hash : hex(hash); - }; - - return FlashRuntime.register( 'Md5', { - init: function() { - // do nothing. - }, - - loadFromBlob: function( file ) { - var blob = file.getSource(), - chunkSize = 2 * 1024 * 1024, - chunks = Math.ceil( blob.size / chunkSize ), - chunk = 0, - owner = this.owner, - spark = new SparkMD5.ArrayBuffer(), - me = this, - blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice, - loadNext, fr; - - fr = new FileReader(); - - loadNext = function() { - var start, end; - - start = chunk * chunkSize; - end = Math.min( start + chunkSize, blob.size ); - - fr.onload = function( e ) { - spark.append( e.target.result ); - owner.trigger( 'progress', { - total: file.size, - loaded: end - }); - }; - - fr.onloadend = function() { - fr.onloadend = fr.onload = null; - - if ( ++chunk < chunks ) { - setTimeout( loadNext, 1 ); - } else { - setTimeout(function(){ - owner.trigger('load'); - me.result = spark.end(); - loadNext = file = blob = spark = null; - owner.trigger('complete'); - }, 50 ); - } - }; - - fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) ); - }; - - loadNext(); - }, - - getResult: function() { - return this.result; - } - }); - }); - /** - * @fileOverview FlashRuntime - */ - define('runtime/flash/runtime',[ - 'base', - 'runtime/runtime', - 'runtime/compbase' - ], function( Base, Runtime, CompBase ) { - - var $ = Base.$, - type = 'flash', - components = {}; - - - function getFlashVersion() { - var version; - - try { - version = navigator.plugins[ 'Shockwave Flash' ]; - version = version.description; - } catch ( ex ) { - try { - version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') - .GetVariable('$version'); - } catch ( ex2 ) { - version = '0.0'; - } - } - version = version.match( /\d+/g ); - return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); - } - - function FlashRuntime() { - var pool = {}, - clients = {}, - destroy = this.destroy, - me = this, - jsreciver = Base.guid('webuploader_'); - - Runtime.apply( me, arguments ); - me.type = type; - - - // 这个方法的调用者,实际上是RuntimeClient - me.exec = function( comp, fn/*, args...*/ ) { - var client = this, - uid = client.uid, - args = Base.slice( arguments, 2 ), - instance; - - clients[ uid ] = client; - - if ( components[ comp ] ) { - if ( !pool[ uid ] ) { - pool[ uid ] = new components[ comp ]( client, me ); - } - - instance = pool[ uid ]; - - if ( instance[ fn ] ) { - return instance[ fn ].apply( instance, args ); - } - } - - return me.flashExec.apply( client, arguments ); - }; - - function handler( evt, obj ) { - var type = evt.type || evt, - parts, uid; - - parts = type.split('::'); - uid = parts[ 0 ]; - type = parts[ 1 ]; - - // console.log.apply( console, arguments ); - - if ( type === 'Ready' && uid === me.uid ) { - me.trigger('ready'); - } else if ( clients[ uid ] ) { - clients[ uid ].trigger( type.toLowerCase(), evt, obj ); - } - - // Base.log( evt, obj ); - } - - // flash的接受器。 - window[ jsreciver ] = function() { - var args = arguments; - - // 为了能捕获得到。 - setTimeout(function() { - handler.apply( null, args ); - }, 1 ); - }; - - this.jsreciver = jsreciver; - - this.destroy = function() { - // @todo 删除池子中的所有实例 - return destroy && destroy.apply( this, arguments ); - }; - - this.flashExec = function( comp, fn ) { - var flash = me.getFlash(), - args = Base.slice( arguments, 2 ); - - return flash.exec( this.uid, comp, fn, args ); - }; - - // @todo - } - - Base.inherits( Runtime, { - constructor: FlashRuntime, - - init: function() { - var container = this.getContainer(), - opts = this.options, - html; - - // if not the minimal height, shims are not initialized - // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) - container.css({ - position: 'absolute', - top: '-8px', - left: '-8px', - width: '9px', - height: '9px', - overflow: 'hidden' - }); - - // insert flash object - html = '' + - '' + - '' + - '' + - ''; - - container.html( html ); - }, - - getFlash: function() { - if ( this._flash ) { - return this._flash; - } - - this._flash = $( '#' + this.uid ).get( 0 ); - return this._flash; - } - - }); - - FlashRuntime.register = function( name, component ) { - component = components[ name ] = Base.inherits( CompBase, $.extend({ - - // @todo fix this later - flashExec: function() { - var owner = this.owner, - runtime = this.getRuntime(); - - return runtime.flashExec.apply( owner, arguments ); - } - }, component ) ); - - return component; - }; - - if ( getFlashVersion() >= 11.4 ) { - Runtime.addRuntime( type, FlashRuntime ); - } - - return FlashRuntime; - }); - /** - * @fileOverview FilePicker - */ - define('runtime/flash/filepicker',[ - 'base', - 'runtime/flash/runtime' - ], function( Base, FlashRuntime ) { - var $ = Base.$; - - return FlashRuntime.register( 'FilePicker', { - init: function( opts ) { - var copy = $.extend({}, opts ), - len, i; - - // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. - len = copy.accept && copy.accept.length; - for ( i = 0; i < len; i++ ) { - if ( !copy.accept[ i ].title ) { - copy.accept[ i ].title = 'Files'; - } - } - - delete copy.button; - delete copy.id; - delete copy.container; - - this.flashExec( 'FilePicker', 'init', copy ); - }, - - destroy: function() { - this.flashExec( 'FilePicker', 'destroy' ); - } - }); - }); - /** - * @fileOverview 图片压缩 - */ - define('runtime/flash/image',[ - 'runtime/flash/runtime' - ], function( FlashRuntime ) { - - return FlashRuntime.register( 'Image', { - // init: function( options ) { - // var owner = this.owner; - - // this.flashExec( 'Image', 'init', options ); - // owner.on( 'load', function() { - // debugger; - // }); - // }, - - loadFromBlob: function( blob ) { - var owner = this.owner; - - owner.info() && this.flashExec( 'Image', 'info', owner.info() ); - owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); - - this.flashExec( 'Image', 'loadFromBlob', blob.uid ); - } - }); - }); - /** - * @fileOverview Transport flash实现 - */ - define('runtime/flash/transport',[ - 'base', - 'runtime/flash/runtime', - 'runtime/client' - ], function( Base, FlashRuntime, RuntimeClient ) { - var $ = Base.$; - - return FlashRuntime.register( 'Transport', { - init: function() { - this._status = 0; - this._response = null; - this._responseJson = null; - }, - - send: function() { - var owner = this.owner, - opts = this.options, - xhr = this._initAjax(), - blob = owner._blob, - server = opts.server, - binary; - - xhr.connectRuntime( blob.ruid ); - - if ( opts.sendAsBinary ) { - server += (/\?/.test( server ) ? '&' : '?') + - $.param( owner._formData ); - - binary = blob.uid; - } else { - $.each( owner._formData, function( k, v ) { - xhr.exec( 'append', k, v ); - }); - - xhr.exec( 'appendBlob', opts.fileVal, blob.uid, - opts.filename || owner._formData.name || '' ); - } - - this._setRequestHeader( xhr, opts.headers ); - xhr.exec( 'send', { - method: opts.method, - url: server, - forceURLStream: opts.forceURLStream, - mimeType: 'application/octet-stream' - }, binary ); - }, - - getStatus: function() { - return this._status; - }, - - getResponse: function() { - return this._response || ''; - }, - - getResponseAsJson: function() { - return this._responseJson; - }, - - abort: function() { - var xhr = this._xhr; - - if ( xhr ) { - xhr.exec('abort'); - xhr.destroy(); - this._xhr = xhr = null; - } - }, - - destroy: function() { - this.abort(); - }, - - _initAjax: function() { - var me = this, - xhr = new RuntimeClient('XMLHttpRequest'); - - xhr.on( 'uploadprogress progress', function( e ) { - var percent = e.loaded / e.total; - percent = Math.min( 1, Math.max( 0, percent ) ); - return me.trigger( 'progress', percent ); - }); - - xhr.on( 'load', function() { - var status = xhr.exec('getStatus'), - readBody = false, - err = '', - p; - - xhr.off(); - me._xhr = null; - - if ( status >= 200 && status < 300 ) { - readBody = true; - } else if ( status >= 500 && status < 600 ) { - readBody = true; - err = 'server'; - } else { - err = 'http'; - } - - if ( readBody ) { - me._response = xhr.exec('getResponse'); - me._response = decodeURIComponent( me._response ); - - // flash 处理可能存在 bug, 没辙只能靠 js 了 - // try { - // me._responseJson = xhr.exec('getResponseAsJson'); - // } catch ( error ) { - - p = window.JSON && window.JSON.parse || function( s ) { - try { - return new Function('return ' + s).call(); - } catch ( err ) { - return {}; - } - }; - me._responseJson = me._response ? p(me._response) : {}; - - // } - } - - xhr.destroy(); - xhr = null; - - return err ? me.trigger( 'error', err ) : me.trigger('load'); - }); - - xhr.on( 'error', function() { - xhr.off(); - me._xhr = null; - me.trigger( 'error', 'http' ); - }); - - me._xhr = xhr; - return xhr; - }, - - _setRequestHeader: function( xhr, headers ) { - $.each( headers, function( key, val ) { - xhr.exec( 'setRequestHeader', key, val ); - }); - } - }); - }); - /** - * @fileOverview Blob Html实现 - */ - define('runtime/flash/blob',[ - 'runtime/flash/runtime', - 'lib/blob' - ], function( FlashRuntime, Blob ) { - - return FlashRuntime.register( 'Blob', { - slice: function( start, end ) { - var blob = this.flashExec( 'Blob', 'slice', start, end ); - - return new Blob( blob.uid, blob ); - } - }); - }); - /** - * @fileOverview Md5 flash实现 - */ - define('runtime/flash/md5',[ - 'runtime/flash/runtime' - ], function( FlashRuntime ) { - - return FlashRuntime.register( 'Md5', { - init: function() { - // do nothing. - }, - - loadFromBlob: function( blob ) { - return this.flashExec( 'Md5', 'loadFromBlob', blob.uid ); - } - }); - }); - /** - * @fileOverview 完全版本。 - */ - define('preset/all',[ - 'base', - - // widgets - 'widgets/filednd', - 'widgets/filepaste', - 'widgets/filepicker', - 'widgets/image', - 'widgets/queue', - 'widgets/runtime', - 'widgets/upload', - 'widgets/validator', - 'widgets/md5', - - // runtimes - // html5 - 'runtime/html5/blob', - 'runtime/html5/dnd', - 'runtime/html5/filepaste', - 'runtime/html5/filepicker', - 'runtime/html5/imagemeta/exif', - 'runtime/html5/androidpatch', - 'runtime/html5/image', - 'runtime/html5/transport', - 'runtime/html5/md5', - - // flash - 'runtime/flash/filepicker', - 'runtime/flash/image', - 'runtime/flash/transport', - 'runtime/flash/blob', - 'runtime/flash/md5' - ], function( Base ) { - return Base; - }); - define('webuploader',[ - 'preset/all' - ], function( preset ) { - return preset; - }); - return require('webuploader'); -}); diff --git a/src/main/webapp/assets/webuploader.nolog.min.js b/src/main/webapp/assets/webuploader.nolog.min.js new file mode 100644 index 0000000..fcb94da --- /dev/null +++ b/src/main/webapp/assets/webuploader.nolog.min.js @@ -0,0 +1,3 @@ +/* WebUploader 0.1.6 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.require,c=a.__dollar||a.jQuery||a.Zepto||b("jquery")||b("zepto");if(!c)throw new Error("jQuery or Zepto not found!");return c}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.6",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b,void 0):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?("name"===a&&(g.name=c.name),void 0):(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a,void 0)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file",style:"webuploader-pick"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button,h=e.style;h&&g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":h&&g.addClass("webuploader-pick-hover");break;case"mouseleave":h&&g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.on("dialogopen",function(){e.owner.trigger("dialogOpen",j.button)}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)}),void 0):(b(!0),void 0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):(b.owner.trigger("error","Q_TYPE_DENIED",a),void 0):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),a.length&&(b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20))},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),b||f.request("start-upload"),void 0;for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b?(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT?(b.setStatus(h.QUEUED),f.each(c.pool,function(a,c){c.file===b&&(c.transport&&c.transport.send(),b.setStatus(h.PROGRESS))})):b.getStatus()!==h.PROGRESS&&b.setStatus(h.QUEUED)):f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)}),c.runing)return a.nextTick(c.__tick);c.runing=!0;var d=[];b||f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(c._trigged=!1,d.push(e),b.transport&&b.transport.send())}),f.each(d,function(){this.setStatus(h.PROGRESS)}),b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")},stopUpload:function(b,c){var d,e=this;if(b===!0&&(c=b,b=null),e.runing!==!1){if(b){if(b=b.id?b:e.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(e.pool,function(a,c){return c.file===b?(d=c,!1):void 0}),d.transport&&d.transport.abort(),c&&(e._putback(d),e._popBlock(d)),a.nextTick(e.__tick)}e.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(e.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),e.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send() +},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("lib/md5",["runtime/client","mediator"],function(a,b){function c(){a.call(this,"Md5")}return b.installTo(c.prototype),c.prototype.loadFromBlob=function(a){var b=this;b.getRuid()&&b.disconnectRuntime(),b.connectRuntime(a.ruid,function(){b.exec("init"),b.exec("loadFromBlob",a)})},c.prototype.getResult=function(){return this.exec("getResult")},c}),b("widgets/md5",["base","uploader","lib/md5","lib/blob","widgets/widget"],function(a,b,c,d){return b.register({name:"md5",md5File:function(b,e,f){var g=new c,h=a.Deferred(),i=b instanceof d?b:this.request("get-file",b).source;return g.on("progress load",function(a){a=a||{},h.notify(a.total?a.loaded/a.total:1)}),g.on("complete",function(){h.resolve(g.getResult())}),g.on("error",function(a){h.reject(a)}),arguments.length>1&&(e=e||0,f=f||0,0>e&&(e=i.size+e),0>f&&(f=i.size+f),f=Math.min(f,i.size),i=i.slice(e,f)),g.loadFromBlob(i),h.promise()}})}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return g.dndOver=!1,g.elem.removeClass(e+"over"),f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("capture","camera"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(a){k.trigger("click"),a.stopPropagation(),h.trigger("dialogopen")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return a.log("Invalid Exif data: Invalid tag type."),void 0;if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return a.log("Invalid Exif data: Invalid data offset."),void 0;if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return a.log("Invalid Exif data: Invalid directory offset."),void 0;if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return a.log("Invalid Exif data: Invalid directory size."),void 0;for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return a.log("Invalid Exif data: Invalid segment size."),void 0;if(0!==b.getUint16(d+8))return a.log("Invalid Exif data: Missing byte alignment offset."),void 0;switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return a.log("Invalid Exif data: Invalid byte alignment marker."),void 0}if(42!==b.getUint16(i+2,g))return a.log("Invalid Exif data: Missing TIFF marker."),void 0;h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/jpegencoder",[],function(){function a(a){function b(a){for(var b=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],c=0;64>c;c++){var d=y((b[c]*a+50)/100);1>d?d=1:d>255&&(d=255),z[P[c]]=d}for(var e=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],f=0;64>f;f++){var g=y((e[f]*a+50)/100);1>g?g=1:g>255&&(g=255),A[P[f]]=g}for(var h=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],i=0,j=0;8>j;j++)for(var k=0;8>k;k++)B[i]=1/(8*z[P[i]]*h[j]*h[k]),C[i]=1/(8*A[P[i]]*h[j]*h[k]),i++}function c(a,b){for(var c=0,d=0,e=new Array,f=1;16>=f;f++){for(var g=1;g<=a[f];g++)e[b[d]]=[],e[b[d]][0]=c,e[b[d]][1]=f,d++,c++;c*=2}return e}function d(){t=c(Q,R),u=c(U,V),v=c(S,T),w=c(W,X)}function e(){for(var a=1,b=2,c=1;15>=c;c++){for(var d=a;b>d;d++)E[32767+d]=c,D[32767+d]=[],D[32767+d][1]=c,D[32767+d][0]=d;for(var e=-(b-1);-a>=e;e++)E[32767+e]=c,D[32767+e]=[],D[32767+e][1]=c,D[32767+e][0]=b-1+e;a<<=1,b<<=1}}function f(){for(var a=0;256>a;a++)O[a]=19595*a,O[a+256>>0]=38470*a,O[a+512>>0]=7471*a+32768,O[a+768>>0]=-11059*a,O[a+1024>>0]=-21709*a,O[a+1280>>0]=32768*a+8421375,O[a+1536>>0]=-27439*a,O[a+1792>>0]=-5329*a}function g(a){for(var b=a[0],c=a[1]-1;c>=0;)b&1<J&&(255==I?(h(255),h(0)):h(I),J=7,I=0)}function h(a){H.push(N[a])}function i(a){h(255&a>>8),h(255&a)}function j(a,b){var c,d,e,f,g,h,i,j,k,l=0,m=8,n=64;for(k=0;m>k;++k){c=a[l],d=a[l+1],e=a[l+2],f=a[l+3],g=a[l+4],h=a[l+5],i=a[l+6],j=a[l+7];var o=c+j,p=c-j,q=d+i,r=d-i,s=e+h,t=e-h,u=f+g,v=f-g,w=o+u,x=o-u,y=q+s,z=q-s;a[l]=w+y,a[l+4]=w-y;var A=.707106781*(z+x);a[l+2]=x+A,a[l+6]=x-A,w=v+t,y=t+r,z=r+p;var B=.382683433*(w-z),C=.5411961*w+B,D=1.306562965*z+B,E=.707106781*y,G=p+E,H=p-E;a[l+5]=H+C,a[l+3]=H-C,a[l+1]=G+D,a[l+7]=G-D,l+=8}for(l=0,k=0;m>k;++k){c=a[l],d=a[l+8],e=a[l+16],f=a[l+24],g=a[l+32],h=a[l+40],i=a[l+48],j=a[l+56];var I=c+j,J=c-j,K=d+i,L=d-i,M=e+h,N=e-h,O=f+g,P=f-g,Q=I+O,R=I-O,S=K+M,T=K-M;a[l]=Q+S,a[l+32]=Q-S;var U=.707106781*(T+R);a[l+16]=R+U,a[l+48]=R-U,Q=P+N,S=N+L,T=L+J;var V=.382683433*(Q-T),W=.5411961*Q+V,X=1.306562965*T+V,Y=.707106781*S,Z=J+Y,$=J-Y;a[l+40]=$+W,a[l+24]=$-W,a[l+8]=Z+X,a[l+56]=Z-X,l++}var _;for(k=0;n>k;++k)_=a[k]*b[k],F[k]=_>0?0|_+.5:0|_-.5;return F}function k(){i(65504),i(16),h(74),h(70),h(73),h(70),h(0),h(1),h(1),h(0),i(1),i(1),h(0),h(0)}function l(a,b){i(65472),i(17),h(8),i(b),i(a),h(3),h(1),h(17),h(0),h(2),h(17),h(1),h(3),h(17),h(1)}function m(){i(65499),i(132),h(0);for(var a=0;64>a;a++)h(z[a]);h(1);for(var b=0;64>b;b++)h(A[b])}function n(){i(65476),i(418),h(0);for(var a=0;16>a;a++)h(Q[a+1]);for(var b=0;11>=b;b++)h(R[b]);h(16);for(var c=0;16>c;c++)h(S[c+1]);for(var d=0;161>=d;d++)h(T[d]);h(1);for(var e=0;16>e;e++)h(U[e+1]);for(var f=0;11>=f;f++)h(V[f]);h(17);for(var g=0;16>g;g++)h(W[g+1]);for(var j=0;161>=j;j++)h(X[j])}function o(){i(65498),i(12),h(3),h(1),h(0),h(2),h(17),h(3),h(17),h(0),h(63),h(0)}function p(a,b,c,d,e){for(var f,h=e[0],i=e[240],k=16,l=63,m=64,n=j(a,b),o=0;m>o;++o)G[P[o]]=n[o];var p=G[0]-c;c=G[0],0==p?g(d[0]):(f=32767+p,g(d[E[f]]),g(D[f]));for(var q=63;q>0&&0==G[q];q--);if(0==q)return g(h),c;for(var r,s=1;q>=s;){for(var t=s;0==G[s]&&q>=s;++s);var u=s-t;if(u>=k){r=u>>4;for(var v=1;r>=v;++v)g(i);u=15&u}f=32767+G[s],g(e[(u<<4)+E[f]]),g(D[f]),s++}return q!=l&&g(h),c}function q(){for(var a=String.fromCharCode,b=0;256>b;b++)N[b]=a(b)}function r(a){if(0>=a&&(a=1),a>100&&(a=100),x!=a){var c=0;c=50>a?Math.floor(5e3/a):Math.floor(200-2*a),b(c),x=a}}function s(){a||(a=50),q(),d(),e(),f(),r(a)}Math.round;var t,u,v,w,x,y=Math.floor,z=new Array(64),A=new Array(64),B=new Array(64),C=new Array(64),D=new Array(65535),E=new Array(65535),F=new Array(64),G=new Array(64),H=[],I=0,J=7,K=new Array(64),L=new Array(64),M=new Array(64),N=new Array(256),O=new Array(2048),P=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],Q=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],R=[0,1,2,3,4,5,6,7,8,9,10,11],S=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],T=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],U=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],V=[0,1,2,3,4,5,6,7,8,9,10,11],W=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],X=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];this.encode=function(a,b){b&&r(b),H=new Array,I=0,J=7,i(65496),k(),m(),l(a.width,a.height),n(),o();var c=0,d=0,e=0;I=0,J=7,this.encode.displayName="_encode_";for(var f,h,j,q,s,x,y,z,A,D=a.data,E=a.width,F=a.height,G=4*E,N=0;F>N;){for(f=0;G>f;){for(s=G*N+f,x=s,y=-1,z=0,A=0;64>A;A++)z=A>>3,y=4*(7&A),x=s+z*G+y,N+z>=F&&(x-=G*(N+1+z-F)),f+y>=G&&(x-=f+y-G+4),h=D[x++],j=D[x++],q=D[x++],K[A]=(O[h]+O[j+256>>0]+O[q+512>>0]>>16)-128,L[A]=(O[h+768>>0]+O[j+1024>>0]+O[q+1280>>0]>>16)-128,M[A]=(O[h+1280>>0]+O[j+1536>>0]+O[q+1792>>0]>>16)-128;c=p(K,B,c,t,v),d=p(L,C,d,u,w),e=p(M,C,e,u,w),f+=32}N+=8}if(J>=0){var P=[];P[1]=J+1,P[0]=(1<i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/html5/md5",["runtime/html5/runtime"],function(a){var b=function(a,b){return 4294967295&a+b},c=function(a,c,d,e,f,g){return c=b(b(c,a),b(e,g)),b(c<>>32-f,d)},d=function(a,b,d,e,f,g,h){return c(b&d|~b&e,a,b,f,g,h)},e=function(a,b,d,e,f,g,h){return c(b&e|d&~e,a,b,f,g,h)},f=function(a,b,d,e,f,g,h){return c(b^d^e,a,b,f,g,h)},g=function(a,b,d,e,f,g,h){return c(d^(b|~e),a,b,f,g,h)},h=function(a,c){var h=a[0],i=a[1],j=a[2],k=a[3];h=d(h,i,j,k,c[0],7,-680876936),k=d(k,h,i,j,c[1],12,-389564586),j=d(j,k,h,i,c[2],17,606105819),i=d(i,j,k,h,c[3],22,-1044525330),h=d(h,i,j,k,c[4],7,-176418897),k=d(k,h,i,j,c[5],12,1200080426),j=d(j,k,h,i,c[6],17,-1473231341),i=d(i,j,k,h,c[7],22,-45705983),h=d(h,i,j,k,c[8],7,1770035416),k=d(k,h,i,j,c[9],12,-1958414417),j=d(j,k,h,i,c[10],17,-42063),i=d(i,j,k,h,c[11],22,-1990404162),h=d(h,i,j,k,c[12],7,1804603682),k=d(k,h,i,j,c[13],12,-40341101),j=d(j,k,h,i,c[14],17,-1502002290),i=d(i,j,k,h,c[15],22,1236535329),h=e(h,i,j,k,c[1],5,-165796510),k=e(k,h,i,j,c[6],9,-1069501632),j=e(j,k,h,i,c[11],14,643717713),i=e(i,j,k,h,c[0],20,-373897302),h=e(h,i,j,k,c[5],5,-701558691),k=e(k,h,i,j,c[10],9,38016083),j=e(j,k,h,i,c[15],14,-660478335),i=e(i,j,k,h,c[4],20,-405537848),h=e(h,i,j,k,c[9],5,568446438),k=e(k,h,i,j,c[14],9,-1019803690),j=e(j,k,h,i,c[3],14,-187363961),i=e(i,j,k,h,c[8],20,1163531501),h=e(h,i,j,k,c[13],5,-1444681467),k=e(k,h,i,j,c[2],9,-51403784),j=e(j,k,h,i,c[7],14,1735328473),i=e(i,j,k,h,c[12],20,-1926607734),h=f(h,i,j,k,c[5],4,-378558),k=f(k,h,i,j,c[8],11,-2022574463),j=f(j,k,h,i,c[11],16,1839030562),i=f(i,j,k,h,c[14],23,-35309556),h=f(h,i,j,k,c[1],4,-1530992060),k=f(k,h,i,j,c[4],11,1272893353),j=f(j,k,h,i,c[7],16,-155497632),i=f(i,j,k,h,c[10],23,-1094730640),h=f(h,i,j,k,c[13],4,681279174),k=f(k,h,i,j,c[0],11,-358537222),j=f(j,k,h,i,c[3],16,-722521979),i=f(i,j,k,h,c[6],23,76029189),h=f(h,i,j,k,c[9],4,-640364487),k=f(k,h,i,j,c[12],11,-421815835),j=f(j,k,h,i,c[15],16,530742520),i=f(i,j,k,h,c[2],23,-995338651),h=g(h,i,j,k,c[0],6,-198630844),k=g(k,h,i,j,c[7],10,1126891415),j=g(j,k,h,i,c[14],15,-1416354905),i=g(i,j,k,h,c[5],21,-57434055),h=g(h,i,j,k,c[12],6,1700485571),k=g(k,h,i,j,c[3],10,-1894986606),j=g(j,k,h,i,c[10],15,-1051523),i=g(i,j,k,h,c[1],21,-2054922799),h=g(h,i,j,k,c[8],6,1873313359),k=g(k,h,i,j,c[15],10,-30611744),j=g(j,k,h,i,c[6],15,-1560198380),i=g(i,j,k,h,c[13],21,1309151649),h=g(h,i,j,k,c[4],6,-145523070),k=g(k,h,i,j,c[11],10,-1120210379),j=g(j,k,h,i,c[2],15,718787259),i=g(i,j,k,h,c[9],21,-343485551),a[0]=b(h,a[0]),a[1]=b(i,a[1]),a[2]=b(j,a[2]),a[3]=b(k,a[3])},i=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a.charCodeAt(b)+(a.charCodeAt(b+1)<<8)+(a.charCodeAt(b+2)<<16)+(a.charCodeAt(b+3)<<24);return c},j=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a[b]+(a[b+1]<<8)+(a[b+2]<<16)+(a[b+3]<<24);return c},k=function(a){var b,c,d,e,f,g,j=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;j>=b;b+=64)h(k,i(a.substring(b-64,b)));for(a=a.substring(b-64),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a.charCodeAt(b)<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*j,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},l=function(a){var b,c,d,e,f,g,i=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;i>=b;b+=64)h(k,j(a.subarray(b-64,b)));for(a=i>b-64?a.subarray(b-64):new Uint8Array(0),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a[b]<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*i,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},m=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"],n=function(a){var b,c="";for(b=0;4>b;b+=1)c+=m[15&a>>8*b+4]+m[15&a>>8*b];return c},o=function(a){var b;for(b=0;b>16)+(b>>16)+(c>>16);return d<<16|65535&c}),q.prototype.append=function(a){return/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a))),this.appendBinary(a),this},q.prototype.appendBinary=function(a){this._buff+=a,this._length+=a.length;var b,c=this._buff.length;for(b=64;c>=b;b+=64)h(this._state,i(this._buff.substring(b-64,b)));return this._buff=this._buff.substr(b-64),this},q.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(b=0;e>b;b+=1)f[b>>2]|=d.charCodeAt(b)<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.prototype._finish=function(a,b){var c,d,e,f=b;if(a[f>>2]|=128<<(f%4<<3),f>55)for(h(this._state,a),f=0;16>f;f+=1)a[f]=0;c=8*this._length,c=c.toString(16).match(/(.*?)(.{0,8})$/),d=parseInt(c[2],16),e=parseInt(c[1],16)||0,a[14]=d,a[15]=e,h(this._state,a)},q.prototype.reset=function(){return this._buff="",this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.prototype.destroy=function(){delete this._state,delete this._buff,delete this._length},q.hash=function(a,b){/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a)));var c=k(a);return b?c:o(c) +},q.hashBinary=function(a,b){var c=k(a);return b?c:o(c)},q.ArrayBuffer=function(){this.reset()},q.ArrayBuffer.prototype.append=function(a){var b,c=this._concatArrayBuffer(this._buff,a),d=c.length;for(this._length+=a.byteLength,b=64;d>=b;b+=64)h(this._state,j(c.subarray(b-64,b)));return this._buff=d>b-64?c.subarray(b-64):new Uint8Array(0),this},q.ArrayBuffer.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(b=0;e>b;b+=1)f[b>>2]|=d[b]<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.ArrayBuffer.prototype._finish=q.prototype._finish,q.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.ArrayBuffer.prototype.destroy=q.prototype.destroy,q.ArrayBuffer.prototype._concatArrayBuffer=function(a,b){var c=a.length,d=new Uint8Array(c+b.byteLength);return d.set(a),d.set(new Uint8Array(b),c),d},q.ArrayBuffer.hash=function(a,b){var c=l(new Uint8Array(a));return b?c:o(c)},a.register("Md5",{init:function(){},loadFromBlob:function(a){var b,c,d=a.getSource(),e=2097152,f=Math.ceil(d.size/e),g=0,h=this.owner,i=new q.ArrayBuffer,j=this,k=d.mozSlice||d.webkitSlice||d.slice;c=new FileReader,b=function(){var l,m;l=g*e,m=Math.min(l+e,d.size),c.onload=function(b){i.append(b.target.result),h.trigger("progress",{total:a.size,loaded:m})},c.onloadend=function(){c.onloadend=c.onload=null,++g'+''+''+''+"
",c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/image",["runtime/flash/runtime"],function(a){return a.register("Image",{loadFromBlob:function(a){var b=this.owner;b.info()&&this.flashExec("Image","info",b.info()),b.meta()&&this.flashExec("Image","meta",b.meta()),this.flashExec("Image","loadFromBlob",a.uid)}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=function(b){try{return a.JSON&&a.JSON.parse?JSON.parse(b):new Function("return "+b).call()}catch(c){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(this.getRuid(),d)}})}),b("runtime/flash/md5",["runtime/flash/runtime"],function(a){return a.register("Md5",{init:function(){},loadFromBlob:function(a){return this.flashExec("Md5","loadFromBlob",a.uid)}})}),b("preset/all",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","widgets/md5","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/androidpatch","runtime/html5/image","runtime/html5/transport","runtime/html5/md5","runtime/flash/filepicker","runtime/flash/image","runtime/flash/transport","runtime/flash/blob","runtime/flash/md5"],function(a){return a}),b("webuploader",["preset/all"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/src/main/webapp/fonts/glyphicons-halflings-regular.ttf b/src/main/webapp/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..1413fc6 Binary files /dev/null and b/src/main/webapp/fonts/glyphicons-halflings-regular.ttf differ diff --git a/src/main/webapp/fonts/glyphicons-halflings-regular.woff b/src/main/webapp/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..9e61285 Binary files /dev/null and b/src/main/webapp/fonts/glyphicons-halflings-regular.woff differ diff --git a/src/main/webapp/fonts/glyphicons-halflings-regular.woff2 b/src/main/webapp/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000..64539b5 Binary files /dev/null and b/src/main/webapp/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp index 308bb51..d593fa4 100644 --- a/src/main/webapp/index.jsp +++ b/src/main/webapp/index.jsp @@ -7,8 +7,9 @@ FileUpload -文件上传 -图片上传 +文件上传 +图片上传 大文件上传 +多选择器多文件上传