/** * Copyright (c) 2015 unfoldingWord * http://creativecommons.org/licenses/MIT/ * See LICENSE file for details. * Contributors: * PJ Fechner <pj@actsmedia.com> */ package services; import android.app.Service; import android.content.Intent; import android.os.HandlerThread; import android.os.IBinder; import android.os.Process; import org.json.JSONException; import org.json.JSONObject; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import de.greenrobot.event.EventBus; import eventbusmodels.DownloadCancelEvent; import eventbusmodels.DownloadResult; import eventbusmodels.DownloadingVersionsEvent; import model.daoModels.Version; import model.parsers.MediaType; import runnables.UpdateProjectsRunnable; import tasks.JsonDownloadTask; import utils.UWPreferenceManager; /** * Created by PJ Fechner * Service for handling updates to the database */ public class UWUpdaterService extends Service { private static final String TAG = "UpdateService"; public static final String PROJECTS_JSON_KEY = "cat"; public static final String MODIFIED_JSON_KEY = "mod"; // int numberPending = 0; // private Map<Long, Map<MediaType, AtomicInteger>> downloadTracker = new HashMap<>(); private static final int unRelatedId = -1; private static final MediaType unrelatedMediaType = MediaType.MEDIA_TYPE_NONE; @Override public IBinder onBind(Intent intent) { return null; } protected UWUpdaterService getThis(){ return this; } @Override public void onCreate() { HandlerThread thread = new HandlerThread("DataDownloadServiceThread", Process.THREAD_PRIORITY_DEFAULT); thread.start(); super.onCreate(); } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); } /** * Adds a runnable to the Update Manager pool * @param runnable runnable to add */ synchronized public void addRunnable(Runnable runnable){ UpdateManager.addRunnable(runnable); addToDownloadTracker(-1, MediaType.MEDIA_TYPE_NONE); // Log.d(TAG, "added runnable"); } synchronized public void addRunnable(Runnable runnable, Version version, MediaType type){ UpdateManager.addRunnable(runnable, version.getId(), type); addToDownloadTracker(version.getId(), type); // Log.d(TAG, "added runnable for version: " + version.getId() + " And mediaType: " + type.toString()); sendEvent(DownloadingVersionsEvent.getEventAdding(version, type)); } private void sendEvent(DownloadingVersionsEvent event){ if(event != null){ EventBus.getDefault().postSticky(event); } } private void addToDownloadTracker(long id, MediaType type){ if(!downloadTracker.containsKey(id)){ downloadTracker.put(id, new HashMap<MediaType, AtomicInteger>()); } if(!downloadTracker.get(id).containsKey(type)){ downloadTracker.get(id).put(type, new AtomicInteger(0)); } int runnableNumber = downloadTracker.get(id).get(type).incrementAndGet(); // Log.d(TAG, "added runnable for version: " + id + " And mediaType: " + type.toString()); } /** * @param id * @param type * @return true if remaining is > 0 */ private boolean decrementDownloadTracker(long id, MediaType type){ if(!downloadTracker.containsKey(id) || !downloadTracker.get(id).containsKey(type)){ // Log.d(TAG, "tracker number doesn't exist for: " + id + " And mediaType: " + type.toString()); return false; } else{ int numberLeft = downloadTracker.get(id).get(type).decrementAndGet(); // Log.d(TAG, "tracker decremented to: " + numberLeft + " for version: " + id + " And mediaType: " + type.toString()); return numberLeft > 0; } } private void resetDownloadTracker(long id, MediaType type){ if(downloadTracker.containsKey(id)){ downloadTracker.remove(id); } } private boolean isActive(){ int total = 0; for (Map.Entry<Long, Map<MediaType, AtomicInteger>> versionEntry : downloadTracker.entrySet()) { for(Map.Entry<MediaType, AtomicInteger> typeEntry : versionEntry.getValue().entrySet()){ total += typeEntry.getValue().get(); } } return total > 0; } /** * Should be called when a runnable finishes running. */ public void runnableFinished(){ if(!decrementDownloadTracker(-1, MediaType.MEDIA_TYPE_NONE) && !isActive()){ downloadFinished(DownloadResult.DOWNLOAD_RESULT_SUCCESS); if(!isActive()){ stopService(); } } // Log.d(TAG, "runnable finished"); } /** * * @param version version of runnable * @param type media type of runnable */ public void runnableFinished(Version version, MediaType type){ if(!decrementDownloadTracker(version.getId(), type)){ downloadFinished(version, type, DownloadResult.DOWNLOAD_RESULT_SUCCESS); if(!isActive()){ stopService(); } } // Log.d(TAG, "runnable finished for version: " + version.getId() + " And mediaType: " + type.toString()); } public void runnableFailed(){ downloadFinished(DownloadResult.DOWNLOAD_RESULT_FAILED); } public void runnableFailed(Version version, MediaType type){ downloadFinished(version, type, DownloadResult.DOWNLOAD_RESULT_FAILED); } public void downloadFinished(DownloadResult success){ UpdateManager.haltQueue(); resetDownloadTracker(-1, MediaType.MEDIA_TYPE_NONE); EventBus.getDefault().post(success); } public void downloadFinished(Version version, MediaType type, DownloadResult success){ UpdateManager.haltQueue(version.getId(), type); resetDownloadTracker(version.getId(), type); sendEvent(DownloadingVersionsEvent.getEventRemoving(version, type)); EventBus.getDefault().post(success); } private void haltDownload(Version version, MediaType type){ UpdateManager.haltQueue(version.getId(), type); downloadFinished(DownloadResult.DOWNLOAD_RESULT_CANCELED); if(!isActive()){ stopService(); } } private void haltDownload(){ UpdateManager.haltQueue(); downloadFinished(DownloadResult.DOWNLOAD_RESULT_CANCELED); if(!isActive()){ stopService(); } } private void haltAllDownloads(){ downloadFinished(DownloadResult.DOWNLOAD_RESULT_CANCELED); stopService(); } protected void stopService(){ EventBus.getDefault().unregister(this); UpdateManager.haltAllThreads(); this.stopSelf(); } @Override public boolean onUnbind(Intent intent) { EventBus.getDefault().unregister(this); return super.onUnbind(intent); } @Override public void onDestroy() { EventBus.getDefault().unregister(this); super.onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { addRunnable(new UpdateRunnable()); EventBus.getDefault().register(this); return START_STICKY; } @Override public void onTaskRemoved(Intent rootIntent) { super.onTaskRemoved(rootIntent); } public void onEventBackgroundThread(DownloadCancelEvent event){ if(event.type == null || event.version == null){ if(event.haltAll){ haltAllDownloads(); } else { haltDownload(); } } else{ haltDownload(event.version, event.type); } } class UpdateRunnable implements Runnable { @Override public void run() { new JsonDownloadTask(new JsonDownloadTask.DownloadTaskListener() { @Override public void downloadFinishedWithJson(String jsonString) { try{ JSONObject json = new JSONObject(jsonString); long lastModified = json.getLong(MODIFIED_JSON_KEY); long currentUpdated = UWPreferenceManager.getLastUpdatedDate(getApplicationContext()); if(lastModified > currentUpdated) { UWPreferenceManager.setLastUpdatedDate(getApplicationContext(), lastModified); addRunnable(new UpdateProjectsRunnable(json.getJSONArray(PROJECTS_JSON_KEY), getThis())); } runnableFinished(); } catch (JSONException e){ e.printStackTrace(); runnableFinished(); } catch (NullPointerException e) { e.printStackTrace(); runnableFinished(); } } }).execute(UWPreferenceManager.getDataDownloadUrl(getApplicationContext())); } } }