<commit_before>package com.battlelancer.seriesguide.dataliberation;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.widget.Toast;
import com.battlelancer.seriesguide.R;
import com.battlelancer.seriesguide.dataliberation.model.Episode;
import com.battlelancer.seriesguide.dataliberation.model.List;
import com.battlelancer.seriesguide.dataliberation.model.ListItem;
import com.battlelancer.seriesguide.dataliberation.model.Movie;
import com.battlelancer.seriesguide.dataliberation.model.Season;
import com.battlelancer.seriesguide.dataliberation.model.Show;
import com.battlelancer.seriesguide.interfaces.OnTaskFinishedListener;
import com.battlelancer.seriesguide.interfaces.OnTaskProgressListener;
import com.battlelancer.seriesguide.provider.SeriesGuideContract;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Episodes;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.ListItemTypes;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.ListItems;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Seasons;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Shows;
import com.battlelancer.seriesguide.settings.AdvancedSettings;
import com.battlelancer.seriesguide.settings.BackupSettings;
import com.battlelancer.seriesguide.util.EpisodeTools;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.stream.JsonWriter;
import com.uwetrottmann.androidutils.AndroidUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import timber.log.Timber;

import static com.battlelancer.seriesguide.provider.SeriesGuideContract.Movies;

/**
 * Export the show database to a human-readable JSON file on external storage. By default meta-data
 * like descriptions, ratings, actors, etc. will not be included.
 */
public class JsonExportTask extends AsyncTask<Void, Integer, Integer> {

    public static final String EXPORT_FOLDER = "SeriesGuide";
    public static final String EXPORT_FOLDER_AUTO = "SeriesGuide" + File.separator + "AutoBackup";
    public static final String EXPORT_JSON_FILE_SHOWS = "sg-shows-export.json";
    public static final String EXPORT_JSON_FILE_LISTS = "sg-lists-export.json";
    public static final String EXPORT_JSON_FILE_MOVIES = "sg-movies-export.json";

    public static final int BACKUP_SHOWS = 1;
    public static final int BACKUP_LISTS = 2;
    public static final int BACKUP_MOVIES = 3;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            BACKUP_SHOWS,
            BACKUP_LISTS,
            BACKUP_MOVIES
    })
    public @interface BackupType {
    }

    private static final int SUCCESS = 1;
    private static final int ERROR_FILE_ACCESS = 0;
    private static final int ERROR = -1;

    /**
     * Show status used when exporting data. Compare with {@link com.battlelancer.seriesguide.util.ShowTools.Status}.
     */
    public interface ShowStatusExport {
        String CONTINUING = "continuing";
        String ENDED = "ended";
        String UNKNOWN = "unknown";
    }

    public interface ListItemTypesExport {
        String SHOW = "show";
        String SEASON = "season";
        String EPISODE = "episode";
    }

    private Context context;
    private OnTaskProgressListener progressListener;
    private OnTaskFinishedListener finishedListener;
    private boolean isFullDump;
    private boolean isAutoBackupMode;
    private boolean isUseDefaultFolders;

    public static File getExportPath(boolean isAutoBackupMode) {
        return new File(
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
                isAutoBackupMode ? EXPORT_FOLDER_AUTO : EXPORT_FOLDER);
    }

    /**
     * Same as {@link JsonExportTask} but allows to set parameters.
     *
     * @param isFullDump Whether to also export meta-data like descriptions, ratings, actors, etc.
     * Increases file size about 2-4 times.
     * @param isAutoBackupMode Whether to run an auto backup, also shows no result toasts.
     */
    public JsonExportTask(Context context, OnTaskProgressListener progressListener,
            OnTaskFinishedListener listener, boolean isFullDump,
            boolean isAutoBackupMode) {
        this.context = context.getApplicationContext();
        this.progressListener = progressListener;
        finishedListener = listener;
        this.isFullDump = isFullDump;
        this.isAutoBackupMode = isAutoBackupMode;
        // use Storage Access Framework on KitKat and up to select custom backup files,
        // on older versions use default folders
        // also auto backup by default uses default folders
        isUseDefaultFolders = !AndroidUtils.isKitKatOrHigher()
                || (isAutoBackupMode && BackupSettings.isUseAutoBackupDefaultFiles(context));
    }

    @SuppressLint("CommitPrefEdits")
    @Override
    protected Integer doInBackground(Void... params) {
        File exportPath = null;
        if (isUseDefaultFolders) {
            // Ensure external storage is available
            if (!AndroidUtils.isExtStorageAvailable()) {
                return ERROR_FILE_ACCESS;
            }

            // Ensure the export directory exists
            exportPath = getExportPath(isAutoBackupMode);
            if (!exportPath.mkdirs() && !exportPath.isDirectory()) {
                return ERROR_FILE_ACCESS;
            }
        }

        // last chance to abort
        if (isCancelled()) {
            return ERROR;
        }

        int result = exportData(exportPath, BACKUP_SHOWS);
        if (result != SUCCESS) {
            return result;
        }
        if (isCancelled()) {
            return ERROR;
        }

        result = exportData(exportPath, BACKUP_LISTS);
        if (result != SUCCESS) {
            return result;
        }
        if (isCancelled()) {
            return ERROR;
        }

        result = exportData(exportPath, BACKUP_MOVIES);
        if (result != SUCCESS) {
            return result;
        }
        // no need to return early here if canceled, we are almost done anyhow

        if (isAutoBackupMode) {
            // store current time = last backup time
            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
            prefs.edit().putLong(AdvancedSettings.KEY_LASTBACKUP, System.currentTimeMillis())
                    .commit();
        }<commit_msg>Use prefs apply() in JsonExportTask.<commit_after>package com.battlelancer.seriesguide.dataliberation;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.widget.Toast;
import com.battlelancer.seriesguide.R;
import com.battlelancer.seriesguide.dataliberation.model.Episode;
import com.battlelancer.seriesguide.dataliberation.model.List;
import com.battlelancer.seriesguide.dataliberation.model.ListItem;
import com.battlelancer.seriesguide.dataliberation.model.Movie;
import com.battlelancer.seriesguide.dataliberation.model.Season;
import com.battlelancer.seriesguide.dataliberation.model.Show;
import com.battlelancer.seriesguide.interfaces.OnTaskFinishedListener;
import com.battlelancer.seriesguide.interfaces.OnTaskProgressListener;
import com.battlelancer.seriesguide.provider.SeriesGuideContract;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Episodes;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.ListItemTypes;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.ListItems;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Seasons;
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Shows;
import com.battlelancer.seriesguide.settings.AdvancedSettings;
import com.battlelancer.seriesguide.settings.BackupSettings;
import com.battlelancer.seriesguide.util.EpisodeTools;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.stream.JsonWriter;
import com.uwetrottmann.androidutils.AndroidUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import timber.log.Timber;

import static com.battlelancer.seriesguide.provider.SeriesGuideContract.Movies;

/**
 * Export the show database to a human-readable JSON file on external storage. By default meta-data
 * like descriptions, ratings, actors, etc. will not be included.
 */
public class JsonExportTask extends AsyncTask<Void, Integer, Integer> {

    public static final String EXPORT_FOLDER = "SeriesGuide";
    public static final String EXPORT_FOLDER_AUTO = "SeriesGuide" + File.separator + "AutoBackup";
    public static final String EXPORT_JSON_FILE_SHOWS = "sg-shows-export.json";
    public static final String EXPORT_JSON_FILE_LISTS = "sg-lists-export.json";
    public static final String EXPORT_JSON_FILE_MOVIES = "sg-movies-export.json";

    public static final int BACKUP_SHOWS = 1;
    public static final int BACKUP_LISTS = 2;
    public static final int BACKUP_MOVIES = 3;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            BACKUP_SHOWS,
            BACKUP_LISTS,
            BACKUP_MOVIES
    })
    public @interface BackupType {
    }

    private static final int SUCCESS = 1;
    private static final int ERROR_FILE_ACCESS = 0;
    private static final int ERROR = -1;

    /**
     * Show status used when exporting data. Compare with {@link com.battlelancer.seriesguide.util.ShowTools.Status}.
     */
    public interface ShowStatusExport {
        String CONTINUING = "continuing";
        String ENDED = "ended";
        String UNKNOWN = "unknown";
    }

    public interface ListItemTypesExport {
        String SHOW = "show";
        String SEASON = "season";
        String EPISODE = "episode";
    }

    private Context context;
    private OnTaskProgressListener progressListener;
    private OnTaskFinishedListener finishedListener;
    private boolean isFullDump;
    private boolean isAutoBackupMode;
    private boolean isUseDefaultFolders;

    public static File getExportPath(boolean isAutoBackupMode) {
        return new File(
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
                isAutoBackupMode ? EXPORT_FOLDER_AUTO : EXPORT_FOLDER);
    }

    /**
     * Same as {@link JsonExportTask} but allows to set parameters.
     *
     * @param isFullDump Whether to also export meta-data like descriptions, ratings, actors, etc.
     * Increases file size about 2-4 times.
     * @param isAutoBackupMode Whether to run an auto backup, also shows no result toasts.
     */
    public JsonExportTask(Context context, OnTaskProgressListener progressListener,
            OnTaskFinishedListener listener, boolean isFullDump,
            boolean isAutoBackupMode) {
        this.context = context.getApplicationContext();
        this.progressListener = progressListener;
        finishedListener = listener;
        this.isFullDump = isFullDump;
        this.isAutoBackupMode = isAutoBackupMode;
        // use Storage Access Framework on KitKat and up to select custom backup files,
        // on older versions use default folders
        // also auto backup by default uses default folders
        isUseDefaultFolders = !AndroidUtils.isKitKatOrHigher()
                || (isAutoBackupMode && BackupSettings.isUseAutoBackupDefaultFiles(context));
    }

    @SuppressLint("CommitPrefEdits")
    @Override
    protected Integer doInBackground(Void... params) {
        File exportPath = null;
        if (isUseDefaultFolders) {
            // Ensure external storage is available
            if (!AndroidUtils.isExtStorageAvailable()) {
                return ERROR_FILE_ACCESS;
            }

            // Ensure the export directory exists
            exportPath = getExportPath(isAutoBackupMode);
            if (!exportPath.mkdirs() && !exportPath.isDirectory()) {
                return ERROR_FILE_ACCESS;
            }
        }

        // last chance to abort
        if (isCancelled()) {
            return ERROR;
        }

        int result = exportData(exportPath, BACKUP_SHOWS);
        if (result != SUCCESS) {
            return result;
        }
        if (isCancelled()) {
            return ERROR;
        }

        result = exportData(exportPath, BACKUP_LISTS);
        if (result != SUCCESS) {
            return result;
        }
        if (isCancelled()) {
            return ERROR;
        }

        result = exportData(exportPath, BACKUP_MOVIES);
        if (result != SUCCESS) {
            return result;
        }
        // no need to return early here if canceled, we are almost done anyhow

        if (isAutoBackupMode) {
            // store current time = last backup time
            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
            prefs.edit()
                    .putLong(AdvancedSettings.KEY_LASTBACKUP, System.currentTimeMillis())
                    .apply();
        }
{'fraction_non_alphanumeric': 0.07907240621232536, 'fraction_numerical': 0.0011346712999078079, 'mean_word_length': 3.2824172487093835, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '423797', 'n_tokens_mistral': 3963, 'n_tokens_neox': 3830, 'n_words': 919}