com.ultramegasoft.flavordex2.dialog.FileImportDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.ultramegasoft.flavordex2.dialog.FileImportDialog.java

Source

/*
 * The MIT License (MIT)
 * Copyright  2016 Steve Guidetti
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the Software?), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED AS IS?, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.ultramegasoft.flavordex2.dialog;

import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;

import com.ultramegasoft.flavordex2.R;
import com.ultramegasoft.flavordex2.util.CSVUtils;
import com.ultramegasoft.flavordex2.util.EntryUtils;
import com.ultramegasoft.flavordex2.util.FileUtils;
import com.ultramegasoft.flavordex2.util.PhotoUtils;
import com.ultramegasoft.flavordex2.widget.CSVListAdapter;
import com.ultramegasoft.flavordex2.widget.EntryHolder;
import com.ultramegasoft.flavordex2.widget.PhotoHolder;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Dialog for importing journal entries from CSV files.
 *
 * @author Steve Guidetti
 */
public class FileImportDialog extends ImportDialog implements LoaderManager.LoaderCallbacks<CSVUtils.CSVHolder> {
    private static final String TAG = "FileImportDialog";

    /**
     * Keys for the Fragment arguments
     */
    private static final String ARG_FILE_PATH = "file_path";

    /**
     * Request codes for external Activities
     */
    private static final int REQUEST_DUPLICATES = 1000;
    private static final int REQUEST_SET_CATEGORY = 1001;

    /**
     * Keys for the saved state
     */
    private static final String STATE_DATA = "data";

    /**
     * The data loaded from the CSV file
     */
    @Nullable
    private CSVUtils.CSVHolder mData;

    /**
     * The path to the selected file
     */
    private String mFilePath;

    /**
     * Whether the import is a Zip file
     */
    private boolean mIsZipFile;

    /**
     * Show the dialog.
     *
     * @param fm       The FragmentManager to use
     * @param filePath The path to the selected file
     */
    public static void showDialog(@NonNull FragmentManager fm, @NonNull String filePath) {
        final DialogFragment fragment = new FileImportDialog();

        final Bundle args = new Bundle();
        args.putString(ARG_FILE_PATH, filePath);
        fragment.setArguments(args);

        fragment.show(fm, TAG);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        final Bundle args = getArguments();
        if (args != null) {
            mFilePath = args.getString(ARG_FILE_PATH);
            mIsZipFile = mFilePath != null && mFilePath.endsWith(FileUtils.EXT_ZIP);
        }
        if (savedInstanceState != null) {
            mData = savedInstanceState.getParcelable(STATE_DATA);
        }

        if (mData != null) {
            final Context context = getContext();
            if (context != null) {
                setListAdapter(new CSVListAdapter(context, mData));
            }
        } else if (mFilePath != null) {
            getLoaderManager().initLoader(0, null, this).forceLoad();
        }

        if (mIsZipFile) {
            mIncludeImages.setVisibility(View.VISIBLE);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putParcelable(STATE_DATA, mData);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case REQUEST_DUPLICATES:
            if (resultCode == Activity.RESULT_OK) {
                uncheckDuplicates();
            }
            break;
        case REQUEST_SET_CATEGORY:
            final FragmentManager fm = getFragmentManager();
            if (fm != null) {
                CatListDialog.closeDialog(fm);
            }

            if (resultCode == Activity.RESULT_OK && data != null && mData != null) {
                final long catId = data.getLongExtra(CatListDialog.EXTRA_CAT_ID, 0);
                for (EntryHolder entry : mData.entries) {
                    entry.catId = catId;
                }
                mData.hasCategory = true;
                validateData();
            } else {
                dismiss();
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    /**
     * Uncheck duplicate entries.
     */
    private void uncheckDuplicates() {
        if (mData == null) {
            return;
        }
        final ListView listView = getListView();
        for (int i = 0; i < mData.entries.size(); i++) {
            listView.setItemChecked(i, !mData.duplicates.contains(mData.entries.get(i)));
        }

        final int numDuplicates = mData.duplicates.size();
        if (numDuplicates > 0) {
            final String duplicates = getResources().getQuantityString(R.plurals.duplicates, numDuplicates);
            final String were = getResources().getQuantityString(R.plurals.were, numDuplicates);
            final String message = getString(R.string.message_duplicates_unchecked, numDuplicates, duplicates,
                    were);
            Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show();
        }
    }

    @Override
    protected void insertSelected() {
        final FragmentManager fm = getFragmentManager();
        final CSVListAdapter adapter = (CSVListAdapter) getListAdapter();
        if (fm != null && adapter != null) {
            final ArrayList<EntryHolder> entries = new ArrayList<>();
            for (long i : getListView().getCheckedItemIds()) {
                entries.add(adapter.getItem((int) i));
            }

            if (mIsZipFile && mIncludeImages.isChecked()) {
                DataSaverFragment.init(fm, entries, mFilePath);
            } else {
                DataSaverFragment.init(fm, entries);
            }
        }
    }

    @SuppressWarnings("ConstantConditions")
    @NonNull
    @Override
    public Loader<CSVUtils.CSVHolder> onCreateLoader(int id, Bundle args) {
        final Context context = getContext();
        if (context == null) {
            return null;
        }

        setListShown(false);
        return new CsvLoader(context, mFilePath);
    }

    /**
     * Check the data and prompt the user for input as needed.
     */
    private void validateData() {
        invalidateButtons();

        final FragmentManager fm = getFragmentManager();
        if (fm == null || mData == null) {
            return;
        }

        if (!mData.hasCategory) {
            CatListDialog.showDialog(fm, this, REQUEST_SET_CATEGORY);
        } else if (!mData.duplicates.isEmpty()) {
            DuplicatesDialog.showDialog(fm, this, REQUEST_DUPLICATES, mData.duplicates.size());
        }
    }

    @Override
    public void onLoadFinished(@NonNull Loader<CSVUtils.CSVHolder> loader, CSVUtils.CSVHolder data) {
        final Context context = getContext();
        if (context != null && data != null) {
            setListShown(true);
            setListAdapter(new CSVListAdapter(context, data));

            final ListView listView = getListView();
            for (int i = 0; i < data.entries.size(); i++) {
                listView.setItemChecked(i, true);
            }

            mData = data;
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    validateData();
                }
            });
        } else {
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    final FragmentManager fm = getFragmentManager();
                    if (fm != null) {
                        MessageDialog.showDialog(fm, getString(R.string.title_error),
                                getString(R.string.error_csv_parse), R.drawable.ic_warning);
                    }
                    dismiss();
                }
            });
        }

        getLoaderManager().destroyLoader(0);
    }

    @Override
    public void onLoaderReset(@NonNull Loader<CSVUtils.CSVHolder> loader) {
    }

    /**
     * Asynchronously loads CSV files.
     */
    private static class CsvLoader extends AsyncTaskLoader<CSVUtils.CSVHolder> {
        /**
         * The path to the selected file
         */
        @NonNull
        private final String mFilePath;

        /**
         * @param context  The Context
         * @param filePath The path to the selected file
         */
        CsvLoader(@NonNull Context context, @NonNull String filePath) {
            super(context);
            mFilePath = filePath;
        }

        @Override
        public CSVUtils.CSVHolder loadInBackground() {
            if (mFilePath.substring(mFilePath.lastIndexOf('.')).toLowerCase().equals(FileUtils.EXT_ZIP)) {
                final File file = getCsvFromZip();
                if (file == null) {
                    return null;
                }
                final CSVUtils.CSVHolder holder = CSVUtils.importCSV(getContext(), file);
                //noinspection ResultOfMethodCallIgnored
                file.delete();
                return holder;
            } else {
                return CSVUtils.importCSV(getContext(), new File(mFilePath));
            }
        }

        /**
         * Extract the CSV file from the Zip archive to a temp file.
         *
         * @return The CSV file
         */
        @Nullable
        private File getCsvFromZip() {
            ZipFile zipFile = null;
            try {
                zipFile = new ZipFile(mFilePath);
                final String entryName = mFilePath.substring(0, mFilePath.lastIndexOf('.'))
                        .substring(mFilePath.lastIndexOf('/') + 1) + FileUtils.EXT_CSV;
                final ZipEntry zipEntry = zipFile.getEntry(entryName);
                if (zipEntry != null) {
                    final File file = File.createTempFile("import_", entryName);
                    FileUtils.dumpStream(zipFile.getInputStream(zipEntry), file);

                    return file;
                }
            } catch (IOException e) {
                Log.e(TAG, "Unable to extract file", e);
            } finally {
                if (zipFile != null) {
                    try {
                        zipFile.close();
                    } catch (IOException ignored) {
                    }
                }
            }

            return null;
        }
    }

    /**
     * Dialog to show the user when duplicate entries are detected.
     */
    @SuppressWarnings("SameParameterValue")
    public static class DuplicatesDialog extends DialogFragment {
        private static final String TAG = "DuplicatesDialog";

        /**
         * Keys for the Fragment arguments
         */
        private static final String ARG_NUM = "num";

        /**
         * Show the dialog.
         *
         * @param fm          The FragmentManager to use
         * @param target      The Fragment to notify of the result
         * @param requestCode A number to identify this request
         * @param num         The number of duplicates
         */
        static void showDialog(@NonNull FragmentManager fm, @Nullable Fragment target, int requestCode, int num) {
            final DialogFragment fragment = new DuplicatesDialog();
            fragment.setTargetFragment(target, requestCode);

            final Bundle args = new Bundle();
            args.putInt(ARG_NUM, num);
            fragment.setArguments(args);

            fragment.show(fm, TAG);
        }

        @NonNull
        @Override
        @SuppressWarnings("MethodDoesntCallSuperMethod")
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            final Bundle args = getArguments();
            final int num = args != null ? args.getInt(ARG_NUM) : 0;

            final Resources res = getResources();
            final String duplicates = res.getQuantityString(R.plurals.duplicates, num);
            final String duplicatesCapitalized = Character.toUpperCase(duplicates.charAt(0))
                    + duplicates.substring(1);
            final String were = res.getQuantityString(R.plurals.were, num);

            final String message = getString(R.string.message_duplicates_detected, num, duplicates, were);
            final String button = getString(R.string.button_uncheck_duplicates, duplicatesCapitalized);
            return new android.app.AlertDialog.Builder(getContext()).setTitle(R.string.title_duplicates)
                    .setIcon(R.drawable.ic_info).setMessage(message)
                    .setPositiveButton(button, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            final Fragment fragment = getTargetFragment();
                            if (fragment != null) {
                                fragment.onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, null);
                            }
                        }
                    }).setNegativeButton(R.string.button_ignore, null).create();
        }
    }

    /**
     * Fragment for saving the selected entries in the background.
     */
    public static class DataSaverFragment extends BackgroundProgressDialog {
        private static final String TAG = "DataSaverFragment";

        /**
         * Keys for the Fragment arguments
         */
        private static final String ARG_ENTRIES = "entries";
        private static final String ARG_ZIP_FILE = "zip_file";

        /**
         * The list of entries
         */
        private ArrayList<EntryHolder> mEntries;

        /**
         * The path to the Zip file containing images
         */
        @Nullable
        private String mImageZip;

        /**
         * Start a new instance of this Fragment.
         *
         * @param fm      The FragmentManager to use
         * @param entries The list of entries
         */
        static void init(@NonNull FragmentManager fm, @NonNull ArrayList<EntryHolder> entries) {
            init(fm, entries, null);
        }

        /**
         * Start a new instance of this Fragment.
         *
         * @param fm       The FragmentManager to use
         * @param entries  The list of entries
         * @param imageZip The path to the Zip file containing images
         */
        static void init(@NonNull FragmentManager fm, @NonNull ArrayList<EntryHolder> entries,
                @Nullable String imageZip) {
            final DialogFragment fragment = new DataSaverFragment();

            final Bundle args = new Bundle();
            args.putParcelableArrayList(ARG_ENTRIES, entries);
            args.putString(ARG_ZIP_FILE, imageZip);
            fragment.setArguments(args);

            fragment.show(fm, TAG);
        }

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            final Bundle args = getArguments();
            if (args != null) {
                mEntries = args.getParcelableArrayList(ARG_ENTRIES);
                mImageZip = args.getString(ARG_ZIP_FILE);
            }
        }

        @NonNull
        @Override
        @SuppressWarnings("MethodDoesntCallSuperMethod")
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            final ProgressDialog dialog = new ProgressDialog(getContext());

            dialog.setIcon(R.drawable.ic_import);
            dialog.setTitle(R.string.title_importing);
            dialog.setIndeterminate(false);
            dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            dialog.setMax(mEntries.size());

            return dialog;
        }

        @Override
        protected void startTask() {
            final Context context = getContext();
            if (context != null) {
                new SaveTask(context, this, mEntries, mImageZip).execute();
            }
        }

        /**
         * Task for saving entries in the background.
         */
        private static class SaveTask extends AsyncTask<Void, Integer, Void> {
            /**
             * The Context reference
             */
            @NonNull
            private final WeakReference<Context> mContext;

            /**
             * The Fragment
             */
            @NonNull
            private final DataSaverFragment mFragment;

            /**
             * The list of entries
             */
            @NonNull
            private final ArrayList<EntryHolder> mEntries;

            /**
             * The path to the Zip file containing images
             */
            @Nullable
            private final String mImageZip;

            /**
             * The Zip file containing the files being imported
             */
            @Nullable
            private ZipFile mZipFile;

            /**
             * Map of directories in the Zip file
             */
            private HashMap<String, HashMap<String, ZipEntry>> mZipDirs;

            /**
             * @param context  The Context
             * @param fragment The Fragment
             * @param entries  The list of entries
             * @param imageZip The path to the Zip file containing images
             */
            SaveTask(@NonNull Context context, @NonNull DataSaverFragment fragment,
                    @NonNull ArrayList<EntryHolder> entries, @Nullable String imageZip) {
                mContext = new WeakReference<>(context.getApplicationContext());
                mFragment = fragment;
                mEntries = entries;
                mImageZip = imageZip;
            }

            @Override
            protected Void doInBackground(Void... params) {
                final Context context = mContext.get();
                if (context == null) {
                    return null;
                }

                if (mImageZip != null) {
                    try {
                        mZipFile = new ZipFile(mImageZip);
                        mZipDirs = new HashMap<>();
                        ZipEntry entry;
                        String[] parts;
                        for (Enumeration<? extends ZipEntry> e = mZipFile.entries(); e.hasMoreElements();) {
                            entry = e.nextElement();
                            parts = TextUtils.split(entry.getName(), "/");
                            if (parts.length != 2) {
                                continue;
                            }

                            if (!mZipDirs.containsKey(parts[0])) {
                                mZipDirs.put(parts[0], new HashMap<String, ZipEntry>());
                            }

                            mZipDirs.get(parts[0]).put(parts[1], entry);
                        }
                    } catch (IOException e) {
                        Log.w(TAG, "Failed to open Zip file", e);
                    }
                }

                int i = 0;
                for (EntryHolder entry : mEntries) {
                    try {
                        importImages(entry);
                        EntryUtils.insertEntry(context, entry);
                    } catch (SQLiteException e) {
                        Log.e(TAG, "Failed to insert entry: " + entry.title, e);
                    }
                    publishProgress(++i);
                }

                if (mZipFile != null) {
                    try {
                        mZipFile.close();
                    } catch (IOException ignored) {
                    }
                }

                return null;
            }

            /**
             * Import images from the Zip file.
             *
             * @param entry The entry
             */
            private void importImages(@NonNull EntryHolder entry) {
                if (mZipFile == null) {
                    return;
                }

                final HashMap<String, ZipEntry> zipEntries = mZipDirs.get(entry.uuid);
                if (zipEntries == null) {
                    return;
                }

                InputStream inputStream = null;
                for (PhotoHolder photoHolder : entry.getPhotos()) {
                    String fileName = photoHolder.uri.getLastPathSegment();
                    if (!zipEntries.containsKey(fileName)) {
                        continue;
                    }

                    try {
                        inputStream = mZipFile.getInputStream(zipEntries.get(fileName));
                        final File file = PhotoUtils.savePhotoFromStream(inputStream,
                                fileName.substring(fileName.indexOf('_') + 1));
                        photoHolder.uri = Uri.fromFile(file);
                    } catch (IOException e) {
                        Log.w(TAG, "Failed to save image file", e);
                    } finally {
                        if (inputStream != null) {
                            try {
                                inputStream.close();
                            } catch (IOException ignored) {
                            }
                        }
                    }
                }
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                super.onProgressUpdate(values);

                final ProgressDialog dialog = (ProgressDialog) mFragment.getDialog();
                if (dialog != null) {
                    dialog.setProgress(values[0]);
                }
            }

            @Override
            protected void onPostExecute(Void result) {
                super.onPostExecute(result);

                final Context context = mContext.get();
                if (context != null) {
                    Toast.makeText(context, R.string.message_import_complete, Toast.LENGTH_LONG).show();
                }

                mFragment.dismiss();
            }
        }
    }
}