package com.androideasyapps.phoenix.shared; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import com.androideasyapps.phoenix.dao.H2PersistenceManager; import com.androideasyapps.phoenix.dao.MediaFile; import com.androideasyapps.phoenix.dao.Server; import com.androideasyapps.phoenix.services.phoenix.PhoenixService; import com.androideasyapps.phoenix.services.sagetv.SageTVRequestInterceptor; import com.androideasyapps.phoenix.services.sagetv.SageTVService; import com.androideasyapps.phoenix.shared.prefs.PhoenixPreferences; import com.androideasyapps.phoenix.shared.prefs.PreferenceManager; import com.androideasyapps.phoenix.util.OnCompleteFuture; import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.assist.ImageScaleType; import com.nostra13.universalimageloader.core.assist.QueueProcessingType; import com.squareup.otto.ThreadEnforcer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileFilter; import java.util.Calendar; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import retrofit.RestAdapter; import rx.Observable; import rx.Subscriber; import rx.android.schedulers.AndroidSchedulers; import rx.schedulers.Schedulers; /** * Created by seans on 21/12/14. */ public class AppInstance { private static final Logger log = LoggerFactory.getLogger(AppInstance.class); public static AppInstance getInstance(Context ctx) { if (instance == null) { instance = new AppInstance(ctx); } return instance; } private static AppInstance instance; private SageTVService sageTVService; private PhoenixService phoenixService; private AndroidBus uiBus = new AndroidBus(ThreadEnforcer.ANY); private Server server; private MediaFile selectedMediaFile; private PhoenixPreferences preferences; final ExecutorService executor; final Future<H2PersistenceManager> futureDAOManager; final OnCompleteFuture<Observable<MediaFile>> syncFuture = new OnCompleteFuture<>(); private SageTVRequestInterceptor requestInterceptor; private final Context context; private Map<String, MediaSource> mediaSources = new LinkedHashMap<>(); public SageTVRequestInterceptor getRequestInterceptor() { return requestInterceptor; } public AppInstance(Context ctx) { this.context = ctx; preferences = PreferenceManager.getPreferences(PhoenixPreferences.class, android.preference.PreferenceManager.getDefaultSharedPreferences(ctx)); executor = Executors.newCachedThreadPool(); futureDAOManager = executor.submit(new Callable<H2PersistenceManager>() { @Override public H2PersistenceManager call() throws Exception { final String dbname = "sagetv.db"; File cache = new File(context.getFilesDir(), dbname); log.info("Starting Database: {}", cache); //if (cache.exists()) cache.delete(); String jdbcUrl = "jdbc:h2:" + cache.getAbsolutePath() + ";TRACE_LEVEL_FILE=0;CREATE=true"; H2PersistenceManager daoManager = new H2PersistenceManager(jdbcUrl); try { daoManager.createTables(); } catch (Exception e) { log.error("failed -- will need to rebuild :(", e); log.warn("Deleting: {}", cache); cache.delete(); cache.getParentFile().listFiles(new FileFilter() { @Override public boolean accept(File pathname) { if (pathname.getName().startsWith(dbname)) { log.warn("Deleting: {}", pathname); pathname.delete(); } return false; } }); daoManager = new H2PersistenceManager(jdbcUrl); try { daoManager.createTables(); } catch (Throwable t) { log.error("Giving Up... We can't re-create database :(", t); } } log.info("Database Online: " + cache); return daoManager; } }); MediaSource recentMoviesUnwatched = new MediaSource("Recent Movies (Unwatched)", "watched != true and mediatype='movie' and hasmetadata = true order by mediafileid desc limit " + preferences().recent_limit(30)); // MediaSource recentTVUnwatched = new MediaSource("Recent TV (Unwatched)", "watched != true and mediatype='tv' order by originalairdate desc"); MediaSource recentTVUnwatchedGrouped = new MediaSource("Recent TV (Unwatched/Grouped)", "select distinct title, mediatype, max(originalairdate) as originalairdate, max(season), count(mediatype) as parts from mediafile where watched != true and mediatype='tv' group by title order by originalairdate desc", new SubGroupMediaSource("%s", "watched != true and mediatype='tv' and title=? order by originalairdate desc", new SubGroupMediaSource.SubGroupResolver() { @Override public Object getValue(MediaFile item) { return item.getTitle(); } }, null ) ); MediaSource recentNoMetadata = new MediaSource("Recent No Metadata", "hasmetadata != true and watched != true order by mediafileid desc limit " + preferences().recent_limit(30)); mediaSources.put(recentMoviesUnwatched.title, recentMoviesUnwatched); // mediaSources.put(recentTVUnwatched.title, recentTVUnwatched); mediaSources.put(recentTVUnwatchedGrouped.title, recentTVUnwatchedGrouped); mediaSources.put(recentNoMetadata.title, recentNoMetadata); initImageLoader(); // start the scheduler for syncing scheduleSyncUpdate(context, false); scheduleRecommendationUpdate(context, false); } public PhoenixService getPhoenixService() { if (phoenixService==null) { log.error("PhoenixService is not Yet Configured", new Throwable("PhoenixService is not Yet Configured")); } return phoenixService; } public SageTVService getSageTVService() { if (sageTVService==null) { log.error("SageTVService is not Yet Configured", new Throwable("SageTVService is not Yet Configured")); } return sageTVService; } public Future<H2PersistenceManager> getDAOManager() { return futureDAOManager; } public AndroidBus bus() { return uiBus; } protected void initializeServices(Server server) { requestInterceptor =new SageTVRequestInterceptor(new SageTVRequestInterceptor.Credentials(server.getUsername(), "frey"), new AndroidBase64Encoder()); RestAdapter restAdapter = getRestAdapter(server); sageTVService = restAdapter.create(SageTVService.class); phoenixService = restAdapter.create(PhoenixService.class); } /** * Used to get the Persistence Manager as an observable, so that you can be notified when it's online. * * @return */ public Observable<H2PersistenceManager> getDAOObservalbe() { return Observable.create(new Observable.OnSubscribe<H2PersistenceManager>() { @Override public void call(Subscriber<? super H2PersistenceManager> subscriber) { subscriber.onStart(); try { subscriber.onNext(getDAOManager().get()); subscriber.onCompleted(); } catch (Throwable e) { log.error("get Observable for Persistence Manager failed :(", e); subscriber.onError(e); } } }); } Server getFirstConfiguredServer() throws Exception { // find the first server, may be null. Server server = getDAOManager().get().getServerDAO().queryFirst("isdefault = true"); if (server == null) { server = getDAOManager().get().getServerDAO().queryFirst("serverid is not null"); } return server; } public Observable<Server> getServerObservable() { return Observable.create(new Observable.OnSubscribe<Server>() { @Override public void call(Subscriber<? super Server> subscriber) { subscriber.onStart(); try { // find the first server, may be null. Server server = getFirstConfiguredServer(); subscriber.onNext(server); subscriber.onCompleted(); } catch (Exception e) { subscriber.onError(e); } } }); } /** * Gets the curent Server... This could be null if there isn't a configured server. * * @return */ public Server getServer() { if (server == null) { log.error("You current server is NOT current set. This is a programming error.", new Throwable("SHOULD CONFIGURE SERVER FIRST!!!")); try { server = getFirstConfiguredServer(); } catch (Exception e) { log.error("Failed to configure server", e); } } if (server==null) { throw new RuntimeException("SERVER is not configured and yet, we are trying to access it."); } return server; } RestAdapter getRestAdapter(Server server) { RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint("http://" + server.getHost() + ":" + server.getPort() + "/sagex") .setLogLevel(RestAdapter.LogLevel.NONE) .setRequestInterceptor(new SageTVRequestInterceptor(new SageTVRequestInterceptor.Credentials(server.getUsername(), server.getPassword()), new AndroidBase64Encoder())) .build(); return restAdapter; } public SageTVService getSageTVService(Server server) { RestAdapter restAdapter = getRestAdapter(server); return restAdapter.create(SageTVService.class); } public void setServer(Server server) { this.server = server; initializeServices(server); } public MediaFile getSelectedMediaFile() { return selectedMediaFile; } public void setSelectedMediaFile(MediaFile selectedMediaFile) { this.selectedMediaFile = selectedMediaFile; } public void initImageLoader() { DisplayImageOptions options = new DisplayImageOptions.Builder() //.showImageOnLoading(R.drawable.ic_stub) // resource or drawable //.showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable //.showImageOnFail(R.drawable.ic_error) // resource or drawable //.resetViewBeforeLoading(false) // default //.delayBeforeLoading(1000) .cacheInMemory(true) // default .cacheOnDisk(true) // default .bitmapConfig(Bitmap.Config.RGB_565) .imageScaleType(ImageScaleType.EXACTLY) //.displayer(new FadeInBitmapDisplayer(300,true,true,true)) .build(); ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context) .threadPriority(Thread.NORM_PRIORITY - 2) .denyCacheImageMultipleSizesInMemory() .diskCacheFileNameGenerator(new Md5FileNameGenerator()) .diskCacheSize(550 * 1024 * 1024) // 550 Mb .memoryCacheSize(2 * 1024 * 1024) .tasksProcessingOrder(QueueProcessingType.LIFO) //.writeDebugLogs() // Remove for release app .defaultDisplayImageOptions(options) .build(); // Initialize ImageLoader with configuration. if (ImageLoader.getInstance().isInited()) { ImageLoader.getInstance().destroy(); } ImageLoader.getInstance().init(config); } public Map<String,MediaSource> getMediaSources() { return mediaSources; } public Observable<Collection<MediaFile>> getMediaItems(final Future<H2PersistenceManager> dao, final String query) { return Observable.create(new Observable.OnSubscribe<Collection<MediaFile>>() { @Override public void call(Subscriber<? super Collection<MediaFile>> subscriber) { subscriber.onStart(); try { subscriber.onNext(dao.get().getMediaFileDAO().query(query)); } catch (Exception e) { subscriber.onError(e); } subscriber.onCompleted(); } }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); } public Observable<Collection<MediaFile>> getMediaItems(final Future<H2PersistenceManager> dao, final MediaSource source, final MediaFile sourceFile) { return Observable.create(new Observable.OnSubscribe<Collection<MediaFile>>() { @Override public void call(Subscriber<? super Collection<MediaFile>> subscriber) { subscriber.onStart(); try { if (source instanceof SubGroupMediaSource) { subscriber.onNext(dao.get().getMediaFileDAO().query(source.query, new Object[]{((SubGroupMediaSource)source).resolver.getValue(sourceFile)})); } else { subscriber.onNext(dao.get().getMediaFileDAO().query(source.query)); } } catch (Exception e) { subscriber.onError(e); } subscriber.onCompleted(); } }); } public PhoenixPreferences preferences() { return preferences; } public boolean isReady() { try { // getServer() will thrown an exception if we can't get the server. Server server = getServer(); return true; } catch (Throwable t) { return false; } } public void scheduleSyncUpdate(Context context, boolean update) { Calendar now = Calendar.getInstance(); Calendar later = Calendar.getInstance(); later.add(Calendar.DAY_OF_YEAR, 1); later.set(Calendar.HOUR_OF_DAY, 0); later.set(Calendar.MINUTE, 0); later.set(Calendar.SECOND, 0); later.set(Calendar.MILLISECOND, 0); log.info("SageTV Scheduling Sync update to happen every day starting at " + later.getTime()); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent syncIntent = new Intent(context, SageTVSyncService.class); if (!update && checkAlarmIfExists(alarmManager, context, syncIntent)) { log.info("Alarm already configured for {}", syncIntent); return; } cancelAlarmIfExists(alarmManager, context, syncIntent); PendingIntent alarmIntent = PendingIntent.getService(context, 0, syncIntent, 0); alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, later.getTimeInMillis() - now.getTimeInMillis(), AlarmManager.INTERVAL_DAY, alarmIntent); } public void scheduleRecommendationUpdate(Context context, boolean update) { log.info("SageTV Scheduling recommendations update"); try { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent recommendationIntent = new Intent(context, Class.forName("com.androideasyapps.phoenix.recommend.UpdateRecommendationsService")); if (!update && checkAlarmIfExists(alarmManager, context, recommendationIntent)) { log.info("Alarm already configured for {}", recommendationIntent); return; } cancelAlarmIfExists(alarmManager, context, recommendationIntent); PendingIntent alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0); alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 5000, AlarmManager.INTERVAL_HALF_HOUR, alarmIntent); } catch (Throwable t) { log.error("Failed to install recommendation alarm", t); } } void cancelAlarmIfExists(AlarmManager manager, Context mContext, Intent intent) { try { PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); if (pendingIntent != null) { log.info("Cancelling Alarm for {}", intent); manager.cancel(pendingIntent); } else { log.info("No Pending Alarm for {}", intent); } } catch (Exception e) { log.warn("Error during cancelling Alarm for {}", intent); } } boolean checkAlarmIfExists(AlarmManager manager, Context mContext, Intent intent) { try { PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); return pendingIntent != null; } catch (Exception e) { log.warn("Error during checking Alarm for {}", intent); } return false; } }