package org.openintents.filemanager.util; import android.content.Context; import android.os.AsyncTask; import android.widget.Toast; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.openintents.filemanager.R; import org.openintents.filemanager.files.FileHolder; /** * This class helps simplify copying and moving of files and folders by providing a simple interface to the operations and handling the actual operation transparently. * @author George Venios * */ public class CopyHelper { private static final int COPY_BUFFER_SIZE = 32 * 1024; public enum Operation { COPY, CUT } private Context mContext; private List<FileHolder> mClipboard; private Operation mOperation; private OnOperationFinishedListener mListener; public CopyHelper(Context c){ mContext = c; } public int getItemsCount() { if (canPaste()){ return mClipboard.size(); } else { return 0; } } public void copy(List<FileHolder> tbc){ mOperation = Operation.COPY; mClipboard = tbc; } public void copy(FileHolder tbc){ ArrayList<FileHolder> tbcl = new ArrayList<>(); tbcl.add(tbc); copy(tbcl); } public void cut(List<FileHolder> tbc){ mOperation = Operation.CUT; mClipboard = tbc; } public void cut(FileHolder tbc){ ArrayList<FileHolder> tbcl = new ArrayList<>(); tbcl.add(tbc); cut(tbcl); } public void clear() { mClipboard.clear(); } /** * Call this to check whether there are file references on the clipboard. */ public boolean canPaste(){ return mClipboard != null && !mClipboard.isEmpty(); } public Operation getOperationType(){ return mOperation; } /** * Call this to actually copy. * @param dest The path to copy the clipboard into. * @return false if ANY error has occurred. This may mean that some files have been successfully copied, but not all. */ private boolean performCopy(File dest){ boolean res = true; for(FileHolder fh : mClipboard){ if(fh.getFile().isFile()) res &= copyFile(fh.getFile(), FileUtils.createUniqueCopyName(mContext, dest, fh.getName())); else res &= copyFolder(fh.getFile(), FileUtils.createUniqueCopyName(mContext, dest, fh.getName())); } return res; } /** * Copy a file. * @param oldFile File to copy. * @param newFile The file to be created. * @return Was copy successful? */ private boolean copyFile(File oldFile, File newFile) { FileInputStream input = null; FileOutputStream output = null; try { input = new FileInputStream(oldFile); output = new FileOutputStream(newFile); byte[] buffer = new byte[COPY_BUFFER_SIZE]; while (true) { int bytes = input.read(buffer); if (bytes <= 0) { break; } output.write(buffer, 0, bytes); } // Request media scan MediaScannerUtils.informFileAdded(mContext, newFile); } catch (Exception e) { return false; } finally { try { input.close(); } catch (IOException e) { // ignore } try { output.close(); } catch (IOException e) { // ignore } } return true; } /** * Recursively copy a folder. * @param oldFile Folder to copy. * @param newFile The dir to be created. * @return Was copy successful? */ private boolean copyFolder(File oldFile, File newFile) { boolean res = true; if (oldFile.isDirectory()) { // if directory not exists, create it if (!newFile.exists()) { newFile.mkdir(); // System.out.println("Directory copied from " + src + " to " + dest); } // list all the directory contents String[] files = oldFile.list(); for (String file : files) { // construct the src and dest file structure File srcFile = new File(oldFile, file); File destFile = new File(newFile, file); // recursive copy res &= copyFolder(srcFile, destFile); } } else { res &= copyFile(oldFile, newFile); } return res; } /** * Call this to actually move. * @param dest The path to move the clipboard into. * @return false if ANY error has occurred. This may mean that some files have been successfully moved, but not all. */ private boolean performCut(File dest){ boolean res = true; boolean deleteOk; File from; for(FileHolder fh : mClipboard){ from = fh.getFile().getAbsoluteFile(); deleteOk = fh.getFile().renameTo(FileUtils.getFile(dest, fh.getName())); // Inform media scanner if(deleteOk) { MediaScannerUtils.informFileDeleted(mContext, from); MediaScannerUtils.informFileAdded(mContext, FileUtils.getFile(dest, fh.getName())); } res &= deleteOk; } return res; } /** * Paste the copied/cut items. * @param copyTo Path to paste to. * @param listener Listener that will be informed on operation finish. CAN'T BE NULL. */ public void paste(File copyTo, OnOperationFinishedListener listener){ mListener = listener; // Quick check just to make sure. Normally this should never be the case as the path we get is not user-generated. if(!copyTo.isDirectory()) return; switch (mOperation) { case COPY: new CopyAsync().execute(copyTo); break; case CUT: new MoveAsync().execute(copyTo); break; default: return; } } private class CopyAsync extends AsyncTask<File, Void, Boolean> { @Override protected void onPreExecute() { Toast.makeText(mContext, R.string.copying, Toast.LENGTH_SHORT).show(); } @Override protected Boolean doInBackground(File... params) { return performCopy(params[0]); } @Override protected void onPostExecute(Boolean result) { Toast.makeText(mContext, result ? R.string.copied : R.string.copy_error, Toast.LENGTH_SHORT).show(); // Clear as the references have been invalidated. mClipboard.clear(); mListener.operationFinished(result); // Invalidate listener. mListener = null; } } private class MoveAsync extends AsyncTask<File, Void, Boolean> { @Override protected void onPreExecute() { Toast.makeText(mContext, R.string.moving, Toast.LENGTH_SHORT).show(); } @Override protected Boolean doInBackground(File... params) { return performCut(params[0]); } @Override protected void onPostExecute(Boolean result) { Toast.makeText(mContext, result ? R.string.moved : R.string.move_error, Toast.LENGTH_SHORT).show(); // Clear as the references have been invalidated. mClipboard.clear(); mListener.operationFinished(result); // Invalidate listener. mListener = null; } } public interface OnOperationFinishedListener { /** * @param success Whether the operation was entirely successful. */ public void operationFinished(boolean success); } }