/SJQScripts/tools/src/com/google/code/sagetvaddons/groovy/api/GlobalHelpers.groovy
Groovy | 464 lines | 140 code | 28 blank | 296 comment | 34 complexity | 76fa7a3141c8688a8252a9a6844b6dec MD5 | raw file
Possible License(s): LGPL-2.0, BSD-3-Clause
- package com.google.code.sagetvaddons.groovy.api
-
- import sagex.UIContext
- import sagex.api.*
-
- import com.google.code.sagetvaddons.groovy.net.ExtenderClient
-
- /**
- * Various helper methods that provide common queries not provided by the SageTV Global API
- * @author dbattams
- * @version $Id: GlobalHelpers.groovy 1540 2011-06-11 15:20:44Z derek@battams.ca $
- */
- class GlobalHelpers {
-
- /**
- * Is there at least one SageClient connected to the server
- * @return True if there is at least one client connected to the server or false otherwise
- */
- static boolean isClientConnected() {
- return Global.GetConnectedClients().size() > 0
- }
-
- /**
- * Is there at least one extender connected to the server. PlaceShifter is considered an extender along with MediaMVP and HDx00 devices
- * @return True if there is at least one extender connected to the server or false otherwise
- */
- static boolean isExtenderConnected() {
- return Global.GetUIContextNames().size() > 0
- }
-
- /**
- * Check to see if there is at least one client or extender currently connected to the server
- * @return True if there is at least one client or extender connected to the server or false otherwise
- */
- static boolean isAnythingConnected() {
- return isClientConnected() || isExtenderConnected()
- }
-
- /**
- * Get the amount of total space available for tv recording
- * <p>This method has assumptions and if any one of them is not met then the results of this method <b>CANNOT</b> be trusted.</p>
- * <ul>
- * <li>There is only <b>one (1)</b> recording directory defined on any single physical disk/partition; e.g. D:\tv1\ and D:\tv2\ will render the results of this method invalid</li>
- * </ul>
- * @return The total amount of recording space, in bytes, available for tv recording
- */
- static long getTotalRecordingSpace() {
- long total = 0L
- Configuration.GetVideoDirectories().each {
- total += it.getTotalSpace()
- }
- return total
- }
-
- /**
- * Get the amount of free space available for tv recording, as a percentage
- * <p>This method has assumptions and if any one of them is not met then the results of this method <b>CANNOT</b> be trusted.</p>
- * <ul>
- * <li>There is only <b>one (1)</b> recording directory defined on any single physical disk/partition; e.g. D:\tv1\ and D:\tv2\ will render the results of this method invalid</li>
- * </ul>
- * @return A value between 0-100 representing, as a percentage, the amount of free space available for recording tv or -1 if an error is encountered during calculations
- */
- static long getFreeRecordingSpacePercentage() {
- long free = Global.GetTotalDiskspaceAvailable()
- long total = getTotalRecordingSpace()
- if(total == 0)
- return -1L
- return 100L * free / total
- }
-
- /**
- * Check if there is something currently recording
- * @return True if there is something currently recording or false otherwise
- */
- static boolean isSomethingRecording() {
- return Global.GetCurrentlyRecordingMediaFiles().size() > 0
- }
-
- /**
- * Check if media is being played somewhere on any connected device
- * @return True if media is being played on any connected device or false otherwise
- */
- static boolean isMediaPlaying() {
- def connected = []
- connected += Global.GetUIContextNames().toList()
- connected += Global.GetConnectedClients().toList()
- for(def c : connected)
- if(MediaPlayerAPI.GetCurrentMediaFile(new UIContext(c)) != null)
- return true
- return false
- }
-
- /**
- * Get the number of seconds until the start of the next scheduled recording
- * @return The number of seconds until the next scheduled recording; returns Long.MAX_VALUE if there are no scheduled recordings
- */
- static long getSecondsUntilNextRecording() {
- def sched = Database.Sort(Global.GetScheduledRecordings(), false, "GetScheduleStartTime")
- def now = System.currentTimeMillis()
- if(sched.size() == 0)
- return Long.MAX_VALUE
- def start = 0
- for(def i = 0; i < sched.size(); ++i) {
- def time = AiringAPI.GetScheduleStartTime(sched[i])
- if(time > now) {
- start = time
- break
- }
- }
- if(start == 0)
- return Long.MAX_VALUE
- return (start - now) / 1000L
- }
-
- /**
- * <p>Check if an extender is powered on</p>
- * <p>Use this method to check if an extender device, identified by hostname, is powered on and connected to a SageTV server.</p>
- * <p>
- * <b>NOTE:</b> The <pre>GlobalHelpers.isExtenderMacAddrPoweredOn()</pre> method will perform much better than this method.
- * This method connects to the specified device directly via telnet and checks the status via a relatively slow network conversation.
- * If you know the MAC address of the device you are querying about then you should use the other method.
- * </p>
- * <p>Device Compatibility</p>
- * <ul>
- * <li><b>MediaMVP:</b> Untested; status unknown</li>
- * <li><b>HD100:</b> Tested; works</li>
- * <li><b>HD200:</b> Tested; works</li>
- * <li><b>HD300:</b> Untested; should work</li>
- * </ul>
- * @param host The hostname or IP address of the device to query
- * @return True if the specified extender is powered on and connected to a SageTV server, false otherwise
- * @throws IOException In case of any failure while communicating with the specified device
- * @since 1.0.3
- */
- static boolean isExtenderHostPoweredOn(String host) throws IOException {
- def clnt = new ExtenderClient(host)
- return clnt.executeCommand('pidof miniclient') == 0
- clnt.disconnect()
- }
-
- /**
- * <p>Check if an extender device is powered on and connected to a SageTV server</p>
- * <p>Device Compatibility</p>
- * <ul>
- * <li><b>MediaMVP:</b> Works</li>
- * <li><b>HD100:</b> Works</li>
- * <li><b>HD200:</b> Works</li>
- * <li><b>HD300:</b> Works</li>
- * </ul>
- * @param mac The MAC address of the device to query; must be lowercase letters and numbers only
- * @return True if the specified device is on and connected to a SageTV server, false otherwise
- * @throws IllegalArgumentException If the provided MAC address does not follow the required format and/or is invalid
- * @since 1.0.3
- */
- static boolean isExtenderMacAddrPoweredOn(String mac) throws IllegalArgumentException {
- if(mac != null)
- mac = mac.toLowerCase()
- if(!mac || !(mac ==~ /[a-f0-9]{12}/))
- throw new IllegalArgumentException("Invalid MAC address! [$mac]")
- def connections = Global.GetUIContextNames() as List
- return connections.contains(mac)
- }
-
- /**
- * <p>Check if an extender device is powered off and not connected to a SageTV server</p>
- * <p>Device Compatibility</p>
- * <ul>
- * <li><b>MediaMVP:</b> Works</li>
- * <li><b>HD100:</b> Works</li>
- * <li><b>HD200:</b> Works</li>
- * <li><b>HD300:</b> Works</li>
- * </ul>
- * @param mac The MAC address of the device to query; must be lowercase letters and numbers only
- * @return True if the specified device is off and not connected to a SageTV server, false otherwise
- * @throws IllegalArgumentException If the provided MAC address does not follow the required format and/or is invalid
- * @since 1.0.3
- */
- static boolean isExtenderMacAddrPoweredOff(String mac) throws IllegalArgumentException {
- return !isExtenderMacAddrPoweredOn(mac)
- }
-
- /**
- * <p>Check if an extender is powered off</p>
- * <p>Use this method to check if an extender device, identified by hostname, is powered off and not connected to a SageTV server.</p>
- * <p>
- * <b>NOTE:</b> The <pre>GlobalHelpers.isExtenderMacAddrPoweredOff()</pre> method will perform much better than this method.
- * This method connects to the specified device directly via telnet and checks the status via a relatively slow network conversation.
- * If you know the MAC address of the device you are querying about then you should use the other method.
- * </p>
- * <p>Device Compatibility</p>
- * <ul>
- * <li><b>MediaMVP:</b> Untested; status unknown</li>
- * <li><b>HD100:</b> Tested; works</li>
- * <li><b>HD200:</b> Tested; works</li>
- * <li><b>HD300:</b> Untested; should work</li>
- * </ul>
- * @param host The hostname or IP address of the device to query
- * @return True if the specified extender is powered off and not connected to a SageTV server, false otherwise
- * @throws IOException In case of any failure while communicating with the specified device
- * @since 1.0.3
- */
- static boolean isExtenderHostPoweredOff(String host) throws IOException {
- return !isExtenderHostPoweredOn(host)
- }
-
- /**
- * <p>Attempt to power off the specified extender device</p>
- * <p>
- * This method will connect to the specified device and attempt to power it off. Because of the
- * way this is done, the method cannot guarantee that the device will be turned off. All that
- * you can determine is that if this method does not throw an IOException then the command was
- * issued successfully. To determine if the device is actually powered off after this call, make
- * a call to isExtenderHostPoweredOff() by passing it the same host argument used to make this call.
- * </p>
- * <p>Device Compatability</p>
- * <ul>
- * <li><b>MediaMVP:</b> Untested; status unknown</li>
- * <li><b>HD100:</b> Tested; works</li>
- * <li><b>HD200:</b> Tested; works</li>
- * <li><b>HD300:</b> Untested; should work</li>
- * </ul>
- * @param host The hostname or IP address of the device to power off
- * @throws IOException If there were any errors communicating with the device during the request
- * @since 1.0.3
- */
- static void powerOffExtenderHost(String host) throws IOException {
- def clnt = new ExtenderClient(host)
- clnt.executeCommand('killall miniclient')
- clnt.disconnect()
- }
-
- /**
- * <p>Attempt to power off the specified extender device</p>
- * <p>
- * This method will connect to the specified device and attempt to power it off. Because of the
- * way this is done, the method cannot guarantee that the device will be turned off. All that
- * you can determine is that if this method does not throw an IOException then the command was
- * issued successfully. To determine if the device is actually powered off after this call, make
- * a call to isExtenderMacAddrPoweredOff() by passing it the same host argument used to make this call.
- * </p>
- * <p>Device Compatability</p>
- * <ul>
- * <li><b>MediaMVP:</b> Untested; status unknown</li>
- * <li><b>HD100:</b> Tested; works</li>
- * <li><b>HD200:</b> Tested; works</li>
- * <li><b>HD300:</b> Untested; should work</li>
- * </ul>
- * @param mac The MAC address of the device to power off; lowercase letters and numbers only
- * @throws IOException If there were any errors communicating with the device during the request
- * @throws IllegalArgumentException If the MAC address is invalid or cannot be resolved to an IP address, which would happen if the device is not connected to the SageTV server
- * @since 1.0.3
- */
- static void powerOffExtenderMacAddr(String mac) throws IOException, IllegalArgumentException {
- def host = UtilityHelpers.getIpAddrForMacAddr(mac)
- if(!host)
- throw new IllegalArgumentException("Cannot resolve MAC to IP! [$mac]")
- powerOffExtenderHost(host)
- }
-
- /**
- * <p>Attempt to power on the specified extender device</p>
- * <p>Device Compatibility</p>
- * <ul>
- * <li><b>MediaMVP:</b> Untested; status unknown</li>
- * <li><b>HD100:</b> Tested; works</li>
- * <li><b>HD200:</b> Tested; works</li>
- * <li><b>HD300:</b> Untested; should work</li>
- * </ul>
- * @param host The hostname or IP address of the device to power off
- * @return True if the device was powered on, false otherwise
- * @throws IOException If there were any errors communicating with the device during the request
- * @since 1.0.3
- */
- static boolean powerOnExtenderHost(String host) throws IOException {
- def clnt = new ExtenderClient(host)
- def rc = clnt.executeCommand('killall waitpower')
- clnt.disconnect()
- return rc == 0
- }
-
- /**
- * <p>Attempt to power on the specified extender device</p>
- * <p>
- * <b>NOTE:</b> This method will rarely, if ever, work because it must lookup the device's IP
- * address from the SageTV server's ARP cache. However, the ARP cache will not contain the data
- * because, presumably, the device will not have been connected to the SageTV server (since
- * you're attempting to turn it on, it is probably currently off). However, this method is
- * provided because there are cases where it could work. For example, turning the device off
- * then turning it back on very quickly would likely succeed since the ARP cache is unlikely to
- * have been flushed that quickly. Users should expect this method to fail much more than it
- * succeeds. For more reliable extender power on, use <code>GlobalHelpers.powerOnExtenderHost()</code>.
- * </p>
- * <p>Device Compatability</p>
- * <ul>
- * <li><b>MediaMVP:</b> Untested; status unknown</li>
- * <li><b>HD100:</b> Tested; works</li>
- * <li><b>HD200:</b> Tested; works</li>
- * <li><b>HD300:</b> Untested; should work</li>
- * </ul>
- * @param mac The MAC address of the device to power off; lowercase letters and numbers only
- * @return True if the device was powered on or false otherwise
- * @throws IOException If there were any errors communicating with the device during the request
- * @throws IllegalArgumentException If the MAC address is invalid or cannot be resolved to an IP address, which would happen if the device is not connected to the SageTV server
- * @since 1.0.3
- */
- static boolean powerOnExtenderMacAddr(String mac) throws IOException, IllegalArgumentException {
- def host = UtilityHelpers.getIpAddrForMacAddr(mac)
- if(!host)
- throw new IllegalArgumentException("Cannot resolve MAC to IP! [$mac]")
- return powerOnExtenderHost(host)
- }
-
- /**
- * <p>Attempt to reboot the specified extender device</p>
- * <p>
- * Because issuing the reboot command immediately reboots the device, the communication link is
- * lost, which would result in an IOException being thrown. However, in this case, that is
- * normal and expected behaviour. So this method eats any IOExceptions and treats them as normal,
- * which means this method cannot throw IOExceptions. What is does instead is, if the link to
- * the device is still active after issuing the reboot command then it is assumed that the
- * attempt to reboot has failed and an IllegalStateException will be thrown to denote this
- * situation. It is not guaranteed that the device was rebooted successfully if this method
- * does not throw an exception. All that is guaranteed is that the command to reboot was issued
- * successfully. You cannot rely on this method to always successfully reboot the device (though,
- * in practice/testing, it would appear that as long as this method doesn't throw an exception then
- * the reboot sequence will occur).
- * </p>
- * <p>Device Compatibility</p>
- * <ul>
- * <li><b>MediaMVP:</b> Untested; status unknown</li>
- * <li><b>HD100:</b> Tested; works</li>
- * <li><b>HD200:</b> Tested; works</li>
- * <li><b>HD300:</b> Untested; should work</li>
- * </ul>
- * @param host The hostname or IP address of the device to reboot
- * @since 1.0.3
- * @throws IllegalStateException If the attempt to reboot failed
- */
- static void rebootExtenderHost(String host) {
- try {
- def clnt = new ExtenderClient(host)
- clnt.executeCommand('reboot')
- clnt.disconnect()
- throw new IllegalStateException("Reboot attempt of '$host' failed!")
- } catch(IOException e) {
- // ignore it for this case
- }
- }
-
- /**
- * <p>Attempt to reboot the specified extender device</p>
- * <p>
- * Because issuing the reboot command immediately reboots the device, the communication link is
- * lost, which would result in an IOException being thrown. However, in this case, that is
- * normal and expected behaviour. So this method eats any IOExceptions and treats them as normal,
- * which means this method cannot throw IOExceptions. What is does instead is, if the link to
- * the device is still active after issuing the reboot command then it is assumed that the
- * attempt to reboot has failed and an IllegalStateException will be thrown to denote this
- * situation. It is not guaranteed that the device was rebooted successfully if this method
- * does not throw an exception. All that is guaranteed is that the command to reboot was issued
- * successfully. You cannot rely on this method to always successfully reboot the device (though,
- * in practice/testing, it would appear that as long as this method doesn't throw an exception then
- * the reboot sequence will occur).
- * </p>
- * <p>Device Compatibility</p>
- * <ul>
- * <li><b>MediaMVP:</b> Untested; status unknown</li>
- * <li><b>HD100:</b> Tested; works</li>
- * <li><b>HD200:</b> Tested; works</li>
- * <li><b>HD300:</b> Untested; should work</li>
- * </ul>
- * @param mac The MAC address of the device to reboot; lowercase letters and numbers only
- * @since 1.0.3
- * @throws IllegalStateException If the attempt to reboot failed
- * @throws IllegalArgumentException If the MAC address is invalid or cannot be resolved to an IP address, which would happen if the device is not connected to the SageTV server
- */
- static void rebootExtenderMacAddr(String mac) throws IllegalArgumentException, IllegalStateException {
- def host = UtilityHelpers.getIpAddrForMacAddr(mac)
- if(!host)
- throw new IllegalArgumentException("Cannot resolve MAC to IP! [$mac]")
- rebootExtenderHost(host)
- }
-
- /**
- * <p>Determine if the given MAC address is a media extender device</p>
- * <p>A media extender device is any of the following:</p>
- * <ul>
- * <li>MediaMVP</li>
- * <li>HD100</li>
- * <li>HD200</li>
- * <li>HD300</li>
- * </ul>
- * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
- * @param mac The MAC address of the device; lowercase letters and numbers only
- * @return True if the device is connected to the server and is an extender, false otherwise; false is returned even if the MAC is an extender, but isn't currently connected to the server
- * @since 1.0.3
- */
- static boolean isMacAddrExtender(String mac) {
- def uic = new UIContext(mac)
- return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) =~ /^[SH]D Media/
- }
-
- /**
- * <p>Determine if the given MAC address is a Placeshifter</p>
- * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
- * @param mac The MAC address of the device; lowercase letters and numbers only
- * @return True if the device is connected to the server and is a placeshifter, false otherwise; false is returned even if the MAC is a placeshifter, but isn't currently connected to the server
- * @since 1.0.3
- */
- static boolean isMacAddrPlaceshifter(String mac) {
- def uic = new UIContext(mac)
- return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) == 'Placeshifter'
- }
-
- /**
- * <p>Determine if the given MAC address is a MediaMVP</p>
- * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
- * @param mac The MAC address of the device; lowercase letters and numbers only
- * @return True if the device is connected to the server and is a MediaMVP, false otherwise; false is returned even if the MAC is a MediaMVP, but isn't currently connected to the server
- * @since 1.0.3
- */
- static boolean isMacAddrMediaMVP(String mac) {
- def uic = new UIContext(mac)
- return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) == 'SD Media Extender'
- }
-
- /**
- * <p>Determine if the given MAC address is an HD100</p>
- * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
- * @param mac The MAC address of the device; lowercase letters and numbers only
- * @return True if the device is connected to the server and is an HD100, false otherwise; false is returned even if the MAC is an HD100, but isn't currently connected to the server
- * @since 1.0.3
- */
- static boolean isMacAddrHD100(String mac) {
- def uic = new UIContext(mac)
- return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) == 'HD Media Extender'
- }
-
- /**
- * <p>Determine if the given MAC address is an HD200</p>
- * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
- * @param mac The MAC address of the device; lowercase letters and numbers only
- * @return True if the device is connected to the server and is an HD200, false otherwise; false is returned even if the MAC is an HD200, but isn't currently connected to the server
- * @since 1.0.3
- */
- static boolean isMacAddrHD200(String mac) {
- def uic = new UIContext(mac)
- return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) == 'HD Media Player' && !(Configuration.GetAudioOutputOptions(uic) as List).contains('HDMIHBR')
- }
-
- /**
- * <p>Determine if the given MAC address is an HD300</p>
- * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
- * @param mac The MAC address of the device; lowercase letters and numbers only
- * @return True if the device is connected to the server and is an HD300, false otherwise; false is returned even if the MAC is an HD300, but isn't currently connected to the server
- * @since 1.0.3
- */
- static boolean isMacAddrHD300(String mac) {
- def uic = new UIContext(mac)
- return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) == 'HD Media Player' && (Configuration.GetAudioOutputOptions(uic) as List).contains('HDMIHBR')
- }
-
- private GlobalHelpers() {}
- }