SageTV Community  

Go Back   SageTV Community > SageTV Development and Customizations > SageTV Studio
Forum Rules FAQs Community Downloads Today's Posts Search

Notices

SageTV Studio Discussion related to the SageTV Studio application produced by SageTV. Questions, issues, problems, suggestions, etc. relating to the Studio software application should be posted here.

Reply
 
Thread Tools Search this Thread Display Modes
  #1  
Old 04-12-2011, 08:03 PM
Slugger Slugger is offline
SageTVaholic
 
Join Date: Mar 2007
Location: Kingston, ON
Posts: 4,008
A "groovy" way to write plugins...

I've just installed my first PoC build of a plugin completely written in Groovy. This may not excite many, but it sure excited me. While it's all fresh in my mind, I'm going to dump a few notes here about it. A complete wiki doc explaining all the details (and there really aren't many, actually) on how to do this is forthcoming.

Motivation: Be able to write plugins quickly, easily. Why Groovy? It's Java with a lot of the boilerplate code removed and a lot of "groovy" features added.

Requirements:
  • 1) Your plugin must depend on the Groovy plugin, which is already in the repository
  • 2) You must compile your Groovy scripts into Java bytecode so that the Sage core can consume your Groovy code

And that's really it. From there, you just write Groovy code and compile it into bytecode when your done. Once your groovy scripts are in bytecode form, it's really just Java as far as anything else (i.e. the Sage core) is concerned.

Why I wanted to try this: Combined with Sean's AbstractPlugin class from the sagex-api plugin, I can write plugin implementations in but a few lines of code. Here is the entire Groovy script of the plugin I'm working on.

Code:
package com.google.code.sagetvaddons.sop

import sage.SageTVPlugin
import sage.SageTVPluginRegistry
import sagex.api.Global
import sagex.plugin.AbstractPlugin
import sagex.plugin.SageEvent

final class Plugin extends AbstractPlugin {
	static final String PROP_PREFIX = 'sop_plugin/'
	static final String PROP_CLEAN_FREQ = PROP_PREFIX + 'clean_freq'
	
	private Timer timer
	
	Plugin(SageTVPluginRegistry reg) {
		super(reg)
		addProperty(SageTVPlugin.CONFIG_INTEGER, PROP_CLEAN_FREQ, '360', 'Cleaner Frequency (mins)', 'How often, in minutes, the cleaner thread will cleanup properties for non-existant objects.  Changes to this value require a plugin restart.')
	}
	
	@Override
	void start() {
		super.start()
		if(timer)
			timer.cancel()
		timer = new Timer(true)
		def freq = getConfigValue(PROP_CLEAN_FREQ).toInteger() * 60000
		timer.scheduleAtFixedRate(new TimerTask() {
			@Override
			void run() {
				DataStore.get().cleanup()
			}
		}, freq, freq)
	}
	
	@Override
	void stop() {
		super.stop()
		if(timer)
			timer.cancel()
		timer = null
	}
	
	@SageEvent("SystemMessagePosted")
	void handleEvent(String name, Map args) {
		Global.DebugLog("Event handled the Groovy way!")
	}
}
Except for a few missing semicolons and public declarations (public is default in Groovy), you might be thinking, "hey, that's no different than what the Java code would look like for the same thing!" You're right, but where Groovy really shines is in doing things like working with SQL:

This is my entire DataStore class, written in Groovy. Why I love Groovy for things like this:
  • All the try/catch/finally blocks associated with JDBC code are removed; though a future refactor will probably end up doing some error checking, but it's not necessary in Groovy
  • JDBC resource management is handled automatically by Groovy; no need to close result sets, statements, etc.
  • Look how easy it is to play with rows from queries!
  • Look how easy it is to work in transactions!
  • It's just easier and quicker to write Groovy scripts for small plugins (like this one I'm working on)
Code:
package com.google.code.sagetvaddons.sop

import groovy.sql.Sql

import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.SQLException

import sagex.api.AiringAPI
import sagex.api.ShowAPI

@PackageScope
final class DataStore {

	static private final DataStore INSTANCE = new DataStore()
	static final DataStore get() { return INSTANCE }

	Sql sql

	private DataStore() {
		initDb()
	}

	private void initDb() {
		sql = Sql.newInstance('jdbc:h2:tcp://localhost/plugins/sop/sop', 'admin', 'admin', 'org.h2.Driver')
		sql.execute 'CREATE TABLE IF NOT EXISTS airings (id INTEGER NOT NULL, key VARCHAR(255) NOT NULL, val LONGVARCHAR, PRIMARY KEY(id, key))'
		sql.execute 'CREATE TABLE IF NOT EXISTS shows (id VARCHAR(255) NOT NULL, key VARCHAR(255) NOT NULL, val LONGVARCHAR, PRIMARY KEY(id, key))'
	}

	boolean setAiringProperty(def airing, def key, def val) {
		def id = AiringAPI.GetAiringID(airing)
		sql.withTransaction {
			sql.execute "DELETE FROM airings WHERE id = $id"
			sql.execute "INSERT INTO airings (id, key, val) VALUES ($id, ${key.toString()}, ${val.toString()})"
		}
	}

	boolean setShowProperty(def show, def key, def val) {
		def id = ShowAPI.GetShowExternalID(show)
		sql.withTransaction {
			sql.execute "DELETE FROM shows WHERE id = $id"
			sql.execute "INSERT INTO shows (id, key, val) VALUES ($id, ${key.toString()}, ${val.toString()})"
		}
	}

	String getAiringProperty(def airing, def key, def defVal) {
		try {
			def row = sql.firstRow("SELECT val FROM airings WHERE id = ${AiringAPI.GetAiringID(airing)} AND key = ${key.toString()}")
			return row ? row[0].toString() : String.valueOf(defVal)
		} catch(SQLException e) {
			return String.valueOf(defVal)
		}
	}

	String getShowProperty(def show, def key, def defVal) {
		try {
			def row = sql.firstRow("SELECT val FROM shows WHERE id = ${ShowAPI.GetShowExternalID(show)} AND key = ${key.toString()}")
			return row ? row[0].toString() : String.valueOf(defVal)
		} catch(SQLException e) {
			return String.valueOf(defVal)
		}
	}
	
	void cleanup() {
		sql.withTransaction {
			sql.eachRow('SELECT id FROM airings') {
				if(!AiringAPI.GetAiringForID(it[0]))
					sql.execute("DELETE FROM airings WHERE id = ${it[0]}")
			}
		}
		
		sql.withTransaction {
			sql.eachRow('SELECT id FROM shows') {
				if(!ShowAPI.GetShowForExternalID(it[0]))
					sql.execute("DELETE FROM shows WHERE id = ${it[0]}")
			}
		}
	}
}
Try to implement this DataStore class in such a little amount of Java code!

As I say, I've actually installed this plugin on my test server and it's running happily so SageTV plugins written in Groovy is definitely a possibility! A wiki doc with a few more details is in the works and I'll post the link when it's done.

EDIT: Entire source code for plugin is here.
__________________
Twitter: @ddb_db
Server: Intel i5-4570 Quad Core, 16GB RAM, 1 x 128GB OS SSD (Win7 Pro x64 SP1), 1 x 2TB media drive
Capture: 2 x Colossus
STB Controller: 1 x USB-UIRT
Software:Java 1.7.0_71; SageTV 7.1.9
Clients: 1 x HD300, 2 x HD200, 1 x SageClient, 1 x PlaceShifter
Plugins: Too many to list now...

Last edited by Slugger; 04-12-2011 at 08:06 PM. Reason: Add link to plugin src
Reply With Quote
  #2  
Old 04-12-2011, 08:58 PM
PLUCKYHD PLUCKYHD is offline
SageTVaholic
 
Join Date: Dec 2007
Posts: 6,257
Oh thanks for sharing definately peaked my interest.
Reply With Quote
  #3  
Old 04-12-2011, 10:09 PM
evilpenguin's Avatar
evilpenguin evilpenguin is offline
SageTVaholic
 
Join Date: Aug 2003
Location: Seattle, WA
Posts: 3,696
Well, of coarse, you had me at 'scripting'

Groovy's got some pretty robust webscraping libraries, I may have to play around with this
__________________
Clients: 1xHD200 Connected to 50" TH-50PZ750U Plasma
Server : Shuttle SFF SSH55J2 w/ Win7 Home, SageTV v7, Core i3 540, 2GB RAM, 30GB SSD for OS, 1.5TB+2x1TB WDGP for Recordings, BluRay, 2xHDHR, 1xFirewire
SageTV : PlayOn, SJQ, MediaShrink, Comskip, Jetty, Web Client, BMT


Having a problem? Don't forget to include a log! (Instructions for: PlayOn For SageTV v1.5, MediaShrink)
Reply With Quote
  #4  
Old 04-13-2011, 06:42 AM
stuckless's Avatar
stuckless stuckless is offline
SageTVaholic
 
Join Date: Oct 2007
Location: London, Ontario, Canada
Posts: 9,713
That's very cool.
Reply With Quote
  #5  
Old 05-11-2011, 02:58 PM
evilpenguin's Avatar
evilpenguin evilpenguin is offline
SageTVaholic
 
Join Date: Aug 2003
Location: Seattle, WA
Posts: 3,696
I'm up and running with Groovy + Eclipse, any tips you want to share before I dive in?
__________________
Clients: 1xHD200 Connected to 50" TH-50PZ750U Plasma
Server : Shuttle SFF SSH55J2 w/ Win7 Home, SageTV v7, Core i3 540, 2GB RAM, 30GB SSD for OS, 1.5TB+2x1TB WDGP for Recordings, BluRay, 2xHDHR, 1xFirewire
SageTV : PlayOn, SJQ, MediaShrink, Comskip, Jetty, Web Client, BMT


Having a problem? Don't forget to include a log! (Instructions for: PlayOn For SageTV v1.5, MediaShrink)
Reply With Quote
  #6  
Old 05-11-2011, 10:36 PM
Slugger Slugger is offline
SageTVaholic
 
Join Date: Mar 2007
Location: Kingston, ON
Posts: 4,008
Quote:
Originally Posted by evilpenguin View Post
I'm up and running with Groovy + Eclipse, any tips you want to share before I dive in?
Not really, except for one tip: If you're not familiar with Groovy then take the time to learn and experiment on how to do things the "groovy way." I never even heard of Groovy until I started researching SJQv4 and my early script examples show that. My first Groovy scripts read almost like 100% Java then as you study my later examples over time you'll see I've started to learn the groovy way to do some things and then you really start to see it if you look at my plugin that I wrote (in my svn repository under trunk/sop). But then every day I play with Groovy I learn something new - there's lots of things to learn.

Two things I love that come to the top of my head as I write this:

Java:

Code:
// Check for string equality
if("FOO".equals(myvar)) { 
   // do whatever
}
Groovy:

Code:
if("FOO" == myvar) {
  // do whatever
}
They are both equivalent in Groovy, but the latter does not work (as intended) in Java, of course. I didn't even know of this for probably 2 or 3 months after I started with Groovy.

#2 Groovy thing off the top of my head: switch statements that work on any type:

Code:
def val = 'groovy'

switch(val) {
   case 'x': // whatever
   case 'y':
   default:
}
If you're switching on numbers you can even use ranges as a case, but I don't show that here because the exact syntax slips my mind.

Anyway, my point is that once you're comfortable with Groovy and learn some groovy shortcuts then I have no doubt plugin dev will be much, much quicker than doing the equivalent in Java, but there is some learning to do.

Note: Don't forget to depend on the groovy plugin to ensure your plugin users download and install the groovy runtime needed to run your groovy code. The current groovy plugin is at version 1.7.10. I intend to keep it there until I return to coding in the fall and then I'll probably upgrade to the recently released 1.8.0, which has a very groovy feature that finally allows the timing out of scripts automatically, but 1.7 -> 1.8 looks like too much of a change for me to be tackling over the summer.
__________________
Twitter: @ddb_db
Server: Intel i5-4570 Quad Core, 16GB RAM, 1 x 128GB OS SSD (Win7 Pro x64 SP1), 1 x 2TB media drive
Capture: 2 x Colossus
STB Controller: 1 x USB-UIRT
Software:Java 1.7.0_71; SageTV 7.1.9
Clients: 1 x HD300, 2 x HD200, 1 x SageClient, 1 x PlaceShifter
Plugins: Too many to list now...

Last edited by Slugger; 05-11-2011 at 10:38 PM.
Reply With Quote
  #7  
Old 05-15-2011, 07:43 PM
sdsean's Avatar
sdsean sdsean is offline
Sage Expert
 
Join Date: Jul 2008
Posts: 571
Groovy seems very very similar to JavaScript. . . and btw couldn't you do the same thing
with JavaScript itself?? You could just use the Rhino runtime, and then call various apis and what not. . . what does groovy give you specifically (besides just scripting, which I don't want to downplay. . is TOTALLY Cool).
__________________
AMD Ryzen 9 3900XT 12 Core+HT, 64GB DDR5, GeForce 1060, MSI Prestige x570 Creation Mobo, SIIG 4 port Serial PCIe Card, Win10, 1TB M.2 SSD OS HDD, 1 URay HDMI Network Encoder, 3 HD-PVR, 4 DirecTV STB serial tuned


Reply With Quote
  #8  
Old 05-15-2011, 09:11 PM
Slugger Slugger is offline
SageTVaholic
 
Join Date: Mar 2007
Location: Kingston, ON
Posts: 4,008
Groovy can be compiled to Java bytecode, which means that Sage can directly load Groovy code and run it as a plugin implementation class.

To use other languages, such as JavaScript, as a Sage plugin requires one of two things:

1) The language in use must compile directly to Java bytecode to be used directly by Sage as a plugin implementation class. To my knowledge, only Groovy supports this.

2) If your language of choice doesn't support #1 then you must write a Java wrapper that creates a JSR-223 script engine (or equivalent, depending on which language you want to use) in Java that then calls your scripts at runtime. This is certainly possible, but seems like a lot of overhead given that Groovy relieves you of all of these necessities.

In a nutshell, Groovy scripts can be compiled to Java bytecode and once they are then Sage sees them as nothing more than Java code that depends on the Groovy runtime jar file. This, to me, makes it the perfect candidate if you're looking at scripting for Sage plugins.
__________________
Twitter: @ddb_db
Server: Intel i5-4570 Quad Core, 16GB RAM, 1 x 128GB OS SSD (Win7 Pro x64 SP1), 1 x 2TB media drive
Capture: 2 x Colossus
STB Controller: 1 x USB-UIRT
Software:Java 1.7.0_71; SageTV 7.1.9
Clients: 1 x HD300, 2 x HD200, 1 x SageClient, 1 x PlaceShifter
Plugins: Too many to list now...

Last edited by Slugger; 05-15-2011 at 09:14 PM.
Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
The "What's your favorite plugins?" thread (screenshots welcome) mkanet SageTV v7 Customizations 15 12-27-2010 01:29 PM
Are plugins: "Ortus MQ" & "SageTV H2 Mobile Database" needed? mkanet SageTV Customizations 2 12-06-2010 06:56 AM
"Backdrops" "SageTV" "Covers" folders - what's creating them mp328 Sage My Movies 4 09-20-2010 05:31 PM
"Set defaults" for Series forgets "Keep"/"Auto-delete" setting maxpower SageMC Custom Interface 9 05-14-2008 09:44 PM
Installing/Uninstalling "Got All Media" caused Sage to report "No capture device" VorpalBlade Hardware Support 3 10-15-2005 01:30 AM


All times are GMT -6. The time now is 12:57 PM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2023, vBulletin Solutions Inc.
Copyright 2003-2005 SageTV, LLC. All rights reserved.