package ca.carleton.gcrc.couch.onUpload;
import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import ca.carleton.gcrc.couch.client.CouchDesignDocument;
import ca.carleton.gcrc.couch.client.CouchQuery;
import ca.carleton.gcrc.couch.client.CouchQueryResults;
import ca.carleton.gcrc.couch.onUpload.mail.MailNotification;
import ca.carleton.gcrc.olkit.multimedia.file.SystemFile;
public class UploadWorkerThread extends Thread {
final protected Logger logger = Logger.getLogger(this.getClass());
private boolean isShuttingDown = false;
private CouchDesignDocument dd;
private File mediaDir;
private MailNotification mailNotification;
private Set<String> docIdsToSkip = new HashSet<String>();
private List<FileConversionPlugin> fileConverters;
protected UploadWorkerThread(
CouchDesignDocument dd
,File mediaDir
,MailNotification mailNotification
,List<FileConversionPlugin> fileConverters
) {
this.dd = dd;
this.mediaDir = mediaDir;
this.mailNotification = mailNotification;
this.fileConverters = fileConverters;
}
public void shutdown() {
logger.info("Shutting down upload worker thread");
synchronized(this) {
isShuttingDown = true;
this.notifyAll();
}
}
@Override
public void run() {
logger.info("Start upload worker thread");
boolean done = false;
do {
synchronized(this) {
done = isShuttingDown;
}
if( false == done ) {
activity();
}
} while( false == done );
logger.info("Upload worker thread exiting");
}
private void activity() {
CouchQuery query = new CouchQuery();
query.setViewName("server_work");
CouchQueryResults results;
try {
results = dd.performQuery(query);
} catch (Exception e) {
logger.error("Error accessing server",e);
waitMillis(60 * 1000); // wait a minute
return;
}
// Check for work
String docId = null;
JSONArray state = null;
for(JSONObject row : results.getRows()) {
String id = row.optString("id");
if( false == docIdsToSkip.contains(id) ) {
// Found some work
docId = id;
state = row.optJSONArray("key");
break;
}
}
if( null == docId ) {
// Nothing to do, wait 5 secs
waitMillis(5 * 1000);
return;
} else {
try {
// Handle this work
performWork(docId, state);
} catch(Exception e) {
logger.error("Error processing document "+docId+" ("+state+")",e);
docIdsToSkip.add(docId);
}
}
}
private void performWork(String docId, JSONArray state) throws Exception {
logger.info("Upload worker thread processing: "+docId+" ("+state+")");
String work = state.getString(0);
if( UploadConstants.UPLOAD_STATUS_SUBMITTED.equals(work) ) {
String attachmentName = state.getString(1);
performSubmittedWork(docId, attachmentName);
} else if( UploadConstants.UPLOAD_STATUS_ANALYZED.equals(work) ) {
String attachmentName = state.getString(1);
performAnalyzedWork(docId, attachmentName);
} else if( UploadConstants.UPLOAD_STATUS_APPROVED.equals(work) ) {
String attachmentName = state.getString(1);
performApprovedWork(docId, attachmentName);
} else {
throw new Exception("Unrecognized state: "+state);
}
logger.info("Upload worker thread completed: "+docId+" ("+state+")");
}
private void performSubmittedWork(String docId, String attachmentName) throws Exception {
JSONObject doc = dd.getDatabase().getDocument(docId);
FileConversionContext conversionContext =
new FileConversionContext(doc,dd,attachmentName,mediaDir);
JSONObject submittedObj = conversionContext.getAttachmentDescription();
if( null == submittedObj) {
logger.info("Submission can not be found");
} else if( false == UploadConstants.UPLOAD_STATUS_SUBMITTED.equals( submittedObj.optString(UploadConstants.UPLOAD_STATUS_KEY) ) ) {
logger.info("File not in submit state");
} else {
JSONObject originalObj = submittedObj.optJSONObject("original");
if( null == originalObj) {
logger.error("Submission does not contain original");
throw new Exception("Submission does not contain original");
}
// Set file size
File file = conversionContext.getOriginalFile();
long fileSize = file.length();
originalObj.put(UploadConstants.SIZE_KEY, fileSize);
// Mime type, encoding type and file class
boolean pluginFound = false;
String mimeType = null;
String mimeEncoding = null;
String fileClass = null;
for(FileConversionPlugin fcp : this.fileConverters) {
FileConversionMetaData md = fcp.getFileMetaData(file);
if( md.isFileConvertable() ) {
mimeType = md.getMimeType();
fileClass = md.getFileClass();
mimeEncoding = md.getMimeEncoding();
pluginFound = true;
break;
}
}
if( false == pluginFound ) {
logger.info("No plugin found for uploaded file");
SystemFile sf = SystemFile.getSystemFile(file);
mimeType = sf.getMimeType();
mimeEncoding = sf.getMimeEncoding();
}
if( null != mimeType ) {
originalObj.put(UploadConstants.MIME_KEY, mimeType);
}
if( null != mimeEncoding ) {
originalObj.put(UploadConstants.ENCODING_KEY, mimeEncoding);
}
if( null != fileClass ){
submittedObj.put(UploadConstants.MIME_CLASS_KEY, fileClass);
}
// Update status
conversionContext.setStatus(UploadConstants.UPLOAD_STATUS_ANALYZED);
conversionContext.saveDocument();
}
}
private void performAnalyzedWork(String docId, String attachmentName) throws Exception {
JSONObject doc = dd.getDatabase().getDocument(docId);
FileConversionContext conversionContext =
new FileConversionContext(doc,dd,attachmentName,mediaDir);
JSONObject analyzedObj = conversionContext.getAttachmentDescription();
if( null == analyzedObj ) {
logger.info("Analysis object not found");
} else if( false == UploadConstants.UPLOAD_STATUS_ANALYZED.equals( analyzedObj.optString(UploadConstants.UPLOAD_STATUS_KEY) ) ) {
logger.info("File not in analyzed state");
} else {
JSONObject originalObj = analyzedObj.optJSONObject("original");
if( null == originalObj ) {
logger.error("Analysis required but original object is not present");
throw new Exception("Analysis required but original object is not present");
}
boolean pluginFound = false;
String fileClass = conversionContext.getFileClass();
for(FileConversionPlugin fcp : this.fileConverters) {
if( fcp.handlesFileClass(fileClass) ) {
fcp.analyzeFile(conversionContext);
pluginFound = true;
break;
}
}
if( false == pluginFound ) {
logger.info("No plugin found to analyze file class: "+fileClass);
// By default, original file is used
analyzedObj.put(UploadConstants.MEDIA_FILE_KEY, originalObj.opt(UploadConstants.MEDIA_FILE_KEY));
analyzedObj.put(UploadConstants.MIME_KEY, originalObj.opt(UploadConstants.MIME_KEY));
analyzedObj.put(UploadConstants.ENCODING_KEY, originalObj.opt(UploadConstants.ENCODING_KEY));
analyzedObj.put(UploadConstants.SIZE_KEY, originalObj.opt(UploadConstants.SIZE_KEY));
}
// Update document
conversionContext.setStatus(UploadConstants.UPLOAD_STATUS_WAITING_FOR_APPROVAL);
conversionContext.saveDocument();
// Notify that upload is available
sendContributionApprovalRequest(docId, doc, attachmentName);
}
}
private void performApprovedWork(String docId, String attachmentName) throws Exception {
JSONObject doc = dd.getDatabase().getDocument(docId);
FileConversionContext conversionContext =
new FileConversionContext(doc,dd,attachmentName,mediaDir);
JSONObject approvedObj = conversionContext.getAttachmentDescription();
if( null == approvedObj ) {
logger.info("Approved object not found");
} else if( false == UploadConstants.UPLOAD_STATUS_APPROVED.equals( approvedObj.optString(UploadConstants.UPLOAD_STATUS_KEY) ) ) {
logger.info("File not in analyzed state");
} else {
boolean pluginFound = false;
String fileClass = conversionContext.getFileClass();
for(FileConversionPlugin fcp : this.fileConverters) {
if( fcp.handlesFileClass(fileClass) ) {
fcp.approveFile(conversionContext);
pluginFound = true;
break;
}
}
if( false == pluginFound ) {
logger.info("No plugin found for uploaded file class: "+fileClass);
String mimeType = conversionContext.getContentType();
if( null == mimeType ) {
mimeType = "application/octet-stream";
}
// By default, upload original file
conversionContext.uploadFile(
conversionContext.getAttachmentName()
,conversionContext.getFile()
,mimeType
);
}
// Update status
conversionContext.setStatus(UploadConstants.UPLOAD_STATUS_ATTACHED);
conversionContext.saveDocument();
}
}
private void sendContributionApprovalRequest(String docId, JSONObject doc, String attachmentName) {
// Notify that upload is available
try {
String title = "[not set]";
String description = "[not set]";
JSONObject contributionObj = doc.optJSONObject("nunaliit_contribution");
if( null != contributionObj ) {
if( contributionObj.has("title") ) {
title = contributionObj.getString("title");
}
if( contributionObj.has("description") ) {
description = contributionObj.getString("description");
}
}
mailNotification.uploadNotification(docId, title, description, attachmentName);
} catch(Exception e) {
logger.error("Failed mail notification",e);
}
}
private boolean waitMillis(int millis) {
synchronized(this) {
if( true == isShuttingDown ) {
return false;
}
try {
this.wait(millis);
} catch (InterruptedException e) {
// Interrupted
return false;
}
}
return true;
}
}