Dear community,
I started this thread some time ago in the internal team forums, but we think it is time to share it with you. It is about three new server plugins for MP2 which - when they are ready - shall provide web services and a web interface for MP2. The reason for starting this thread was that a fellow Team MediaPortal member mentioned that it would be great to have a possibility to edit the metadata stored in our MP2 Server's MediaLibrary. We thought about a separate windows application such as the configuration utility for MP1. But somehow I preferred the idea to do such things in a browser - mainly because we are platform independent and there is no need to install yet another application. So i started reading about web interfaces and web services and we quickly realized that we could do much more things with a browser front end than edit metadata. When you read through this thread you will find a lot of ideas. Let's see how many of them we can realize...
First of all, there is a reason why this thread is called "proof of concept". It means that these three plugins currently do not provide any benefit for a pure user of MP2. So if you are not interested in new technologies and you don't like to try new and cool software even if it is of no practical use, yet, this thread is probably not interesting for you.
Technical Background of the Plugins
For all the others I'll try to explain shortly what these plugins are about. As mentioned there are three plugins:
OwinService provides the possibility to start "WebApps". To do so, it uses the Miscrosoft implementation of Owin, called Katana. A WebApp may contain any kind of service which is in most cases accessible via http and therefore e.g. via your browser. We currently support serving static files (to be able to provide a web interface) and ASP.NET WebAPI. We will probably also support SignalR and the OData extension of WebAPI.
MPAnywhereService
MPAnywhereService uses the OwinService to provide currently two WebApps. One serves static files on port 80, the other serves an ASP.NET WebAPI on port 81.
The static file server is used to provide you with a web interface. This web interface uses Sencha ExtJS to provide you with a modern and snappy "single page application" in your browser. We will later also make use of Sencha Touch to have a tailor made interface for touch devices such as tablets and phones.
MPAnywhereService.Core
OwinService and MPAnywhereService were coded with the intention to be as extendable as anyhow possible by other plugins. Although we could have integrated all the functionality into the MPAnywhereService, I decided not to do so. Instead, MPAnywhereService itself only provides the groundwork for a web interface and web services. It will offer you a web interface - but with no usable functionality in it. It will offer you web services, but without the possibility to get useful data out of them. All this end-user useful functionality will be contained in MPAnywhereService.Core for two reasons: (1) I want to make sure that MPAnywhereService is actually extendable by other plugins. (2) Developers who want to extend MPAnywhereService can use MPAnywhereService.Core as a blueprint for their extensions.
As mentioned before, there is no really useful functionality (such as the possibility to edit metadata), yet, but when it will be integrated, it will be in MPAnywhereService.Core.
How to Try
Enough background - let's get it started. You can download the latest version of the three plugins at the end of this post as zip file. The zip file contains two directories:
Now start a browser on any computer in your local network and type
and of course replace [MP2_Server_Name] with the name or the IP address of the computer on which your MP2 Server is running. What you see is what I called "web interface" and it hopefully explains more than a thousand words...
But i also talked about web services - in particular about ASP.NET WebAPI. To see what this is about, try
where [GUID] is the ID of a particular MediaItem in your MediaLibrary. If you don't know the GUIDs of your MediaItems, you can use the first line above to show all your MediaItems in your MediaLibrary, but be careful when you have 50k songs in it - it may be quite a lot of data
[SearchTerm] can be a part of any text field in your MediaLibrary. (Such as "heaven", "Clapton", etc. - but without the "")
When you use FireFox, you will see your MediaItems as XML in your browser. When you use Internet Explorer, you are prompted to download a .json file. Please do so and open it with a text editor - and you will see the objects in Json format.
That's basically all you can do for now. And I warned you: It is of no use, YET... But at least for me, it's quite cool already
Why publish this thread if it's not ready, yet?
But let me start from the beginning: As you will notice, this thread is public, but read only. The reason is that I want to keep this thread clean and focused on a purely technical discussion. When you read through this thread (and I encourage all of you, who were interested enough to read until here, to do so) you will realize that I used this thread as kind of a public memory and wrote down a lot of things I learned about web services in the last months. The reason is easy: I am no web developer. In my real live I'm not a developer at all. So for me all this stuff was new, but I found it interesting enough to write a lot of it down in this thread.
This, however, does not mean that we don't want your comments on this. To the contrary! The main reason to publish this thread is to get your feedback and help. But please do so in the MPAnywhere Public Discussion Thread [INSERT LINK WHEN READY]. When should you post in that thread? Well.....
When you read through this thread, you will notice that there have already been some complete rewrites of the plugins. As a result, the information contained at the beginning of this thread is already outdated. But I did not want to edit all the threads to bring them up to date because I want to keep them as kind of a history so that interested people can still read why we went one route and not the other. When I find the time, I will create a few pages in our Wiki, where I will try to always have the latest structure and functionality explained. But please bear with me - this may take a while...
When is this ready?
This mainly depends on you If we find 20 new and skilled developers willing to spend all their free time on this project, we may have this feature complete in 3 months. If we don't, it may take another 2 years until you can do something useful with it. So if you want to use this as soon as possible, please do not ask this question - but please help us to make it happen...
And now let's get the discussion started in the other thread. And remember: There is no comment which is not useful (except for asking when it's ready ). Please share all your thoughts - we need them!
Michael
-----------------------------------------------------------------------------------------------------------------------------
[Original Thread]
Hi everybody,
I want to share something in a very, very, very early stage for discussion. It already works quite impressively for me - although it currently doesn't provide any benefit for the user, yet. I cannot rule out that there are still a lot of bugs and I know for sure that there are for MP2 purposes many many code comments missing and a general code cleanup is also necessary. This is the reason why I did not post in this (readonly) public thread, which however inspired me to start some work on it. If you think we can publish it there to let our community know what's going on, please feel free.
I wasn't sure whether or not I should post it here already, but there are so many conceptual design decisions to make, that I don't want to make them alone. I am by no means a web developer and I am absolutely sure that I did not think about countless usecases making it necessary to refactor everything and in particular the interfaces I defined from the ground. Therefore I would like to discuss the concepts here, put all the missing features together and make sure that this extension develops the MP2 way: Think about the concepts first and build a solid base, which can be extended later by many features without running into conceptual limitations. This approach takes much longer (which is IMO one of the reasons why MP2 took so long), but the result is much better.
So if you have any comments, remarks or even if you just want to say "It doesn't work like this because...", please feel free. I really appreciate any comment. And as usual: helping hands are of course also welcome I will continue to develop this extension, but don't hold your breath - it will take a lot of time...
Enough talking - what do we have here: Attached are the binaries and the source of three plugins (binaries should work with current dev and latest Alpha 2):
ASPNETWebStack
As proposed in the other thread, I used a self hosted ASP.NET web stack, which is provided by the ASPNETWebStack service. This seems to be the most powerful option and we can later on for the webservice still choose between Web API and OData for example. I decided to put this into a separate plugin and publish the service in our ServiceRegistration so that other plugins (and maybe the core) can make use of it. Unlike e.g. MPExtended with WebMediaPortal (which I really love btw.) we don't need any IIS or similar. It's all contained in the plugin.
The ASPNETWebStackService implements quite a simple interface:
A client plugin making use of the ASPNETWebStackService has to fill a HttpSelfHostConfiguration object with all the necessry information (mainly the URI to listen on, optionally paths for the webservice and handlers for http-requests, etc.) and call RegisterServer. That's basically it. the ASPNETWebStackService will then instantiate a HttpSelfHostServer and start it. I did not implement a wrapper around HttpSelfHostConfiguration since IMO this is too complex and would hide a lot of the power of the HttpSelfHostServer.
It currently only allows one server per port. It seems to be possible to have multiple servers listening to different paths on one port, but this just creates possible error sources and we have 2^10 ports on every computer, which should be more than enough. If the start was successful, it returns true, otherwise false (same for all other methods returning a bool value).
Internally, the service holds a dictionary<port, HttpSelfHostServer>. When starting a server, the port is included in the HttpSelfHostConfiguration.
UnregisterServer needs the port as argument and then stops and disposes the respective server.
This means that the calling plugin should hold at least the port of a server it registered. Maybe it should even hold the HttpSelfHostConfiguration to be able to modify the server later on. My finding on this is that you can modify the server when it's already running (I e.g. tried to start a server first and then add a message handler without stopping the server and it worked).
Shutdown stops and disposes all servers which were registered with the ASPNETWebStack. It is called automatically when the system shuts down. So it wouldn't be necessary to call UnregisterServer from a plugin, which registered a server, but I would consider it best practice.
The ASPNETWebStackService should be thread safe.
I already have some ideas how to improve this service, but I will put the improvement ideas for all the plugins into a separate post below...
MPAnywhere
This is the core plugin of this extension and of course depends on the ASPNETWebService. It provides three functionalities:
Start() and Shutdown() should be self-explanatory
Virtual HTTP file server
The virtual http file server is quite readily implemented. It is one instance of a HttpSelfHostServer with a special "VirtualFileHandler". It currently listens to port 80 on your MP2 server (will of course be configurable later on - currently hard coded so that it will only work if port 80 on your MP2 server is free, sorry for that.)
Via RegisterVirtualRootPath other plugins can register a local file system path on the MP2 server (typically a subdirectory of the respective plugin directory), which is then available to clients via http. To do so, you also have to provide a "ClientType". I currently introduced three client types (and the respective combinations):
The reason behind that is that I think in the end we will need three client interfaces:
The MPAnywhereService will now search for a file called index2.html in its (virtual) root directory. Why is it virtual? Because there is no real "root directory", there is a virtual root directory consisting of (currently) six "layers". In our case, it will start with [MPAnywherePluginDirectory]\wwwroot\computer\ but will realize that the iPad is not a "Computer", but a "Tablet", so this directory is not visible to the iPad. Then it checks [MPAnywherePluginDirectory]\wwwroot\Tablet\. This directory is visible for the iPad, because it has been registered for the ClientType "Tablet". But there is no file called index2.html in this local directory, there is only index.html. So we continue to search until we find [EditMediaItemsPluginDirectory]\wwwroot\Tablet\. There is a file called index2.html and so this file is delivered to the iPad-client.
Only if the MPAnywhereService does not find the requested file in any registered root directory (or subdirectory of course if the client specifies this subdirectory in the URI), which is visible to the specific client, it returns a file not found response.
Please note that the device detection is nearly not implemented, yet. It just detects if you are using an iPad and then directs you to the virtual directory for tablets. With any other device you are currently treated as a "Computer". I think we need some external library for this to work reliable later...
The example just mentioned is by the way real - if you have an iPad, browse to http://MP2ServerName/index2.html. Then do the same with any other device and you will see the little difference...
UnregisterVirtualRootPath should be self explaining - use it if your plugin does not want its directories be part of the virtual root directory anymore. The files will just disappear from our MPAnywhere web file server.
Webinterface with MenuTree
Now we are getting in a region, where the implementation is anything but complete...
The idea is that we have three webinterfaces:
The interface for computers and tablets shall be structured with three simple panels. One panel at the top with some basic information on the system, one menu panel on the left (as a treeview for computers and maybe something like the iPad's mail app on the left side for tablets) and a "main application tab" in the (right) center. For phones we should probably omit the top panel and display the menu full screen until the user starts an "application", which is then also displayed fullscreen.
For computers you can already see what I mean by just browsing to http://MP2ServerName. It should look something like this:
(Now it's clear that I'm not a designer when you look at the top panel - as mentioned, any help is appreciated )
Currently, the menu entries are hard coded. But the idea is that the MPAnywhereService provides the menu tree dynamically via a webservice. This also makes clear what the RegisterMenuItem and UnregisterMenuItem methods are for. Other MP2 plugins can register menu items which will then automatically be provided via the webservice and displayed automatically in the menu tree. The parameters of these methods are just first ideas. I'm not yet happy with them, since I still need to think about how to store the tree in the server.
The menuPath parameter says where in the menu tree this menu items shall be displayed. The menuText is the text that is displayed in the menu at the given path.
Then you have to specify the viewClassName of your "application". This class must be derived from the extjs TabPanel class and your plugin must provide the respective js-file in the right place via the VirtualDirectory. It is then automatically loaded and shown in the main application panel when the user clicks on the respective menu item in the menu tree. The automatic loading of the class files already works from the extjs side. If you e.g. click on "Some Cool Extension", the TabPanel on the right is provided by the EditMediaItems plugin - not the MPAnywhere plugin. Fancy, isn't it However, as mentioned, the menu tree (including the viewClassName) is not yet provided by a webapi. The class names are currently hard coded in the js-code.
Finally, there is a parameter called "parameters". The idea behind it is the following: Let's assume we later have a plugin providing access to our MP2 server's settings. We would probably include the settings tree of MP2 as a sub-menu tree below the node "MP2 Server Settings" in our menu tree on the left. Now we probably don't want to provide a separate TabPanel for every node in the settings tree of our MP2 server, but we want to provide a generic TabPanel which is called with some parameters that tell the panel, which settings to display. Therefore the viewClassName would be the same for every leaf-node below "MP2 Server Settings", but our TabPanel class would be able to determine what settings to display by fetching the parameters stored in the menu tree for the selected node.
As mentioned, these parameters are not yet as I want them to be - they are intended only to explain what my idea behind it is...
WebService
The MPAnywhereService also registers a second HttpSelfHostServer (currently hardcoded on port 81 - again sorry, this will be configurable later). At this point, it becomes clear that this is really only a proof of concept. MPAnywhereService does not provide any webservice, yet.
It just maps a http route for testing purposes "http://MP2ServerName:81/api/{Controller}/{id}", which is obviously a WebApi route. I'm not decided at all, yet, whether we shall use WebApi or OData. WebApi seems to be easier to implement, but you have to write many methods like GetMediaItemsByMediaCategory, GetMediaItemsByID, GetMediaItemsByName, etc. whereas with OData you can send queries to a single provider method, which then evaluates the query and returns the requested MediaItems. But on the other hand, OData would require a lot more work on the server side. This will in the end be a try and error process. Let's see what suits us best in the end.
Also the fact that I currently start a second HttpSelfHostServer for the WebService is not a final decision, yet (the same applies to everything I mention here...). Starting the first HttpSelfHostServer takes about 0.4 seconds on my old Laptop in a virtual machine. Starting this second one takes about 0.07 seconds, so I think it is not really having two completely separate instances but the second and further ones use the resources together with the first instance. The overhead seems to be minimal. But we can also reserve the "/api/" and maybe the "/OData/" paths from our web file server (already tested, requires only minimal changes). But I wanted to keep these two things separate in the beginning to easier determine possible bugs. In the end, it may be too complicated on the js side to have the webservice listening on a different port of the server. While you can easily address e.g. "../../api/MediaItems" in a js file when the file as well as the webservice is available via the same server port, the js file cannot know the URI of the webservice if it is on another port. So we would have to provide a dynamically created file on our web file server containing information on where the js file can reach the webservice. Shouldn't be too complicated, but I don't know whether it's worth the effort...
Now why am I providing a webservice without any controllers, yet? Well, the concept I wanted to prove is that you can provide a WebApi controller from a different plugin. In this case the MPAnywhere.EditMediaItems plugin. Besides registering the three VirtualRootPaths above, it also provide an APIController for MediaItems. PLEASE NOTE: This is definitely not the final controller API and the data is just dummy data without any connection to the MediaLibrary. There is still a long way to go until this will be the case (And sorry, Lehmden, if this made you hope too early - the plugin is already called EditMediaItems, because this was your intention in the other thread. But as you can see, this is not (yet) possible, but I'm sure it will be possible...). I just wanted to make sure that our MPAnywhere webservice is extendable by other MP2 plugins - which it is. It works because our PluginManager loads the assemblies of all plugins which are set to AutoActivate in the plugin.xml at the startup of the MP2 server. So if you want to provide API-Controllers via additional plugins, make sure you set AutoActivate to true.
If you want to test the WebApi, you can do so by e.g. browsing to:
http://Mp2ServerName:81/api/MediaItems
http://Mp2ServerName:81/api/MediaItems/2 or e.g.
http://Mp2ServerName:81/api/MediaItems?category=pictures
When you do so with FireFox, you will immediately see the objects in XML-format. If you use IE, it by default requests JSON-format, which is also available automatically and you are prompted to download a file called *.json - save it and open it with the editor and you will see the result.
Finally we have the WebServiceServerConfiguration property in the MPAnywhereService. This returns the HttpSelfHostConfiguration of the webservice server to make it possible for other plugins to modify the configuration e.g. by adding additional http routes (not used, yet, because the route used by the EditMediaItems plugin is currently registered as standard route by MPAnywhere).
MPAnywhere.EditMediaItems
As mentioned above, this is really just a "proof of concept plugin" at the moment. It registers three virtual root paths with the MPAnywhereService's virtual web file server and provides a dummy controller for MPAnywhere's webservice server - nothing more, yet. I'm also thinking about renaming it to MPAnywhere.CoreExtensions because I think we can provide some core functionality, but the rest is open to the community (thinking about a MediaPlayer plugin, etc.). We could also integrate the core functionality directly into MPAnywhere, but to make it easier for community developers I think it is a good idea to keep this separated so that they can just copy this plugin and start modifying it.
Ok, that's basically it for now. And I think that it is understandable now why I would really appreciate an absolutely open discussion about this. In particular, I would be very happy if this discussion is not only made by people already specializing in MP2. I still hope that there are a lot of team members having the "vision" we discussed about so often in the last months. So if you have visions don't go to the doctor (that's probably a joke only for Germans... ) but post your visions here. I am willing to rewrite and rethink everything I have implemented so far to make this THE Web(Inter)face of MP2, but I need your comments, your criticism and your ideas (and a lot more time, but this is a different story...). In particular, I am completely lost on the js and extjs side. Never did any js programming before so if there is someone with deeper knowledge there, any help or hints would be greatly appreciated.
I will reserve the next two posts for concrete improvement ideas and a change log. I have many ideas already and will post them later, just to make sure we don't forget them. And now let the discussion start!
cheers,
Michael
I started this thread some time ago in the internal team forums, but we think it is time to share it with you. It is about three new server plugins for MP2 which - when they are ready - shall provide web services and a web interface for MP2. The reason for starting this thread was that a fellow Team MediaPortal member mentioned that it would be great to have a possibility to edit the metadata stored in our MP2 Server's MediaLibrary. We thought about a separate windows application such as the configuration utility for MP1. But somehow I preferred the idea to do such things in a browser - mainly because we are platform independent and there is no need to install yet another application. So i started reading about web interfaces and web services and we quickly realized that we could do much more things with a browser front end than edit metadata. When you read through this thread you will find a lot of ideas. Let's see how many of them we can realize...
First of all, there is a reason why this thread is called "proof of concept". It means that these three plugins currently do not provide any benefit for a pure user of MP2. So if you are not interested in new technologies and you don't like to try new and cool software even if it is of no practical use, yet, this thread is probably not interesting for you.
Technical Background of the Plugins
For all the others I'll try to explain shortly what these plugins are about. As mentioned there are three plugins:
- OwinService
- MPAnywhereService
- MPAnywhereService.Core
OwinService provides the possibility to start "WebApps". To do so, it uses the Miscrosoft implementation of Owin, called Katana. A WebApp may contain any kind of service which is in most cases accessible via http and therefore e.g. via your browser. We currently support serving static files (to be able to provide a web interface) and ASP.NET WebAPI. We will probably also support SignalR and the OData extension of WebAPI.
MPAnywhereService
MPAnywhereService uses the OwinService to provide currently two WebApps. One serves static files on port 80, the other serves an ASP.NET WebAPI on port 81.
The static file server is used to provide you with a web interface. This web interface uses Sencha ExtJS to provide you with a modern and snappy "single page application" in your browser. We will later also make use of Sencha Touch to have a tailor made interface for touch devices such as tablets and phones.
MPAnywhereService.Core
OwinService and MPAnywhereService were coded with the intention to be as extendable as anyhow possible by other plugins. Although we could have integrated all the functionality into the MPAnywhereService, I decided not to do so. Instead, MPAnywhereService itself only provides the groundwork for a web interface and web services. It will offer you a web interface - but with no usable functionality in it. It will offer you web services, but without the possibility to get useful data out of them. All this end-user useful functionality will be contained in MPAnywhereService.Core for two reasons: (1) I want to make sure that MPAnywhereService is actually extendable by other plugins. (2) Developers who want to extend MPAnywhereService can use MPAnywhereService.Core as a blueprint for their extensions.
As mentioned before, there is no really useful functionality (such as the possibility to edit metadata), yet, but when it will be integrated, it will be in MPAnywhereService.Core.
How to Try
Enough background - let's get it started. You can download the latest version of the three plugins at the end of this post as zip file. The zip file contains two directories:
- \SRC contains the complete source code for the plugins.
- \BIN contains the compiled binaries which can be directly used with MP2.
Now start a browser on any computer in your local network and type
Code:
http://[MP2_Server_Name]
But i also talked about web services - in particular about ASP.NET WebAPI. To see what this is about, try
Code:
http://[MP2_Server_Name]:81/api/MediaItems
http://[MP2_Server_Name]:81/api/MediaItems/[GUID]
http://[MP2_Server_Name]:81/api/MediaItems?searchString=[SearchTerm]
[SearchTerm] can be a part of any text field in your MediaLibrary. (Such as "heaven", "Clapton", etc. - but without the "")
When you use FireFox, you will see your MediaItems as XML in your browser. When you use Internet Explorer, you are prompted to download a .json file. Please do so and open it with a text editor - and you will see the objects in Json format.
That's basically all you can do for now. And I warned you: It is of no use, YET... But at least for me, it's quite cool already
Why publish this thread if it's not ready, yet?
Because WE NEED YOU
But let me start from the beginning: As you will notice, this thread is public, but read only. The reason is that I want to keep this thread clean and focused on a purely technical discussion. When you read through this thread (and I encourage all of you, who were interested enough to read until here, to do so) you will realize that I used this thread as kind of a public memory and wrote down a lot of things I learned about web services in the last months. The reason is easy: I am no web developer. In my real live I'm not a developer at all. So for me all this stuff was new, but I found it interesting enough to write a lot of it down in this thread.
This, however, does not mean that we don't want your comments on this. To the contrary! The main reason to publish this thread is to get your feedback and help. But please do so in the MPAnywhere Public Discussion Thread [INSERT LINK WHEN READY]. When should you post in that thread? Well.....
- When you tried the plugins and they work
- When you tried the plugins and they don't work
- When you have an idea what else could be done with this
- When you have an idea how the functionality you find in the plugins or the ideas in this thread can be improved
- When you think we are on the wrong track and you have an idea how to do it better
- When for other reasons you feel like posting something
When you read through this thread, you will notice that there have already been some complete rewrites of the plugins. As a result, the information contained at the beginning of this thread is already outdated. But I did not want to edit all the threads to bring them up to date because I want to keep them as kind of a history so that interested people can still read why we went one route and not the other. When I find the time, I will create a few pages in our Wiki, where I will try to always have the latest structure and functionality explained. But please bear with me - this may take a while...
When is this ready?
This mainly depends on you If we find 20 new and skilled developers willing to spend all their free time on this project, we may have this feature complete in 3 months. If we don't, it may take another 2 years until you can do something useful with it. So if you want to use this as soon as possible, please do not ask this question - but please help us to make it happen...
And now let's get the discussion started in the other thread. And remember: There is no comment which is not useful (except for asking when it's ready ). Please share all your thoughts - we need them!
Michael
-----------------------------------------------------------------------------------------------------------------------------
[Original Thread]
Hi everybody,
I want to share something in a very, very, very early stage for discussion. It already works quite impressively for me - although it currently doesn't provide any benefit for the user, yet. I cannot rule out that there are still a lot of bugs and I know for sure that there are for MP2 purposes many many code comments missing and a general code cleanup is also necessary. This is the reason why I did not post in this (readonly) public thread, which however inspired me to start some work on it. If you think we can publish it there to let our community know what's going on, please feel free.
I wasn't sure whether or not I should post it here already, but there are so many conceptual design decisions to make, that I don't want to make them alone. I am by no means a web developer and I am absolutely sure that I did not think about countless usecases making it necessary to refactor everything and in particular the interfaces I defined from the ground. Therefore I would like to discuss the concepts here, put all the missing features together and make sure that this extension develops the MP2 way: Think about the concepts first and build a solid base, which can be extended later by many features without running into conceptual limitations. This approach takes much longer (which is IMO one of the reasons why MP2 took so long), but the result is much better.
So if you have any comments, remarks or even if you just want to say "It doesn't work like this because...", please feel free. I really appreciate any comment. And as usual: helping hands are of course also welcome I will continue to develop this extension, but don't hold your breath - it will take a lot of time...
Enough talking - what do we have here: Attached are the binaries and the source of three plugins (binaries should work with current dev and latest Alpha 2):
- ASPNETWebStack
- MPAnywhere
- MPAnywhere.EditMediaItems
ASPNETWebStack
As proposed in the other thread, I used a self hosted ASP.NET web stack, which is provided by the ASPNETWebStack service. This seems to be the most powerful option and we can later on for the webservice still choose between Web API and OData for example. I decided to put this into a separate plugin and publish the service in our ServiceRegistration so that other plugins (and maybe the core) can make use of it. Unlike e.g. MPExtended with WebMediaPortal (which I really love btw.) we don't need any IIS or similar. It's all contained in the plugin.
The ASPNETWebStackService implements quite a simple interface:
Code:
public interface IASPNETWebStackService
{
bool RegisterServer(HttpSelfHostConfiguration config);
bool UnregisterServer(int port);
void Shutdown();
}
A client plugin making use of the ASPNETWebStackService has to fill a HttpSelfHostConfiguration object with all the necessry information (mainly the URI to listen on, optionally paths for the webservice and handlers for http-requests, etc.) and call RegisterServer. That's basically it. the ASPNETWebStackService will then instantiate a HttpSelfHostServer and start it. I did not implement a wrapper around HttpSelfHostConfiguration since IMO this is too complex and would hide a lot of the power of the HttpSelfHostServer.
It currently only allows one server per port. It seems to be possible to have multiple servers listening to different paths on one port, but this just creates possible error sources and we have 2^10 ports on every computer, which should be more than enough. If the start was successful, it returns true, otherwise false (same for all other methods returning a bool value).
Internally, the service holds a dictionary<port, HttpSelfHostServer>. When starting a server, the port is included in the HttpSelfHostConfiguration.
UnregisterServer needs the port as argument and then stops and disposes the respective server.
This means that the calling plugin should hold at least the port of a server it registered. Maybe it should even hold the HttpSelfHostConfiguration to be able to modify the server later on. My finding on this is that you can modify the server when it's already running (I e.g. tried to start a server first and then add a message handler without stopping the server and it worked).
Shutdown stops and disposes all servers which were registered with the ASPNETWebStack. It is called automatically when the system shuts down. So it wouldn't be necessary to call UnregisterServer from a plugin, which registered a server, but I would consider it best practice.
The ASPNETWebStackService should be thread safe.
I already have some ideas how to improve this service, but I will put the improvement ideas for all the plugins into a separate post below...
MPAnywhere
This is the core plugin of this extension and of course depends on the ASPNETWebService. It provides three functionalities:
- A "virtual" http file server, which is extendable by other plugins and takes care of the device used to browse the server,
- A basic webinterface with a menu tree (also extendable by other plugins), and
- a very basic webservice (also extendable by other plugins)
Code:
public interface IMPAnywhereService
{
bool RegisterVirtualRootPath(String virtualRootPath, MPAnywhereService.ClientType clientType);
bool UnregisterVirtualRootPath(String virtualRootPath);
bool RegisterMenuItem(String menuPath, String menuText, String viewClassName, String[] parameters);
bool UnRegisterMenuItem(String menuPath);
void Start();
void Shutdown();
HttpSelfHostConfiguration WebServiceServerConfiguration
{
get;
}
}
Start() and Shutdown() should be self-explanatory
Virtual HTTP file server
The virtual http file server is quite readily implemented. It is one instance of a HttpSelfHostServer with a special "VirtualFileHandler". It currently listens to port 80 on your MP2 server (will of course be configurable later on - currently hard coded so that it will only work if port 80 on your MP2 server is free, sorry for that.)
Via RegisterVirtualRootPath other plugins can register a local file system path on the MP2 server (typically a subdirectory of the respective plugin directory), which is then available to clients via http. To do so, you also have to provide a "ClientType". I currently introduced three client types (and the respective combinations):
Code:
public enum ClientType
{
Computer,
Tablet,
Phone,
Touch = Tablet | Phone,
Any = Computer | Tablet | Phone
}
- one for computers to be used with a mouse and keyboard,
- one for tablets, such as an iPad, and
- one for phones such as iPhone or Android devices.
- [MPAnywherePluginDirectory]\wwwroot\computer\ for ClientType "Computer",
- [MPAnywherePluginDirectory]\wwwroot\Tablet\ for ClientType "Tablet" and
- [MPAnywherePluginDirectory]\wwwroot\Phone\ for ClientType "Phone"
- [EditMediaItemsPluginDirectory]\wwwroot\computer\ for ClientType "Computer",
- [EditMediaItemsPluginDirectory]\wwwroot\Tablet\ for ClientType "Tablet" and
- [EditMediaItemsPluginDirectory]\wwwroot\Phone\ for ClientType "Phone"
The MPAnywhereService will now search for a file called index2.html in its (virtual) root directory. Why is it virtual? Because there is no real "root directory", there is a virtual root directory consisting of (currently) six "layers". In our case, it will start with [MPAnywherePluginDirectory]\wwwroot\computer\ but will realize that the iPad is not a "Computer", but a "Tablet", so this directory is not visible to the iPad. Then it checks [MPAnywherePluginDirectory]\wwwroot\Tablet\. This directory is visible for the iPad, because it has been registered for the ClientType "Tablet". But there is no file called index2.html in this local directory, there is only index.html. So we continue to search until we find [EditMediaItemsPluginDirectory]\wwwroot\Tablet\. There is a file called index2.html and so this file is delivered to the iPad-client.
Only if the MPAnywhereService does not find the requested file in any registered root directory (or subdirectory of course if the client specifies this subdirectory in the URI), which is visible to the specific client, it returns a file not found response.
Please note that the device detection is nearly not implemented, yet. It just detects if you are using an iPad and then directs you to the virtual directory for tablets. With any other device you are currently treated as a "Computer". I think we need some external library for this to work reliable later...
The example just mentioned is by the way real - if you have an iPad, browse to http://MP2ServerName/index2.html. Then do the same with any other device and you will see the little difference...
UnregisterVirtualRootPath should be self explaining - use it if your plugin does not want its directories be part of the virtual root directory anymore. The files will just disappear from our MPAnywhere web file server.
Webinterface with MenuTree
Now we are getting in a region, where the implementation is anything but complete...
The idea is that we have three webinterfaces:
- One for computers, which I will implement with Sencha extjs (this one you can already see)
- One for tablets, which I'll probably implement with Sencha Touch (not implemented at all, yet) and
- One for phones with a touch interface (also not implemented, yet). I think we can also use Sencha Touch for this and reuse many of the components from the tablet interface. But an iPhone's display is just not big enough to display a menu and the application at the same time.
The interface for computers and tablets shall be structured with three simple panels. One panel at the top with some basic information on the system, one menu panel on the left (as a treeview for computers and maybe something like the iPad's mail app on the left side for tablets) and a "main application tab" in the (right) center. For phones we should probably omit the top panel and display the menu full screen until the user starts an "application", which is then also displayed fullscreen.
For computers you can already see what I mean by just browsing to http://MP2ServerName. It should look something like this:
(Now it's clear that I'm not a designer when you look at the top panel - as mentioned, any help is appreciated )
Currently, the menu entries are hard coded. But the idea is that the MPAnywhereService provides the menu tree dynamically via a webservice. This also makes clear what the RegisterMenuItem and UnregisterMenuItem methods are for. Other MP2 plugins can register menu items which will then automatically be provided via the webservice and displayed automatically in the menu tree. The parameters of these methods are just first ideas. I'm not yet happy with them, since I still need to think about how to store the tree in the server.
The menuPath parameter says where in the menu tree this menu items shall be displayed. The menuText is the text that is displayed in the menu at the given path.
Then you have to specify the viewClassName of your "application". This class must be derived from the extjs TabPanel class and your plugin must provide the respective js-file in the right place via the VirtualDirectory. It is then automatically loaded and shown in the main application panel when the user clicks on the respective menu item in the menu tree. The automatic loading of the class files already works from the extjs side. If you e.g. click on "Some Cool Extension", the TabPanel on the right is provided by the EditMediaItems plugin - not the MPAnywhere plugin. Fancy, isn't it However, as mentioned, the menu tree (including the viewClassName) is not yet provided by a webapi. The class names are currently hard coded in the js-code.
Finally, there is a parameter called "parameters". The idea behind it is the following: Let's assume we later have a plugin providing access to our MP2 server's settings. We would probably include the settings tree of MP2 as a sub-menu tree below the node "MP2 Server Settings" in our menu tree on the left. Now we probably don't want to provide a separate TabPanel for every node in the settings tree of our MP2 server, but we want to provide a generic TabPanel which is called with some parameters that tell the panel, which settings to display. Therefore the viewClassName would be the same for every leaf-node below "MP2 Server Settings", but our TabPanel class would be able to determine what settings to display by fetching the parameters stored in the menu tree for the selected node.
As mentioned, these parameters are not yet as I want them to be - they are intended only to explain what my idea behind it is...
WebService
The MPAnywhereService also registers a second HttpSelfHostServer (currently hardcoded on port 81 - again sorry, this will be configurable later). At this point, it becomes clear that this is really only a proof of concept. MPAnywhereService does not provide any webservice, yet.
It just maps a http route for testing purposes "http://MP2ServerName:81/api/{Controller}/{id}", which is obviously a WebApi route. I'm not decided at all, yet, whether we shall use WebApi or OData. WebApi seems to be easier to implement, but you have to write many methods like GetMediaItemsByMediaCategory, GetMediaItemsByID, GetMediaItemsByName, etc. whereas with OData you can send queries to a single provider method, which then evaluates the query and returns the requested MediaItems. But on the other hand, OData would require a lot more work on the server side. This will in the end be a try and error process. Let's see what suits us best in the end.
Also the fact that I currently start a second HttpSelfHostServer for the WebService is not a final decision, yet (the same applies to everything I mention here...). Starting the first HttpSelfHostServer takes about 0.4 seconds on my old Laptop in a virtual machine. Starting this second one takes about 0.07 seconds, so I think it is not really having two completely separate instances but the second and further ones use the resources together with the first instance. The overhead seems to be minimal. But we can also reserve the "/api/" and maybe the "/OData/" paths from our web file server (already tested, requires only minimal changes). But I wanted to keep these two things separate in the beginning to easier determine possible bugs. In the end, it may be too complicated on the js side to have the webservice listening on a different port of the server. While you can easily address e.g. "../../api/MediaItems" in a js file when the file as well as the webservice is available via the same server port, the js file cannot know the URI of the webservice if it is on another port. So we would have to provide a dynamically created file on our web file server containing information on where the js file can reach the webservice. Shouldn't be too complicated, but I don't know whether it's worth the effort...
Now why am I providing a webservice without any controllers, yet? Well, the concept I wanted to prove is that you can provide a WebApi controller from a different plugin. In this case the MPAnywhere.EditMediaItems plugin. Besides registering the three VirtualRootPaths above, it also provide an APIController for MediaItems. PLEASE NOTE: This is definitely not the final controller API and the data is just dummy data without any connection to the MediaLibrary. There is still a long way to go until this will be the case (And sorry, Lehmden, if this made you hope too early - the plugin is already called EditMediaItems, because this was your intention in the other thread. But as you can see, this is not (yet) possible, but I'm sure it will be possible...). I just wanted to make sure that our MPAnywhere webservice is extendable by other MP2 plugins - which it is. It works because our PluginManager loads the assemblies of all plugins which are set to AutoActivate in the plugin.xml at the startup of the MP2 server. So if you want to provide API-Controllers via additional plugins, make sure you set AutoActivate to true.
If you want to test the WebApi, you can do so by e.g. browsing to:
http://Mp2ServerName:81/api/MediaItems
http://Mp2ServerName:81/api/MediaItems/2 or e.g.
http://Mp2ServerName:81/api/MediaItems?category=pictures
When you do so with FireFox, you will immediately see the objects in XML-format. If you use IE, it by default requests JSON-format, which is also available automatically and you are prompted to download a file called *.json - save it and open it with the editor and you will see the result.
Finally we have the WebServiceServerConfiguration property in the MPAnywhereService. This returns the HttpSelfHostConfiguration of the webservice server to make it possible for other plugins to modify the configuration e.g. by adding additional http routes (not used, yet, because the route used by the EditMediaItems plugin is currently registered as standard route by MPAnywhere).
MPAnywhere.EditMediaItems
As mentioned above, this is really just a "proof of concept plugin" at the moment. It registers three virtual root paths with the MPAnywhereService's virtual web file server and provides a dummy controller for MPAnywhere's webservice server - nothing more, yet. I'm also thinking about renaming it to MPAnywhere.CoreExtensions because I think we can provide some core functionality, but the rest is open to the community (thinking about a MediaPlayer plugin, etc.). We could also integrate the core functionality directly into MPAnywhere, but to make it easier for community developers I think it is a good idea to keep this separated so that they can just copy this plugin and start modifying it.
Ok, that's basically it for now. And I think that it is understandable now why I would really appreciate an absolutely open discussion about this. In particular, I would be very happy if this discussion is not only made by people already specializing in MP2. I still hope that there are a lot of team members having the "vision" we discussed about so often in the last months. So if you have visions don't go to the doctor (that's probably a joke only for Germans... ) but post your visions here. I am willing to rewrite and rethink everything I have implemented so far to make this THE Web(Inter)face of MP2, but I need your comments, your criticism and your ideas (and a lot more time, but this is a different story...). In particular, I am completely lost on the js and extjs side. Never did any js programming before so if there is someone with deeper knowledge there, any help or hints would be greatly appreciated.
I will reserve the next two posts for concrete improvement ideas and a change log. I have many ideas already and will post them later, just to make sure we don't forget them. And now let the discussion start!
cheers,
Michael
Attachments
Last edited: