Jump to content

Globally used JS libraries and persist them from a plugin environment


chef

Recommended Posts

chef

Figuring out adding, updating, and persisting globally used javascript files in emby web app utilizing plugin environments to side load the script.

 

 

This is experimental! A proof of concept.

 

 

Part 1: Creating Javascript to run globally outside your plugin environment

 

In this part you can pretty much write whatever javascript you want to run inside emby.

 

The best part is that all of emby's javascript modules should be available to you when the file loads.

 

Objects like the Javascript ApiClient are extremely useful, and are completely available :)

 

The most important part here is to save this javascript file as an "EmbeddedResource" in your plugin!

 

 

Part 2: IServerEntryPoint Run Function - a perfect opportunity

 

In your plugins IServerEntryPoint class, there is a 'Run()' function which runs when the plugin is loaded.

public class ServerEntryPoint : IServerEntryPoint
This is a perfect opportunity to copy the javascript file created in part 1, into Emby Server root directory, and make changes to embys index to load the script. We can also persist this file during emby updates.

 

 

Part 3: Locating the servers root folder:

 

First we're going to utilize Embys API IServerApplicationHost Interface to find out the servers system info, and therefore it's root folder directory:

private IServerApplicationHost host { get; set; }
Next we'll use 'GetSystemInfo()' to request directory data of the Emby Server
var sysInfo = host.GetSystemInfo(CancellationToken.None).Result; //yes calling Result... ...

The closest we can get to the root application folder is the 'programdata' folder (which is where we can find community plugins folders etc.)

 

"EmbyServer/programdata"

 

So we'll have to get our path string without the 'programdata' folder by replaceing 'programdata' with and empty string.

 var serverRootFolder = sysInfo.ProgramDataPath.Replace("programdata", ""); //The full path to the servers root
Now that we have the servers root (which will change across different platforms) we have the opportunity to move around the directory wherever we want!

 

 

Create a variable to with the path to embys index file:

var indexPath        = serverRootFolder + @"system\dashboard-ui\index.html";
And also the path to just the 'dashboard-ui' folder
var dashboardUiPath  = serverRootFolder + @"system\dashboard-ui";
These paths will be important later.

 

Finally, create a variable which holds the name of the embedded resource javascript file (I called mine "Dashboard_Extras_1-0.js"):

var dashboardExtraJs = "Dashboard_Extras_1-0.js";
Step 4: Copy Embedded Resource to the file system:

 

Now create a function inside the ServerEntryPoint which copies an embedded resource from the plugin Class library file into Embys folder structure.

        private static void WriteResourceToFile(string resourceName, string fileName)
        {
            using (var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
            {
                using (var file = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                {
                    resource?.CopyTo(file);
                }
            }
        }
Magical!

 

 

Step 5: When to copy the Resource:

 

Moving back into the Run() function we can create conditions which should help decided when the custom javascript file should be copied into the folder structure.

 

Hint: This should only happen if it doesn't already exist...

 

Note: When Emby updates, it copies over a new dashboard-ui folder, which ultimately removes the custom javascript file. So our plugin should make sure it copies the file back if it doesn't exist.

            if (!File.Exists($"{dashboardUiPath}\\{dashboardExtraJs}"))
            {
                WriteResourceToFile(GetType().Namespace + ".Assets." + dashboardExtraJs, $"{dashboardUiPath}\\{dashboardExtraJs}");
            }
Step 6: Editing the index:

 

We still have to tell the browser where we want to load it from and use it.

 

The fastest and easiest way is to append some '<script>' tags to the server dashboard 'index.html' file.

 

 

The fastest way insert a line into a text file, seems to be by putting each line into a list, inserting our custom line and then writing the list back to the file - saving it in the file system. This function will work.

        private static List<string> GetIndexToList(string indexPath)
        {
            var indexLines = new List<string>();
            using (var sr = new StreamReader(indexPath))
            {
                var line = string.Empty;
                
                while ((line = sr.ReadLine()) != null)
                {
                    indexLines.Add(line);
                }
            }

            return indexLines;
        }
And we call it back in our Run() function like this:
var indexLines = GetIndexToList(indexPath);

            
Now loop through and decide when to insert the 'script' tag lines. (I choose right above the 'apploader.js' file)

            var i = 0;
            for (var l = 0; l <= indexLines.Count - 1; l++)
            {
                if (indexLines[l].Contains("apploader.js")) i = l - 1; //Find line to insert new version of the file (right above the apploader.js)                
            }
            
            if (!indexLines.Exists(l => l == "<script src=\"" + dashboardExtraJs + "\"></script>")) //If the index doesn't have the custom script tag... 
            {
                indexLines.Insert(i, "<script src=\"" + dashboardExtraJs + "\"></script>"); //... insert it
            }
Finally write the index back to the file system:
    
            File.WriteAllLines(indexPath, indexLines); //Save the index file back to the file system for loading.
Here is the entire ServerEntryPoint with the ability to update your javascript file by increasing the version number in it's name:
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;

namespace DashboardExtras
{
    public class ServerEntryPoint : IServerEntryPoint
    {
        private IFileSystem FileSystem { get; set; }
        private ILogger logger         { get; set; }
        private ILogManager LogManager { get; set; }
        private IServerApplicationHost host { get; set; }
        public ServerEntryPoint(IFileSystem file, ILogManager logManager, IServerApplicationHost serverApplicationHost)
        {
            FileSystem = file;
            LogManager = logManager;
            logger     = LogManager.GetLogger(Plugin.Instance.Name);
            host = serverApplicationHost;
        }
        public void Dispose()
        {
            
        }

        public void Run()
        {
            Plugin.Instance.UpdateConfiguration(Plugin.Instance.Configuration);

            var sysInfo = host.GetSystemInfo(CancellationToken.None).Result; //yes calling result...
            var serverRootFolder = sysInfo.ProgramDataPath.Replace("programdata", "");
            logger.Info(serverRootFolder);

            //This will take care of updating and loading the javascript file into the web app when the plugin loads
            const int version = 1;
            var indexPath        = serverRootFolder + @"system\dashboard-ui\index.html";
            var dashboardUiPath  = serverRootFolder + @"system\dashboard-ui";
            var dashboardExtraJs = $"Dashboard_Extras_1-{version}.js";
            
            //Add newest version of the file
            if (!File.Exists($"{dashboardUiPath}\\{dashboardExtraJs}"))
            {
                WriteResourceToFile(GetType().Namespace + ".Assets." + dashboardExtraJs, $"{dashboardUiPath}\\{dashboardExtraJs}");
            }

            //Remove older version of the javascript file from the file system.
            if(File.Exists($"{dashboardUiPath}\\Dashboard_Extras_1-{version - 1}.js")) //<-- plugin updates must happen in increments of 1 in order to do this properly
            {
                File.Delete($"{dashboardUiPath}\\Dashboard_Extras_1-{version - 1}.js");
            }

            var indexLines = GetIndexToList(indexPath);

            var i = 0;
            for (var l = 0; l <= indexLines.Count - 1; l++)
            {
                if (indexLines[l].Contains("apploader.js")) i = l - 1; //Find line to insert new version of the file (right above the apploader.js)
                if (indexLines[l] == $"<script src=\"Dashboard_Extras_1-{version - 1}.js\"></script>") indexLines.RemoveAt(l); //Remove script tags from older version if they exist
            }
            
            if (!indexLines.Exists(l => l == "<script src=\"" + dashboardExtraJs + "\"></script>"))
            {
                indexLines.Insert(i, "<script src=\"" + dashboardExtraJs + "\"></script>");
            }
                
            File.WriteAllLines(indexPath, indexLines); //Save the index file back to the file system for loading.
        }

        private static List<string> GetIndexToList(string indexPath)
        {
            var indexLines = new List<string>();
            using (var sr = new StreamReader(indexPath))
            {
                var line = string.Empty;
                
                while ((line = sr.ReadLine()) != null)
                {
                    indexLines.Add(line);
                }
            }

            return indexLines;
        }

        private static void WriteResourceToFile(string resourceName, string fileName)
        {
            using (var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
            {
                using (var file = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                {
                    resource?.CopyTo(file);
                }
            }
        }
    }
Conclusion:

 

Is it worth it?

 

Maybe... ... you can now add a bunch cool libraries, edit themes quickly, insert extra data into the dashboard, and this will take care of persisting it during updates.

Each time the custom javascript file is updated, copy over the new plugin and it will add it to the Server.

 

 

Is it complicated?

 

Yes, this is not an easy thing to do or figure out. But, I wanted to share it anyway because it can be done.

Edited by chef
  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...