<commit_before>/*
 * Copyright (c) 2017 <NAME> <<EMAIL>>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ie.macinnes.tvheadend.sync;

import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteFullException;
import android.media.tv.TvContentRating;
import android.media.tv.TvContract;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.SparseArray;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;

import ie.macinnes.htsp.HtspFileInputStream;
import ie.macinnes.htsp.HtspMessage;
import ie.macinnes.htsp.HtspNotConnectedException;
import ie.macinnes.htsp.tasks.Authenticator;
import ie.macinnes.tvheadend.BuildConfig;
import ie.macinnes.tvheadend.Constants;
import ie.macinnes.tvheadend.DvbMappings;
import ie.macinnes.tvheadend.R;
import ie.macinnes.tvheadend.TvContractUtils;

public class EpgSyncTask implements HtspMessage.Listener, Authenticator.Listener {
    private static final String TAG = EpgSyncTask.class.getSimpleName();
    private static final Set<String> HANDLED_METHODS = new HashSet<>(Arrays.asList(new String[]{
            "channelAdd", "channelUpdate", "channelDelete",
            "eventAdd", "eventUpdate", "eventDelete",
            "initialSyncCompleted",
    }));
    private static final boolean IS_BRAVIA = Build.MODEL.contains("BRAVIA");

    private static final String CHANNEL_ID_KEY = "channelId";
    private static final String CHANNEL_NUMBER_KEY = "channelNumber";
    private static final String CHANNEL_NUMBER_MINOR_KEY = "channelNumberMinor";
    private static final String CHANNEL_NAME_KEY = "channelName";
    private static final String CHANNEL_ICON_KEY = "channelIcon";

    private static final String PROGRAM_DESCRIPTION_KEY = "description";
    private static final String PROGRAM_SUMMARY_KEY = "summary";
    private static final String PROGRAM_TITLE_KEY = "title";
    private static final String PROGRAM_EPISODE_TITLE_KEY = "subtitle";
    private static final String PROGRAM_CONTENT_TYPE_KEY = "contentType";
    private static final String PROGRAM_AGE_RATING_KEY = "ageRating";
    private static final String PROGRAM_START_TIME_KEY = "start";
    private static final String PROGRAM_FINISH_TIME_KEY = "stop";
    private static final String PROGRAM_SEASON_NUMBER_KEY = "seasonNumber";
    private static final String PROGRAM_EPISODE_NUMBER_KEY = "episodeNumber";
    private static final String PROGRAM_IMAGE = "image";

    private static final String EVENT_ID_KEY = "eventId";

    private static final int TWO_HOURS = 2*60*60;

    /**
     * A listener for EpgSync events
     */
    public interface Listener {
        /**
         * Returns the Handler on which to execute the callback.
         *
         * @return Handler, or null.
         */
        Handler getHandler();

        /**
         * Called when the initial sync has completed
         */
        void onInitialSyncCompleted();
    }

    private final Context mContext;
    private final HtspMessage.Dispatcher mDispatcher;
    private boolean mQuickSync = false;

    private final SharedPreferences mSharedPreferences;
    private final ContentResolver mContentResolver;

    private final HandlerThread mHandlerThread;
    private final Handler mHandler;

    private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();

    private boolean mInitialSyncCompleted = false;

    private final SparseArray<Uri> mChannelUriMap;
    private final SparseArray<Uri> mProgramUriMap;

    private final ArrayList<PendingChannelAddUpdate> mPendingChannelOps = new ArrayList<>();
    private final ArrayList<PendingEventAddUpdate> mPendingProgramOps = new ArrayList<>();

    private final Queue<PendingChannelLogoFetch> mPendingChannelLogoFetches = new ConcurrentLinkedQueue<>();
    private final byte[] mImageBytes = new byte[102400];

    private Set<Integer> mSeenChannels = new HashSet<>();
    private Set<Integer> mSeenPrograms = new HashSet<>();

    private final class PendingChannelAddUpdate {
        //TODO: Should probably make these private with accessors if necessary
        public int channelId;
        public int channelNumber;
        public ContentProviderOperation operation;

        public PendingChannelAddUpdate(int channelId, int channelNumber, ContentProviderOperation operation) {
            this.channelId = channelId;
            this.channelNumber = channelNumber;
            this.operation = operation;
        }
    }

    private final class PendingChannelLogoFetch {
        public int channelId;
        public Uri logoUri;

        public PendingChannelLogoFetch(int channelId, Uri logoUri) {
            this.channelId = channelId;
            this.logoUri = logoUri;
        }
    }

    private final class PendingEventAddUpdate {
        public int eventId;
        public ContentProviderOperation operation;

        public PendingEventAddUpdate(int eventId, ContentProviderOperation operation) {
            this.eventId = eventId;
            this.operation = operation;
        }
    }

    public EpgSyncTask(Context context, @NonNull HtspMessage.Dispatcher dispatcher, boolean quickSync) {
        this(context, dispatcher);

        mQuickSync = quickSync;
    }

    public EpgSyncTask(Context context, @NonNull HtspMessage.Dispatcher dispatcher) {
        mContext = context;
        mDispatcher = dispatcher;

        mSharedPreferences = context.getSharedPreferences(
                Constants.PREFERENCE_TVHEADEND, Context.MODE_PRIVATE);

        mContentResolver = context.getContentResolver();

        mHandlerThread = new HandlerThread("EpgSyncService Handler Thread");
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());

        mChannelUriMap = TvContractUtils.buildChannelUriMap(context);
        mProgramUriMap = TvContractUtils.buildProgramUriMap(context);
    }

    public void addEpgSyncListener(Listener listener) {
        if (mListeners.contains(listener)) {
            Log.w(TAG, "Attempted to add duplicate epg sync listener");
            return;
        }
        mListeners.add(listener);
    }

    public void removeEpgSyncListener(Listener listener) {
        if (!mListeners.contains(listener)) {
            Log.w(TAG, "Attempted to remove non existing epg sync listener");
            return;
        }
        mListeners.remove(listener);
    }

    // Authenticator.Listener Methods
    @Override
    public void onAuthenticationStateChange(@NonNull Authenticator.State state) {
        if (state == Authenticator.State.AUTHENTICATED) {
            long epgMaxTime = Long.parseLong(
                    mSharedPreferences.getString(<commit_msg>HandlerThread only used in one place<commit_after>/*
 * Copyright (c) 2017 <NAME> <<EMAIL>>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ie.macinnes.tvheadend.sync;

import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteFullException;
import android.media.tv.TvContentRating;
import android.media.tv.TvContract;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.SparseArray;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;

import ie.macinnes.htsp.HtspFileInputStream;
import ie.macinnes.htsp.HtspMessage;
import ie.macinnes.htsp.HtspNotConnectedException;
import ie.macinnes.htsp.tasks.Authenticator;
import ie.macinnes.tvheadend.BuildConfig;
import ie.macinnes.tvheadend.Constants;
import ie.macinnes.tvheadend.DvbMappings;
import ie.macinnes.tvheadend.R;
import ie.macinnes.tvheadend.TvContractUtils;

public class EpgSyncTask implements HtspMessage.Listener, Authenticator.Listener {
    private static final String TAG = EpgSyncTask.class.getSimpleName();
    private static final Set<String> HANDLED_METHODS = new HashSet<>(Arrays.asList(new String[]{
            "channelAdd", "channelUpdate", "channelDelete",
            "eventAdd", "eventUpdate", "eventDelete",
            "initialSyncCompleted",
    }));
    private static final boolean IS_BRAVIA = Build.MODEL.contains("BRAVIA");

    private static final String CHANNEL_ID_KEY = "channelId";
    private static final String CHANNEL_NUMBER_KEY = "channelNumber";
    private static final String CHANNEL_NUMBER_MINOR_KEY = "channelNumberMinor";
    private static final String CHANNEL_NAME_KEY = "channelName";
    private static final String CHANNEL_ICON_KEY = "channelIcon";

    private static final String PROGRAM_DESCRIPTION_KEY = "description";
    private static final String PROGRAM_SUMMARY_KEY = "summary";
    private static final String PROGRAM_TITLE_KEY = "title";
    private static final String PROGRAM_EPISODE_TITLE_KEY = "subtitle";
    private static final String PROGRAM_CONTENT_TYPE_KEY = "contentType";
    private static final String PROGRAM_AGE_RATING_KEY = "ageRating";
    private static final String PROGRAM_START_TIME_KEY = "start";
    private static final String PROGRAM_FINISH_TIME_KEY = "stop";
    private static final String PROGRAM_SEASON_NUMBER_KEY = "seasonNumber";
    private static final String PROGRAM_EPISODE_NUMBER_KEY = "episodeNumber";
    private static final String PROGRAM_IMAGE = "image";

    private static final String EVENT_ID_KEY = "eventId";

    private static final int TWO_HOURS = 2*60*60;

    /**
     * A listener for EpgSync events
     */
    public interface Listener {
        /**
         * Returns the Handler on which to execute the callback.
         *
         * @return Handler, or null.
         */
        Handler getHandler();

        /**
         * Called when the initial sync has completed
         */
        void onInitialSyncCompleted();
    }

    private final Context mContext;
    private final HtspMessage.Dispatcher mDispatcher;
    private boolean mQuickSync = false;

    private final SharedPreferences mSharedPreferences;
    private final ContentResolver mContentResolver;

    private final Handler mHandler;

    private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();

    private boolean mInitialSyncCompleted = false;

    private final SparseArray<Uri> mChannelUriMap;
    private final SparseArray<Uri> mProgramUriMap;

    private final ArrayList<PendingChannelAddUpdate> mPendingChannelOps = new ArrayList<>();
    private final ArrayList<PendingEventAddUpdate> mPendingProgramOps = new ArrayList<>();

    private final Queue<PendingChannelLogoFetch> mPendingChannelLogoFetches = new ConcurrentLinkedQueue<>();
    private final byte[] mImageBytes = new byte[102400];

    private Set<Integer> mSeenChannels = new HashSet<>();
    private Set<Integer> mSeenPrograms = new HashSet<>();

    private final class PendingChannelAddUpdate {
        //TODO: Should probably make these private with accessors if necessary
        public int channelId;
        public int channelNumber;
        public ContentProviderOperation operation;

        public PendingChannelAddUpdate(int channelId, int channelNumber, ContentProviderOperation operation) {
            this.channelId = channelId;
            this.channelNumber = channelNumber;
            this.operation = operation;
        }
    }

    private final class PendingChannelLogoFetch {
        public int channelId;
        public Uri logoUri;

        public PendingChannelLogoFetch(int channelId, Uri logoUri) {
            this.channelId = channelId;
            this.logoUri = logoUri;
        }
    }

    private final class PendingEventAddUpdate {
        public int eventId;
        public ContentProviderOperation operation;

        public PendingEventAddUpdate(int eventId, ContentProviderOperation operation) {
            this.eventId = eventId;
            this.operation = operation;
        }
    }

    public EpgSyncTask(Context context, @NonNull HtspMessage.Dispatcher dispatcher, boolean quickSync) {
        this(context, dispatcher);

        mQuickSync = quickSync;
    }

    public EpgSyncTask(Context context, @NonNull HtspMessage.Dispatcher dispatcher) {
        mContext = context;
        mDispatcher = dispatcher;

        mSharedPreferences = context.getSharedPreferences(
                Constants.PREFERENCE_TVHEADEND, Context.MODE_PRIVATE);

        mContentResolver = context.getContentResolver();

        HandlerThread mHandlerThread = new HandlerThread("EpgSyncService Handler Thread");
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());

        mChannelUriMap = TvContractUtils.buildChannelUriMap(context);
        mProgramUriMap = TvContractUtils.buildProgramUriMap(context);
    }

    public void addEpgSyncListener(Listener listener) {
        if (mListeners.contains(listener)) {
            Log.w(TAG, "Attempted to add duplicate epg sync listener");
            return;
        }
        mListeners.add(listener);
    }

    public void removeEpgSyncListener(Listener listener) {
        if (!mListeners.contains(listener)) {
            Log.w(TAG, "Attempted to remove non existing epg sync listener");
            return;
        }
        mListeners.remove(listener);
    }

    // Authenticator.Listener Methods
    @Override
    public void onAuthenticationStateChange(@NonNull Authenticator.State state) {
        if (state == Authenticator.State.AUTHENTICATED) {
            long epgMaxTime = Long.parseLong(
                    mSharedPreferences.getString(
{'fraction_non_alphanumeric': 0.07656378211197375, 'fraction_numerical': 0.0023985356308779903, 'mean_word_length': 3.6476972719272514, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6645832', 'n_tokens_mistral': 4307, 'n_tokens_neox': 4157, 'n_words': 1165}