/**
* Copyright 2014 Comcast Cable Communications Management, LLC
*
* This file is part of CATS.
*
* CATS 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.
*
* CATS 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 CATS. If not, see <http://www.gnu.org/licenses/>.
*/
package com.comcast.cats.service.impl;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.comcast.cats.RemoteCommand;
import com.comcast.cats.RemoteLayout;
import com.comcast.cats.info.RemoteCommandSequence;
import com.comcast.cats.service.IRManager;
import com.comcast.cats.service.IRServiceConstants;
import com.comcast.cats.service.KeyManager;
@Stateless
@TransactionAttribute( TransactionAttributeType.NOT_SUPPORTED )
public class LegacyIRServiceHandler implements LegacyIRServiceFacade
{
/**
* The regex used to extract the ir port #.
*/
private static final Pattern IR_PORT_PATTERN = Pattern.compile( ".*port=([1-6]+).*" );
private static final Logger logger = LoggerFactory.getLogger( IRServiceWSImpl.class );
/**
* The regular expression that should be used to validate a direct tune
* channel.
*/
private static final Pattern CHANNEL_VALIDATOR = Pattern.compile( "\\d{1,4}" );
/**
* When identical keys are sent to a device in succession the device can
* misinterpret these as a "holding" of the button not a secondary command.
* Add a unique delay for this scenario to improve tune and pressKey
* commands.
*/
private static final Integer REPEAT_IR_COMMAND_DELAY = 100;
@EJB
private IRManager irManager;
/**
* Create the singleton class here, because it makes sense.
*/
@EJB
private KeyManager keyManager;
public LegacyIRServiceHandler()
{
// Do nothing @EJB should inject dependencies.
}
public LegacyIRServiceHandler( IRManager irManager, KeyManager keyManager )
{
this.irManager = irManager;
this.keyManager = keyManager;
}
public boolean pressKey( final URI path, final String irKeySet, final RemoteCommand command )
{
Long tstart = System.currentTimeMillis();
final String irCode = keyManager.getIrCode( irKeySet, "PRONTO", command.name() );
Long tgetCode = System.currentTimeMillis();
if ( irCode != null )
{
logger.debug( "irCode found for KeySet[" + irKeySet + "]" + irCode );
Long tbeforeIR = System.currentTimeMillis();
boolean rtn = performIRCommand( path, irCode, 1, 1 );
Long tafterIR = System.currentTimeMillis();
logger.info( "Key Retrieval: " + ( tgetCode - tstart ) + "ms | IR Transmission = "
+ ( tafterIR - tbeforeIR ) );
return rtn;
}
else
{
logger.warn( "irCode not found for KeySet[" + irKeySet + "] Command[" + command.name() + "]" );
}
return false;
}
public boolean pressKeyAndHold( final URI path, final String irKeySet, final RemoteCommand command,
final Integer count )
{
final String irCode = keyManager.getIrCode( irKeySet, "PRONTO", command.name() );
if ( irCode != null )
{
logger.info( "irCode found for KeySet[" + irKeySet + "]" + irCode );
return performIRCommand( path, irCode, count, 0 );
}
else
{
logger.warn( "irCode not found for KeySet[" + irKeySet );
}
return false;
}
/**
* I hate having to catch the stupid exception in my real code, so create a
* method to handle this for me.
*
* @param delay
*/
private void sleep( int delay )
{
try
{
Thread.sleep( delay );
}
catch ( InterruptedException ex )
{
logger.warn( "Interrupted during IRServiceWSImpl sleep", ex );
}
}
public boolean pressKeys( URI path, String irKeySet, List< RemoteCommand > commands, int delayMillis )
{
if ( path == null || irKeySet == null || commands == null || commands.isEmpty() )
{
logger.warn( "pressKeys() has been called with invalid arguments." );
return false;
}
boolean rtn = true;
RemoteCommand lastCommand = null;
int delay = delayMillis;
for ( RemoteCommand command : commands )
{
// If command is same as last command insert a delay of
// REPEAT_IR_COMMAND_DELAY
delay = ( null != lastCommand && lastCommand.equals( command ) ) ? REPEAT_IR_COMMAND_DELAY : delayMillis;
/**
* If lastCommand is not set, then this is the first key, so ignore
* the sleep.
*/
if ( null != lastCommand )
{
sleep( delay );
}
rtn = pressKey( path, irKeySet, command );
lastCommand = command;
if ( rtn == false )
{
break;
}
}
return rtn;
}
public boolean tune( URI path, String irKeySet, String channel, boolean autoTuneEnabled, int delayMillis )
{
if ( path == null )
{
logger.warn( "tune() path=null" );
return false;
}
List< RemoteCommand > commands = getRemoteCommandFromChannel( channel );
if ( commands == null || commands.size() == 0 )
{
return false;
}
if ( !autoTuneEnabled )
{
commands.add( RemoteCommand.SELECT );
}
// When debug is enabled show all the keys being transmitted.
if ( logger.isDebugEnabled() )
{
String keyStr = new String();
for ( RemoteCommand command : commands )
{
keyStr += command.toString() + ":";
}
logger.debug( "Sending Keys [" + keyStr + "]" );
}
return pressKeys( path, irKeySet, commands, delayMillis );
}
/**
* Send Raw IR code based on path
*/
public boolean sendIR( URI path, String irCode )
{
// return sendIR(path, irCode, 1, 1);
if ( irCode != null )
{
return performIRCommand( path, irCode, 1, 1 );
}
return false;
}
@Override
public List< RemoteLayout > getRemoteCommands( String irKeySet )
{
return null;
}
private List< RemoteCommand > getRemoteCommandFromChannel( String channel )
{
if ( !CHANNEL_VALIDATOR.matcher( channel ).matches() )
{
logger.warn( "Channel doesn't match expression: " + CHANNEL_VALIDATOR.toString() );
return null;
}
char[] digits = channel.toCharArray();
List< RemoteCommand > commands = new ArrayList< RemoteCommand >();
for ( char digit : digits )
{
String dStr = new String();
dStr += digit;
commands.add( RemoteCommand.parse( dStr ) );
}
return commands;
}
private boolean performIRCommand( final URI path, final String irCode, final Integer count, final Integer offset )
{
IRCommunicator com;
boolean rtn = false;
try
{
if ( logger.isDebugEnabled() )
{
logger.debug( "================================ Begin: performIRCommand =================================" );
}
com = irManager.retrieveIRCommunicator( path );
int port = parsePortParameter( path );
long start = System.currentTimeMillis();
rtn = com.transmitIR( irCode, port, count, offset );
long end = System.currentTimeMillis();
if ( logger.isDebugEnabled() )
{
logger.debug( "IR Command for " + path.toString() + " took " + Long.toString( end - start ) + " ms" );
logger.debug( "================================ End: performIRCommand =================================" );
}
}
catch ( UnknownHostException e )
{
logger.error( "Error found looking up IRCommunicator", e );
}
return rtn;
}
/**
* Convenience method to parse out the port parameter of the path.
*
* @param path
* The path to parse
* @return The ir port number present in the path
*/
private int parsePortParameter( final URI path )
{
final Matcher m = IR_PORT_PATTERN.matcher( path.getQuery() );
if ( !m.find() )
{
throw new IllegalArgumentException(
"The URI is missing the port parameter (a number between 1 an n where n is the number of ir ports installed" );
}
return Integer.parseInt( m.group( 1 ) );
}
@Override
public boolean enterCustomKeySequence( final URI path, String irKeySet, List< RemoteCommand > commands,
List< Integer > repeatCount, final List< Integer > delay )
{
boolean retVal = true;
if ( null == path || null == irKeySet || null == commands || null == repeatCount || null == delay )
{
logger.warn( "enterCustomKeySequence() has been called with invalid arguments." );
return false;
}
if ( commands.size() != repeatCount.size() )
{
logger.warn( "enterCustomKeySequence() has been called commands size and repeatSize not matching" );
return false;
}
if ( commands.size() != delay.size() )
{
logger.warn( "enterCustomKeySequence() has been called with invalid arguments." );
return false;
}
Iterator< RemoteCommand > commandIter = commands.iterator();
Iterator< Integer > repeatCountIetr = repeatCount.iterator();
Iterator< Integer > delayIterator = delay.iterator();
while ( commandIter.hasNext() )
{
RemoteCommand command = commandIter.next();
Integer repCount = repeatCountIetr.next();
Integer del = delayIterator.next();
logger.info( "enterCustomKeySeq : command:" + command + " repeatCount :" + repCount + " delay:" + delay );
if ( repCount > 0 )
{
retVal = pressKeyAndHold( path, irKeySet, command, repCount );
}
else
{
retVal = pressKey( path, irKeySet, command );
}
if ( !retVal )
{
logger.error( "enterCustomKeySeq failed during pressKey" );
break;
}
sleep( del );
}
return retVal;
}
@Override
public boolean sendText( URI path, String irKeySet, String stringToBeEntered )
{
boolean returnVal = false;
if ( !( null == stringToBeEntered || stringToBeEntered.isEmpty() ) )
{
char[] digits = stringToBeEntered.toCharArray();
List< RemoteCommand > commands = new ArrayList< RemoteCommand >();
for ( char digit : digits )
{
commands.add( RemoteCommand.parse( Character.toString( digit ) ) );
}
logger.info( "sendText : commands:" + commands );
returnVal = pressKeys( path, irKeySet, commands, REPEAT_IR_COMMAND_DELAY );
}
return returnVal;
}
@Override
public boolean enterRemoteCommandSequence( URI path, String irKeySet, List< RemoteCommandSequence > commands )
{
boolean retVal = true;
if ( null == path || null == irKeySet || null == commands )
{
logger.warn( "enterCustomKeySequence() has been called with invalid arguments." );
return false;
}
if ( commands.size() <= 0 )
{
logger.warn( "enterCustomKeySequence() has been called commands size =" + commands.size() );
return false;
}
Iterator< RemoteCommandSequence > commandIter = commands.iterator();
while ( commandIter.hasNext() )
{
RemoteCommandSequence commandSeq = commandIter.next();
RemoteCommand command = commandSeq.getCommand();
Integer repeatCount = commandSeq.getRepeatCount();
Integer delay = commandSeq.getDelay();
logger.info( "enterCustomKeySeq : command:" + command + " repeatCount :" + repeatCount + " delay:" + delay );
if ( repeatCount == null || delay == null )
{
retVal = false;
break;
}
if ( repeatCount > 0 )
{
retVal = pressKeyAndHold( path, irKeySet, command, repeatCount );
}
else
{
retVal = pressKey( path, irKeySet, command );
}
if ( !retVal )
{
logger.error( "enterCustomKeySeq failed during pressKey" );
break;
}
sleep( delay );
}
return retVal;
}
@Override
public List< com.comcast.cats.keymanager.domain.Remote > getRemotes()
{
throw new UnsupportedOperationException( "LegacyIRServiceFacade.getRemotes() is not supported" );
}
@Override
public String getVersion()
{
throw new UnsupportedOperationException( "LegacyIRServiceFacade.getVersion() is not supported" );
}
@Override
public List<RemoteLayout> getRemoteLayout(String remoteType) {
return null;
}
}