qzind / tray

Browser plugin for sending documents and raw commands to a printer or attached device.
https://qz.io
Other
851 stars 276 forks source link

Plugin API #63

Open tresf opened 8 years ago

tresf commented 8 years ago

QZ Tray could be the home of a plugin API which would allow 3rd party library support with the software to add proprietary libraries without violating the LGPL 2.1 license:

http://stackoverflow.com/a/10913898/3196753

dsanders11 commented 7 years ago

I've added basic support for UVC (USB Video Class) devices in dsanders11/tray@e0eb79647ab32bd0ccc94baa1466aa2232ef8d49 which might be useful to this enhancement. The code is not polished (it's a component of a component of a larger project, so Good Enough™ is in play) but it is functional. If there was a plugin API it would have been implemented as such, instead it was integrated directly into QZ Tray. For more color, it is used to control settings on a webcam (zoom, focus, contrast, etc) which aren't exposed in the HTML5 webcam API.

May be useful to peruse as a real-world example of what types of hooks would be useful for the plugin API. In particular I added a parent interface to DeviceIO to make it a bit more generic for supporting a class which doesn't have readData/sendData or setStreaming/isStreaming although the implementation is a bit iffy (requires some nasty casting and could collide if you opened a USB device and a UVC device with the same identifiers).

Another change which might be useful to pull back into QZ Tray is adding support for serial numbers on USB devices. In my use case I have two identical USB devices connected to the same host so I need to use serial numbers on the devices to differentiate them. Looks like there's support in the USB library that QZ Tray uses for getting the USB device's serial number so it could be implemented there. My implementation is a bit iffy, and uses Apache Commons Collections lib for MultiKey to extend the open devices mapping. Punted on how to address the optional nature of the serial number field and the complexities that can add.

BTW, if you want to play with the branch it should work with any Logitech webcam (they're UVC complaint) and probably a good number of other webcam brands.

tresf commented 7 years ago

@dsanders11 great feedback, thanks! I've opened up two bug reports to help nudge this along -- 1. Serial Numbers for USB support #147 -- and -- 2. UVC support #148. 👍

mrloop commented 6 years ago

@tresf has there been any work done on the Plugin API? Having a look at the https://github.com/qzind/tray/commits/2.1 can't see any obvious commits.

tresf commented 6 years ago

No, none, sorry. This won't make 2.1.

dsanders11 commented 6 years ago

@tresf, if you're still considering this, Apache Felix seems like an interesting way to go (and a quick SO question about how to expose packages to the 'bundles'). There's also Eclipse Equinox, they're both based on OSGi.

tresf commented 6 years ago

@dsanders11 thanks. The only concern I have is that if the proprietary libraries aren't OSGi compliant, we'd be forced to write a wrapper for each one, which may come with proprietary API restrictions.

One example -- and a huge untapped market -- is Bematech. Bematech offers a Java API for talking to their fiscal printers but may introduce licensing issues.

Another example which is way out of left field is the Evolis Premium SDK... Take this code for example...

String ip = "11.1.24.210";
int port = 18000;
char[] data = new char[1024];
String request = "{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"CMD.SendCommand\",\"params\":{\"command\":\"Rfv\", \"device\":\"Evolis Primacy\", \"timeout\":\"5000\"}}"; 
String answer = "";

I suppose we could create these modules as our own, separate "proprietary" OSGi components and go from there. Thoughts and ideas welcome.

dsanders11 commented 6 years ago

@tresf, yea, I think a wrapper is going to be required either way. I'm not sure how plugins would work without one. Needs to be some glue code for interacting with the QZ Tray code, and the logical place for that is in the 'plugin' so that QZ Tray doesn't need any knowledge of them.

I've currently got three different extensions to QZ Tray that it'd be preferable to turn into plugins: webcam control, pulling jobs from an HTTP queue, and renewing Let's Encrypt certs automatically. Looking at the Felix examples I can more or less imagine how they could be turned into bundles. The first would require a method for providing new commands to QZ Tray (probably a mapping of names to functions), the second needs access to some of the QZ Tray print classes. The third is the most standalone, it only needs to be able to reload QZ Tray so it's a very small hook. I could maybe try to do a quick and dirty proof of concept for this last one.

dsanders11 commented 6 years ago

@tresf, I made a little time and created a quick and dirty proof of concept of using Apache Felix as a plugin system. Unfortunately I couldn't find any JavaDocs online for Felix (kind of annoying) so I had to poke around a bit in the dark.

Can take a look at it in commit 6ff83862d2be44a471687e67b22fd406b60d3936, which was forked from a branch I have that has other changes, but the only changes in that commit are ones relevant to the plugin proof of concept.

There's a bunch of (hastily made) design choices I made there, but basically it loads OSGi bundles under the /plugins directory in QZ Tray's installed location. It also puts a /felix-cache directory in that location as well, that's a requirement of Felix (and OSGi), but it gets cleared on every startup, and can be easily blown away with no repercussions AFAIK. Could be changed to be in system temp instead, that's easily configurable.

Quick rundown to make the diff make more sense on initial look, I pulled out the code I'd written to automatically renew Let's Encrypt certificates (which contacts a custom API running on a server) for the proof of concept as it has very few dependencies on the rest of QZ's code. It just needs to get the tray properties so it can open the keystore. Then it runs once a day checking the cert to see if it needs to renew. For testing purposes I commented out and changed some stuff so it runs immediately and every 10 seconds so I can easily see that it's working. So the only real dependencies on QZ are access to the properties, and the ability to reload QZ (which depends on the changes I made in #275).

Here's a high-level overview of how I implemented the proof of concept:

Here are my thoughts having played with this:

So there we are. Apologies if I forgot anything or started rambling, writing this rather late. It's definitely dirty and I'd do things slightly differently if I started from scratch (all mentioned in the above), but it's functional, so it definitely seems viable as a method for implementing plugins and moving extended functionality to built-in plugins.

tresf commented 6 years ago

Thanks! We'll take a look and address all points. Much appreciated! The proof-of-concept helps immensely because it gives us a baseline.

This may end up being a way to segment some of our existing code as well. Will have an update once the dust settles on pushing 2.1 out the door.

dsanders11 commented 6 years ago

Is there an ETA on 2.1? End of the year or further?

tresf commented 6 years ago

End of the year for sure. Hopefully before then. We have a few bugs to squash first.