package com.openfarmanager.android.fragments;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.github.junrar.Archive;
import com.openfarmanager.android.App;
import com.openfarmanager.android.R;
import com.openfarmanager.android.adapters.ArchiveAdapter;
import com.openfarmanager.android.adapters.FileSystemAdapter;
import com.openfarmanager.android.core.CancelableCommand;
import com.openfarmanager.android.core.archive.ArchiveScanner;
import com.openfarmanager.android.core.archive.ArchiveUtils;
import com.openfarmanager.android.dialogs.ExtractArchiveDialog;
import com.openfarmanager.android.filesystem.ArchiveFile;
import com.openfarmanager.android.model.exeptions.NoPasswordException;
import com.openfarmanager.android.utils.Extensions;
import com.openfarmanager.android.view.ToastNotification;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.model.FileHeader;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.archivers.zip.UnsupportedZipFeatureException;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import java.io.*;
import java.util.LinkedList;
import java.util.List;
import static com.openfarmanager.android.controllers.FileSystemController.*;
import static com.openfarmanager.android.utils.Extensions.isNullOrEmpty;
public class ArchivePanel extends MainPanel {
private ArchiveScanner.File mCurrentArchiveItem;
protected boolean mIsArchiveCompressed;
private OpenArchiveTask mOpenArchiveTask = new OpenArchiveTask();
private boolean mEncryptedArchive;
protected String mEncryptedArchivePassword;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setupHandler();
View view = super.onCreateView(inflater, container, savedInstanceState);
/*
mFileSystemList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
ArchiveScanner.File item = (ArchiveScanner.File) adapterView.getItemAtPosition(i);
if (item.isUpNavigator()) {
item = item.getParent();
if (item.isRoot()) { // exit from archive
exitFromArchive();
return;
} else {
item = item.getParent();
openArchiveDirectory(item);
return;
}
} else if (!item.isDirectory()) {
onLongClick(adapterView, i);
}
if (item.isDirectory()) {
openArchiveDirectory(item);
}
}
});
*/
mFileSystemList.setOnItemClickListener(new FileSystemAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
FileSystemAdapter adapterView = (FileSystemAdapter) mFileSystemList.getAdapter();
ArchiveScanner.File item = (ArchiveScanner.File) adapterView.getItem(position);
if (item.isUpNavigator()) {
item = item.getParent();
if (item.isRoot()) { // exit from archive
exitFromArchive();
return;
} else {
item = item.getParent();
openArchiveDirectory(item);
return;
}
} else if (!item.isDirectory()) {
onLongClick(position);
}
if (item.isDirectory()) {
openArchiveDirectory(item);
}
}
@Override
public void onItemLongClick(View view, int position) {
onLongClick(position);
}
});
// archive panel always starts from 'loading...' state
setIsLoading(true);
postInitialization();
return view;
}
public void exitFromArchive() {
// ToastNotification.makeText(App.sInstance.getApplicationContext(), getString(R.string.exit_from_archive), Toast.LENGTH_SHORT).show();
mEncryptedArchivePassword = null;
ArchiveScanner.sInstance.clearArchive();
mHandler.sendEmptyMessage(EXIT_FROM_ARCHIVE);
}
protected boolean onLongClick(int position) {
ArchiveFile file = (ArchiveFile) getAdapter().getItem(position);
if (!file.isUpNavigator()) {
mCurrentArchiveItem = file;
mHandler.sendMessage(mHandler.obtainMessage(EXTRACT_ARCHIVE));
}
return true;
}
public void extractArchive(final MainPanel inactivePanel) {
if (inactivePanel == null || !inactivePanel.isFileSystemPanel()) {
return;
}
showDialog(new ExtractArchiveDialog(getActivity(), mFileActionHandler, inactivePanel,
mIsArchiveCompressed, inactivePanel.getCurrentDir().getAbsolutePath()));
}
public void openCompressedArchive(final File item) {
if (!mIsInitialized) {
addToPendingList(new Runnable() {
@Override
public void run() {
openCompressedArchive(item);
}
});
return;
}
try {
mLastSelectedFile = item;
openArchive(item, new CompressorStreamFactory().createCompressorInputStream(
new BufferedInputStream(new FileInputStream(item))));
setCurrentPath(null);
mIsArchiveCompressed = true;
} catch (Exception e) {
openArchiveFailed();
}
}
public void openArchive(final File item) {
if (!mIsInitialized) {
addToPendingList(new Runnable() {
@Override
public void run() {
openArchive(item);
}
});
return;
}
try {
mLastSelectedFile = item;
openArchive(item, new FileInputStream(item));
} catch (Exception e) {
openArchiveFailed();
}
}
private void openArchive(final File archiveFile, final InputStream stream) {
setIsLoading(true);
mOpenArchiveTask.init(archiveFile, stream, mEncryptedArchivePassword);
Extensions.runAsync(mOpenArchiveTask);
}
private void openArchiveDirectory(ArchiveScanner.File selectedDirectory) {
mCurrentArchiveItem = selectedDirectory;
setCurrentPath(selectedDirectory);
((ArchiveAdapter) mFileSystemList.getAdapter()).setItems(selectedDirectory);
}
private void setCurrentPath(ArchiveScanner.File file) {
String prefix = mLastSelectedFile.getName();
if (file != null && !file.isRoot()) {
prefix += " : /" + file.getFullPath();
}
mActionBar.updateCurrentPath(prefix);
}
public String getCurrentPath() {
return "/" + mCurrentArchiveItem.getFullPath();
}
@Override
public void openDirectory(String path) {
openArchiveDirectory(mCurrentArchiveItem.findInTree(path));
}
public boolean isRootDirectory() {
// definetely something going wrong so it's better to tell that this is root and exit from archive to avoid crash.
return mCurrentArchiveItem == null || mCurrentArchiveItem.isRoot();
}
public void navigateParent() {
if (mCurrentArchiveItem != null && !mCurrentArchiveItem.isRoot()) {
openArchiveDirectory(mCurrentArchiveItem.getParent());
} else {
exitFromArchive();
}
}
protected boolean isBookmarksSupported() {
return false;
}
private void openArchiveFailed() {
postIfAttached(mOpenArchiveFailed);
}
public ArchiveScanner.File getCurrentArchiveItem() {
return mCurrentArchiveItem;
}
protected boolean isCopyFolderSupported() {
return false;
}
public boolean isFileSystemPanel() {
return false;
}
public boolean isSearchSupported() {
return false;
}
public String getPanelType() {
return getSafeString(R.string.archive);
}
private Runnable mOpenArchiveFailed = new Runnable() {
@Override
public void run() {
ToastNotification.makeText(App.sInstance.getApplicationContext(), getSafeString(R.string.error_open_archive), Toast.LENGTH_LONG).show();
exitFromArchive();
}
};
private Runnable mSetupArchiveView = new Runnable() {
@Override
public void run() {
if (!mIsInitialized) {
addToPendingList(new Runnable() {
@Override
public void run() {
setupArchiveView();
}
});
return;
}
setupArchiveView();
}
private void setupArchiveView() {
if (ArchiveScanner.sInstance.root().getSortedChildren() != null) {
mFileSystemList.initAdapter(new ArchiveAdapter(ArchiveScanner.sInstance.root()));
}
setIsLoading(false);
setCurrentPath(null);
}
};
private Runnable mRequestPassword = new Runnable() {
@Override
public void run() {
try {
RequestPasswordDialog.newInstance(mRequestPasswordCommand).show(fragmentManager(), "confirmDialog");
} catch (Exception e) {
}
}
};
private class OpenArchiveTask implements Runnable {
private File mArchiveFile;
private InputStream mStream;
private String mPassword;
public void init(File archiveFile, InputStream stream, String password) {
mArchiveFile = archiveFile;
mStream = stream;
mPassword = password;
}
@Override
public void run() {
mEncryptedArchive = false;
ArchiveInputStream inputStream = null;
// special cases
Archive rarArchive = null;
ZipFile zipFile = null;
SevenZFile sevenZFile = null;
try {
ArchiveScanner.sInstance.clearArchive();
if (ArchiveUtils.isRarArchive(mArchiveFile)) {
rarArchive = new Archive(mArchiveFile);
} else if (ArchiveUtils.is7zArchive(mArchiveFile)) {
sevenZFile = new SevenZFile(mArchiveFile, mPassword == null ? null : mPassword.getBytes());
} else {
inputStream = ArchiveUtils.createInputStream(mStream);
}
if (inputStream instanceof ZipArchiveInputStream) {
zipFile = new ZipFile(mLastSelectedFile);
mEncryptedArchive = zipFile.isEncrypted();
}
LinkedList<ArchiveEntry> entries = new LinkedList<>();
if (mEncryptedArchive) {
if (isNullOrEmpty(mPassword)) {
throw new NoPasswordException();
}
//noinspection ConstantConditions
zipFile.setPassword(mPassword);
//noinspection unchecked
List<FileHeader> fileHeaders = zipFile.getFileHeaders();
for (final FileHeader header : fileHeaders) {
ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(header.getFileName());
zipArchiveEntry.setSize(header.getUncompressedSize());
entries.add(zipArchiveEntry);
}
} else if (rarArchive != null) { // special case: completely different api
if (rarArchive.isEncrypted()) {
if (isNullOrEmpty(mPassword)) {
throw new NoPasswordException();
}
}
List<com.github.junrar.rarfile.FileHeader> fileHeaders = rarArchive.getFileHeaders();
for (final com.github.junrar.rarfile.FileHeader header : fileHeaders) {
if (header.isDirectory()) {
continue;
}
ZipArchiveEntry entry = new ZipArchiveEntry(header.getFileNameString());
entry.setSize(header.getFullUnpackSize());
entries.add(entry);
}
} else if (sevenZFile != null) { // special case: completely different api
SevenZArchiveEntry entry = sevenZFile.getNextEntry();
while (entry != null) {
if (!entry.isDirectory()) {
entries.add(entry);
}
byte[] content = new byte[(int) entry.getSize()];
sevenZFile.read(content, 0, content.length);
entry = sevenZFile.getNextEntry();
}
sevenZFile.close();
} else {
// read all entries from archive first.
// it's essential since for 'DEFLATED' zip items some data is available only after entry actually been read.
ArchiveEntry entry = inputStream.getNextEntry();
do {
entries.add(entry);
} while ((entry = inputStream.getNextEntry()) != null);
}
for (ArchiveEntry theEntry : entries) {
ArchiveScanner.sInstance.root().processFile(theEntry.getName(), theEntry.getSize());
}
entries.clear();
} catch (NoPasswordException | UnsupportedZipFeatureException e) {
postIfAttached(mRequestPassword);
return;
} catch (IOException e) {
if (e.getMessage().equals("Cannot read encrypted files without a password")) {
postIfAttached(mRequestPassword);
} else {
e.printStackTrace();
openArchiveFailed();
}
} catch (Exception e) {
e.printStackTrace();
openArchiveFailed();
} finally {
try {
//noinspection ConstantConditions
inputStream.close();
mStream.close();
} catch (Exception ignore) {}
}
mCurrentArchiveItem = ArchiveScanner.sInstance.root();
postIfAttached(mSetupArchiveView);
}
}
protected CancelableCommand mRequestPasswordCommand = new CancelableCommand() {
@Override
public void execute(final Object... args) {
mEncryptedArchivePassword = (String) args[0];
openArchive(mLastSelectedFile);
}
@Override
public void cancel() {
exitFromArchive();
}
};
protected String getArchivePassword() {
return mEncryptedArchivePassword;
}
}