mirror of
https://github.com/sismics/docs.git
synced 2024-11-22 22:07:56 +01:00
Ability to upload files without document (no OCR, no Lucene)
+ New resource to attach a document to a file and OCR/Lucene it
This commit is contained in:
parent
5cf0532db7
commit
c36014b46f
@ -93,12 +93,32 @@ public class FileDao {
|
|||||||
q.setParameter("id", file.getId());
|
q.setParameter("id", file.getId());
|
||||||
File fileFromDb = (File) q.getSingleResult();
|
File fileFromDb = (File) q.getSingleResult();
|
||||||
|
|
||||||
// Update the user
|
// Update the file
|
||||||
fileFromDb.setContent(file.getContent());
|
fileFromDb.setContent(file.getContent());
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the document of a file.
|
||||||
|
*
|
||||||
|
* @param file File to update
|
||||||
|
* @return Updated file
|
||||||
|
*/
|
||||||
|
public File updateDocument(File file) {
|
||||||
|
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||||
|
|
||||||
|
// Get the file
|
||||||
|
Query q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null");
|
||||||
|
q.setParameter("id", file.getId());
|
||||||
|
File fileFromDb = (File) q.getSingleResult();
|
||||||
|
|
||||||
|
// Update the file
|
||||||
|
fileFromDb.setDocumentId(file.getDocumentId());
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a file by its ID.
|
* Gets a file by its ID.
|
||||||
*
|
*
|
||||||
|
@ -1 +1 @@
|
|||||||
db.version=6
|
db.version=7
|
@ -1,3 +1,3 @@
|
|||||||
api.current_version=${project.version}
|
api.current_version=${project.version}
|
||||||
api.min_version=1.0
|
api.min_version=1.0
|
||||||
db.version=6
|
db.version=7
|
@ -33,6 +33,7 @@ import javax.ws.rs.core.StreamingOutput;
|
|||||||
import org.codehaus.jettison.json.JSONException;
|
import org.codehaus.jettison.json.JSONException;
|
||||||
import org.codehaus.jettison.json.JSONObject;
|
import org.codehaus.jettison.json.JSONObject;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||||
@ -65,7 +66,7 @@ import com.sun.jersey.multipart.FormDataParam;
|
|||||||
@Path("/file")
|
@Path("/file")
|
||||||
public class FileResource extends BaseResource {
|
public class FileResource extends BaseResource {
|
||||||
/**
|
/**
|
||||||
* Add a file to a document.
|
* Add a file (with or without a document).
|
||||||
*
|
*
|
||||||
* @param documentId Document ID
|
* @param documentId Document ID
|
||||||
* @param fileBodyPart File to add
|
* @param fileBodyPart File to add
|
||||||
@ -83,21 +84,24 @@ public class FileResource extends BaseResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate input data
|
// Validate input data
|
||||||
ValidationUtil.validateRequired(documentId, "id");
|
|
||||||
ValidationUtil.validateRequired(fileBodyPart, "file");
|
ValidationUtil.validateRequired(fileBodyPart, "file");
|
||||||
|
|
||||||
// Get the document
|
// Get the current user
|
||||||
DocumentDao documentDao = new DocumentDao();
|
|
||||||
FileDao fileDao = new FileDao();
|
|
||||||
UserDao userDao = new UserDao();
|
UserDao userDao = new UserDao();
|
||||||
Document document;
|
User user = userDao.getById(principal.getId());
|
||||||
User user;
|
|
||||||
|
// Get the document
|
||||||
|
Document document = null;
|
||||||
|
if (Strings.isNullOrEmpty(documentId)) {
|
||||||
|
documentId = null;
|
||||||
|
} else {
|
||||||
|
DocumentDao documentDao = new DocumentDao();
|
||||||
try {
|
try {
|
||||||
document = documentDao.getDocument(documentId, principal.getId());
|
document = documentDao.getDocument(documentId, principal.getId());
|
||||||
user = userDao.getById(principal.getId());
|
|
||||||
} catch (NoResultException e) {
|
} catch (NoResultException e) {
|
||||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Keep unencrypted data in memory, because we will need it two times
|
// Keep unencrypted data in memory, because we will need it two times
|
||||||
byte[] fileData;
|
byte[] fileData;
|
||||||
@ -121,27 +125,32 @@ public class FileResource extends BaseResource {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Get files of this document
|
// Get files of this document
|
||||||
|
FileDao fileDao = new FileDao();
|
||||||
int order = 0;
|
int order = 0;
|
||||||
|
if (documentId != null) {
|
||||||
for (File file : fileDao.getByDocumentId(documentId)) {
|
for (File file : fileDao.getByDocumentId(documentId)) {
|
||||||
file.setOrder(order++);
|
file.setOrder(order++);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create the file
|
// Create the file
|
||||||
File file = new File();
|
File file = new File();
|
||||||
file.setOrder(order);
|
file.setOrder(order);
|
||||||
file.setDocumentId(document.getId());
|
file.setDocumentId(documentId);
|
||||||
file.setMimeType(mimeType);
|
file.setMimeType(mimeType);
|
||||||
String fileId = fileDao.create(file);
|
String fileId = fileDao.create(file);
|
||||||
|
|
||||||
// Save the file
|
// Save the file
|
||||||
FileUtil.save(fileInputStream, file, user.getPrivateKey());
|
FileUtil.save(fileInputStream, file, user.getPrivateKey());
|
||||||
|
|
||||||
// Raise a new file created event
|
// Raise a new file created event if we have a document
|
||||||
|
if (documentId != null) {
|
||||||
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
|
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
|
||||||
fileCreatedAsyncEvent.setDocument(document);
|
fileCreatedAsyncEvent.setDocument(document);
|
||||||
fileCreatedAsyncEvent.setFile(file);
|
fileCreatedAsyncEvent.setFile(file);
|
||||||
fileCreatedAsyncEvent.setInputStream(fileInputStream);
|
fileCreatedAsyncEvent.setInputStream(fileInputStream);
|
||||||
AppContext.getInstance().getAsyncEventBus().post(fileCreatedAsyncEvent);
|
AppContext.getInstance().getAsyncEventBus().post(fileCreatedAsyncEvent);
|
||||||
|
}
|
||||||
|
|
||||||
// Always return ok
|
// Always return ok
|
||||||
JSONObject response = new JSONObject();
|
JSONObject response = new JSONObject();
|
||||||
@ -153,6 +162,71 @@ public class FileResource extends BaseResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach a file to a document.
|
||||||
|
*
|
||||||
|
* @param id File ID
|
||||||
|
* @return Response
|
||||||
|
* @throws JSONException
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("{id: [a-z0-9\\-]+}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public Response attach(
|
||||||
|
@PathParam("id") String id,
|
||||||
|
@FormParam("id") String documentId) throws JSONException {
|
||||||
|
if (!authenticate()) {
|
||||||
|
throw new ForbiddenClientException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate input data
|
||||||
|
ValidationUtil.validateRequired(documentId, "id");
|
||||||
|
|
||||||
|
// Get the current user
|
||||||
|
UserDao userDao = new UserDao();
|
||||||
|
User user = userDao.getById(principal.getId());
|
||||||
|
|
||||||
|
// Get the document and the file
|
||||||
|
DocumentDao documentDao = new DocumentDao();
|
||||||
|
FileDao fileDao = new FileDao();
|
||||||
|
Document document;
|
||||||
|
File file;
|
||||||
|
try {
|
||||||
|
file = fileDao.getFile(id);
|
||||||
|
document = documentDao.getDocument(documentId, principal.getId());
|
||||||
|
} catch (NoResultException e) {
|
||||||
|
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the file is orphan
|
||||||
|
if (file.getDocumentId() != null) {
|
||||||
|
throw new ClientException("IllegalFile", MessageFormat.format("File not orphan: {0}", id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the file
|
||||||
|
file.setDocumentId(documentId);
|
||||||
|
fileDao.updateDocument(file);
|
||||||
|
|
||||||
|
// Raise a new file created event (it wasn't sent during file creation)
|
||||||
|
try {
|
||||||
|
java.io.File storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), id).toFile();
|
||||||
|
InputStream fileInputStream = new FileInputStream(storedfile);
|
||||||
|
final InputStream responseInputStream = EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey());
|
||||||
|
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
|
||||||
|
fileCreatedAsyncEvent.setDocument(document);
|
||||||
|
fileCreatedAsyncEvent.setFile(file);
|
||||||
|
fileCreatedAsyncEvent.setInputStream(responseInputStream);
|
||||||
|
AppContext.getInstance().getAsyncEventBus().post(fileCreatedAsyncEvent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ClientException("AttachError", "Error attaching file to document", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always return ok
|
||||||
|
JSONObject response = new JSONObject();
|
||||||
|
response.put("status", "ok");
|
||||||
|
return Response.ok().entity(response).build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reorder files.
|
* Reorder files.
|
||||||
*
|
*
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
angular.module('docs',
|
angular.module('docs',
|
||||||
// Dependencies
|
// Dependencies
|
||||||
['ui.router', 'ui.route', 'ui.bootstrap', 'ui.keypress', 'ui.validate', 'dialog',
|
['ui.router', 'ui.route', 'ui.bootstrap', 'ui.keypress', 'ui.validate', 'dialog',
|
||||||
'ui.sortable', 'restangular', 'ngSanitize', 'ngTouch', 'colorpicker.module']
|
'ui.sortable', 'restangular', 'ngSanitize', 'ngTouch', 'colorpicker.module', 'angularFileUpload']
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
<script src="lib/angular.ui-sortable.js" type="text/javascript"></script>
|
<script src="lib/angular.ui-sortable.js" type="text/javascript"></script>
|
||||||
<script src="lib/angular.restangular.js" type="text/javascript"></script>
|
<script src="lib/angular.restangular.js" type="text/javascript"></script>
|
||||||
<script src="lib/angular.colorpicker.js" type="text/javascript"></script>
|
<script src="lib/angular.colorpicker.js" type="text/javascript"></script>
|
||||||
|
<script src="lib/angular.file-upload.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/app.js" type="text/javascript"></script>
|
<script src="app/docs/app.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/Main.js" type="text/javascript"></script>
|
<script src="app/docs/controller/Main.js" type="text/javascript"></script>
|
||||||
<script src="app/docs/controller/Document.js" type="text/javascript"></script>
|
<script src="app/docs/controller/Document.js" type="text/javascript"></script>
|
||||||
|
602
docs-web/src/main/webapp/src/lib/angular.file-upload.js
Normal file
602
docs-web/src/main/webapp/src/lib/angular.file-upload.js
Normal file
@ -0,0 +1,602 @@
|
|||||||
|
/**
|
||||||
|
* AngularJS file upload/drop directive and service with progress and abort
|
||||||
|
* @author Danial <danial.farid@gmail.com>
|
||||||
|
* @version 3.1.1
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
function patchXHR(fnName, newFn) {
|
||||||
|
window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.XMLHttpRequest && !window.XMLHttpRequest.__isFileAPIShim) {
|
||||||
|
patchXHR('setRequestHeader', function(orig) {
|
||||||
|
return function(header, value) {
|
||||||
|
if (header === '__setXHR_') {
|
||||||
|
var val = value(this);
|
||||||
|
// fix for angular < 1.2.0
|
||||||
|
if (val instanceof Function) {
|
||||||
|
val(this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
orig.apply(this, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var angularFileUpload = angular.module('angularFileUpload', []);
|
||||||
|
|
||||||
|
angularFileUpload.version = '3.1.1';
|
||||||
|
angularFileUpload.service('$upload', ['$http', '$q', '$timeout', function($http, $q, $timeout) {
|
||||||
|
function sendHttp(config) {
|
||||||
|
config.method = config.method || 'POST';
|
||||||
|
config.headers = config.headers || {};
|
||||||
|
config.transformRequest = config.transformRequest || function(data, headersGetter) {
|
||||||
|
if (window.ArrayBuffer && data instanceof window.ArrayBuffer) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return $http.defaults.transformRequest[0](data, headersGetter);
|
||||||
|
};
|
||||||
|
var deferred = $q.defer();
|
||||||
|
var promise = deferred.promise;
|
||||||
|
|
||||||
|
config.headers['__setXHR_'] = function() {
|
||||||
|
return function(xhr) {
|
||||||
|
if (!xhr) return;
|
||||||
|
config.__XHR = xhr;
|
||||||
|
config.xhrFn && config.xhrFn(xhr);
|
||||||
|
xhr.upload.addEventListener('progress', function(e) {
|
||||||
|
e.config = config;
|
||||||
|
deferred.notify ? deferred.notify(e) : promise.progress_fn && $timeout(function(){promise.progress_fn(e)});
|
||||||
|
}, false);
|
||||||
|
//fix for firefox not firing upload progress end, also IE8-9
|
||||||
|
xhr.upload.addEventListener('load', function(e) {
|
||||||
|
if (e.lengthComputable) {
|
||||||
|
e.config = config;
|
||||||
|
deferred.notify ? deferred.notify(e) : promise.progress_fn && $timeout(function(){promise.progress_fn(e)});
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$http(config).then(function(r){deferred.resolve(r)}, function(e){deferred.reject(e)}, function(n){deferred.notify(n)});
|
||||||
|
|
||||||
|
promise.success = function(fn) {
|
||||||
|
promise.then(function(response) {
|
||||||
|
fn(response.data, response.status, response.headers, config);
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
promise.error = function(fn) {
|
||||||
|
promise.then(null, function(response) {
|
||||||
|
fn(response.data, response.status, response.headers, config);
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
promise.progress = function(fn) {
|
||||||
|
promise.progress_fn = fn;
|
||||||
|
promise.then(null, null, function(update) {
|
||||||
|
fn(update);
|
||||||
|
});
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
promise.abort = function() {
|
||||||
|
if (config.__XHR) {
|
||||||
|
$timeout(function() {
|
||||||
|
config.__XHR.abort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
promise.xhr = function(fn) {
|
||||||
|
config.xhrFn = (function(origXhrFn) {
|
||||||
|
return function() {
|
||||||
|
origXhrFn && origXhrFn.apply(promise, arguments);
|
||||||
|
fn.apply(promise, arguments);
|
||||||
|
}
|
||||||
|
})(config.xhrFn);
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.upload = function(config) {
|
||||||
|
config.headers = config.headers || {};
|
||||||
|
config.headers['Content-Type'] = undefined;
|
||||||
|
var origTransformRequest = config.transformRequest;
|
||||||
|
config.transformRequest = config.transformRequest ?
|
||||||
|
(Object.prototype.toString.call(config.transformRequest) === '[object Array]' ?
|
||||||
|
config.transformRequest : [config.transformRequest]) : [];
|
||||||
|
config.transformRequest.push(function(data, headerGetter) {
|
||||||
|
var formData = new FormData();
|
||||||
|
var allFields = {};
|
||||||
|
for (var key in config.fields) allFields[key] = config.fields[key];
|
||||||
|
if (data) allFields['data'] = data;
|
||||||
|
|
||||||
|
if (config.formDataAppender) {
|
||||||
|
for (var key in allFields) {
|
||||||
|
config.formDataAppender(formData, key, allFields[key]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var key in allFields) {
|
||||||
|
var val = allFields[key];
|
||||||
|
if (val !== undefined) {
|
||||||
|
if (Object.prototype.toString.call(val) === '[object String]') {
|
||||||
|
formData.append(key, val);
|
||||||
|
} else {
|
||||||
|
if (config.sendObjectsAsJsonBlob && typeof val === 'object') {
|
||||||
|
formData.append(key, new Blob([val], { type: 'application/json' }));
|
||||||
|
} else {
|
||||||
|
formData.append(key, JSON.stringify(val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.file != null) {
|
||||||
|
var fileFormName = config.fileFormDataName || 'file';
|
||||||
|
|
||||||
|
if (Object.prototype.toString.call(config.file) === '[object Array]') {
|
||||||
|
var isFileFormNameString = Object.prototype.toString.call(fileFormName) === '[object String]';
|
||||||
|
for (var i = 0; i < config.file.length; i++) {
|
||||||
|
formData.append(isFileFormNameString ? fileFormName : fileFormName[i], config.file[i],
|
||||||
|
(config.fileName && config.fileName[i]) || config.file[i].name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formData.append(fileFormName, config.file, config.fileName || config.file.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return formData;
|
||||||
|
});
|
||||||
|
|
||||||
|
return sendHttp(config);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.http = function(config) {
|
||||||
|
return sendHttp(config);
|
||||||
|
};
|
||||||
|
}]);
|
||||||
|
|
||||||
|
angularFileUpload.directive('ngFileSelect', [ '$parse', '$timeout', '$compile',
|
||||||
|
function($parse, $timeout, $compile) { return {
|
||||||
|
restrict: 'AEC',
|
||||||
|
require:'?ngModel',
|
||||||
|
link: function(scope, elem, attr, ngModel) {
|
||||||
|
handleFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile);
|
||||||
|
}
|
||||||
|
}}]);
|
||||||
|
|
||||||
|
function handleFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile) {
|
||||||
|
function isInputTypeFile() {
|
||||||
|
return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file';
|
||||||
|
}
|
||||||
|
|
||||||
|
var watchers = [];
|
||||||
|
|
||||||
|
function watchForRecompile(attrVal) {
|
||||||
|
$timeout(function() {
|
||||||
|
if (elem.parent().length) {
|
||||||
|
watchers.push(scope.$watch(attrVal, function(val, oldVal) {
|
||||||
|
if (val != oldVal) {
|
||||||
|
recompileElem();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function recompileElem() {
|
||||||
|
var clone = elem.clone();
|
||||||
|
if (elem.attr('__afu_gen__')) {
|
||||||
|
angular.element(document.getElementById(elem.attr('id').substring(1))).remove();
|
||||||
|
}
|
||||||
|
if (elem.parent().length) {
|
||||||
|
for (var i = 0; i < watchers.length; i++) {
|
||||||
|
watchers[i]();
|
||||||
|
}
|
||||||
|
elem.replaceWith(clone);
|
||||||
|
$compile(clone)(scope);
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindAttr(bindAttr, attrName) {
|
||||||
|
if (bindAttr) {
|
||||||
|
watchForRecompile(bindAttr);
|
||||||
|
var val = $parse(bindAttr)(scope);
|
||||||
|
if (val) {
|
||||||
|
elem.attr(attrName, val);
|
||||||
|
attr[attrName] = val;
|
||||||
|
} else {
|
||||||
|
elem.attr(attrName, null);
|
||||||
|
delete attr[attrName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindAttr(attr.ngMultiple, 'multiple');
|
||||||
|
bindAttr(attr.ngAccept, 'ng-accept');
|
||||||
|
bindAttr(attr.ngCapture, 'capture');
|
||||||
|
|
||||||
|
if (attr['ngFileSelect'] != '') {
|
||||||
|
attr.ngFileChange = attr.ngFileSelect;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChangeFn(evt) {
|
||||||
|
var files = [], fileList, i;
|
||||||
|
fileList = evt.__files_ || (evt.target && evt.target.files);
|
||||||
|
updateModel(fileList, attr, ngModel, scope, evt);
|
||||||
|
};
|
||||||
|
|
||||||
|
var fileElem = elem;
|
||||||
|
if (!isInputTypeFile()) {
|
||||||
|
fileElem = angular.element('<input type="file">')
|
||||||
|
if (elem.attr['multiple']) fileElem.attr('multiple', elem.attr['multiple']);
|
||||||
|
if (elem.attr['accept']) fileElem.attr('accept', elem.attr['accept']);
|
||||||
|
if (elem.attr['capture']) fileElem.attr('capture', elem.attr['capture']);
|
||||||
|
for (var key in attr) {
|
||||||
|
if (key.indexOf('inputFile') == 0) {
|
||||||
|
var name = key.substring('inputFile'.length);
|
||||||
|
name = name[0].toLowerCase() + name.substring(1);
|
||||||
|
fileElem.attr(name, attr[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileElem.css('width', '0px').css('height', '0px').css('position', 'absolute').css('padding', 0).css('margin', 0)
|
||||||
|
.css('overflow', 'hidden').attr('tabindex', '-1').css('opacity', 0).attr('__afu_gen__', true);
|
||||||
|
elem.attr('__refElem__', true);
|
||||||
|
fileElem[0].__refElem__ = elem[0];
|
||||||
|
elem.parent()[0].insertBefore(fileElem[0], elem[0])
|
||||||
|
elem.css('overflow', 'hidden');
|
||||||
|
elem.bind('click', function(e) {
|
||||||
|
if (!resetAndClick(e)) {
|
||||||
|
fileElem[0].click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
elem.bind('click', resetAndClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAndClick(evt) {
|
||||||
|
if (fileElem[0].value != null && fileElem[0].value != '') {
|
||||||
|
fileElem[0].value = null;
|
||||||
|
// IE 11 already fires change event when you set the value to null
|
||||||
|
if (navigator.userAgent.indexOf("Trident/7") === -1) {
|
||||||
|
onChangeFn({target: {files: []}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if this is manual click trigger we don't need to reset again
|
||||||
|
if (!elem.attr('__afu_clone__')) {
|
||||||
|
// fix for IE10 cannot set the value of the input to null programmatically by cloning and replacing input
|
||||||
|
// IE 11 setting the value to null event will be fired after file change clearing the selected file so
|
||||||
|
// we just recreate the element for IE 11 as well
|
||||||
|
if (navigator.appVersion.indexOf("MSIE 10") !== -1 || navigator.userAgent.indexOf("Trident/7") !== -1) {
|
||||||
|
var clone = recompileElem();
|
||||||
|
clone.attr('__afu_clone__', true);
|
||||||
|
clone[0].click();
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
elem.attr('__afu_clone__', null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileElem.bind('change', onChangeFn);
|
||||||
|
|
||||||
|
elem.on('$destroy', function() {
|
||||||
|
for (var i = 0; i < watchers.length; i++) {
|
||||||
|
watchers[i]();
|
||||||
|
}
|
||||||
|
if (elem[0] != fileElem[0]) fileElem.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
watchers.push(scope.$watch(attr.ngModel, function(val, oldVal) {
|
||||||
|
if (val != oldVal && (val == null || !val.length)) {
|
||||||
|
if (navigator.appVersion.indexOf("MSIE 10") !== -1) {
|
||||||
|
recompileElem();
|
||||||
|
} else {
|
||||||
|
fileElem[0].value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
function updateModel(fileList, attr, ngModel, scope, evt) {
|
||||||
|
var files = [], rejFiles = [];
|
||||||
|
var accept = $parse(attr.ngAccept)(scope);
|
||||||
|
var regexp = angular.isString(accept) && accept ? new RegExp(globStringToRegex(accept), 'gi') : null;
|
||||||
|
var acceptFn = regexp ? null : attr.ngAccept;
|
||||||
|
|
||||||
|
for (var i = 0; i < fileList.length; i++) {
|
||||||
|
var file = fileList.item(i);
|
||||||
|
if ((!regexp || file.type.match(regexp) || (file.name != null && file.name.match(regexp))) &&
|
||||||
|
(!acceptFn || $parse(acceptFn)(scope, {$file: file, $event: evt}))) {
|
||||||
|
files.push(file);
|
||||||
|
} else {
|
||||||
|
rejFiles.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$timeout(function() {
|
||||||
|
if (ngModel) {
|
||||||
|
$parse(attr.ngModel).assign(scope, files);
|
||||||
|
ngModel && ngModel.$setViewValue(files != null && files.length == 0 ? '' : files);
|
||||||
|
if (attr.ngModelRejected) {
|
||||||
|
$parse(attr.ngModelRejected).assign(scope, rejFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attr.ngFileChange && attr.ngFileChange != "") {
|
||||||
|
$parse(attr.ngFileChange)(scope, {
|
||||||
|
$files: files,
|
||||||
|
$rejectedFiles: rejFiles,
|
||||||
|
$event: evt
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
angularFileUpload.directive('ngFileDrop', [ '$parse', '$timeout', '$location', function($parse, $timeout, $location) { return {
|
||||||
|
restrict: 'AEC',
|
||||||
|
require:'?ngModel',
|
||||||
|
link: function(scope, elem, attr, ngModel) {
|
||||||
|
handleDrop(scope, elem, attr, ngModel, $parse, $timeout, $location);
|
||||||
|
}
|
||||||
|
}}]);
|
||||||
|
|
||||||
|
angularFileUpload.directive('ngNoFileDrop', function() {
|
||||||
|
return function(scope, elem, attr) {
|
||||||
|
if (dropAvailable()) elem.css('display', 'none')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//for backward compatibility
|
||||||
|
angularFileUpload.directive('ngFileDropAvailable', [ '$parse', '$timeout', function($parse, $timeout) {
|
||||||
|
return function(scope, elem, attr) {
|
||||||
|
if (dropAvailable()) {
|
||||||
|
var fn = $parse(attr['ngFileDropAvailable']);
|
||||||
|
$timeout(function() {
|
||||||
|
fn(scope);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
|
||||||
|
function handleDrop(scope, elem, attr, ngModel, $parse, $timeout, $location) {
|
||||||
|
var available = dropAvailable();
|
||||||
|
if (attr['dropAvailable']) {
|
||||||
|
$timeout(function() {
|
||||||
|
scope.dropAvailable ? scope.dropAvailable.value = available : scope.dropAvailable = available;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!available) {
|
||||||
|
if ($parse(attr.hideOnDropNotAvailable)(scope) != false) {
|
||||||
|
elem.css('display', 'none');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var leaveTimeout = null;
|
||||||
|
var stopPropagation = $parse(attr.stopPropagation)(scope);
|
||||||
|
var dragOverDelay = 1;
|
||||||
|
var accept = $parse(attr.ngAccept)(scope) || attr.accept;
|
||||||
|
var regexp = angular.isString(accept) && accept ? new RegExp(globStringToRegex(accept), 'gi') : null;
|
||||||
|
var acceptFn = regexp ? null : attr.ngAccept;
|
||||||
|
var actualDragOverClass;
|
||||||
|
elem[0].addEventListener('dragover', function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (stopPropagation) evt.stopPropagation();
|
||||||
|
// handling dragover events from the Chrome download bar
|
||||||
|
if (navigator.userAgent.indexOf("Chrome") > -1) {
|
||||||
|
var b = evt.dataTransfer.effectAllowed;
|
||||||
|
evt.dataTransfer.dropEffect = ('move' === b || 'linkMove' === b) ? 'move' : 'copy';
|
||||||
|
}
|
||||||
|
$timeout.cancel(leaveTimeout);
|
||||||
|
if (!scope.actualDragOverClass) {
|
||||||
|
actualDragOverClass = calculateDragOverClass(scope, attr, evt);
|
||||||
|
}
|
||||||
|
elem.addClass(actualDragOverClass);
|
||||||
|
}, false);
|
||||||
|
elem[0].addEventListener('dragenter', function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (stopPropagation) evt.stopPropagation();
|
||||||
|
}, false);
|
||||||
|
elem[0].addEventListener('dragleave', function(evt) {
|
||||||
|
leaveTimeout = $timeout(function() {
|
||||||
|
elem.removeClass(actualDragOverClass);
|
||||||
|
actualDragOverClass = null;
|
||||||
|
}, dragOverDelay || 1);
|
||||||
|
}, false);
|
||||||
|
if (attr['ngFileDrop'] != '') {
|
||||||
|
attr.ngFileChange = scope.ngFileDrop;
|
||||||
|
}
|
||||||
|
elem[0].addEventListener('drop', function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (stopPropagation) evt.stopPropagation();
|
||||||
|
elem.removeClass(actualDragOverClass);
|
||||||
|
actualDragOverClass = null;
|
||||||
|
extractFiles(evt, function(files, rejFiles) {
|
||||||
|
$timeout(function() {
|
||||||
|
if (ngModel) {
|
||||||
|
$parse(attr.ngModel).assign(scope, files);
|
||||||
|
ngModel && ngModel.$setViewValue(files != null && files.length == 0 ? '' : files);
|
||||||
|
}
|
||||||
|
if (attr['ngModelRejected']) {
|
||||||
|
if (scope[attr.ngModelRejected]) {
|
||||||
|
$parse(attr.ngModelRejected).assign(scope, rejFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$timeout(function() {
|
||||||
|
$parse(attr.ngFileChange)(scope, {
|
||||||
|
$files: files,
|
||||||
|
$rejectedFiles: rejFiles,
|
||||||
|
$event: evt
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, $parse(attr.allowDir)(scope) != false, attr.multiple || $parse(attr.ngMultiple)(scope));
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
function calculateDragOverClass(scope, attr, evt) {
|
||||||
|
var valid = true;
|
||||||
|
if (regexp || acceptFn) {
|
||||||
|
var items = evt.dataTransfer.items;
|
||||||
|
if (items != null) {
|
||||||
|
for (var i = 0 ; i < items.length && valid; i++) {
|
||||||
|
valid = valid && (items[i].kind == 'file' || items[i].kind == '') &&
|
||||||
|
((acceptFn && $parse(acceptFn)(scope, {$file: items[i], $event: evt})) ||
|
||||||
|
(regexp && (items[i].type != null && items[i].type.match(regexp)) ||
|
||||||
|
(items[i].name != null && items[i].name.match(regexp))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var clazz = $parse(attr.dragOverClass)(scope, {$event : evt});
|
||||||
|
if (clazz) {
|
||||||
|
if (clazz.delay) dragOverDelay = clazz.delay;
|
||||||
|
if (clazz.accept) clazz = valid ? clazz.accept : clazz.reject;
|
||||||
|
}
|
||||||
|
return clazz || attr['dragOverClass'] || 'dragover';
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractFiles(evt, callback, allowDir, multiple) {
|
||||||
|
var files = [], rejFiles = [], items = evt.dataTransfer.items, processing = 0;
|
||||||
|
|
||||||
|
function addFile(file) {
|
||||||
|
if ((!regexp || file.type.match(regexp) || (file.name != null && file.name.match(regexp))) &&
|
||||||
|
(!acceptFn || $parse(acceptFn)(scope, {$file: file, $event: evt}))) {
|
||||||
|
files.push(file);
|
||||||
|
} else {
|
||||||
|
rejFiles.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items && items.length > 0 && $location.protocol() != 'file') {
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) {
|
||||||
|
var entry = items[i].webkitGetAsEntry();
|
||||||
|
if (entry.isDirectory && !allowDir) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry != null) {
|
||||||
|
traverseFileTree(files, entry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var f = items[i].getAsFile();
|
||||||
|
if (f != null) addFile(f);
|
||||||
|
}
|
||||||
|
if (!multiple && files.length > 0) break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var fileList = evt.dataTransfer.files;
|
||||||
|
if (fileList != null) {
|
||||||
|
for (var i = 0; i < fileList.length; i++) {
|
||||||
|
addFile(fileList.item(i));
|
||||||
|
if (!multiple && files.length > 0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var delays = 0;
|
||||||
|
(function waitForProcess(delay) {
|
||||||
|
$timeout(function() {
|
||||||
|
if (!processing) {
|
||||||
|
if (!multiple && files.length > 1) {
|
||||||
|
var i = 0;
|
||||||
|
while (files[i].type == 'directory') i++;
|
||||||
|
files = [files[i]];
|
||||||
|
}
|
||||||
|
callback(files, rejFiles);
|
||||||
|
} else {
|
||||||
|
if (delays++ * 10 < 20 * 1000) {
|
||||||
|
waitForProcess(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, delay || 0)
|
||||||
|
})();
|
||||||
|
|
||||||
|
function traverseFileTree(files, entry, path) {
|
||||||
|
if (entry != null) {
|
||||||
|
if (entry.isDirectory) {
|
||||||
|
var filePath = (path || '') + entry.name;
|
||||||
|
addFile({name: entry.name, type: 'directory', path: filePath});
|
||||||
|
var dirReader = entry.createReader();
|
||||||
|
var entries = [];
|
||||||
|
processing++;
|
||||||
|
var readEntries = function() {
|
||||||
|
dirReader.readEntries(function(results) {
|
||||||
|
try {
|
||||||
|
if (!results.length) {
|
||||||
|
for (var i = 0; i < entries.length; i++) {
|
||||||
|
traverseFileTree(files, entries[i], (path ? path : '') + entry.name + '/');
|
||||||
|
}
|
||||||
|
processing--;
|
||||||
|
} else {
|
||||||
|
entries = entries.concat(Array.prototype.slice.call(results || [], 0));
|
||||||
|
readEntries();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
processing--;
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}, function() {
|
||||||
|
processing--;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
readEntries();
|
||||||
|
} else {
|
||||||
|
processing++;
|
||||||
|
entry.file(function(file) {
|
||||||
|
try {
|
||||||
|
processing--;
|
||||||
|
file.path = (path ? path : '') + file.name;
|
||||||
|
addFile(file);
|
||||||
|
} catch (e) {
|
||||||
|
processing--;
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}, function(e) {
|
||||||
|
processing--;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dropAvailable() {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
return ('draggable' in div) && ('ondrop' in div);
|
||||||
|
}
|
||||||
|
|
||||||
|
function globStringToRegex(str) {
|
||||||
|
if (str.length > 2 && str[0] === '/' && str[str.length -1] === '/') {
|
||||||
|
return str.substring(1, str.length - 1);
|
||||||
|
}
|
||||||
|
var split = str.split(','), result = '';
|
||||||
|
if (split.length > 1) {
|
||||||
|
for (var i = 0; i < split.length; i++) {
|
||||||
|
result += '(' + globStringToRegex(split[i]) + ')';
|
||||||
|
if (i < split.length - 1) {
|
||||||
|
result += '|'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (str.indexOf('.') == 0) {
|
||||||
|
str= '*' + str;
|
||||||
|
}
|
||||||
|
result = '^' + str.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + '-]', 'g'), '\\$&') + '$';
|
||||||
|
result = result.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ngFileUpload = angular.module('ngFileUpload', []);
|
||||||
|
|
||||||
|
for (var key in angularFileUpload) {
|
||||||
|
ngFileUpload[key] = angularFileUpload[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
@ -5,12 +5,12 @@
|
|||||||
{{ app.document_count }} <small>document{{ app.document_count > 1 ? 's' : '' }} in the database</small>
|
{{ app.document_count }} <small>document{{ app.document_count > 1 ? 's' : '' }} in the database</small>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<blockquote class="pull-right">
|
<!--<div ng-file-drop ng-model="files" style="width: 150px; height: 150px; border: 1px dashed;"
|
||||||
<p>There seems to be a kind of order in the universe, but human life itself is almost pure chaos.</p>
|
drag-over-class="dragover" ng-multiple="true" allow-dir="false"
|
||||||
<small>Katherine Anne Porter</small>
|
accept="image/*,application/pdf,application/zip">Drop files here</div>
|
||||||
</blockquote>
|
<p ng-no-file-drop>File Drag/Drop is not supported for this browser</p>
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
{{ files }}-->
|
||||||
|
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
<ul class="list-inline">
|
<ul class="list-inline">
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
{{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }}</small>
|
{{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }}</small>
|
||||||
<img ng-if="document" ng-src="img/flag/{{ document.language }}.png" title="{{ document.language }}" />
|
<img ng-if="document" ng-src="img/flag/{{ document.language }}.png" title="{{ document.language }}" />
|
||||||
<a ng-href="../api/file/zip?id={{ document.id }}" class="btn btn-default" title="Download all files">
|
<a ng-href="../api/file/zip?id={{ document.id }}" class="btn btn-default" title="Download all files">
|
||||||
<span class="glyphicon glyphicon-compressed"></span>
|
<span class="glyphicon glyphicon-download-alt"></span>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<h1>
|
<h1>
|
||||||
{{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }}</small>
|
{{ document.title }} <small>{{ document.create_date | date: 'yyyy-MM-dd' }}</small>
|
||||||
<a ng-href="../api/file/zip?id={{ document.id }}&share={{ $stateParams.shareId }}" class="btn btn-default" title="Download all files">
|
<a ng-href="../api/file/zip?id={{ document.id }}&share={{ $stateParams.shareId }}" class="btn btn-default" title="Download all files">
|
||||||
<span class="glyphicon glyphicon-compressed"></span>
|
<span class="glyphicon glyphicon-download-alt"></span>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<ul class="list-inline">
|
<ul class="list-inline">
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
api.current_version=${project.version}
|
api.current_version=${project.version}
|
||||||
api.min_version=1.0
|
api.min_version=1.0
|
||||||
db.version=6
|
db.version=7
|
@ -197,4 +197,46 @@ public class TestFileResource extends BaseJerseyTest {
|
|||||||
files = json.getJSONArray("files");
|
files = json.getJSONArray("files");
|
||||||
Assert.assertEquals(1, files.length());
|
Assert.assertEquals(1, files.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOrphanFile() throws Exception {
|
||||||
|
// Login file1
|
||||||
|
clientUtil.createUser("file2");
|
||||||
|
String file2AuthenticationToken = clientUtil.login("file2");
|
||||||
|
|
||||||
|
// Add a file
|
||||||
|
WebResource fileResource = resource().path("/file");
|
||||||
|
fileResource.addFilter(new CookieAuthenticationFilter(file2AuthenticationToken));
|
||||||
|
FormDataMultiPart form = new FormDataMultiPart();
|
||||||
|
InputStream file = this.getClass().getResourceAsStream("/file/PIA00452.jpg");
|
||||||
|
FormDataBodyPart fdp = new FormDataBodyPart("file",
|
||||||
|
new BufferedInputStream(file),
|
||||||
|
MediaType.APPLICATION_OCTET_STREAM_TYPE);
|
||||||
|
form.bodyPart(fdp);
|
||||||
|
ClientResponse response = fileResource.type(MediaType.MULTIPART_FORM_DATA).put(ClientResponse.class, form);
|
||||||
|
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||||
|
JSONObject json = response.getEntity(JSONObject.class);
|
||||||
|
String file1Id = json.getString("id");
|
||||||
|
|
||||||
|
// Create a document
|
||||||
|
WebResource documentResource = resource().path("/document");
|
||||||
|
documentResource.addFilter(new CookieAuthenticationFilter(file2AuthenticationToken));
|
||||||
|
MultivaluedMapImpl postParams = new MultivaluedMapImpl();
|
||||||
|
postParams.add("title", "File test document 1");
|
||||||
|
postParams.add("language", "eng");
|
||||||
|
response = documentResource.put(ClientResponse.class, postParams);
|
||||||
|
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||||
|
json = response.getEntity(JSONObject.class);
|
||||||
|
String document1Id = json.optString("id");
|
||||||
|
Assert.assertNotNull(document1Id);
|
||||||
|
|
||||||
|
// Attach a file to a document
|
||||||
|
documentResource = resource().path("/file/" + file1Id);
|
||||||
|
documentResource.addFilter(new CookieAuthenticationFilter(file2AuthenticationToken));
|
||||||
|
postParams = new MultivaluedMapImpl();
|
||||||
|
postParams.add("id", document1Id);
|
||||||
|
response = documentResource.post(ClientResponse.class, postParams);
|
||||||
|
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||||
|
json = response.getEntity(JSONObject.class);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user