package com.openfarmanager.android.fragments; import android.app.Activity; import android.app.Dialog; import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.annotation.NonNull; import android.support.v7.widget.LinearLayoutManager; import android.text.TextUtils; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.AdapterView; import android.widget.ListAdapter; import android.widget.TextView; import android.widget.Toast; import com.openfarmanager.android.App; import com.openfarmanager.android.R; import com.openfarmanager.android.adapters.FileSystemAdapter; import com.openfarmanager.android.adapters.NetworkEntryAdapter; import com.openfarmanager.android.core.network.datasource.DataSource; import com.openfarmanager.android.core.network.datasource.DropboxDataSource; import com.openfarmanager.android.core.network.datasource.FtpDataSource; import com.openfarmanager.android.core.network.datasource.GoogleDriveDataSource; import com.openfarmanager.android.core.network.datasource.IdPathDataSource; import com.openfarmanager.android.core.network.datasource.MediaFireDataSource; import com.openfarmanager.android.core.network.datasource.RawPathDataSource; import com.openfarmanager.android.core.network.datasource.SftpDataSource; import com.openfarmanager.android.core.network.datasource.SkyDriveDataSource; import com.openfarmanager.android.core.network.datasource.SmbDataSource; import com.openfarmanager.android.core.network.datasource.WebDavDataSource; import com.openfarmanager.android.core.network.datasource.YandexDiskDataSource; import com.openfarmanager.android.dialogs.CreateBookmarkDialog; import com.openfarmanager.android.filesystem.FakeFile; import com.openfarmanager.android.filesystem.FileProxy; import com.openfarmanager.android.model.Bookmark; import com.openfarmanager.android.model.FileActionEnum; import com.openfarmanager.android.model.NetworkAccount; import com.openfarmanager.android.model.NetworkEnum; import com.openfarmanager.android.model.SelectParams; import com.openfarmanager.android.model.exeptions.NetworkException; import com.openfarmanager.android.model.exeptions.RestoreStoragePathException; import com.openfarmanager.android.utils.Extensions; import com.openfarmanager.android.utils.FileUtilsExt; import com.openfarmanager.android.view.ActionBar; import com.openfarmanager.android.view.NetworkActionBar; import com.openfarmanager.android.view.ToastNotification; import org.apache.commons.io.FilenameUtils; import org.json.JSONObject; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import rx.Observable; import rx.Observer; import rx.Subscriber; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; import static com.openfarmanager.android.controllers.FileSystemController.*; /** * Panel for 'network filesystem' (like Dropbox, Google Drive etc). */ public class NetworkPanel extends MainPanel { public static final int MSG_NETWORK_SHOW_PROGRESS = 100000; public static final int MSG_NETWORK_HIDE_PROGRESS = 100001; public static final int MSG_NETWORK_OPEN = 100002; private DataSource mDataSource; private FileProxy mCurrentPath; private FileProxy mUpNavigator; protected FileProxy mLastSelectedFile; private NetworkAccount mCurrentNetworkAccount; private Dialog mProgressDialog; protected List<FileProxy> mPreSelectedFiles = new ArrayList<>(); private Subscription mSubscription; private OpenDirectoryAction mOpenDirectoryAction = new OpenDirectoryAction(); private Observable<DirectoryUiInfo> mOpenDirectoryObservable = Observable.create(mOpenDirectoryAction); private OpenDirectoryObserver mOpenDirectoryObserver = new OpenDirectoryObserver(); @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { setupHandler(); View view = super.onCreateView(inflater, container, savedInstanceState); mFileSystemList.setOnItemClickListener(new FileSystemAdapter.OnItemClickListener() { @Override public void onItemClick(View view, int position) { NetworkEntryAdapter adapter = (NetworkEntryAdapter) mFileSystemList.getAdapter(); final FileProxy file = (FileProxy) adapter.getItem(position); if (mIsMultiSelectMode) { updateLongClickSelection(file, false); return; } mSelectedFiles.clear(); if (position == 0) { if (file.isRoot()) { // exit from network exitFromNetwork(); } else { openDirectory(file); } } else if (file.isDirectory()) { openDirectory(file); String pathKey = file.getParentPath(); if (pathKey.endsWith("/") && !pathKey.equals("/")) { pathKey = pathKey.substring(0, pathKey.length() - 1); } mDirectorySelection.put(pathKey, ((LinearLayoutManager) mFileSystemList.getLayoutManager()).findFirstVisibleItemPosition() + 1); } else { mDataSource.open(file); } } @Override public void onItemLongClick(View view, int position) { onLongClick(position); } }); setIsLoading(true); forceHideNavigationButtons(); postInitialization(); if (mDataSource == null) { ToastNotification.makeText(App.sInstance.getApplicationContext(), getSafeString(R.string.error_unknown_unexpected_error), Toast.LENGTH_SHORT).show(); exitFromNetwork(); return view; } mCurrentNetworkAccount = App.sInstance.getNetworkApi(getNetworkType()).getCurrentNetworkAccount(); mActionBar.updateNavigationItemsVisibility(false, false, isBookmarksSupported()); return view; } public NetworkAccount getCurrentNetworkAccount() { return mCurrentNetworkAccount; } public void setNetworkType(NetworkEnum networkType) { switch (networkType) { case Dropbox: mDataSource = new DropboxDataSource(mHandler); break; case SkyDrive: mDataSource = new SkyDriveDataSource(mHandler); break; case FTP: mDataSource = new FtpDataSource(mHandler); break; case SMB: mDataSource = new SmbDataSource(mHandler); break; case YandexDisk: mDataSource = new YandexDiskDataSource(mHandler); break; case GoogleDrive: mDataSource = new GoogleDriveDataSource(mHandler); break; case MediaFire: mDataSource = new MediaFireDataSource(mHandler); break; case SFTP: mDataSource = new SftpDataSource(mHandler); break; case WebDav: mDataSource = new WebDavDataSource(mHandler); break; } } public DataSource getDataSource() { return mDataSource; } @Override protected ActionBar createActionBar() { return new NetworkActionBar(getContext(), mDataSource.isChangeEncodingSupported(), mDataSource.getNetworkTypeEnum()); } @Override public void onResume() { super.onResume(); // selected files need to be updated after application resumes NetworkEntryAdapter adapter = (NetworkEntryAdapter) mFileSystemList.getAdapter(); if (adapter != null) { adapter.setSelectedFiles(mSelectedFiles); adapter.notifyDataSetChanged(); } setIsActivePanel(mIsActivePanel); } @Override public void onDetach() { super.onDetach(); mDirectorySelection.clear(); } @Override public void onDestroyView() { super.onDestroyView(); //mCharsetLeft.setVisibility(View.GONE); //mCharsetRight.setVisibility(View.GONE); if (mSubscription != null && !mSubscription.isUnsubscribed()) { mSubscription.unsubscribe(); } } protected boolean onLongClick(int position) { FileProxy file = (FileProxy) ((NetworkEntryAdapter) mFileSystemList.getAdapter()).getItem(position); if (!file.isUpNavigator() && !file.isVirtualDirectory()) { mLastSelectedFile = file; updateLongClickSelection(file, true); if (!file.isVirtualDirectory()) { openFileActionMenu(); } } return true; } @Override public void createBookmark(final MainPanel inactivePanel) { showDialog(new CreateBookmarkDialog(getActivity(), mFileActionHandler, inactivePanel, getCurrentPath(), getCurrentPathId(), mCurrentNetworkAccount)); } @Override public void selectAll() { mSelectedFiles.clear(); mSelectedFiles.addAll(((NetworkEntryAdapter) mFileSystemList.getAdapter()).getFiles()); } protected void updateLongClickSelection(FileProxy file, boolean longClick) { if (file.isUpNavigator()) { return; } if (mSelectedFiles.contains(file) && !longClick) { mSelectedFiles.remove(file); } else { if (mSelectedFiles.contains(file)) { return; } mSelectedFiles.add(0, file); } NetworkEntryAdapter adapter = (NetworkEntryAdapter) mFileSystemList.getAdapter(); adapter.setSelectedFiles(mSelectedFiles); adapter.notifyDataSetChanged(); setSelectedFilesSizeVisibility(); calculateSelectedFilesSize(); showQuickActionPanel(); } protected FileActionEnum[] getAvailableActions() { return FileActionEnum.getAvailableActionsForNetwork(mDataSource.getNetworkTypeEnum(), mSelectedFiles); } public FileProxy getLastSelectedFile() { return mLastSelectedFile; } public void navigateParent() { if (mCurrentPath != null && !mCurrentPath.isRoot() && mUpNavigator != null) { openDirectory(mUpNavigator); } else { exitFromNetwork(); } } public NetworkEnum getNetworkType() { return mDataSource.getNetworkTypeEnum(); } public void exitFromNetwork() { if (mDataSource != null) { mDataSource.exitFromNetwork(); } mHandler.sendMessage(mHandler.obtainMessage(EXIT_FROM_NETWORK_STORAGE, getPanelLocation())); } public void openDirectory() { openDirectory(new FakeFile("/", "/", true)); } public void openDirectoryAndSelect(FileProxy file, List<FileProxy> selectedFiles) { mPreSelectedFiles = selectedFiles; openDirectory(file); } public void openBookmark(final String directoryInfo) { if (!mIsInitialized) { addToPendingList(new Runnable() { @Override public void run() { openBookmark(directoryInfo); } }); return; } if (directoryInfo == null) { openDirectory(); } else { try { JSONObject data = new JSONObject(directoryInfo); final String id = data.getString(Bookmark.ID); final String path = data.getString(Bookmark.PATH); if (mDataSource instanceof IdPathDataSource) { setIsLoading(true); Extensions.runAsync(new Runnable() { @Override public void run() { FakeFile proxy = new FakeFile(((IdPathDataSource) mDataSource).requestFileInfo(id)); proxy.setFullPath(path); openDirectory(proxy); } }); } else { String parentPath = FileUtilsExt.getParentPath(path); openDirectory(new FakeFile(id, FileUtilsExt.getFileName(path), parentPath, path, Extensions.isNullOrEmpty(parentPath) || path.equals("/"))); } } catch (Exception e) { e.printStackTrace(); exitFromNetwork(); } } } public void openDirectory(FileProxy file) { openDirectory(file, true); } public void openDirectory(final FileProxy file, final boolean restorePosition) { if (!mIsInitialized) { addToPendingList(new Runnable() { @Override public void run() { openDirectory(file, restorePosition); } }); return; } mOpenDirectoryAction.init(file, restorePosition); mOpenDirectoryObserver.setLoading(); mSubscription = mOpenDirectoryObservable.subscribeOn(Schedulers.computation()). observeOn(AndroidSchedulers.mainThread()).subscribe(mOpenDirectoryObserver); } @Override public void invalidate(boolean forceReloadFiles) { NetworkEntryAdapter adapter = (NetworkEntryAdapter) mFileSystemList.getAdapter(); if (forceReloadFiles) { openDirectory(mCurrentPath, false); } else if (adapter != null) { adapter.setSelectedFiles(mSelectedFiles); adapter.notifyDataSetChanged(); } setSelectedFilesSizeVisibility(); showQuickActionPanel(); } @Override protected void gotoSearchFile(final FileProxy file) { final List<FileProxy> selectedFiles = new ArrayList<FileProxy>(1) {{ add(file); }}; if (mDataSource instanceof RawPathDataSource) { openDirectoryAndSelect(mDataSource.createFakeDirectory(file.getParentPath()), selectedFiles); } else { setIsLoading(true); ((IdPathDataSource) mDataSource).createFakeDirectoryAsync(file, new IdPathDataSource.CreateFakeDirectoryCallback() { @Override public void onCreated(final FileProxy file) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { openDirectoryAndSelect(file, selectedFiles); } }); } }); } } private void setCurrentPath(String path) { mActionBar.updateCurrentPath(mDataSource.getNetworkType() + " : " + path); } public boolean isRootDirectory() { // definetely something going wrong so it's better to tell that this is root and exit from network to avoid crash. return mCurrentPath == null || mCurrentPath.isRoot(); } public List<FileProxy> getFiles() { return mSelectedFiles; } public int getSelectedFilesCount() { return mSelectedFiles.size(); } public boolean isFileSystemPanel() { return false; } protected boolean isCopyFolderSupported() { return false; } public boolean isSearchSupported() { return mDataSource.isSearchSupported(); } public String getPanelType() { return mDataSource.getNetworkType(); } public String getCurrentPath() { return mCurrentPath.getName(); } public String getCurrentPathId() { return mCurrentPath.getFullPath(); } @Override public void openDirectory(String fullPath) { try { openDirectory(mDataSource.createFakeDirectory(fullPath.substring(fullPath.length() == 1 ? 0 : 1))); } catch (RestoreStoragePathException e) { ToastNotification.makeText(App.sInstance.getApplicationContext(), getSafeString(R.string.error_restore_storage_path), Toast.LENGTH_SHORT).show(); } } @Override public int select(SelectParams selectParams) { NetworkEntryAdapter adapter = (NetworkEntryAdapter) mFileSystemList.getAdapter(); List<FileProxy> allFiles = adapter.getFiles(); if (selectParams.getType() == SelectParams.SelectionType.NAME) { String pattern = selectParams.getSelectionString(); App.sInstance.getSharedPreferences("action_dialog", 0).edit(). putString("select_pattern", pattern).apply(); boolean inverseSelection = selectParams.isInverseSelection(); List<FileProxy> contents = new ArrayList<FileProxy>(); for (FileProxy file : allFiles) { if (FilenameUtils.wildcardMatch(file.getName(), pattern)) { contents.add(file); } } mSelectedFiles.clear(); if (contents.size() > 0) { if (inverseSelection) { for (FileProxy file : allFiles) { if (!contents.contains(file)) { mSelectedFiles.add(file); } } } else { for (FileProxy file : contents) { mSelectedFiles.add(file); } } } } else { if (selectParams.isTodayDate()) { Calendar today = Calendar.getInstance(); Calendar currentDay = Calendar.getInstance(); for (FileProxy file : allFiles) { currentDay.setTime(new Date(file.lastModifiedDate())); if (isSameDay(today, currentDay)) { mSelectedFiles.add(file); } } } else { long startDate = selectParams.getDateFrom().getTime(); long endDate = selectParams.getDateTo().getTime(); for (FileProxy file : allFiles) { if (file.lastModifiedDate() > startDate && file.lastModifiedDate() < endDate) { mSelectedFiles.add(file); } } } } adapter.setSelectedFiles(mSelectedFiles); adapter.notifyDataSetChanged(); setSelectedFilesSizeVisibility(); calculateSelectedFilesSize(); showQuickActionPanel(); return mSelectedFiles.size(); } private void handleError(NetworkException e) { try { ErrorDialog.newInstance(e.getLocalizedError()).show(fragmentManager(), "errorDialog"); } catch (Exception ignore) {} } private void handleUnlinkedError(NetworkException e) { try { YesNoDialog.newInstance(e.getLocalizedError(), new YesNoDialog.YesNoDialogListener() { @Override public void yes() { mDataSource.onUnlinkedAccount(); } @Override public void no() { } }, true).show(fragmentManager(), "errorDialog"); } catch (Exception ignore) {} } private void handleErrorAndRetry(NetworkException e, final Runnable command) { try { YesNoDialog.newInstance(e.getLocalizedError(), new YesNoDialog.YesNoDialogListener() { @Override public void yes() { command.run(); } @Override public void no() { exitFromNetwork(); } }, true).show(fragmentManager(), "errorDialog"); } catch (Exception ignore) {} } private class OpenDirectoryAction implements Observable.OnSubscribe<DirectoryUiInfo> { private DirectoryUiInfo directoryInfo = new DirectoryUiInfo(); public void init(FileProxy directory, boolean restorePosition) { directoryInfo.directory = directory; directoryInfo.restorePosition = restorePosition; } @Override public void call(Subscriber<? super DirectoryUiInfo> subscriber) { try { DirectoryScanInfo scanInfo = mDataSource.openDirectory(directoryInfo.directory); // due to files object are changed we need to refresh them. syncSelectedFiles(mSelectedFiles, scanInfo.files); syncSelectedFiles(mPreSelectedFiles, scanInfo.files); directoryInfo.files = scanInfo.files; directoryInfo.parentPath = scanInfo.parentPath; subscriber.onNext(directoryInfo); } catch (NetworkException e) { subscriber.onError(e); } } private void syncSelectedFiles(List<FileProxy> files, List<FileProxy> allFiles) { if (files.size() > 0) { ArrayList<String> preSelectedFiles = new ArrayList<>(); for (FileProxy proxy : files) { preSelectedFiles.add(proxy.getFullPath()); } files.clear(); for (FileProxy fileProxy : allFiles) { if (preSelectedFiles.contains(fileProxy.getFullPath())) { mSelectedFiles.add(fileProxy); } } } } } private class OpenDirectoryObserver implements Observer<DirectoryUiInfo> { public void setLoading() { showQuickActionPanel(); setSelectedFilesSizeVisibility(); setIsLoading(true); } @Override public void onCompleted() { } @Override public void onError(Throwable e) { NetworkException exception = (NetworkException) e; switch (exception.getErrorCause()) { case Unlinked_Error: // exit from network, delete account (optional) handleUnlinkedError(exception); exitFromNetwork(); break; case FTP_Connection_Closed: // exit from network handleError(exception); //exitFromNetwork(); case Yandex_Disk_Error: // exit from network handleUnlinkedError(exception); //exitFromNetwork(); break; case IO_Error: case Common_Error: case Cancel_Error: case Server_error: case Socket_Timeout: default: // error, propose to retry handleErrorAndRetry(exception, new Runnable() { @Override public void run() { openDirectory(mCurrentPath); } }); break; } } @Override public void onNext(DirectoryUiInfo directoryInfo) { setIsLoading(false); FileProxy file = directoryInfo.directory; String path = file.getFullPathRaw(); setCurrentPath(path); boolean isRoot = Extensions.isNullOrEmpty(file.getParentPath()) || file.getFullPathRaw().equals("/"); NetworkEntryAdapter adapter = (NetworkEntryAdapter) mFileSystemList.getAdapter(); // System.out.println(":::::::::::: ---> " + directoryInfo.directory.getFullPathRaw() + " " + directoryInfo.directory.getParentPath() + " " + directoryInfo.parentPath); mUpNavigator = new FakeFile(file.getParentPath(), "..", directoryInfo.parentPath, FileUtilsExt.getParentPath(file.getFullPathRaw()), isRoot); if (adapter != null) { adapter.setItems(directoryInfo.files, mUpNavigator); adapter.setSelectedFiles(mSelectedFiles); } else { mFileSystemList.initAdapter(new NetworkEntryAdapter(directoryInfo.files, mUpNavigator)); } mCurrentPath = new FakeFile(file.getId(), path, file.getParentPath(), file.getFullPathRaw(), isRoot); if (directoryInfo.restorePosition) { Integer selection = mDirectorySelection.get(isRoot ? "/" : path); ((LinearLayoutManager) mFileSystemList.getLayoutManager()).scrollToPositionWithOffset(selection != null ? selection : 0, 0); } // if (mPath == null) { // // something very weired // exitFromNetwork(); // } // // mPath = mDataSource.getPath(mPath); // // if (mPath.endsWith("/")) { // mPath = mPath.substring(0, mPath.length() - 1); // } // // setCurrentPath(Extensions.isNullOrEmpty(mPath) ? "/" : mPath); // // String parentPath = mPath.substring(0, mPath.lastIndexOf("/") + 1); // ListAdapter adapter = mFileSystemList.getAdapter(); // // FakeFile upNavigator = new FakeFile("..", mDataSource.getParentPath(parentPath), Extensions.isNullOrEmpty(parentPath)); // if (adapter != null && adapter instanceof NetworkEntryAdapter) { // ((NetworkEntryAdapter) adapter).setItems(files, upNavigator); // ((NetworkEntryAdapter) adapter).setSelectedFiles(mSelectedFiles); // } else { // mFileSystemList.setAdapter(new NetworkEntryAdapter(files, upNavigator)); // } // mCurrentPath = new FakeFile(Extensions.isNullOrEmpty(mPath) ? "/" : mPath, // mDataSource.getParentPath(parentPath), Extensions.isNullOrEmpty(parentPath)); // // if (mRestorePosition) { // Integer selection = mDirectorySelection.get(Extensions.isNullOrEmpty(mPath) ? "/" : mPath); // mFileSystemList.setSelection(selection != null ? selection : 0); // } if (mPreSelectedFiles.size() > 0) { calculateSelectedFilesSize(); } showQuickActionPanel(); setSelectedFilesSizeVisibility(); if (!mSubscription.isUnsubscribed()) { mSubscription.unsubscribe(); } } } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Activity activity = getActivity(); if (activity == null || activity.isFinishing()) { return; } switch (msg.what) { case MSG_NETWORK_SHOW_PROGRESS: showProgressDialog(); break; case MSG_NETWORK_HIDE_PROGRESS: hideProgressDialog(); break; case MSG_NETWORK_OPEN: open(msg); break; default: NetworkPanel.super.mHandler.sendMessage(Message.obtain(msg)); break; } } }; private void open(Message msg) { hideProgressDialog(); Pair<FileProxy, String> data = (Pair<FileProxy, String>) msg.obj; Intent i = new Intent(Intent.ACTION_VIEW); i.setDataAndType(Uri.parse(data.second), data.first.getMimeType()); try { startActivity(i); } catch (Exception e) { e.printStackTrace(); if (e instanceof ActivityNotFoundException) { ToastNotification.makeText(App.sInstance.getApplicationContext(), getSafeString(R.string.error_activity_not_found), Toast.LENGTH_SHORT).show(); } } } private void hideProgressDialog() { if (mProgressDialog != null) { mProgressDialog.hide(); mProgressDialog = null; } } private void showProgressDialog() { if (mProgressDialog != null && mProgressDialog.isShowing()) { mProgressDialog.hide(); } Activity activity = getActivity(); if (activity == null) { return; } mProgressDialog = new Dialog(activity, android.R.style.Theme_Translucent); mProgressDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); mProgressDialog.setCancelable(false); mProgressDialog.setContentView(R.layout.dialog_progress); ((TextView) mProgressDialog.findViewById(R.id.progress_bar_text)).setText(R.string.loading); mProgressDialog.show(); } public class DirectoryUiInfo { public FileProxy directory; public boolean restorePosition; public List<FileProxy> files; public String parentPath; } public static class DirectoryScanInfo { public List<FileProxy> files; public String parentPath; public DirectoryScanInfo set(List<FileProxy> files, String parentPath) { this.files = files; this.parentPath = parentPath; return this; } } }