Permalink
Browse files

Enable SageTV to load plugins from a SageTVPluginsDev.d directory in …

…addition to the current SageTVPluginsDev.xml.
  • Loading branch information...
1 parent ace23d7 commit a4b97d94f6e858545f6ad1f69a2aefe87d3f0d83 @stuckless stuckless committed Sep 2, 2016
Showing with 190 additions and 10 deletions.
  1. +15 −0 SageTVPluginsDev.md
  2. +43 −3 java/sage/FileDownloader.java
  3. +132 −7 java/sage/plugin/CorePluginManager.java
View
@@ -0,0 +1,15 @@
+# Developing Plugins for SageTV 9
+
+For creating plugins and understanding the Plugin manifest, lifecycle, etc, checkout the [Developing SageTV Plugins](https://forums.sagetv.com/forums/showthread.php?t=51741) document.
+
+## Adding Plugins to SageTV at Development Time
+When you are developing Plugins, you can add them to the `SAGE_HOME/SageTVPluginsDev.xml` file (V7 compatible) or you can use the new V9 model that uses the `SAGE_HOME/SageTVPluginsDev.d/` directory.
+
+To use the `SAGE_HOME/SageTVPluginsDev.d/` you will need to set `devmode=true`
+ in the `Sage.properties`.
+
+SageTV will load the plugins from the .xml file and the directory, by loading the xml file first, and then loading the directory based plugins.
+
+The advantage of using the directory approach is that you can simply drop your plugin manifest xml AND your plugin zip files in the directory. When SageTV loads the plugins from that directory it will update the plugin manifest and version numbers so that SageTV will always think it is newer and can install it.
+
+Plugin resolver will look for filenames in the `SageTVPluginsDev.d/` dir when downloading and installing plugins. ie, if your published url is something like `http://bintray/opensagetv/plugins/myplugin-1.0.zip` then when loading the xml from the `SAGE_HOME/SageTVPluginsDev.d/` directory, SageTV will check if a filename, `myplugin-1.0.zip` exists, and if it does exist, SageTV will rewrite the download url to be a file reference and install it from that local location. No need to run your http server and no need to publish your file to get sagetv to install it.
@@ -17,7 +17,8 @@
import sage.io.BufferedSageFile;
import sage.io.LocalSageFile;
-import sage.io.RemoteSageFile;
+import java.io.IOException;
+import java.net.URI;
public class FileDownloader extends SystemTask
{
@@ -152,7 +153,7 @@ public synchronized Object downloadFile(String serverName, String srcFile, java.
if (Sage.client && serverName == null)
serverName = Sage.preferredServer;
remoteUIXfer = uiMgr != null && uiMgr.getUIClientType() == UIClient.REMOTE_UI && uiMgr.hasRemoteFSSupport();
- if (serverName != null && (serverName.startsWith("http:") || serverName.startsWith("https:") || serverName.startsWith("ftp:")))
+ if (serverName != null && (serverName.startsWith("http:") || serverName.startsWith("https:") || serverName.startsWith("ftp:") ||serverName.startsWith("file:")))
remoteUIXfer = false;
if (serverName == null && !remoteUIXfer)
return Boolean.FALSE;
@@ -165,7 +166,11 @@ public synchronized Object downloadFile(String serverName, String srcFile, java.
return Boolean.FALSE;
gotSMBAccess = true;
}
- mySrcFile = (srcFile == null) ? null : new java.io.File(srcFile);
+ if (myServerName!=null && myServerName.startsWith("file:"))
+ mySrcFile = new java.io.File(URI.create(myServerName));
+ else
+ mySrcFile = (srcFile == null) ? null : new java.io.File(srcFile);
+
myDestFile = destFile;
if (!remoteUIXfer)
{
@@ -287,6 +292,16 @@ else if (remoteUIXfer)
}
fsXferOpWeak = new java.lang.ref.WeakReference(fsXferOp);
}
+ else if (myServerName!=null && mySrcFile!=null && mySrcFile.isFile() && myServerName.startsWith("file:"))
+ {
+ if (Sage.DBG) System.out.println("FileDownloader: Local File Copy: From: " + mySrcFile + " to " + destFile);
+ // file url passed as the url to download
+ if (!mySrcFile.exists())
+ {
+ cleanup();
+ return Boolean.FALSE;
+ }
+ }
else
{
try
@@ -376,6 +391,10 @@ else if (remoteUIXfer)
{
remoteUITaskRun();
}
+ if (myServerName!=null && myServerName.startsWith("file:"))
+ {
+ fileTaskRun();
+ }
else
{
stvTaskRun();
@@ -396,6 +415,27 @@ else if (remoteUIXfer)
}
}
+ private void fileTaskRun() {
+ try {
+ downloadedBytes=myDestFile.length();
+ statusMessage="100%";
+
+ if (mySrcFile.equals(myDestFile)) {
+ succeeded();
+ return;
+ }
+
+ IOUtils.copyFile(mySrcFile, myDestFile);
+ succeeded();
+ }
+ catch (IOException e)
+ {
+ if (Sage.DBG) System.out.println("ERROR during file download/copy of:" + e);
+ Sage.printStackTrace(e);
+ statusMessage = "Error:" + e.toString();
+ }
+ }
+
private void cleanup()
{
if (gotMSAccess)
@@ -21,8 +21,15 @@
import org.xml.sax.helpers.*;
import java.io.File;
+import java.io.FilenameFilter;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
import java.util.List;
+import java.util.regex.Pattern;
/**
* This class is responsible for managing the currently active plugins in the system
@@ -437,6 +444,8 @@ private synchronized void loadRepoPlugins(List<File> repoXmlFiles)
// Use a SAX parser for this so we can reuse this code on the embedded systems
if (repoHandler == null)
repoHandler = new RepoSAXHandler();
+ if (devRepoHandler == null && isDevMode())
+ devRepoHandler = new DevRepoSAXHandler();
if (allRepoPluginsTemp == null)
allRepoPluginsTemp = new java.util.HashMap();
else
@@ -466,7 +475,10 @@ private void processRepoXmlFile(java.io.File repoXmlFile)
if (Sage.DBG) System.out.println("Analyzing plugin repository XML file: " + repoXmlFile);
inStream = new java.io.BufferedInputStream(new java.io.FileInputStream(repoXmlFile));
factory.setValidating(false);
- factory.newSAXParser().parse(inStream, repoHandler);
+ if (devRepoHandler!=null && devRepoHandler.canHandleFile(repoXmlFile))
+ factory.newSAXParser().parse(inStream, devRepoHandler);
+ else
+ factory.newSAXParser().parse(inStream, repoHandler);
if (Sage.DBG) System.out.println("Done processing plugin repository XML file["+repoXmlFile+"] " + " repositoryVersion=" + repoVer);
}
catch (Exception e)
@@ -810,6 +822,26 @@ public synchronized boolean refreshAvailablePlugins()
// lastly, add in the "SageTVPluginsDev.xml"
localFiles.add(getLocalPluginFile("SageTVPluginsDev.xml"));
+ if (isDevMode()) {
+ // SageTV developer mode, find all .xml files in the SageTVPluginsDev.d folder and process them
+ // SageTVPluginsDev.d is a cleaner way to dynamically add new plugins to sagetv for development testing
+ // Useful especially when you are working several different plugins and you are trying to coordinate
+ // the dependencies between each, for test installing, etc.
+ File devPluginsDir = getDevPluginsDir();
+ if (devPluginsDir.exists() && devPluginsDir.isDirectory()) {
+ File plugins[] = devPluginsDir.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File file, String s) {
+ return s.toLowerCase().endsWith(".xml");
+ }
+ });
+
+ if (plugins != null) {
+ localFiles.addAll(Arrays.asList(plugins));
+ }
+ }
+ }
+
loadRepoPlugins(localFiles);
}
@@ -818,6 +850,18 @@ public synchronized boolean refreshAvailablePlugins()
return true;
}
+ /**
+ * Returns the "devmode" Server property. devmode is a special mode for running SageTV Server. If Dev Mode is true
+ * then a few things might happen.
+ * 1. SageTV Plugins will look in the SageTVPluginsDev.d/ folder for plugin xmls and zip files
+ * 2. When installing a plugin, if the plugin filename is in the SageTVPluginsDev.d/ folder then it will be "downloaded" from that location.
+ * 3. MD5 will be ignored when installing from the SageTVPluginsDev.d/ folder
+ * @return
+ */
+ public static boolean isDevMode() {
+ return getServerBoolProperty("devmode", false);
+ }
+
private void postMessagesForAvailableUpdates()
{
String[] myPluginIDs = (String[]) myPlugins.keySet().toArray(Pooler.EMPTY_STRING_ARRAY);
@@ -2266,6 +2310,11 @@ else if ("FanArt".equals(packages[i].type))
return needRestart ? "RESTART" : "OK";
}
+ public static File getDevPluginsDir() {
+ // sagetv appears to assume we'll always be run from the SageTV home directory
+ return new File("SageTVPluginsDev.d");
+ }
+
private boolean canModifyPath(java.io.File f)
{
if (pendingFilesystem.containsKey(f))
@@ -2624,6 +2673,7 @@ public String getPluginProgress()
private java.util.Map latestRepoPluginsTemp;
private SAXParserFactory factory = SAXParserFactory.newInstance();
private DefaultHandler repoHandler;
+ private DevRepoSAXHandler devRepoHandler;
private String repoVer = "";
private boolean needRestart;
private int globalInstallCount;
@@ -2641,12 +2691,12 @@ public String getPluginProgress()
private class RepoSAXHandler extends DefaultHandler
{
- private String current_tag;
- private StringBuffer buff = new StringBuffer();
- private PluginWrapper currPlugin;
- private PluginWrapper.Dependency currDependency;
- private PluginWrapper.Package currPackage;
- private boolean osRestrictions;
+ String current_tag;
+ StringBuffer buff = new StringBuffer();
+ PluginWrapper currPlugin;
+ PluginWrapper.Dependency currDependency;
+ PluginWrapper.Package currPackage;
+ boolean osRestrictions;
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
{
if ("PluginRepository".equalsIgnoreCase(qName))
@@ -2888,6 +2938,81 @@ private long parseMetaDate(String s)
}
}
+ /**
+ * DevRepoSAXHandler upgrades the Location and Version tags for dev plugins in the SageTVPluginsDev.d directory
+ * ensuring that if there is a local package install, then the URL is set to a File url for it, and that the
+ * version of the plugin is updated so that sagetv will think it needs to install it every time. This allows
+ * for continuous pushing of packages to the SageTVPluginDev.d directory without having to manually update the
+ * version field to get sagetv to install it.
+ */
+ public class DevRepoSAXHandler extends RepoSAXHandler
+ {
+ private String getFileName(String file)
+ {
+ URI uri = URI.create(file);
+ return new File(uri.getPath()).getName();
+ }
+
+ public void endElement(String uri, String localName, String qName)
+ {
+ boolean handled=false;
+
+ if (currPackage != null)
+ {
+ if ("Location".equalsIgnoreCase(qName))
+ {
+ String data = buff.toString().trim();
+ if (data.length()>0)
+ {
+ File localFile = new File(getDevPluginsDir(), getFileName(data));
+ if (localFile.exists())
+ {
+ try
+ {
+ // set the download url to be the local file
+ currPackage.url = localFile.toURI().toURL().toString();
+ // set the md5 automatically from the local file
+ currPackage.md5 = IOUtils.calcMD5(localFile);
+ // super will already populate the version, but in dev mode, we force an increment on the version
+ // so that sagetv will try to install it every time
+ // LIMITATION: can only update once a minute :)
+ SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmm");
+ currPlugin.setVersion(currPlugin.getVersion()+"."+sdf.format(new Date()));
+ if (Sage.DBG) System.out.println("Changed Package Url, MD5, and Version to " + currPackage.url + ", "+currPackage.md5+", " + currPlugin.getVersion() + " for " + currPlugin.getName() + "("+currPlugin.getId()+")");
+ handled=true;
+ }
+ catch (MalformedURLException e)
+ {
+ System.out.println("Failed to override package url with local url for " + localFile);
+ }
+ }
+ }
+ }
+ else if ("MD5".equalsIgnoreCase(qName))
+ {
+ // only set the MD5 from the xml if we haven't already calculated it
+ if (currPackage.md5!=null)
+ handled=true;
+ }
+ }
+
+ if (handled)
+ {
+ if (qName.equals(current_tag))
+ buff = new StringBuffer();
+ }
+ else
+ {
+ super.endElement(uri, localName, qName);
+ }
+ }
+
+ public boolean canHandleFile(File repoXmlFile)
+ {
+ return repoXmlFile!=null && repoXmlFile.getParentFile()!=null && repoXmlFile.getParentFile().getName().equals("SageTVPluginsDev.d");
+ }
+ }
+
public static final java.util.Comparator intStringSorter = new java.util.Comparator()
{
public int compare(Object o1, Object o2)

0 comments on commit a4b97d9

Please sign in to comment.