package com.yeokm1.nussocprintandroid.print_activities.printing; import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; import android.content.res.AssetManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ListView; import com.jcraft.jsch.SftpProgressMonitor; import com.yeokm1.nussocprintandroid.R; import com.yeokm1.nussocprintandroid.core.FlurryFunctions; import com.yeokm1.nussocprintandroid.core.HelperFunctions; import com.yeokm1.nussocprintandroid.network.ConnectionTask; import com.yeokm1.nussocprintandroid.print_activities.FatDialogActivity; import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class PrintingActivity extends FatDialogActivity { public static final String INTENT_FILE_PATH = "filePath"; public static final String INTENT_PRINTER_NAME = "printerName"; public static final String INTENT_PAGES_PER_SHEET = "pagesPerSheet"; public static final String INTENT_PAGE_START_RANGE = "startRange"; public static final String INTENT_PAGE_END_RANGE = "endRange"; private static final int INVALID_INTENT_INT = 0; private final int POSITION_CONNECTING = 0; private final int POSITION_HOUSEKEEPING = 1; private final int POSITION_DOWNLOADING_DOC_CONVERTER = 2; private final int POSITION_UPLOADING_PDF_CONVERTER = 3; private final int POSITION_UPLOADING_USER_DOC = 4; private final int POSITION_CONVERTING_TO_PDF = 5; private final int POSITION_TRIM_PDF_TO_PAGE_RANGE = 6; private final int POSITION_FORMATTING_PDF = 7; private final int POSITION_CONVERTING_TO_POSTSCRIPT = 8; private final int POSITION_SENDING_TO_PRINTER = 9; private final int POSITION_COMPLETED = 10; private ListView printProgress; private Button finishButton; private String filePath; private String filename; private String printer; private int pagesPerSheet; private int startPageRange; private int endPageRange; private String[] HEADER_TEXT; private String SUBTITLE_PROGRESS_TEXT; private String SUBTITLE_INDETERMINATE_TEXT; private String SUBTITLE_DOWNLOAD_DOC_CONVERTER_SEC_SITE; private boolean[] PROGRESS_INDETERMINATE = {true ,true ,true ,false ,false ,true ,true ,true ,true ,true}; private boolean needToConvertDocToPDF = true; private boolean needToFormatPDF = true; private boolean needToTrimPDFToPageRange = false; private boolean nowDownloadingDocConverterFromSecondarySite = false; private long pdfConvSize = 0; private long pdfConvUploaded = 0; private long docToPrintSize = 0; private int docToPrintUploaded = 0; private int currentProgress = 0; private PrintingTask printingTask; private PrintingProgressItemAdapter itemsAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_printing); resizeDialogWindow(); printProgress = (ListView) findViewById(R.id.printing_list); finishButton = (Button) findViewById(R.id.printing_button_finish); finishButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onFinishButtonPress(); } }); HEADER_TEXT = getResources().getStringArray(R.array.printing_progress_title_text); SUBTITLE_PROGRESS_TEXT = getString(R.string.printing_progress_subtitle_progress); SUBTITLE_INDETERMINATE_TEXT = getString(R.string.printing_progress_subtitle_progress_indeterminate); SUBTITLE_DOWNLOAD_DOC_CONVERTER_SEC_SITE = getString(R.string.printing_progress_doc_converter_secondary_site); Intent intent = getIntent(); filePath = intent.getStringExtra(INTENT_FILE_PATH); Uri fileUri = Uri.parse(filePath); filename = fileUri.getLastPathSegment(); printer = intent.getStringExtra(INTENT_PRINTER_NAME); pagesPerSheet = intent.getIntExtra(INTENT_PAGES_PER_SHEET, INVALID_INTENT_INT); startPageRange = intent.getIntExtra(INTENT_PAGE_START_RANGE, INVALID_INTENT_INT); endPageRange = intent.getIntExtra(INTENT_PAGE_END_RANGE, INVALID_INTENT_INT); if (pagesPerSheet == 1) { needToFormatPDF = false; } if (isFileAPdf(filePath)) { needToConvertDocToPDF = false; } if (startPageRange > 0) { needToTrimPDFToPageRange = true; } printingTask = new PrintingTask(this); printingTask.execute(); itemsAdapter = new PrintingProgressItemAdapter(this, generateItems()); printProgress.setAdapter(itemsAdapter); } @Override public void onBackPressed(){ onFinishButtonPress(); } public void setFinishButtonTextToClose(){ if(finishButton != null){ finishButton.setText(R.string.printing_progress_button_close); } } public void onFinishButtonPress(){ if(printingTask == null){ finish(); } else { DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (which){ case DialogInterface.BUTTON_POSITIVE: if(printingTask != null){ printingTask.cancel(true); printingTask = null; } finish(); break; case DialogInterface.BUTTON_NEGATIVE: //Do nothing break; } } }; HelperFunctions.showYesNoAlert(this, getString(R.string.printing_progress_premature_cancel_title), null, dialogClickListener); } } public void refreshList(){ if(itemsAdapter != null) { itemsAdapter.clear(); List<PrintingProgressItem> items = generateItems(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { itemsAdapter.addAll(items); } else { for (PrintingProgressItem item : items) { itemsAdapter.add(item); } } itemsAdapter.notifyDataSetChanged(); } } private List<PrintingProgressItem> generateItems(){ ArrayList<PrintingProgressItem> items = new ArrayList<PrintingProgressItem>(); for(int row = 0; row < HEADER_TEXT.length; row++){ if(row == POSITION_DOWNLOADING_DOC_CONVERTER && !needToConvertDocToPDF || row == POSITION_CONVERTING_TO_PDF && !needToConvertDocToPDF || row == POSITION_UPLOADING_PDF_CONVERTER && !needToFormatPDF || row == POSITION_FORMATTING_PDF && !needToFormatPDF || row == POSITION_TRIM_PDF_TO_PAGE_RANGE && !needToTrimPDFToPageRange){ //Don't have to do continue; } String title; String subtitle; String headerText = HEADER_TEXT[row]; if(row == POSITION_UPLOADING_USER_DOC){ title = String.format(headerText, filename); } else if(row == POSITION_FORMATTING_PDF){ title = String.format(headerText, pagesPerSheet); } else if(row == POSITION_SENDING_TO_PRINTER){ title = String.format(headerText, printer); } else if(row == POSITION_TRIM_PDF_TO_PAGE_RANGE){ title = String.format(headerText, startPageRange, endPageRange); } else { title = headerText; } boolean isThisDone; boolean isError; boolean isInProgress; if(currentProgress > row){ isThisDone = true; isError = false; isInProgress = false; } else if(row == currentProgress){ isThisDone = false; if(printingTask == null){ //Means the operation has ended on the current progress, show a cross to mean an error isError = true; isInProgress = false; } else { isError = false; isInProgress = true; } } else { isThisDone = false; isError = false; isInProgress = false; } float progressFraction = 0; boolean progressIndeterminate = PROGRESS_INDETERMINATE[row]; if(row == POSITION_UPLOADING_PDF_CONVERTER){ progressFraction = generateProgressFraction(pdfConvUploaded, pdfConvSize); subtitle = generateProgressString(pdfConvUploaded, pdfConvSize, progressFraction); } else if(row == POSITION_UPLOADING_USER_DOC) { progressFraction = generateProgressFraction(docToPrintUploaded, docToPrintSize); subtitle = generateProgressString(docToPrintUploaded, docToPrintSize, progressFraction); } else if(row == POSITION_DOWNLOADING_DOC_CONVERTER && nowDownloadingDocConverterFromSecondarySite){ subtitle = SUBTITLE_DOWNLOAD_DOC_CONVERTER_SEC_SITE; } else if(row == POSITION_CONVERTING_TO_PDF || row == POSITION_TRIM_PDF_TO_PAGE_RANGE || row == POSITION_CONVERTING_TO_POSTSCRIPT){ subtitle = SUBTITLE_INDETERMINATE_TEXT; } else { subtitle = ""; } if(isThisDone && !isError){ progressFraction = 1.0f; } PrintingProgressItem item = new PrintingProgressItem(title, subtitle, progressFraction, isThisDone, isError, progressIndeterminate, isInProgress); items.add(item); } return items; } private String generateProgressString(long currentSize, long totalSize, float progressFraction){ String currentSizeStr = humanReadableByteCount(currentSize, false); String totalSizeStr = humanReadableByteCount(totalSize, false); String progressStr = String.format(SUBTITLE_PROGRESS_TEXT, currentSizeStr, totalSizeStr, progressFraction * 100); return progressStr; } private float generateProgressFraction(long currentSize, long totalSize){ double currentSizeDbl = currentSize; double totalSizeDbl = totalSize; float fraction = 0; if(totalSize != 0){ fraction = (float) (currentSizeDbl / totalSizeDbl); } return fraction; } private String getFileExtension(String path){ String extension = path.substring(path.lastIndexOf(".")); return extension.toLowerCase(); } private boolean isFileAPdf(String path){ String extension = getFileExtension(path); if(extension.equalsIgnoreCase(".pdf")){ return true; } else { return false; } } //http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java public static String humanReadableByteCount(long bytes, boolean si) { int unit = si ? 1000 : 1024; if (bytes < unit) return bytes + " B"; int exp = (int) (Math.log(bytes) / Math.log(unit)); String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); } class PrintingTask extends ConnectionTask { public static final String FLURRY_PRINT_EVENT = "Print Job"; String PDF_CONVERTER_NAME = "nup_pdf"; String PDF_CONVERTER_FILENAME = "nup_pdf.jar"; String PDF_CONVERTER_FILEPATH = "socPrint/nup_pdf.jar"; //This converter is for 6 pages/sheet as nup_pdf cannot generate such a file String PDF_CONVERTER_6PAGE_NAME = "Multivalent"; String PDF_CONVERTER_6PAGE_FILENAME = "Multivalent.jar"; String PDF_CONVERTER_6PAGE_FILEPATH = "socPrint/Multivalent.jar"; String DOC_CONVERTER_NAME = "docs-to-pdf-converter-1.7"; String DOC_CONVERTER_FILENAME = "docs-to-pdf-converter-1.7.jar"; String DOC_CONVERTER_FILEPATH = "socPrint/docs-to-pdf-converter-1.7.jar"; String PDF_CONVERTER_MD5 = "C1F8FF3F9DE7B2D2A2B41FBC0085888B"; String PDF_CONVERTER_6PAGE_MD5 = "813BB651A1CC6EA230F28AAC47F78051"; String DOC_CONVERTER_MD5 = "1FC140AD8074E333F9082300F4EA38DC"; String UPLOAD_FILENAME = "source"; //Add .path extension later String UPLOAD_SOURCE_PDF_FILEPATH = "socPrint/source.pdf"; String UPLOAD_PDF_TRIMMED_FILEPATH = "socPrint/source-trimmed.pdf"; String UPLOAD_PDF_FORMATTED_TRIMMED_6PAGE_FILEPATH = "socPrint/source-trimmed-up.pdf"; String UPLOAD_PDF_FORMATTED_6PAGE_FILEPATH = "socPrint/source-up.pdf"; String UPLOAD_PDF_FORMATTED_FILEPATH = "socPrint/formatted.pdf"; String UPLOAD_PS_FILEPATH_FORMAT = "socPrint/\"%s.ps\""; String TEMP_DIRECTORY_NO_SLASH = "socPrint"; String TEMP_DIRECTORY = "socPrint/"; String[] ERROR_TITLE_TEXT; String uploadedFilename; public PrintingTask(Activity activity){ super(activity); ERROR_TITLE_TEXT = getResources().getStringArray(R.array.printing_progress_error_title); uploadedFilename = UPLOAD_FILENAME + getFileExtension(filePath); } public void onPreExecute(){ super.onPreExecute(); //Step -1: Tell Google Analytics about printing actions String fileType = getFileExtension(filePath); // Capture author info & user status Map<String, String> printEventParams = new HashMap<String, String>(); printEventParams.put("printer", printer); printEventParams.put("pagesPerSheet", Integer.toString(pagesPerSheet)); printEventParams.put("fileType", fileType); if(needToTrimPDFToPageRange){ printEventParams.put("startPage", Integer.toString(startPageRange)); printEventParams.put("endPage", Integer.toString(endPageRange)); } FlurryFunctions.logTimedEvent(FLURRY_PRINT_EVENT, printEventParams); } @Override protected String doInBackground(String... params) { //Step 0: Connecting to server currentProgress = POSITION_CONNECTING; publishProgress(); try { startConnection(); //Step 1: Housekeeping, creating socPrint folder if not yet, delete all files except converters if(!isCancelled()){ currentProgress = POSITION_HOUSEKEEPING; publishProgress(); createDirectory(TEMP_DIRECTORY); String houseKeepingCommand = "find " + TEMP_DIRECTORY + " -type f \\( \\! -name '" + PDF_CONVERTER_FILENAME + "' \\) \\( \\! -name '" + DOC_CONVERTER_FILENAME + "' \\) \\( \\! -name '" + PDF_CONVERTER_6PAGE_FILENAME + "' \\) -exec rm '{}' \\;"; connection.runCommand(houseKeepingCommand); } //Step 2 : Downloading DOC converter to server if(needToConvertDocToPDF && !isCancelled()){ currentProgress = POSITION_DOWNLOADING_DOC_CONVERTER; publishProgress(); boolean needToUpload = doesThisFileNeedToBeUploaded(DOC_CONVERTER_FILEPATH, DOC_CONVERTER_MD5); if(needToUpload){ deleteFile(DOC_CONVERTER_FILEPATH); //Unnecessary for wget -N but just in case. String primaryDownloadCommand = "wget -N http://www.comp.nus.edu.sg/~yeokm1/nus-soc-print-tools/docs-to-pdf-converter-1.7.jar -P " + TEMP_DIRECTORY_NO_SLASH; connection.runCommand(primaryDownloadCommand); if(doesThisFileNeedToBeUploaded(DOC_CONVERTER_FILEPATH, DOC_CONVERTER_MD5)){ nowDownloadingDocConverterFromSecondarySite = true; publishProgress(); deleteFile(DOC_CONVERTER_FILEPATH); //If don't do this, wget will download to a another filename. String secondaryDownloadCommand = "wget --no-check-certificate https://github.com/yeokm1/docs-to-pdf-converter/releases/download/v1.7/docs-to-pdf-converter-1.7.jar -P " + TEMP_DIRECTORY_NO_SLASH; connection.runCommand(secondaryDownloadCommand); boolean stillNeedToBeUploaded = doesThisFileNeedToBeUploaded(DOC_CONVERTER_FILEPATH, DOC_CONVERTER_MD5); if(stillNeedToBeUploaded){ String message = getString(R.string.printing_progress_doc_conv_upload_error_message); throw new Exception(message); } } } } //Step 3 : Uploading PDF converter if(needToFormatPDF && !isCancelled()){ currentProgress = POSITION_UPLOADING_PDF_CONVERTER; publishProgress(); String actualFilePath; String actualMD5; String actualName; if(pagesPerSheet == 6){ actualFilePath = PDF_CONVERTER_6PAGE_FILEPATH; actualMD5 = PDF_CONVERTER_6PAGE_MD5; actualName = PDF_CONVERTER_6PAGE_FILENAME; } else { actualFilePath = PDF_CONVERTER_FILEPATH; actualMD5 = PDF_CONVERTER_MD5; actualName = PDF_CONVERTER_FILENAME; } boolean needToUpload = doesThisFileNeedToBeUploaded(actualFilePath, actualMD5); AssetManager assetMgr = getAssets(); InputStream pdfConvStream = assetMgr.open(actualName); pdfConvSize = pdfConvStream.available(); if(needToUpload){ connection.uploadFile(pdfConvStream, TEMP_DIRECTORY_NO_SLASH, actualName, new SftpProgressMonitor() { @Override public void init(int op, String src, String dest, long max) { } @Override public boolean count(long count) { pdfConvUploaded += count; publishProgress(); if(isCancelled()){ return false; } else { return true; } } @Override public void end() { } }); boolean stillNeedToBeUploaded = doesThisFileNeedToBeUploaded(actualFilePath, actualMD5); if(stillNeedToBeUploaded){ String message = getString(R.string.printing_progress_pdf_format_upload_error_message); throw new Exception(message); } } else { //Already exists, just use existing file pdfConvUploaded = pdfConvSize; publishProgress(); } } //Step 4 : Uploading document if(!isCancelled()){ currentProgress = POSITION_UPLOADING_USER_DOC; publishProgress(); FileInputStream docStream = new FileInputStream(filePath); docToPrintSize = docStream.available(); connection.uploadFile(docStream, TEMP_DIRECTORY_NO_SLASH, uploadedFilename, new SftpProgressMonitor() { @Override public void init(int op, String src, String dest, long max) { } @Override public boolean count(long count) { docToPrintUploaded += count; publishProgress(); if(isCancelled()){ return false; } else { return true; } } @Override public void end() { } }); } //Step 5 : Convert document to PDF if necessary if(needToConvertDocToPDF && !isCancelled()){ currentProgress = POSITION_CONVERTING_TO_PDF; publishProgress(); String conversionCommand = "java -jar " + DOC_CONVERTER_FILEPATH + " -i " + TEMP_DIRECTORY + uploadedFilename + " -o " + UPLOAD_SOURCE_PDF_FILEPATH; String reply = connection.runCommand(conversionCommand); if(reply.length() > 0){ throw new Exception(reply); } } String pdfFilepathToFormat = ""; //Step 6: Trim PDF to page range if necessary if(!isCancelled()){ if(needToTrimPDFToPageRange){ currentProgress = POSITION_TRIM_PDF_TO_PAGE_RANGE; publishProgress(); String startNumberString = Integer.toString(startPageRange); String endNumberString = Integer.toString(endPageRange); String trimCommand = "gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -dSAFER -dFirstPage=" + startNumberString + " -dLastPage=" + endNumberString + " -sOutputFile=" + UPLOAD_PDF_TRIMMED_FILEPATH + " " + UPLOAD_SOURCE_PDF_FILEPATH; String reply = connection.runCommand(trimCommand); int startIndex = reply.indexOf("Requested FirstPage is greater than the number of pages in the file"); //-1 implies no substring found. If we get another value, means the above string exists and there is an error if(startIndex != -1){ String errorMsg = reply.substring(startIndex); throw new Exception(errorMsg); } pdfFilepathToFormat = UPLOAD_PDF_TRIMMED_FILEPATH; } else { pdfFilepathToFormat = UPLOAD_SOURCE_PDF_FILEPATH; } } //Step 7 : Format PDF to required pages per sheet if required String pdfFilepathToConvertToPS = ""; if(!isCancelled()){ if(needToFormatPDF){ currentProgress = POSITION_FORMATTING_PDF; publishProgress(); String formattingCommand; if(pagesPerSheet == 6){ formattingCommand = "java -classpath " + PDF_CONVERTER_6PAGE_FILEPATH + " tool.pdf.Impose -paper a4 -nup 6 " + pdfFilepathToFormat; if(needToTrimPDFToPageRange){ pdfFilepathToConvertToPS = UPLOAD_PDF_FORMATTED_TRIMMED_6PAGE_FILEPATH; } else { pdfFilepathToConvertToPS = UPLOAD_PDF_FORMATTED_6PAGE_FILEPATH; } } else { formattingCommand = "java -jar " + PDF_CONVERTER_FILEPATH + " " + pdfFilepathToFormat + " " + UPLOAD_PDF_FORMATTED_FILEPATH + " " + pagesPerSheet; pdfFilepathToConvertToPS = UPLOAD_PDF_FORMATTED_FILEPATH; } connection.runCommand(formattingCommand); } else { if(needToTrimPDFToPageRange){ pdfFilepathToConvertToPS = UPLOAD_PDF_TRIMMED_FILEPATH; } else { pdfFilepathToConvertToPS = UPLOAD_SOURCE_PDF_FILEPATH; } } } //Remove extension String psFileNameNoString = filename.replaceFirst("[.][^.]+$", ""); String psFilePath = String.format(UPLOAD_PS_FILEPATH_FORMAT, psFileNameNoString); //Step 8 : Converting to Postscript if(!isCancelled()){ currentProgress = POSITION_CONVERTING_TO_POSTSCRIPT; publishProgress(); String conversionCommand = "pdftops " + pdfFilepathToConvertToPS + " " + psFilePath; String reply = connection.runCommand(conversionCommand); if(reply.length() != 0){ throw new Exception(reply); } } //Final Step 9 : Send to printer!!! if(!isCancelled()){ currentProgress = POSITION_SENDING_TO_PRINTER; publishProgress(); String printingCommand = "lpr -P " + printer + " " + psFilePath; String output = connection.runCommand(printingCommand); if(output.length() != 0){ throw new Exception(output); } } currentProgress = POSITION_COMPLETED; publishProgress(); } catch (Exception e) { publishProgress(e.getMessage()); } disconnect(); publishProgress(); return null; } @Override protected void onProgressUpdate(String... progress){ if(progress.length > 0){ String errorTitle = ERROR_TITLE_TEXT[currentProgress]; if(currentProgress == POSITION_TRIM_PDF_TO_PAGE_RANGE){ errorTitle = String.format(errorTitle, startPageRange, endPageRange); } else if(currentProgress == POSITION_FORMATTING_PDF){ errorTitle = String.format(errorTitle, pagesPerSheet); } HelperFunctions.showAlert(activity, errorTitle, progress[0]); } refreshList(); } @Override protected void onPostExecute(String output){ super.onPostExecute(output); cleanUpTask(); } @Override protected void onCancelled(){ super.onCancelled(); cleanUpTask(); } private void cleanUpTask(){ printingTask = null; setFinishButtonTextToClose(); refreshList(); FlurryFunctions.endTimedEvent(FLURRY_PRINT_EVENT); } } }