Java tutorial
/** * This file is part of MythTV Android Frontend * * MythTV Android Frontend is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MythTV Android Frontend is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MythTV Android Frontend. If not, see <http://www.gnu.org/licenses/>. * * This software can be found at <https://github.com/MythTV-Clients/MythTV-Android-Frontend/> */ /** * */ package org.mythtv.service.dvr.v25; import java.util.ArrayList; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.mythtv.client.ui.preferences.LocationProfile; import org.mythtv.db.AbstractBaseHelper; import org.mythtv.db.dvr.RecordingRuleConstants; import org.mythtv.db.dvr.model.RecRule; import org.mythtv.db.http.model.EtagInfoDelegate; import org.mythtv.service.util.DateUtils; import org.mythtv.service.util.NetworkHelper; import org.mythtv.services.api.ApiVersion; import org.mythtv.services.api.MythServiceApiRuntimeException; import org.mythtv.services.api.connect.MythAccessFactory; import org.mythtv.services.api.v025.MythServicesTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import android.content.ContentProviderOperation; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.database.Cursor; import android.os.RemoteException; import android.util.Log; /** * @author Daniel Frey * */ public class RecordingRuleHelperV25 extends AbstractBaseHelper { private static final String TAG = RecordingRuleHelperV25.class.getSimpleName(); private static final ApiVersion mApiVersion = ApiVersion.v025; private static final String[] recRuleProjection = new String[] { RecordingRuleConstants._ID }; private static MythServicesTemplate mMythServicesTemplate; private static RecordingRuleHelperV25 singleton; /** * Returns the one and only RecordingRuleHelperV25. init() must be called before * any * @return */ public static RecordingRuleHelperV25 getInstance() { if (null == singleton) { synchronized (RecordingRuleHelperV25.class) { if (null == singleton) { singleton = new RecordingRuleHelperV25(); } } } return singleton; } /** * Constructor. No one but getInstance() can do this. */ private RecordingRuleHelperV25() { } public boolean process(final Context context, final LocationProfile locationProfile) { Log.v(TAG, "process : enter"); if (!NetworkHelper.getInstance().isMasterBackendConnected(context, locationProfile)) { Log.w(TAG, "process : Master Backend '" + locationProfile.getHostname() + "' is unreachable"); return false; } mMythServicesTemplate = (MythServicesTemplate) MythAccessFactory.getServiceTemplateApiByVersion(mApiVersion, locationProfile.getUrl()); boolean passed = true; try { downloadRecordinRules(context, locationProfile); } catch (Exception e) { Log.e(TAG, "process : error", e); passed = false; } Log.v(TAG, "process : exit"); return passed; } public boolean add(final Context context, final LocationProfile locationProfile, final RecRule recordingRule) { Log.v(TAG, "add : enter"); if (!NetworkHelper.getInstance().isMasterBackendConnected(context, locationProfile)) { Log.w(TAG, "add : Master Backend '" + locationProfile.getHostname() + "' is unreachable"); return false; } mMythServicesTemplate = (MythServicesTemplate) MythAccessFactory.getServiceTemplateApiByVersion(mApiVersion, locationProfile.getUrl()); boolean passed = true; try { addRecordingRule(context, locationProfile, recordingRule); } catch (Exception e) { Log.e(TAG, "add : error", e); passed = false; } Log.v(TAG, "add : exit"); return passed; } public boolean update(final Context context, final LocationProfile locationProfile, final RecRule recordingRule) { Log.v(TAG, "update : enter"); if (!NetworkHelper.getInstance().isMasterBackendConnected(context, locationProfile)) { Log.w(TAG, "update : Master Backend '" + locationProfile.getHostname() + "' is unreachable"); return false; } mMythServicesTemplate = (MythServicesTemplate) MythAccessFactory.getServiceTemplateApiByVersion(mApiVersion, locationProfile.getUrl()); boolean passed = true; try { passed = updateRecordingRule(context, locationProfile, recordingRule); } catch (Exception e) { Log.e(TAG, "update : error", e); passed = false; } Log.v(TAG, "update : exit"); return passed; } public boolean remove(final Context context, final LocationProfile locationProfile, final RecRule recordingRule) { Log.v(TAG, "remove : enter"); if (!NetworkHelper.getInstance().isMasterBackendConnected(context, locationProfile)) { Log.w(TAG, "remove : Master Backend '" + locationProfile.getHostname() + "' is unreachable"); return false; } mMythServicesTemplate = (MythServicesTemplate) MythAccessFactory.getServiceTemplateApiByVersion(mApiVersion, locationProfile.getUrl()); boolean passed = true; try { passed = removeRecordingRule(context, locationProfile, recordingRule); } catch (Exception e) { Log.e(TAG, "remove : error", e); passed = false; } Log.v(TAG, "remove : exit"); return passed; } // internal helpers private void downloadRecordinRules(final Context context, final LocationProfile locationProfile) throws MythServiceApiRuntimeException, RemoteException, OperationApplicationException { Log.v(TAG, "downloadRecordinRules : enter"); EtagInfoDelegate etag = mEtagDaoHelper.findByEndpointAndDataId(context, locationProfile, "GetRecordScheduleList", ""); Log.d(TAG, "downloadRecordinRules : etag=" + etag.getValue()); ResponseEntity<org.mythtv.services.api.v025.beans.RecRuleList> responseEntity = mMythServicesTemplate .dvrOperations().getRecordScheduleList(null, null, etag); DateTime date = new DateTime(DateTimeZone.UTC); if (responseEntity.getStatusCode().equals(HttpStatus.OK)) { Log.i(TAG, "downloadRecordinRules : GetRecordScheduleList returned 200 OK"); org.mythtv.services.api.v025.beans.RecRuleList recRuleList = responseEntity.getBody(); if (null != recRuleList.getRecRules()) { load(context, locationProfile, recRuleList.getRecRules()); if (null != etag.getValue()) { Log.i(TAG, "downloadRecordinRules : saving etag: " + etag.getValue()); etag.setEndpoint("GetRecordScheduleList"); etag.setDate(date); etag.setMasterHostname(locationProfile.getHostname()); etag.setLastModified(date); mEtagDaoHelper.save(context, locationProfile, etag); } } } if (responseEntity.getStatusCode().equals(HttpStatus.NOT_MODIFIED)) { Log.i(TAG, "downloadRecordinRules : GetRecordScheduleList returned 304 Not Modified"); if (null != etag.getValue()) { Log.i(TAG, "downloadRecordinRules : saving etag: " + etag.getValue()); etag.setLastModified(date); mEtagDaoHelper.save(context, locationProfile, etag); } } Log.v(TAG, "downloadRecordinRules : exit"); } private int load(final Context context, final LocationProfile locationProfile, final org.mythtv.services.api.v025.beans.RecRule[] recordingRules) throws RemoteException, OperationApplicationException { Log.d(TAG, "load : enter"); if (null == context) throw new RuntimeException("RecordingRuleHelperV25 is not initialized"); int processed = -1; int count = 0; ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); for (org.mythtv.services.api.v025.beans.RecRule recordingRule : recordingRules) { processRecordingRule(context, locationProfile, ops, recordingRule); count++; if (count > BATCH_COUNT_LIMIT) { Log.i(TAG, "load : applying batch for '" + count + "' transactions"); processBatch(context, ops, processed, count); count = 0; } } if (!ops.isEmpty()) { Log.v(TAG, "load : applying final batch for '" + count + "' transactions"); processBatch(context, ops, processed, count); } ops = new ArrayList<ContentProviderOperation>(); DateTime lastModified = new DateTime(); lastModified = lastModified.minusHours(1); deleteRecordingRules(context, locationProfile, ops, lastModified); if (!ops.isEmpty()) { Log.v(TAG, "load : applying delete batch for transactions"); processBatch(context, ops, processed, count); } // Log.v( TAG, "load : exit" ); return processed; } private void processRecordingRule(final Context context, final LocationProfile locationProfile, ArrayList<ContentProviderOperation> ops, org.mythtv.services.api.v025.beans.RecRule recRule) { Log.d(TAG, "processRecordingRule : enter"); String recRuleSelection = RecordingRuleConstants.FIELD_REC_RULE_ID + " = ?"; recRuleSelection = appendLocationHostname(context, locationProfile, recRuleSelection, RecordingRuleConstants.TABLE_NAME); ContentValues recRuleValues = convertRecRuleToContentValues(locationProfile, recRule); Cursor recRuleCursor = context.getContentResolver().query(RecordingRuleConstants.CONTENT_URI, recRuleProjection, recRuleSelection, new String[] { String.valueOf(recRule.getId()) }, null); if (recRuleCursor.moveToFirst()) { Long id = recRuleCursor.getLong(recRuleCursor.getColumnIndexOrThrow(RecordingRuleConstants._ID)); Log.v(TAG, "processRecordingRule : updating recRule " + id + ":" + recRule.getId() + ":" + recRule.getTitle()); ops.add(ContentProviderOperation .newUpdate(ContentUris.withAppendedId(RecordingRuleConstants.CONTENT_URI, id)) .withValues(recRuleValues).build()); } else { Log.v(TAG, "processRecordingRule : adding recRule " + recRule.getId() + ":" + recRule.getTitle()); ops.add(ContentProviderOperation.newInsert(RecordingRuleConstants.CONTENT_URI).withValues(recRuleValues) .build()); } recRuleCursor.close(); Log.d(TAG, "processRecordingRule : exit"); } private void deleteRecordingRules(final Context context, final LocationProfile locationProfile, ArrayList<ContentProviderOperation> ops, DateTime lastModified) { Log.d(TAG, "deleteRecordingRules : enter"); // Log.v( TAG, "load : remove deleted recordings" ); String deletedSelection = RecordingRuleConstants.TABLE_NAME + "." + RecordingRuleConstants.FIELD_LAST_MODIFIED_DATE + " < ?"; String[] deletedSelectionArgs = new String[] { String.valueOf(lastModified.getMillis()) }; deletedSelection = appendLocationHostname(context, locationProfile, deletedSelection, RecordingRuleConstants.TABLE_NAME); // Log.v( TAG, "load : deleting recRules" ); ops.add(ContentProviderOperation.newDelete(RecordingRuleConstants.CONTENT_URI) .withSelection(deletedSelection, deletedSelectionArgs).build()); Log.d(TAG, "deleteRecordingRules : exit"); } private int addRecordingRule(final Context context, final LocationProfile locationProfile, final RecRule recordingRule) throws RemoteException, OperationApplicationException { Log.d(TAG, "addRecordingRule : enter"); int ret = -1; org.mythtv.services.api.v025.beans.RecRule versionRecRule = convertRecRuleToRecRuleV25(recordingRule); if (null != versionRecRule) { // update existing rule ResponseEntity<org.mythtv.services.api.Int> add = mMythServicesTemplate.dvrOperations() .addRecordSchedule(versionRecRule.getChanId(), versionRecRule.getStartTime(), versionRecRule.getParentId(), versionRecRule.isInactive(), versionRecRule.getSeason(), versionRecRule.getEpisode(), versionRecRule.getInetref(), versionRecRule.getFindId(), versionRecRule.getType(), versionRecRule.getSearchType(), versionRecRule.getRecPriority(), versionRecRule.getPreferredInput(), versionRecRule.getStartOffset(), versionRecRule.getEndOffset(), versionRecRule.getDupMethod(), versionRecRule.getDupIn(), versionRecRule.getFilter(), versionRecRule.getRecProfile(), versionRecRule.getRecGroup(), versionRecRule.getStorageGroup(), versionRecRule.getPlayGroup(), versionRecRule.isAutoExpire(), versionRecRule.getMaxEpisodes(), versionRecRule.isMaxNewest(), versionRecRule.isAutoCommflag(), versionRecRule.isAutoTranscode(), versionRecRule.isAutoMetaLookup(), versionRecRule.isAutoUserJob1(), versionRecRule.isAutoUserJob2(), versionRecRule.isAutoUserJob3(), versionRecRule.isAutoUserJob4(), versionRecRule.getTranscoder()); if (add.getStatusCode().equals(HttpStatus.OK)) { if (add.getBody().getValue() > 0) { ret = add.getBody().getValue(); downloadRecordinRules(context, locationProfile); } } } Log.d(TAG, "addRecordingRule : exit"); return ret; } private boolean updateRecordingRule(final Context context, final LocationProfile locationProfile, final RecRule recordingRule) throws RemoteException, OperationApplicationException { Log.d(TAG, "updateRecordingRule : enter"); boolean ret = false; org.mythtv.services.api.v025.beans.RecRule versionRecRule = convertRecRuleToRecRuleV25(recordingRule); if (null != versionRecRule) { // remove existing rule boolean removed = removeRecordingRule(context, locationProfile, recordingRule); if (removed) { int added = addRecordingRule(context, locationProfile, recordingRule); if (added > 0) { ret = true; downloadRecordinRules(context, locationProfile); } } } Log.d(TAG, "updateRecordingRule : exit"); return ret; } private boolean removeRecordingRule(final Context context, final LocationProfile locationProfile, final RecRule recordingRule) throws RemoteException, OperationApplicationException { Log.d(TAG, "removeRecordingRule : enter"); boolean ret = false; org.mythtv.services.api.v025.beans.RecRule versionRecRule = convertRecRuleToRecRuleV25(recordingRule); if (null != versionRecRule) { // update existing rule ResponseEntity<org.mythtv.services.api.Bool> remove = mMythServicesTemplate.dvrOperations() .removeRecordSchedule(versionRecRule.getId()); if (remove.getStatusCode().equals(HttpStatus.OK)) { ret = remove.getBody().getValue(); if (ret) { downloadRecordinRules(context, locationProfile); } } } Log.d(TAG, "removeRecordingRule : exit"); return ret; } private ContentValues convertRecRuleToContentValues(final LocationProfile locationProfile, final org.mythtv.services.api.v025.beans.RecRule recRule) { // Log.v( TAG, "convertRecRuleToContentValues : enter" ); DateTime startTimestamp = new DateTime(DateTimeZone.UTC); if (null != recRule.getStartTime()) { startTimestamp = recRule.getStartTime(); } DateTime endTimestamp = new DateTime(DateTimeZone.UTC); if (null != recRule.getEndTime()) { endTimestamp = recRule.getEndTime(); } // Log.v( TAG, "convertRecRuleToContentValues : recRule=" + recRule.toString() ); ContentValues values = new ContentValues(); values.put(RecordingRuleConstants.FIELD_REC_RULE_ID, recRule.getId()); values.put(RecordingRuleConstants.FIELD_PARENT_ID, recRule.getParentId()); values.put(RecordingRuleConstants.FIELD_INACTIVE, recRule.isInactive() ? 1 : 0); values.put(RecordingRuleConstants.FIELD_TITLE, recRule.getTitle()); values.put(RecordingRuleConstants.FIELD_SUB_TITLE, recRule.getSubTitle()); values.put(RecordingRuleConstants.FIELD_DESCRIPTION, recRule.getDescription()); values.put(RecordingRuleConstants.FIELD_SEASON, recRule.getSeason()); values.put(RecordingRuleConstants.FIELD_EPISODE, recRule.getEpisode()); values.put(RecordingRuleConstants.FIELD_CATEGORY, recRule.getCategory()); values.put(RecordingRuleConstants.FIELD_START_TIME, startTimestamp.getMillis()); values.put(RecordingRuleConstants.FIELD_END_TIME, endTimestamp.getMillis()); values.put(RecordingRuleConstants.FIELD_SERIES_ID, recRule.getSeriesId()); values.put(RecordingRuleConstants.FIELD_PROGRAM_ID, recRule.getProgramId()); values.put(RecordingRuleConstants.FIELD_INETREF, recRule.getInetref()); values.put(RecordingRuleConstants.FIELD_CHAN_ID, recRule.getChanId()); values.put(RecordingRuleConstants.FIELD_CALLSIGN, recRule.getCallSign()); values.put(RecordingRuleConstants.FIELD_DAY, recRule.getDay()); values.put(RecordingRuleConstants.FIELD_TIME, null != recRule.getTime() ? recRule.getTime().toString() : ""); values.put(RecordingRuleConstants.FIELD_FIND_ID, recRule.getFindId()); values.put(RecordingRuleConstants.FIELD_TYPE, recRule.getType()); values.put(RecordingRuleConstants.FIELD_SEARCH_TYPE, recRule.getSearchType()); values.put(RecordingRuleConstants.FIELD_REC_PRIORITY, recRule.getRecPriority()); values.put(RecordingRuleConstants.FIELD_PREFERRED_INPUT, recRule.getPreferredInput()); values.put(RecordingRuleConstants.FIELD_START_OFFSET, recRule.getStartOffset()); values.put(RecordingRuleConstants.FIELD_END_OFFSET, recRule.getEndOffset()); values.put(RecordingRuleConstants.FIELD_DUP_METHOD, recRule.getDupMethod()); values.put(RecordingRuleConstants.FIELD_DUP_IN, recRule.getDupIn()); values.put(RecordingRuleConstants.FIELD_FILTER, recRule.getFilter()); values.put(RecordingRuleConstants.FIELD_REC_PROFILE, recRule.getRecProfile()); values.put(RecordingRuleConstants.FIELD_REC_GROUP, recRule.getRecGroup()); values.put(RecordingRuleConstants.FIELD_STORAGE_GROUP, recRule.getStorageGroup()); values.put(RecordingRuleConstants.FIELD_PLAY_GROUP, recRule.getPlayGroup()); values.put(RecordingRuleConstants.FIELD_AUTO_EXPIRE, recRule.isAutoExpire() ? 1 : 0); values.put(RecordingRuleConstants.FIELD_MAX_EPISODES, recRule.getMaxEpisodes()); values.put(RecordingRuleConstants.FIELD_MAX_NEWEST, recRule.isMaxNewest() ? 1 : 0); values.put(RecordingRuleConstants.FIELD_AUTO_COMMFLAG, recRule.isAutoCommflag() ? 1 : 0); values.put(RecordingRuleConstants.FIELD_AUTO_TRANSCODE, recRule.isAutoTranscode() ? 1 : 0); values.put(RecordingRuleConstants.FIELD_AUTO_METADATA, recRule.isAutoMetaLookup() ? 1 : 0); values.put(RecordingRuleConstants.FIELD_AUTO_USER_JOB_1, recRule.isAutoUserJob1() ? 1 : 0); values.put(RecordingRuleConstants.FIELD_AUTO_USER_JOB_2, recRule.isAutoUserJob2() ? 1 : 0); values.put(RecordingRuleConstants.FIELD_AUTO_USER_JOB_3, recRule.isAutoUserJob3() ? 1 : 0); values.put(RecordingRuleConstants.FIELD_AUTO_USER_JOB_4, recRule.isAutoUserJob4() ? 1 : 0); values.put(RecordingRuleConstants.FIELD_TRANSCODER, recRule.getTranscoder()); values.put(RecordingRuleConstants.FIELD_NEXT_RECORDING, null != recRule.getNextRecording() ? recRule.getNextRecording().getMillis() : -1); values.put(RecordingRuleConstants.FIELD_LAST_RECORDED, null != recRule.getLastRecorded() ? recRule.getLastRecorded().getMillis() : -1); values.put(RecordingRuleConstants.FIELD_LAST_DELETED, null != recRule.getLastDeleted() ? recRule.getLastDeleted().getMillis() : -1); values.put(RecordingRuleConstants.FIELD_AVERAGE_DELAY, recRule.getAverageDelay()); values.put(RecordingRuleConstants.FIELD_MASTER_HOSTNAME, locationProfile.getHostname()); values.put(RecordingRuleConstants.FIELD_LAST_MODIFIED_DATE, new DateTime().getMillis()); // Log.v( TAG, "convertRecRuleToContentValues : values=" + values.toString() ); // Log.v( TAG, "convertRecRuleToContentValues : exit" ); return values; } private org.mythtv.services.api.v025.beans.RecRule convertRecRuleToRecRuleV25(final RecRule recordingRule) { org.mythtv.services.api.v025.beans.RecRule versionRecRule = new org.mythtv.services.api.v025.beans.RecRule(); versionRecRule.setId(recordingRule.getId()); versionRecRule.setParentId(recordingRule.getParentId()); versionRecRule.setInactive(recordingRule.isInactive()); versionRecRule.setTitle(recordingRule.getTitle()); versionRecRule.setSubTitle(recordingRule.getSubTitle()); versionRecRule.setDescription(recordingRule.getDescription()); versionRecRule.setSeason(recordingRule.getSeason()); versionRecRule.setEpisode(recordingRule.getEpisode()); versionRecRule.setCategory(recordingRule.getCategory()); versionRecRule.setStartTime(recordingRule.getStartTime()); versionRecRule.setEndTime(recordingRule.getEndTime()); versionRecRule.setSeriesId(recordingRule.getSeriesId()); versionRecRule.setProgramId(recordingRule.getProgramId()); versionRecRule.setInetref(recordingRule.getInetref()); versionRecRule.setChanId(recordingRule.getChanId()); versionRecRule.setCallSign(recordingRule.getCallSign()); versionRecRule.setDay(recordingRule.getDay()); versionRecRule.setTime(null != recordingRule.getTime() && !"".equals(recordingRule.getTime()) ? DateUtils.dateTimeFormatter.parseLocalTime(recordingRule.getTime()) : null); versionRecRule.setType(recordingRule.getType()); versionRecRule.setSearchType(recordingRule.getSearchType()); versionRecRule.setRecPriority(recordingRule.getRecPriority()); versionRecRule.setPreferredInput(recordingRule.getPreferredInput()); versionRecRule.setStartOffset(recordingRule.getStartOffset()); versionRecRule.setEndOffset(recordingRule.getEndOffset()); versionRecRule.setDupMethod(recordingRule.getDupMethod()); versionRecRule.setDupIn(recordingRule.getDupIn()); versionRecRule.setFilter(recordingRule.getFilter()); versionRecRule.setRecProfile(recordingRule.getRecProfile()); versionRecRule.setRecGroup(recordingRule.getRecGroup()); versionRecRule.setStorageGroup(recordingRule.getStorageGroup()); versionRecRule.setPlayGroup(recordingRule.getPlayGroup()); versionRecRule.setAutoExpire(recordingRule.isAutoExpire()); versionRecRule.setMaxEpisodes(recordingRule.getMaxEpisodes()); versionRecRule.setMaxNewest(recordingRule.isMaxNewest()); versionRecRule.setAutoCommflag(recordingRule.isAutoCommflag()); versionRecRule.setAutoTranscode(recordingRule.isAutoTranscode()); versionRecRule.setAutoMetaLookup(recordingRule.isAutoMetaLookup()); versionRecRule.setAutoUserJob1(recordingRule.isAutoUserJob1()); versionRecRule.setAutoUserJob2(recordingRule.isAutoUserJob2()); versionRecRule.setAutoUserJob3(recordingRule.isAutoUserJob3()); versionRecRule.setAutoUserJob4(recordingRule.isAutoUserJob4()); versionRecRule.setTranscoder(recordingRule.getTranscoder()); versionRecRule.setNextRecording(recordingRule.getNextRecording()); versionRecRule.setLastRecorded(recordingRule.getLastRecorded()); versionRecRule.setLastDeleted(recordingRule.getLastDeleted()); versionRecRule.setAverageDelay(recordingRule.getAverageDelay()); return versionRecRule; } }