com.xixicm.de.data.storage.SentenceDataRepository.java Source code

Java tutorial

Introduction

Here is the source code for com.xixicm.de.data.storage.SentenceDataRepository.java

Source

/*
 * Copyright (C) 2016 mc
 *
 * 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 com.xixicm.de.data.storage;

import android.net.Uri;
import android.os.SystemClock;
import android.support.annotation.VisibleForTesting;

import com.android.volley.Cache;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.cache.DiskBasedCache;
import com.android.volley.error.VolleyError;
import com.android.volley.request.DownloadRequest;
import com.android.volley.request.StringRequest;
import com.android.volley.toolbox.VolleyTickle;
import com.xixicm.de.data.entity.SentenceEntity;
import com.xixicm.de.data.net.DEVolley;
import com.xixicm.de.data.storage.dao.DaoManager;
import com.xixicm.de.data.storage.dao.SentenceEntityDao;
import com.xixicm.de.domain.Constants;
import com.xixicm.ca.domain.util.LogUtils;
import com.xixicm.ca.domain.util.NetworkUtils;
import com.xixicm.de.domain.model.Sentence;
import com.xixicm.de.domain.model.event.FetchingAudioEvent;
import com.xixicm.de.domain.model.event.FetchingEvent;
import com.xixicm.de.domain.model.event.RefreshWidgetEvent;
import com.xixicm.de.domain.model.event.ScheduleFetchEvent;
import com.xixicm.de.domain.model.event.SentenceChangedEvent;
import com.xixicm.de.domain.repository.SentenceRepository;

import org.greenrobot.eventbus.EventBus;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.util.Date;
import java.util.List;

/**
 * @author mc
 */
public class SentenceDataRepository implements SentenceRepository {
    private static String URL_ICIBA = "http://open.iciba.com/dsapi/";
    private static SentenceDataRepository sInstance;
    private Object mFetchingLock = new Object();
    private boolean mIsFetchingSentence;
    private boolean mIsFetchingAudio;

    public static synchronized SentenceDataRepository getInstance() {
        if (sInstance == null) {
            sInstance = new SentenceDataRepository();
        }
        return sInstance;
    }

    SentenceDataRepository() {
    }

    /**
     * Get local latest sentence.
     * Note: Query in current thread
     *
     * @return latest sentence
     */
    @Override
    public Sentence getLatestSentence() {
        return DaoManager.getInstance().getSentenceEntityDao().queryBuilder()
                .orderDesc(SentenceEntityDao.Properties.Dateline).limit(1).unique();
    }

    /**
     * Get local sentence with specific id.
     * Note: Query in current thread
     *
     * @return latest sentence
     */
    @Override
    public Sentence getSentence(long sentenceId) {
        return DaoManager.getInstance().getSentenceEntityDao().load(sentenceId);
    }

    /**
     * Get local sentences.
     * Note: Query in current thread
     *
     * @param favorite if true return all favorite sentence, if false return all not favorite sentences
     * @return
     */
    @Override
    public List<? extends Sentence> getSentences(boolean favorite) {
        return DaoManager.getInstance().getSentenceEntityDao().queryBuilder()
                .where(SentenceEntityDao.Properties.IsStar.eq(favorite ? Boolean.TRUE : Boolean.FALSE))
                .orderDesc(SentenceEntityDao.Properties.Dateline).list();
    }

    /**
     * Get all local sentences.
     *
     * @return
     */
    @Override
    public List<? extends Sentence> getAllSentences() {
        return DaoManager.getInstance().getSentenceEntityDao().queryBuilder()
                .orderDesc(SentenceEntityDao.Properties.Dateline).list();
    }

    @Override
    public void fetchSentenceAudio(final String audioUrl, final long playToken) {
        synchronized (mFetchingLock) {
            if (isFetchingAudio()) {
                updateFetchingAudioStatusAndNotify(true, FetchingEvent.RESULT.REFRESHING, audioUrl, playToken,
                        false);
                return;
            }
        }
        if (hasDownloadedAudio(audioUrl)) {
            updateFetchingAudioStatusAndNotify(false, FetchingEvent.RESULT.SUCCESS, audioUrl, playToken, true);
            return;
        }
        // mean starting
        updateFetchingAudioStatusAndNotify(true, FetchingEvent.RESULT.NONE, audioUrl, playToken, true);
        LogUtils.d(Constants.TAG, "try to fetchSentenceAudio:" + audioUrl);
        // start downloading, may get from the cache
        // Note, can use sync download here. but keep to use callback.
        startDownloadAudio(audioUrl, playToken, new DownloadAudioCallback());
    }

    public void startDownloadAudio(final String audioUrl, final long playToken,
            final DownloadAudioCallback callback) {
        DownloadRequest request = new DownloadRequest(audioUrl,
                DEVolley.getInstance().getTempAudioDownloadFile().getPath(), new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        callback.onSuccess(audioUrl, playToken);
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        callback.onFail(audioUrl, playToken);
                    }
                });
        request.setRetryPolicy(DEVolley.getInstance().getDefaultRetryPolicy(2));
        request.setTag("fetchSentenceAudio");
        DEVolley.getInstance().getRequestQueue().add(request);
    }

    public class DownloadAudioCallback {
        public void onSuccess(final String audioUrl, final long playToken) {
            updateFetchingAudioStatusAndNotify(false, FetchingEvent.RESULT.SUCCESS, audioUrl, playToken, true);
        }

        public void onFail(final String audioUrl, final long playToken) {
            updateFetchingAudioStatusAndNotify(false, FetchingEvent.RESULT.FAIL, audioUrl, playToken, true);
        }
    }

    public boolean hasDownloadedAudio(String audioUrl) {
        Cache cache = DEVolley.getInstance().getCache();
        if (cache != null) {
            // ignore expired setting
            return cache.get(audioUrl) != null;
        }
        return false;
    }

    private void updateFetchingAudioStatusAndNotify(boolean isFetching, FetchingEvent.RESULT result,
            String audioUrl, long token, boolean stick) {
        synchronized (mFetchingLock) {
            mIsFetchingAudio = isFetching;
        }
        if (stick) {
            getEventBus().postSticky(new FetchingAudioEvent(isFetching, result, audioUrl, token));
        } else {
            getEventBus().post(new FetchingAudioEvent(isFetching, result, audioUrl, token));
        }
    }

    @Override
    public Uri getAudioFile(String audioUrl) {
        DiskBasedCache cache = DEVolley.getInstance().getCache();
        if (audioUrl != null && cache != null) {
            File audioFile = cache.getFileForKey(audioUrl);
            if (audioFile != null && audioFile.exists()) {
                return Uri.fromFile(audioFile);
            }
        }
        return null;
    }

    /**
     * Get todays's sentence from local db first. If can not get, try to fetch it from network.
     * Will post fetch status event {@link FetchingEvent} during the fetching process. If got a new sentence and saved to db,
     * will post {@link SentenceChangedEvent} and  {@link RefreshWidgetEvent}
     *
     * @param manual
     */
    @Override
    public void fetchTodaysSentence(final boolean manual) {
        synchronized (mFetchingLock) {
            if (isFetchingSentence()) {
                LogUtils.d(Constants.TAG,
                        "\nfetchTodaysSentence manual: " + manual + " IsFetchingSentence:" + mIsFetchingSentence);
                updateFetchingSentenceStatusAndNotify(true, manual, FetchingEvent.RESULT.REFRESHING);
                return;
            }
        }
        LogUtils.d(Constants.TAG,
                "\ntry to fetchTodaysSentence, and schedule next auto fetch alarm. manual: " + manual);
        updateFetchingSentenceStatusAndNotify(true, manual, FetchingEvent.RESULT.NONE);
        // set/reset auto fetch alarm
        setFetchAlarm(ScheduleFetchEvent.TYPE.NORMAL);

        String today = Constants.SHORT_DATEFORMAT.format(new Date());
        Sentence sentence = DaoManager.getInstance().getSentenceEntityDao().queryBuilder()
                .where(SentenceEntityDao.Properties.Dateline.eq(today)).unique();
        if (sentence != null) {
            // already existed
            LogUtils.d(Constants.TAG, "already has the sentence of " + today);
            updateFetchingSentenceStatusAndNotify(false, manual,
                    manual ? FetchingEvent.RESULT.EXISTED : FetchingEvent.RESULT.NONE);
            return;
        }
        if (!NetworkUtils.isNetworkAvailable()) {
            // no network, abort fetching
            updateFetchingSentenceStatusAndNotify(false, manual,
                    manual ? FetchingEvent.RESULT.NO_NETWORK : FetchingEvent.RESULT.NONE);
            LogUtils.d(Constants.TAG, "no network, abort fetching");
            return;
        }
        // start network fetching
        updateFetchingSentenceStatusAndNotify(true, !manual, FetchingEvent.RESULT.NONE);
        // not UI thread
        NetworkResponse response = getTodaysSentenceResponse();
        // background
        FetchingEvent.RESULT fetchResult = manual ? FetchingEvent.RESULT.FAIL : FetchingEvent.RESULT.NONE;
        if (response != null && response.statusCode == 200) {
            fetchResult = manual ? FetchingEvent.RESULT.SUCCESS : FetchingEvent.RESULT.NONE;
            String data = VolleyTickle.parseResponse(response);
            try {
                JSONObject result = new JSONObject(data);
                SentenceEntity newSentence = new SentenceEntity();
                newSentence.setDateline(result.optString(SentenceEntityDao.Properties.Dateline.name));
                if (today.equals(newSentence.getDateline()) || DaoManager.getInstance().getSentenceEntityDao()
                        .queryBuilder().where(SentenceEntityDao.Properties.Dateline.eq(newSentence.getDateline()))
                        .build().unique() == null) {
                    // insert today's or first got sentence
                    newSentence.setSid(result.optString(SentenceEntityDao.Properties.Sid.name));
                    newSentence.setContent(result.optString(SentenceEntityDao.Properties.Content.name));
                    newSentence.setAllContent(data);
                    newSentence.setIsStar(false);
                    saveSentence(newSentence);
                    LogUtils.d(Constants.TAG, "inserted one new sentence");
                    getEventBus().post(new SentenceChangedEvent());
                    LogUtils.d(Constants.TAG, "try to updateDEAppWidget");
                    getEventBus().post(new RefreshWidgetEvent());
                    fetchResult = manual ? FetchingEvent.RESULT.GOT_NEW : FetchingEvent.RESULT.NONE;
                }
            } catch (JSONException e) {
            }
        } else {
            // if fetching failed but has network
            if (NetworkUtils.isNetworkAvailable()) {
                // set/reset retry fetch alarm
                setFetchAlarm(ScheduleFetchEvent.TYPE.RETRY);
            }
        }
        updateFetchingSentenceStatusAndNotify(false, true, fetchResult);
    }

    public NetworkResponse getTodaysSentenceResponse() {
        StringRequest request = new StringRequest(Request.Method.GET, URL_ICIBA, null, null);
        request.setRetryPolicy(DEVolley.getInstance().getDefaultRetryPolicy(2));
        request.setTag("fetchTodaysSentence");
        DEVolley.getInstance().getRequestTickle().add(request);
        // sync get
        NetworkResponse response = DEVolley.getInstance().getRequestTickle().start();
        return response;
    }

    @Override
    public void updateSentence(Sentence sentence) {
        if (sentence == null) {
            return;
        }
        DaoManager.getInstance().getDaoSession().update(sentence);
    }

    @Override
    public void saveSentence(Sentence sentence) {
        if (sentence == null) {
            return;
        }
        DaoManager.getInstance().getDaoSession().insertOrReplace(sentence);
    }

    @Override
    public void deleteSentence(long sentenceId) {
        DaoManager.getInstance().getSentenceEntityDao().deleteByKey(sentenceId);
    }

    @Override
    public void deleteAllSentences() {
        DaoManager.getInstance().getSentenceEntityDao().deleteAll();
    }

    private void updateFetchingSentenceStatusAndNotify(boolean isFetching, boolean notify,
            FetchingEvent.RESULT result) {
        synchronized (mFetchingLock) {
            mIsFetchingSentence = isFetching;
        }
        if (notify) {
            FetchingEvent event = new FetchingEvent(isFetching, result);
            LogUtils.d(Constants.TAG, "postSticky:  FetchingEvent:" + event);
            getEventBus().postSticky(event);
        }
    }

    private void setFetchAlarm(ScheduleFetchEvent.TYPE type) {
        LogUtils.d(Constants.TAG, "setFetchAlarm: " + type);
        ScheduleFetchEvent scheduleFetchEvent = new ScheduleFetchEvent(SystemClock.elapsedRealtime(), type);
        getEventBus().post(scheduleFetchEvent);
    }

    @VisibleForTesting
    public EventBus getEventBus() {
        return EventBus.getDefault();
    }

    @VisibleForTesting
    public boolean isFetchingSentence() {
        synchronized (mFetchingLock) {
            return mIsFetchingSentence;
        }
    }

    @VisibleForTesting
    public boolean isFetchingAudio() {
        synchronized (mFetchingLock) {
            return mIsFetchingAudio;
        }
    }

    @VisibleForTesting
    void removeDownloadedAudio(String audioUrl) {
        Cache cache = DEVolley.getInstance().getCache();
        if (cache != null) {
            // ignore expired setting
            cache.remove(audioUrl);
        }
    }
}