mickle026 401 Posted May 6, 2023 Share Posted May 6, 2023 OK, I have a problem which I wish to solve, but I alone seem unable to.... I can create ExternalIds without issue, and I can check in my code if the ExternalId Class even exists, but I want to be able to create and destroy these classes depending on what else is loaded elsewhere in a plugin or my other plugins. List the classes Type[] types = Assembly.GetExecutingAssembly().GetTypes(); List<Type> myTypes = new List<Type>(); foreach (Type t in types) { if (t.Namespace == "NamespaceImChecking") LogInfoTXT(MyLog, DateTime.Now.ToString("dd.MMM.yyy - HH'.'mm'.'ss") + $" [Test] {t.Name}"); } External Ids Duplicate if I use them in more than one plugin, but I want the option to be there whichever plugin I have installed. I have a few ideas. 1. Create a single plugin that simply adds the external providers I wish to use, but no other functionality. So they are available in the UI. (this is the route I have done so far) 2. Enable and dissable ExternalIds depending on what is already loaded. 3. A mixture of both, enable/dissable and allow user to config. A mixture of both would better suit my needs, as then I can add config options to the plugin pages to switch them on/off as needed in the webui/menus. However to do that I would have to create the ExternalId on demand, I looked on line and it shows expandoobject and others, but I am lost here Does anyone have any clues how to do this? Is it possible? Ie, I want to be able to add a checkbox to the UI to turn on/off an id like this: ie, enable/dissable a class. public class ExternalBluRayComPerson : IExternalId { public string Name => "blu-ray.com"; public string ProviderName => "BRC"; public string Key => "BRC"; public string UrlFormatString => null; public bool Supports(IHasProviderIds item) => item is Person; } Link to comment Share on other sites More sharing options...
softworkz 3340 Posted May 6, 2023 Share Posted May 6, 2023 (edited) It is possible to add code at runtime with Reflection.Emit (complicated), but that won't help you here, because Emby is loading types from plugins at the time when a plugin is loaded and before any plugin code is executed. You could probably get some code running before that happens, by using a module initializer (complicated again) - but that still wouldn't work, because even when you would create classes using Emit at that point in time, Emby wouldn't get them, because it reads the types from your plugin assembly and the emitted types wouldn't be part of that. The most natural way for this would be to have a common assembly (library/dll) used by all of your plugins, but Emby doesn't allow your plugin dll to have and bring-in any dependencies. While there is some way (again complicated) to include dependency libs in a plugin, this won't work properly when you have multiple plugins with the same dependency lib. So, it seems to me that you've already found the best possible way: a separate plugin for managing the IDs. You can "switch" those on and of by returning always 'false' from 'Supports' and returning a nonsense Key (be sure to use different nonsense keys for each). Edited May 6, 2023 by softworkz 2 Link to comment Share on other sites More sharing options...
mickle026 401 Posted May 6, 2023 Author Share Posted May 6, 2023 Looks as if the only way for me to prevent duplication in the ui is a standalone dll with them and leave them out of other plugins. Being in the UI part is not strictly necessary, (just convenient) as they work without even being included in the UI, I can add them and read them and write them in code without the class, but I would have prefered them in the UI to be toggled on/off. Thanks for taking the time to reply. 1 Link to comment Share on other sites More sharing options...
Cheesegeezer 3087 Posted May 6, 2023 Share Posted May 6, 2023 Just thinking about this coukd the BasePlugin be loaded and allow for an interface be used for those types? Then the other plugins could set a Task.Delay before initialisation of server entry point. or is the plugin architecture just purely a reflection loaded type and doesn’t allow for interaction between other plugins. having said that, there is c# and JS code that allows you to get the options(config) for sure!!! and this can be done by pluginId or by name also. not sure if that might help you. Link to comment Share on other sites More sharing options...
softworkz 3340 Posted May 6, 2023 Share Posted May 6, 2023 1 hour ago, mickle026 said: Looks as if the only way for me to prevent duplication in the ui is a standalone dll with them and leave them out of other plugins. 7 hours ago, softworkz said: The most natural way for this would be to have a common assembly (library/dll) used by all of your plugins, but Emby doesn't allow your plugin dll to have and bring-in any dependencies. While there is some way (again complicated) to include dependency libs in a plugin, this won't work properly when you have multiple plugins with the same dependency lib. Link to comment Share on other sites More sharing options...
Cheesegeezer 3087 Posted May 6, 2023 Share Posted May 6, 2023 2 minutes ago, softworkz said: I hear you chief, as you are part of the core team you have access to way more stuff, “we” plugin devs are quite resourceful in find workarounds. Hehe. this is why i made suggestions for mickle. I really think the best way is to access the base plugin UI from the other plugins he wants to use. This can be triggers at schedule task run time rather than server entrypoint. not sure if you agree? I’m also quite interested in this topic. It’s a great one that Mickle has posted Link to comment Share on other sites More sharing options...
softworkz 3340 Posted May 6, 2023 Share Posted May 6, 2023 43 minutes ago, Cheesegeezer said: or is the plugin architecture just purely a reflection loaded type and doesn’t allow for interaction between other plugins That's difficult and likely to go wrong and have strange effects. For example, you can't control the order in which Emby loads the plugins and if one plugin requires a reference to another plugin, and that plugin isn't available at load time, then it might not get loaded properly. Of course you can do things like "dynamic" (not nice) or use some framework-provided mechanism for communication (which would be without strong typing). Other possibilities would be using a shared file to communicate, named pipes or RPC (Windows only), the network stack via local loopback interface... That are the first few things that come to my mind. Link to comment Share on other sites More sharing options...
Cheesegeezer 3087 Posted May 6, 2023 Share Posted May 6, 2023 3 minutes ago, softworkz said: That's difficult and likely to go wrong and have strange effects. For example, you can't control the order in which Emby loads the plugins and if one plugin requires a reference to another plugin, and that plugin isn't available at load time, then it might not get loaded properly. completely agree with this 3 minutes ago, softworkz said: Of course you can do things like "dynamic" (not nice) or use some framework-provided mechanism for communication (which would be without strong typing). yup more generic, depends on how much reliance is required from base side… coukd get ugly. 3 minutes ago, softworkz said: Other possibilities would be using a shared file to communicate, named pipes or RPC (Windows only), the network stack via local loopback interface... That are the first few things that come to my mind. Not a good solution, as it’s isolated to one system and Emby is multi OS friendly. thanks Softy! Appreciate your informative responses Link to comment Share on other sites More sharing options...
softworkz 3340 Posted May 6, 2023 Share Posted May 6, 2023 (edited) 10 minutes ago, Cheesegeezer said: Not a good solution, as it’s isolated to one system and Emby is multi OS friendly. Shared file and IP loopback would work cross-platform. But here's a better and easier idea (there are really a lot for ways): var queue = new ConcurrentQueue<Tuple<string, string>>(); AppDomain.CurrentDomain.SetData("MyPluginMessageQueue", queue); All plugins check with .GetData() on load whether the queue exists already, if not it creates on and registers it in the AppDomain. Now, all plugins - once loaded - will have access to the same queue. That queue can be used to send and receive messages between plugins. You can achieve working with typed data by serializing (e.g. to JSON). You can have the same classes in multiple plugins (but important: each under a different namespace!). Then you can serialize and deserialize the data and work with strongly typed classes in all plugins. That's just a rough example, but it's a very robust and reliable method. Edited May 6, 2023 by softworkz 1 Link to comment Share on other sites More sharing options...
Cheesegeezer 3087 Posted May 6, 2023 Share Posted May 6, 2023 3 minutes ago, softworkz said: Shared file and IP loopback would work cross-platform. But here's a better and easier idea (there are really a lot for ways): var queue = new ConcurrentQueue<Tuple<string, string>>(); AppDomain.CurrentDomain.SetData("MyPluginMessageQueue", queue); All plugins check with .GetData() on load whether the queue exists already, if not it creates on and registers it in the AppDomain. Now, all plugins - once loaded - will have access to the same queue. That queue can be used to send and receive messages between plugins. You can achieve working with typed data by serializing (e.g. to JSON). You can have the same classes in multiple plugins (but important: each under a different namespace!). Then you can serialize and deserialize the data and work with strongly typed classes in all plugins. That's just a rough example, but it's a very robust and reliable method. Hence… why you are the guru!! nice solution!! Hope it help mickle out and it maybe useful for me also in the future. thanks fella!! Danke!! 1 Link to comment Share on other sites More sharing options...
softworkz 3340 Posted May 6, 2023 Share Posted May 6, 2023 4 minutes ago, Cheesegeezer said: thanks fella!! Danke!! Sure, you're welcome! There's an even easier way, if the communication between plugins doesn't involve a high volume of communication: Emby SDK Reference: IConfigurationManager You can do as follows: You choose a configuration name which all the plugins will use All plugins register for the event Emby SDK Reference: IConfigurationManager.NamedConfigurationUpdated on startup Any plugin can at any time call Emby SDK Reference: IConfigurationManager.SaveConfiguration(String, Object) For the object data, you'd use "string" (or if that wouldn't work, use something like Tuple<string, string>) When one plugin saves, all the other plugins get the NamedConfigurationUpdated even fired and can act accordingly So essentially, you'd "mis"-use the config store for communication. This would be fine for low-volume communication at least. Link to comment Share on other sites More sharing options...
Cheesegeezer 3087 Posted May 6, 2023 Share Posted May 6, 2023 1 hour ago, Cheesegeezer said: having said that, there is c# and JS code that allows you to get the options(config) for sure!!! and this can be done by pluginId or by name also. 4 minutes ago, softworkz said: So essentially, you'd "mis"-use the config store for communication. This would be fine for low-volume communication at least Yep this was my initial thoughts if a low info exchange was required 1 Link to comment Share on other sites More sharing options...
softworkz 3340 Posted May 6, 2023 Share Posted May 6, 2023 1 minute ago, Cheesegeezer said: Yep this was my initial thoughts if a low info exchange was required Sorry, I had missed that. Eventually it really depends on the use case and the things mentioned above should be appropriate for @mickle026's case. Final note: Of course almost everything is possible in some way, but as this is about Emby, suggesting such things would be like shooting in my own foot, so in this context we really need to stick to reliable methods that are easy to understand and easy to implement (and safe for Emby Server ) 1 Link to comment Share on other sites More sharing options...
Cheesegeezer 3087 Posted May 6, 2023 Share Posted May 6, 2023 agree! The reality is to deliver a robust solution.. and using the emby internal Interfaces are the best way to deliver the plugin, as they have been written to ensure stability. great discussion tho!! And i need to go and look at some concepts I’ve not come across yet… loopbacks is one for sure 1 Link to comment Share on other sites More sharing options...
mickle026 401 Posted May 6, 2023 Author Share Posted May 6, 2023 18 hours ago, softworkz said: So, it seems to me that you've already found the best possible way: a separate plugin for managing the IDs. You can "switch" those on and of by returning always 'false' from 'Supports' and returning a nonsense Key (be sure to use different nonsense keys for each). So how do I do this? - its beyond my skill set I believe, but i am trying - still learning this c# stuff The code below doesn't seem to work I put some code in the IService Get Function just to test this idea and see If I can get it to work. [Route("/APID/ToggleId", "GET", Summary = "ToggleId")] public class ToggleId : IReturn<string> { public string Text { get; set; } } public string Get(ToggleId result) { Person item = new Person(); IExternalId toggle = new ExternalBluRayComPerson(); toggle.Supports(item).Equals(false); toggle.Key.Equals("Gobbledegook"); return _json.SerializeToString(new ToggleId() { Text = $"Test Succeeded - Check if the Id is visible in metadata editor!" }); } The ID is still visible, so I am doing something wrong. Thanks Link to comment Share on other sites More sharing options...
softworkz 3340 Posted May 6, 2023 Share Posted May 6, 2023 public class MyExternalId : IExternalId { public static bool IsEnabled { get; set; } = true; public string Name { get { return "My External ID Example"; } } public string Key { get { if (IsEnabled) { return "MyExtId"; } return "dkjgfogihrgtoihn"; } } public string UrlFormatString { get { return "http://my.extid.com/api?redir={0}"; } } public bool Supports(IHasProviderIds item) { if (IsEnabled) { return item is Series; } return false; } } 1 Link to comment Share on other sites More sharing options...
mickle026 401 Posted May 6, 2023 Author Share Posted May 6, 2023 Ah! You are creating an IsEnabled option to the provider! - I never even thought of that idea! Thank you so much! Link to comment Share on other sites More sharing options...
softworkz 3340 Posted May 7, 2023 Share Posted May 7, 2023 It's a class and you cannot know when and where any instances of this class are created by Emby. The key to the solution is not that IsEnabled is a property of the provider class. The key is that it's static:.It is not bound to any specific instance of the class - it's rather a global singleton value, and so it doesn't matter when and how many instances Emby may create. Using statics is generally not a good pattern, but as we're not into professional application design patterns here, this is nice for its simplicity. The static property could be located anywhere, that doesn't matter. Or - like mentioned above - you could store the settings (that indicate which IDs to enable and which not) in the AppDomain or In Emby's Emby SDK Reference: IConfigurationManager. In the latter case, you would add a constructor to the ID class like follows. This works, because Emby server is using dependency injection to create object instance. public class MyExternalId : IExternalId { private readonly IConfigurationManager configurationManager; private bool? cachedIsEnabled; /// <summary>Initializes a new instance of the <see cref="T:System.Object" /> class.</summary> public MyExternalId(IConfigurationManager configurationManager) { this.configurationManager = configurationManager; } private bool IsEnabled { get { if (!cachedIsEnabled.HasValue) { var config = this.configurationManager.GetConfiguration("MyConfigKey") as string; // now determine for the config string whether this ID should be enabled, so true or false for the next line: this.cachedIsEnabled = false; } return cachedIsEnabled.Value; } } public string Name { get { return "My External ID Example"; } } public string Key { get { if (this.IsEnabled) { return "MyExtId"; } return "dkjgfogihrgtoihn"; } } public string UrlFormatString { get { return "http://my.extid.com/api?redir={0}"; } } public bool Supports(IHasProviderIds item) { if (this.IsEnabled) { return item is Series; } return false; } } In this case the IsEnabled property is playing a totally different role: It's not static and not public. Here, you don't make any setting on the class (instance) from outside to switch on or off, but instead, the class finds that out on its own by reading the configuration. This is a much better approach, because all functionality is encapsulated inside that class and you don't need any code from elsewhere to set the state. There's a lot of potential for errors when such dependencies exist, i.e. when something needs to happen at one place in order to get something working properly at a different place, while you don't have any control over which happens first... 1 Link to comment Share on other sites More sharing options...
mickle026 401 Posted May 7, 2023 Author Share Posted May 7, 2023 Thanks @softworkz, your answers are always so informative and helpful I am pretty sure I will also try to use this approach in my future apps. 1 Link to comment Share on other sites More sharing options...
mickle026 401 Posted May 7, 2023 Author Share Posted May 7, 2023 (edited) This is exactly what I needed, Big Thankyou to @softworkz I'll post here what I did in case it can help others.... I set them all to off in their classes, so they are all off by default. public static bool IsEnabled { get; set; } = false; Used My config page to turn them on as required. This is a sample image I have more than this. Set my IService to call a static void from within the Get rather than doing it all in the Get method, so its shared call so ServerEntryPoint can also access it too [Route("/APID/ToggleId", "GET", Summary = "ToggleId")] public class ToggleId : IReturn<string> { public string Text { get; set; } } public string Get(ToggleId result) { config = Plugin.Instance.Configuration; Current = this; UpdateProviderToggleSettings(); return _json.SerializeToString(new ToggleId() { Text = "Setting Toggled!" }); } public static void UpdateProviderToggleSettings() { config = Plugin.Instance.Configuration; if (config.MMAboutUrl) { ExternalAboutPage1.IsEnabled = true; } else { ExternalAboutPage1.IsEnabled = false; } // rest of code } Then to get around them not loading at emby start utilised the server entry point to load the config and toggle the true/false settings whether they are on or off public void Run() { var config = Plugin.Instance.Configuration; Plugin.Instance.UpdateConfiguration(Plugin.Instance.Configuration); My_Provider_Ids.UpdateProviderToggleSettings(); } It all works , I'm pleased about this as it was becoming a bit of a nightmare. The External Id works fine without the UI programatically, but it is also very handy to do some of the edits in the UI , this enables me to use it as and when i want Edited May 7, 2023 by mickle026 2 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now