PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/SJQScripts/tools/src/com/google/code/sagetvaddons/groovy/api/GlobalHelpers.groovy

http://sagetv-addons.googlecode.com/
Groovy | 464 lines | 140 code | 28 blank | 296 comment | 34 complexity | 76fa7a3141c8688a8252a9a6844b6dec MD5 | raw file
Possible License(s): LGPL-2.0, BSD-3-Clause
  1. package com.google.code.sagetvaddons.groovy.api
  2. import sagex.UIContext
  3. import sagex.api.*
  4. import com.google.code.sagetvaddons.groovy.net.ExtenderClient
  5. /**
  6. * Various helper methods that provide common queries not provided by the SageTV Global API
  7. * @author dbattams
  8. * @version $Id: GlobalHelpers.groovy 1540 2011-06-11 15:20:44Z derek@battams.ca $
  9. */
  10. class GlobalHelpers {
  11. /**
  12. * Is there at least one SageClient connected to the server
  13. * @return True if there is at least one client connected to the server or false otherwise
  14. */
  15. static boolean isClientConnected() {
  16. return Global.GetConnectedClients().size() > 0
  17. }
  18. /**
  19. * Is there at least one extender connected to the server. PlaceShifter is considered an extender along with MediaMVP and HDx00 devices
  20. * @return True if there is at least one extender connected to the server or false otherwise
  21. */
  22. static boolean isExtenderConnected() {
  23. return Global.GetUIContextNames().size() > 0
  24. }
  25. /**
  26. * Check to see if there is at least one client or extender currently connected to the server
  27. * @return True if there is at least one client or extender connected to the server or false otherwise
  28. */
  29. static boolean isAnythingConnected() {
  30. return isClientConnected() || isExtenderConnected()
  31. }
  32. /**
  33. * Get the amount of total space available for tv recording
  34. * <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>
  35. * <ul>
  36. * <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>
  37. * </ul>
  38. * @return The total amount of recording space, in bytes, available for tv recording
  39. */
  40. static long getTotalRecordingSpace() {
  41. long total = 0L
  42. Configuration.GetVideoDirectories().each {
  43. total += it.getTotalSpace()
  44. }
  45. return total
  46. }
  47. /**
  48. * Get the amount of free space available for tv recording, as a percentage
  49. * <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>
  50. * <ul>
  51. * <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>
  52. * </ul>
  53. * @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
  54. */
  55. static long getFreeRecordingSpacePercentage() {
  56. long free = Global.GetTotalDiskspaceAvailable()
  57. long total = getTotalRecordingSpace()
  58. if(total == 0)
  59. return -1L
  60. return 100L * free / total
  61. }
  62. /**
  63. * Check if there is something currently recording
  64. * @return True if there is something currently recording or false otherwise
  65. */
  66. static boolean isSomethingRecording() {
  67. return Global.GetCurrentlyRecordingMediaFiles().size() > 0
  68. }
  69. /**
  70. * Check if media is being played somewhere on any connected device
  71. * @return True if media is being played on any connected device or false otherwise
  72. */
  73. static boolean isMediaPlaying() {
  74. def connected = []
  75. connected += Global.GetUIContextNames().toList()
  76. connected += Global.GetConnectedClients().toList()
  77. for(def c : connected)
  78. if(MediaPlayerAPI.GetCurrentMediaFile(new UIContext(c)) != null)
  79. return true
  80. return false
  81. }
  82. /**
  83. * Get the number of seconds until the start of the next scheduled recording
  84. * @return The number of seconds until the next scheduled recording; returns Long.MAX_VALUE if there are no scheduled recordings
  85. */
  86. static long getSecondsUntilNextRecording() {
  87. def sched = Database.Sort(Global.GetScheduledRecordings(), false, "GetScheduleStartTime")
  88. def now = System.currentTimeMillis()
  89. if(sched.size() == 0)
  90. return Long.MAX_VALUE
  91. def start = 0
  92. for(def i = 0; i < sched.size(); ++i) {
  93. def time = AiringAPI.GetScheduleStartTime(sched[i])
  94. if(time > now) {
  95. start = time
  96. break
  97. }
  98. }
  99. if(start == 0)
  100. return Long.MAX_VALUE
  101. return (start - now) / 1000L
  102. }
  103. /**
  104. * <p>Check if an extender is powered on</p>
  105. * <p>Use this method to check if an extender device, identified by hostname, is powered on and connected to a SageTV server.</p>
  106. * <p>
  107. * <b>NOTE:</b> The <pre>GlobalHelpers.isExtenderMacAddrPoweredOn()</pre> method will perform much better than this method.
  108. * This method connects to the specified device directly via telnet and checks the status via a relatively slow network conversation.
  109. * If you know the MAC address of the device you are querying about then you should use the other method.
  110. * </p>
  111. * <p>Device Compatibility</p>
  112. * <ul>
  113. * <li><b>MediaMVP:</b> Untested; status unknown</li>
  114. * <li><b>HD100:</b> Tested; works</li>
  115. * <li><b>HD200:</b> Tested; works</li>
  116. * <li><b>HD300:</b> Untested; should work</li>
  117. * </ul>
  118. * @param host The hostname or IP address of the device to query
  119. * @return True if the specified extender is powered on and connected to a SageTV server, false otherwise
  120. * @throws IOException In case of any failure while communicating with the specified device
  121. * @since 1.0.3
  122. */
  123. static boolean isExtenderHostPoweredOn(String host) throws IOException {
  124. def clnt = new ExtenderClient(host)
  125. return clnt.executeCommand('pidof miniclient') == 0
  126. clnt.disconnect()
  127. }
  128. /**
  129. * <p>Check if an extender device is powered on and connected to a SageTV server</p>
  130. * <p>Device Compatibility</p>
  131. * <ul>
  132. * <li><b>MediaMVP:</b> Works</li>
  133. * <li><b>HD100:</b> Works</li>
  134. * <li><b>HD200:</b> Works</li>
  135. * <li><b>HD300:</b> Works</li>
  136. * </ul>
  137. * @param mac The MAC address of the device to query; must be lowercase letters and numbers only
  138. * @return True if the specified device is on and connected to a SageTV server, false otherwise
  139. * @throws IllegalArgumentException If the provided MAC address does not follow the required format and/or is invalid
  140. * @since 1.0.3
  141. */
  142. static boolean isExtenderMacAddrPoweredOn(String mac) throws IllegalArgumentException {
  143. if(mac != null)
  144. mac = mac.toLowerCase()
  145. if(!mac || !(mac ==~ /[a-f0-9]{12}/))
  146. throw new IllegalArgumentException("Invalid MAC address! [$mac]")
  147. def connections = Global.GetUIContextNames() as List
  148. return connections.contains(mac)
  149. }
  150. /**
  151. * <p>Check if an extender device is powered off and not connected to a SageTV server</p>
  152. * <p>Device Compatibility</p>
  153. * <ul>
  154. * <li><b>MediaMVP:</b> Works</li>
  155. * <li><b>HD100:</b> Works</li>
  156. * <li><b>HD200:</b> Works</li>
  157. * <li><b>HD300:</b> Works</li>
  158. * </ul>
  159. * @param mac The MAC address of the device to query; must be lowercase letters and numbers only
  160. * @return True if the specified device is off and not connected to a SageTV server, false otherwise
  161. * @throws IllegalArgumentException If the provided MAC address does not follow the required format and/or is invalid
  162. * @since 1.0.3
  163. */
  164. static boolean isExtenderMacAddrPoweredOff(String mac) throws IllegalArgumentException {
  165. return !isExtenderMacAddrPoweredOn(mac)
  166. }
  167. /**
  168. * <p>Check if an extender is powered off</p>
  169. * <p>Use this method to check if an extender device, identified by hostname, is powered off and not connected to a SageTV server.</p>
  170. * <p>
  171. * <b>NOTE:</b> The <pre>GlobalHelpers.isExtenderMacAddrPoweredOff()</pre> method will perform much better than this method.
  172. * This method connects to the specified device directly via telnet and checks the status via a relatively slow network conversation.
  173. * If you know the MAC address of the device you are querying about then you should use the other method.
  174. * </p>
  175. * <p>Device Compatibility</p>
  176. * <ul>
  177. * <li><b>MediaMVP:</b> Untested; status unknown</li>
  178. * <li><b>HD100:</b> Tested; works</li>
  179. * <li><b>HD200:</b> Tested; works</li>
  180. * <li><b>HD300:</b> Untested; should work</li>
  181. * </ul>
  182. * @param host The hostname or IP address of the device to query
  183. * @return True if the specified extender is powered off and not connected to a SageTV server, false otherwise
  184. * @throws IOException In case of any failure while communicating with the specified device
  185. * @since 1.0.3
  186. */
  187. static boolean isExtenderHostPoweredOff(String host) throws IOException {
  188. return !isExtenderHostPoweredOn(host)
  189. }
  190. /**
  191. * <p>Attempt to power off the specified extender device</p>
  192. * <p>
  193. * This method will connect to the specified device and attempt to power it off. Because of the
  194. * way this is done, the method cannot guarantee that the device will be turned off. All that
  195. * you can determine is that if this method does not throw an IOException then the command was
  196. * issued successfully. To determine if the device is actually powered off after this call, make
  197. * a call to isExtenderHostPoweredOff() by passing it the same host argument used to make this call.
  198. * </p>
  199. * <p>Device Compatability</p>
  200. * <ul>
  201. * <li><b>MediaMVP:</b> Untested; status unknown</li>
  202. * <li><b>HD100:</b> Tested; works</li>
  203. * <li><b>HD200:</b> Tested; works</li>
  204. * <li><b>HD300:</b> Untested; should work</li>
  205. * </ul>
  206. * @param host The hostname or IP address of the device to power off
  207. * @throws IOException If there were any errors communicating with the device during the request
  208. * @since 1.0.3
  209. */
  210. static void powerOffExtenderHost(String host) throws IOException {
  211. def clnt = new ExtenderClient(host)
  212. clnt.executeCommand('killall miniclient')
  213. clnt.disconnect()
  214. }
  215. /**
  216. * <p>Attempt to power off the specified extender device</p>
  217. * <p>
  218. * This method will connect to the specified device and attempt to power it off. Because of the
  219. * way this is done, the method cannot guarantee that the device will be turned off. All that
  220. * you can determine is that if this method does not throw an IOException then the command was
  221. * issued successfully. To determine if the device is actually powered off after this call, make
  222. * a call to isExtenderMacAddrPoweredOff() by passing it the same host argument used to make this call.
  223. * </p>
  224. * <p>Device Compatability</p>
  225. * <ul>
  226. * <li><b>MediaMVP:</b> Untested; status unknown</li>
  227. * <li><b>HD100:</b> Tested; works</li>
  228. * <li><b>HD200:</b> Tested; works</li>
  229. * <li><b>HD300:</b> Untested; should work</li>
  230. * </ul>
  231. * @param mac The MAC address of the device to power off; lowercase letters and numbers only
  232. * @throws IOException If there were any errors communicating with the device during the request
  233. * @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
  234. * @since 1.0.3
  235. */
  236. static void powerOffExtenderMacAddr(String mac) throws IOException, IllegalArgumentException {
  237. def host = UtilityHelpers.getIpAddrForMacAddr(mac)
  238. if(!host)
  239. throw new IllegalArgumentException("Cannot resolve MAC to IP! [$mac]")
  240. powerOffExtenderHost(host)
  241. }
  242. /**
  243. * <p>Attempt to power on the specified extender device</p>
  244. * <p>Device Compatibility</p>
  245. * <ul>
  246. * <li><b>MediaMVP:</b> Untested; status unknown</li>
  247. * <li><b>HD100:</b> Tested; works</li>
  248. * <li><b>HD200:</b> Tested; works</li>
  249. * <li><b>HD300:</b> Untested; should work</li>
  250. * </ul>
  251. * @param host The hostname or IP address of the device to power off
  252. * @return True if the device was powered on, false otherwise
  253. * @throws IOException If there were any errors communicating with the device during the request
  254. * @since 1.0.3
  255. */
  256. static boolean powerOnExtenderHost(String host) throws IOException {
  257. def clnt = new ExtenderClient(host)
  258. def rc = clnt.executeCommand('killall waitpower')
  259. clnt.disconnect()
  260. return rc == 0
  261. }
  262. /**
  263. * <p>Attempt to power on the specified extender device</p>
  264. * <p>
  265. * <b>NOTE:</b> This method will rarely, if ever, work because it must lookup the device's IP
  266. * address from the SageTV server's ARP cache. However, the ARP cache will not contain the data
  267. * because, presumably, the device will not have been connected to the SageTV server (since
  268. * you're attempting to turn it on, it is probably currently off). However, this method is
  269. * provided because there are cases where it could work. For example, turning the device off
  270. * then turning it back on very quickly would likely succeed since the ARP cache is unlikely to
  271. * have been flushed that quickly. Users should expect this method to fail much more than it
  272. * succeeds. For more reliable extender power on, use <code>GlobalHelpers.powerOnExtenderHost()</code>.
  273. * </p>
  274. * <p>Device Compatability</p>
  275. * <ul>
  276. * <li><b>MediaMVP:</b> Untested; status unknown</li>
  277. * <li><b>HD100:</b> Tested; works</li>
  278. * <li><b>HD200:</b> Tested; works</li>
  279. * <li><b>HD300:</b> Untested; should work</li>
  280. * </ul>
  281. * @param mac The MAC address of the device to power off; lowercase letters and numbers only
  282. * @return True if the device was powered on or false otherwise
  283. * @throws IOException If there were any errors communicating with the device during the request
  284. * @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
  285. * @since 1.0.3
  286. */
  287. static boolean powerOnExtenderMacAddr(String mac) throws IOException, IllegalArgumentException {
  288. def host = UtilityHelpers.getIpAddrForMacAddr(mac)
  289. if(!host)
  290. throw new IllegalArgumentException("Cannot resolve MAC to IP! [$mac]")
  291. return powerOnExtenderHost(host)
  292. }
  293. /**
  294. * <p>Attempt to reboot the specified extender device</p>
  295. * <p>
  296. * Because issuing the reboot command immediately reboots the device, the communication link is
  297. * lost, which would result in an IOException being thrown. However, in this case, that is
  298. * normal and expected behaviour. So this method eats any IOExceptions and treats them as normal,
  299. * which means this method cannot throw IOExceptions. What is does instead is, if the link to
  300. * the device is still active after issuing the reboot command then it is assumed that the
  301. * attempt to reboot has failed and an IllegalStateException will be thrown to denote this
  302. * situation. It is not guaranteed that the device was rebooted successfully if this method
  303. * does not throw an exception. All that is guaranteed is that the command to reboot was issued
  304. * successfully. You cannot rely on this method to always successfully reboot the device (though,
  305. * in practice/testing, it would appear that as long as this method doesn't throw an exception then
  306. * the reboot sequence will occur).
  307. * </p>
  308. * <p>Device Compatibility</p>
  309. * <ul>
  310. * <li><b>MediaMVP:</b> Untested; status unknown</li>
  311. * <li><b>HD100:</b> Tested; works</li>
  312. * <li><b>HD200:</b> Tested; works</li>
  313. * <li><b>HD300:</b> Untested; should work</li>
  314. * </ul>
  315. * @param host The hostname or IP address of the device to reboot
  316. * @since 1.0.3
  317. * @throws IllegalStateException If the attempt to reboot failed
  318. */
  319. static void rebootExtenderHost(String host) {
  320. try {
  321. def clnt = new ExtenderClient(host)
  322. clnt.executeCommand('reboot')
  323. clnt.disconnect()
  324. throw new IllegalStateException("Reboot attempt of '$host' failed!")
  325. } catch(IOException e) {
  326. // ignore it for this case
  327. }
  328. }
  329. /**
  330. * <p>Attempt to reboot the specified extender device</p>
  331. * <p>
  332. * Because issuing the reboot command immediately reboots the device, the communication link is
  333. * lost, which would result in an IOException being thrown. However, in this case, that is
  334. * normal and expected behaviour. So this method eats any IOExceptions and treats them as normal,
  335. * which means this method cannot throw IOExceptions. What is does instead is, if the link to
  336. * the device is still active after issuing the reboot command then it is assumed that the
  337. * attempt to reboot has failed and an IllegalStateException will be thrown to denote this
  338. * situation. It is not guaranteed that the device was rebooted successfully if this method
  339. * does not throw an exception. All that is guaranteed is that the command to reboot was issued
  340. * successfully. You cannot rely on this method to always successfully reboot the device (though,
  341. * in practice/testing, it would appear that as long as this method doesn't throw an exception then
  342. * the reboot sequence will occur).
  343. * </p>
  344. * <p>Device Compatibility</p>
  345. * <ul>
  346. * <li><b>MediaMVP:</b> Untested; status unknown</li>
  347. * <li><b>HD100:</b> Tested; works</li>
  348. * <li><b>HD200:</b> Tested; works</li>
  349. * <li><b>HD300:</b> Untested; should work</li>
  350. * </ul>
  351. * @param mac The MAC address of the device to reboot; lowercase letters and numbers only
  352. * @since 1.0.3
  353. * @throws IllegalStateException If the attempt to reboot failed
  354. * @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
  355. */
  356. static void rebootExtenderMacAddr(String mac) throws IllegalArgumentException, IllegalStateException {
  357. def host = UtilityHelpers.getIpAddrForMacAddr(mac)
  358. if(!host)
  359. throw new IllegalArgumentException("Cannot resolve MAC to IP! [$mac]")
  360. rebootExtenderHost(host)
  361. }
  362. /**
  363. * <p>Determine if the given MAC address is a media extender device</p>
  364. * <p>A media extender device is any of the following:</p>
  365. * <ul>
  366. * <li>MediaMVP</li>
  367. * <li>HD100</li>
  368. * <li>HD200</li>
  369. * <li>HD300</li>
  370. * </ul>
  371. * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
  372. * @param mac The MAC address of the device; lowercase letters and numbers only
  373. * @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
  374. * @since 1.0.3
  375. */
  376. static boolean isMacAddrExtender(String mac) {
  377. def uic = new UIContext(mac)
  378. return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) =~ /^[SH]D Media/
  379. }
  380. /**
  381. * <p>Determine if the given MAC address is a Placeshifter</p>
  382. * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
  383. * @param mac The MAC address of the device; lowercase letters and numbers only
  384. * @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
  385. * @since 1.0.3
  386. */
  387. static boolean isMacAddrPlaceshifter(String mac) {
  388. def uic = new UIContext(mac)
  389. return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) == 'Placeshifter'
  390. }
  391. /**
  392. * <p>Determine if the given MAC address is a MediaMVP</p>
  393. * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
  394. * @param mac The MAC address of the device; lowercase letters and numbers only
  395. * @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
  396. * @since 1.0.3
  397. */
  398. static boolean isMacAddrMediaMVP(String mac) {
  399. def uic = new UIContext(mac)
  400. return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) == 'SD Media Extender'
  401. }
  402. /**
  403. * <p>Determine if the given MAC address is an HD100</p>
  404. * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
  405. * @param mac The MAC address of the device; lowercase letters and numbers only
  406. * @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
  407. * @since 1.0.3
  408. */
  409. static boolean isMacAddrHD100(String mac) {
  410. def uic = new UIContext(mac)
  411. return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) == 'HD Media Extender'
  412. }
  413. /**
  414. * <p>Determine if the given MAC address is an HD200</p>
  415. * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
  416. * @param mac The MAC address of the device; lowercase letters and numbers only
  417. * @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
  418. * @since 1.0.3
  419. */
  420. static boolean isMacAddrHD200(String mac) {
  421. def uic = new UIContext(mac)
  422. return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) == 'HD Media Player' && !(Configuration.GetAudioOutputOptions(uic) as List).contains('HDMIHBR')
  423. }
  424. /**
  425. * <p>Determine if the given MAC address is an HD300</p>
  426. * <p>This method will only work if the specified device is actively connected to the SageTV server.</p>
  427. * @param mac The MAC address of the device; lowercase letters and numbers only
  428. * @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
  429. * @since 1.0.3
  430. */
  431. static boolean isMacAddrHD300(String mac) {
  432. def uic = new UIContext(mac)
  433. return Global.IsRemoteUI(uic) && Global.GetRemoteUIType(uic) == 'HD Media Player' && (Configuration.GetAudioOutputOptions(uic) as List).contains('HDMIHBR')
  434. }
  435. private GlobalHelpers() {}
  436. }