package com.amaze.filemanager.utils; import android.content.ContentResolver; import android.content.Context; import android.support.v4.provider.DocumentFile; import android.util.Log; import com.amaze.filemanager.R; import com.amaze.filemanager.filesystem.BaseFile; import com.amaze.filemanager.filesystem.FileUtil; import com.amaze.filemanager.filesystem.HFile; import com.amaze.filemanager.filesystem.RootHelper; import com.cloudrail.si.interfaces.CloudStorage; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import static com.amaze.filemanager.activities.MainActivity.dataUtils; /** * Created by vishal on 26/10/16. * * Base class to handle file copy. */ public class GenericCopyUtil { private BaseFile mSourceFile; private HFile mTargetFile; private Context mContext; // context needed to find the DocumentFile in otg/sd card public static final String PATH_FILE_DESCRIPTOR = "/proc/self/fd/"; public static final int DEFAULT_BUFFER_SIZE = 8192; public GenericCopyUtil(Context context) { this.mContext = context; } /** * Starts copy of file * Supports : {@link File}, {@link jcifs.smb.SmbFile}, {@link DocumentFile}, {@link CloudStorage} * @param lowOnMemory defines whether system is running low on memory, in which case we'll switch to * using streams instead of channel which maps the who buffer in memory. * TODO: Use buffers even on low memory but don't map the whole file to memory but * parts of it, and transfer each part instead. * @throws IOException */ private void startCopy(boolean lowOnMemory) throws IOException { FileInputStream inputStream = null; FileOutputStream outputStream = null; FileChannel inChannel = null; FileChannel outChannel = null; BufferedInputStream bufferedInputStream = null; BufferedOutputStream bufferedOutputStream = null; try { // initializing the input channels based on file types if (mSourceFile.isOtgFile()) { // source is in otg ContentResolver contentResolver = mContext.getContentResolver(); DocumentFile documentSourceFile = RootHelper.getDocumentFile(mSourceFile.getPath(), mContext, false); bufferedInputStream = new BufferedInputStream(contentResolver .openInputStream(documentSourceFile.getUri()), DEFAULT_BUFFER_SIZE); } else if (mSourceFile.isSmb()) { // source is in smb bufferedInputStream = new BufferedInputStream(mSourceFile.getInputStream(), DEFAULT_BUFFER_SIZE); } else if (mSourceFile.isDropBoxFile()) { CloudStorage cloudStorageDropbox = dataUtils.getAccount(OpenMode.DROPBOX); bufferedInputStream = new BufferedInputStream(cloudStorageDropbox .download(CloudUtil.stripPath(OpenMode.DROPBOX, mSourceFile.getPath()))); } else if (mSourceFile.isBoxFile()) { CloudStorage cloudStorageBox = dataUtils.getAccount(OpenMode.BOX); bufferedInputStream = new BufferedInputStream(cloudStorageBox .download(CloudUtil.stripPath(OpenMode.BOX, mSourceFile.getPath()))); } else if (mSourceFile.isGoogleDriveFile()) { CloudStorage cloudStorageGdrive = dataUtils.getAccount(OpenMode.GDRIVE); bufferedInputStream = new BufferedInputStream(cloudStorageGdrive .download(CloudUtil.stripPath(OpenMode.GDRIVE, mSourceFile.getPath()))); } else if (mSourceFile.isOneDriveFile()) { CloudStorage cloudStorageOnedrive = dataUtils.getAccount(OpenMode.ONEDRIVE); bufferedInputStream = new BufferedInputStream(cloudStorageOnedrive .download(CloudUtil.stripPath(OpenMode.ONEDRIVE, mSourceFile.getPath()))); } else { // source file is neither smb nor otg; getting a channel from direct file instead of stream File file = new File(mSourceFile.getPath()); if (FileUtil.isReadable(file)) { if (mTargetFile.isOneDriveFile() || mTargetFile.isDropBoxFile() || mTargetFile.isGoogleDriveFile() || mTargetFile.isBoxFile() || lowOnMemory) { // our target is cloud, we need a stream not channel bufferedInputStream = new BufferedInputStream(new FileInputStream(file)); } else { inChannel = new RandomAccessFile(file, "r").getChannel(); } } else { ContentResolver contentResolver = mContext.getContentResolver(); DocumentFile documentSourceFile = FileUtil.getDocumentFile(file, mSourceFile.isDirectory(), mContext); bufferedInputStream = new BufferedInputStream(contentResolver .openInputStream(documentSourceFile.getUri()), DEFAULT_BUFFER_SIZE); } } // initializing the output channels based on file types if (mTargetFile.isOtgFile()) { // target in OTG, obtain streams from DocumentFile Uri's ContentResolver contentResolver = mContext.getContentResolver(); DocumentFile documentTargetFile = RootHelper.getDocumentFile(mTargetFile.getPath(), mContext, true); bufferedOutputStream = new BufferedOutputStream(contentResolver .openOutputStream(documentTargetFile.getUri()), DEFAULT_BUFFER_SIZE); } else if (mTargetFile.isSmb()) { bufferedOutputStream = new BufferedOutputStream(mTargetFile.getOutputStream(mContext), DEFAULT_BUFFER_SIZE); } else if (mTargetFile.isDropBoxFile()) { // API doesn't support output stream, we'll upload the file directly CloudStorage cloudStorageDropbox = dataUtils.getAccount(OpenMode.DROPBOX); if (mSourceFile.isDropBoxFile()) { // we're in the same provider, use api method cloudStorageDropbox.copy(CloudUtil.stripPath(OpenMode.DROPBOX, mSourceFile.getPath()), CloudUtil.stripPath(OpenMode.DROPBOX, mTargetFile.getPath())); return; } else { cloudStorageDropbox.upload(CloudUtil.stripPath(OpenMode.DROPBOX, mTargetFile.getPath()), bufferedInputStream, mSourceFile.getSize(), true); return; } } else if (mTargetFile.isBoxFile()) { // API doesn't support output stream, we'll upload the file directly CloudStorage cloudStorageBox = dataUtils.getAccount(OpenMode.BOX); if (mSourceFile.isBoxFile()) { // we're in the same provider, use api method cloudStorageBox.copy(CloudUtil.stripPath(OpenMode.BOX, mSourceFile.getPath()), CloudUtil.stripPath(OpenMode.BOX, mTargetFile.getPath())); return; } else { cloudStorageBox.upload(CloudUtil.stripPath(OpenMode.BOX, mTargetFile.getPath()), bufferedInputStream, mSourceFile.getSize(), true); bufferedInputStream.close(); return; } } else if (mTargetFile.isGoogleDriveFile()) { // API doesn't support output stream, we'll upload the file directly CloudStorage cloudStorageGdrive = dataUtils.getAccount(OpenMode.GDRIVE); if (mSourceFile.isGoogleDriveFile()) { // we're in the same provider, use api method cloudStorageGdrive.copy(CloudUtil.stripPath(OpenMode.GDRIVE, mSourceFile.getPath()), CloudUtil.stripPath(OpenMode.GDRIVE, mTargetFile.getPath())); return; } else { cloudStorageGdrive.upload(CloudUtil.stripPath(OpenMode.GDRIVE, mTargetFile.getPath()), bufferedInputStream, mSourceFile.getSize(), true); bufferedInputStream.close(); return; } } else if (mTargetFile.isOneDriveFile()) { // API doesn't support output stream, we'll upload the file directly CloudStorage cloudStorageOnedrive = dataUtils.getAccount(OpenMode.ONEDRIVE); if (mSourceFile.isOneDriveFile()) { // we're in the same provider, use api method cloudStorageOnedrive.copy(CloudUtil.stripPath(OpenMode.ONEDRIVE, mSourceFile.getPath()), CloudUtil.stripPath(OpenMode.ONEDRIVE, mTargetFile.getPath())); return; } else { cloudStorageOnedrive.upload(CloudUtil.stripPath(OpenMode.ONEDRIVE, mTargetFile.getPath()), bufferedInputStream, mSourceFile.getSize(), true); bufferedInputStream.close(); return; } } else { // copying normal file, target not in OTG File file = new File(mTargetFile.getPath()); if (FileUtil.isWritable(file)) { if (lowOnMemory) { bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file)); } else { outChannel = new RandomAccessFile(file, "rw").getChannel(); } } else { ContentResolver contentResolver = mContext.getContentResolver(); DocumentFile documentTargetFile = FileUtil.getDocumentFile(file, mTargetFile.isDirectory(), mContext); bufferedOutputStream = new BufferedOutputStream(contentResolver .openOutputStream(documentTargetFile.getUri()), DEFAULT_BUFFER_SIZE); } } if (bufferedInputStream!=null) { if (bufferedOutputStream!=null) copyFile(bufferedInputStream, bufferedOutputStream); else if (outChannel!=null) { copyFile(bufferedInputStream, outChannel); } } else if (inChannel!=null) { if (bufferedOutputStream!=null) copyFile(inChannel, bufferedOutputStream); else if (outChannel!=null) copyFile(inChannel, outChannel); } } catch (IOException e) { e.printStackTrace(); Log.d(getClass().getSimpleName(), "I/O Error!"); throw new IOException(); } catch (OutOfMemoryError e) { e.printStackTrace(); // we ran out of memory to map the whole channel, let's switch to streams AppConfig.toast(mContext, mContext.getResources().getString(R.string.copy_low_memory)); startCopy(true); } finally { try { if (inChannel!=null) inChannel.close(); if (outChannel!=null) outChannel.close(); if (inputStream!=null) inputStream.close(); if (outputStream!=null) outputStream.close(); if (bufferedInputStream!=null) bufferedInputStream.close(); if (bufferedOutputStream!=null) bufferedOutputStream.close(); } catch (IOException e) { e.printStackTrace(); // failure in closing stream } } } /** * Method exposes this class to initiate copy * @param sourceFile the source file, which is to be copied * @param targetFile the target file */ public void copy(BaseFile sourceFile, HFile targetFile) throws IOException { this.mSourceFile = sourceFile; this.mTargetFile = targetFile; startCopy(false); } private void copyFile(BufferedInputStream bufferedInputStream, FileChannel outChannel) throws IOException { MappedByteBuffer byteBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, mSourceFile.getSize()); int count = 0; byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; while (count != -1) { count = bufferedInputStream.read(buffer); if (count!=-1) { byteBuffer.put(buffer, 0, count); ServiceWatcherUtil.POSITION+=count; } } } private void copyFile(FileChannel inChannel, FileChannel outChannel) throws IOException { //MappedByteBuffer inByteBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size()); //MappedByteBuffer outByteBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size()); ReadableByteChannel inByteChannel = new CustomReadableByteChannel(inChannel); outChannel.transferFrom(inByteChannel, 0, mSourceFile.getSize()); } private void copyFile(BufferedInputStream bufferedInputStream, BufferedOutputStream bufferedOutputStream) throws IOException { int count = 0; byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; while (count != -1) { count = bufferedInputStream.read(buffer); if (count!=-1) { bufferedOutputStream.write(buffer, 0 , count); ServiceWatcherUtil.POSITION+=count; } } bufferedOutputStream.flush(); } private void copyFile(FileChannel inChannel, BufferedOutputStream bufferedOutputStream) throws IOException { MappedByteBuffer inBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, mSourceFile.getSize()); byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; while (inBuffer.hasRemaining()) { int count = 0; for (int i=0; i<buffer.length && inBuffer.hasRemaining(); i++) { buffer[i] = inBuffer.get(); count++; } bufferedOutputStream.write(buffer, 0, count); ServiceWatcherUtil.POSITION = inBuffer.position(); } bufferedOutputStream.flush(); } /** * Inner class responsible for getting a {@link ReadableByteChannel} from the input channel * and to watch over the read progress */ private class CustomReadableByteChannel implements ReadableByteChannel { ReadableByteChannel byteChannel; CustomReadableByteChannel(ReadableByteChannel byteChannel) { this.byteChannel = byteChannel; } @Override public int read(ByteBuffer dst) throws IOException { int bytes; if (((bytes = byteChannel.read(dst))>0)) { ServiceWatcherUtil.POSITION += bytes; return bytes; } return 0; } @Override public boolean isOpen() { return byteChannel.isOpen(); } @Override public void close() throws IOException { byteChannel.close(); } } }