Commit a648bca2 authored by Benjamin Rokseth's avatar Benjamin Rokseth
Browse files

Initial commit

parents
kohaprinter
kohaprinter_mapping.json
Kohaprinter
==
A Cups Backend and printer delegator for use in Koha Intra and other services attached.
Purpose is to generate a simple connection between browser and thermal printers connected
in local network, so that you can print any HTML slip or label to a printer in the network.
Requirements
==
To build backend: golang
* wkhtmltopdf (webkit html to pdf renderer) for rastering html
must be placed in /usr/bin on the cups server
https://github.com/wkhtmltopdf/wkhtmltopdf/releases
* compiled backend (kohaprinter) must be placed in /usr/lib/cups/backend/ on the cups server
* a json mapping file (see example) must be placed in same directory as backend and named kohaprinter_mapping.json
* Cups is strict on permissions, so make sure backend is owned by root and chmod 0755
* also beware that backend is run by user `lp` so main log turns up in /tmp/kohaprinter.log and
spool logs and output in /var/spool/cups/
Setup and Testing
==
## 1. In Cups, a Raw Print Queue must be set up to point to backend.
Make sure the Device URI corresponds with the backend, (kohaprinter), as this is picked up automagically by Cups
e.g.
```
lpadmin -p KohaKvittPrinter -v kohaprinter:/kvitt -m raw -E
```
This means any job sent to this Queue on this Cups server will be passed along to the `kohaprinter` backend
with the kohaprinter:/kvitt Device URI. Likewise a `kohaprinter:/label` queue could be made to handle label printing.
If the Cups server was running on host, say, `cupsserver`, the queue would now be accessible as normal by Cups, e.g.
```
ipps://cupsserver/printers/KohaKvittPrinter
```
## 2. The Client Printer must be mapped to correct network printer
On cups server: (See the example mapping file for examples)
/usr/lib/cups/backend/kohaprinter_mapping.json:
```
{
"cupsclient": {
"name": "a koha staff client",
"kvittering": "lab-kvitt-10"
}
}
```
## 3. The Client Must be configured with a Raw printer
Setup a Raw Printer to the Queue created in #1
Eg. in linux:
```
lpadmin -p IppKohaKvitt -v ipps://cupsserver/printers/KohaKvittPrinter8 -m raw -E
```
Now you should be able to print any html to the receipt printer. Eg. in linux:
```
lp -d IppKohaKvitt -o document-format=text/html test.html
```
Behind the scenes
==
What happens is that :
* html document is sent raw to the Remote Print Queue KohaKvittPrinter
* it is delegated to the kohaprinter backend which :
* fetches originating IP and DeviceURI from request
* rasters the html document to PDF with Webkit
* forwards generated PDF to Print Queue of Thermal Printer found in json mapping
package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
wkhtml "github.com/SebastiaanKlippert/go-wkhtmltopdf"
)
type Printer struct {
Name, Kvittering, Etikett string
}
var (
Log *log.Logger
)
/*
* Prerequisites:
* wkhtmltopdf binary MUST be downloaded to /usr/bin
* this file needs to be chown root and chmod 755 executable in /usr/lib/cups/backend/
* job needs job-originating-host-name
* kohaprint.json mapping must exist beside this file
*/
func main() {
lfile, err := os.Create("/tmp/kohaprint.log")
if err != nil {
fail(err)
}
Log = log.New(lfile, "", log.LstdFlags|log.Lshortfile)
var mapping map[string]Printer
cfgFile, err := os.Open("/usr/lib/cups/backend/kohaprinter_mapping.json")
defer cfgFile.Close()
if err != nil {
fail(err)
}
dec := json.NewDecoder(cfgFile)
dec.Decode(&mapping)
Log.Printf("[INFO ] MAPPING: %v", mapping)
// No args? Return status line as cups expects this to discover backend
if len(os.Args) == 1 {
fmt.Println("network kohaprint \"Unknown\" \"Print any job to queue specified in device-URI\"")
os.Exit(0)
} else if len(os.Args) < 6 {
fmt.Println("Usage: kohaprint job-id user title copies options [file]")
os.Exit(0)
}
opts := os.Args[5]
Log.Printf("[INFO ] OPTS: %s", opts)
h := getOriginHostName(opts)
if h == "" {
fail(errors.New("Missing params: job-originating-host-name"))
}
if _, ok := mapping[h]; !ok {
fail(errors.New("Originating Host IP missing from mapping file"))
}
pdfg, err := wkhtml.NewPDFGenerator()
if err != nil {
fail(err)
}
// Read job data from stdin
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
fail(err)
}
Log.Printf("[INFO ] DATA: %s", data)
pdfg.AddPage(wkhtml.NewPageReader(strings.NewReader(string(data))))
err = pdfg.Create()
if err != nil {
fail(err)
}
tmpf, err := ioutil.TempFile("", "kohaprintpdf")
if err != nil {
fail(err)
}
defer os.Remove(tmpf.Name())
if _, err := tmpf.Write(pdfg.Bytes()); err != nil {
fail(err)
}
if err := tmpf.Close(); err != nil {
fail(err)
}
args := []string{"-d", mapping[h].Kvittering, tmpf.Name()}
Log.Printf("[INFO ] ARGS: %s\n", args)
out, err := exec.Command("lp", args...).Output()
if err != nil {
Log.Printf("[INFO ] CMD OUTPUT: %s\n", out)
fail(err)
}
os.Exit(0)
}
func getOriginHostName(opts string) string {
arr := strings.Split(opts, " ")
for _, a := range arr {
p := strings.Split(a, "=")
if p[0] == "job-originating-host-name" {
return p[1]
}
}
return ""
}
func fail(err error) {
Log.Printf("[ERROR] %s", err.Error())
os.Exit(1)
}
{
"10.172.2.10": {
"name": "a test client",
"kvittering": "printer-thermal-in-cups"
},
"10.172.2.11": {
"name": "another client",
"kvittering": "another-thermal-printer-in-cups",
"etikett": "label-printer-in-cups"
}
}
\ No newline at end of file
<!DOCTYPE html>
<html><body><h1>It actually works!</h1>
<p>Give me a <strong>Bloody Beer!</strong></p>
<pre>
/######\
/##########\
/ \###/ \
/ \#/ \
/\| |/\
| | \ ==\ /== / | |
\| \<|>\ /<|>/ |/ /|
\__ | - \ - | /#|
\#\ | | | /###|
\##\ | \| | /#####|
\###\ | _______ | /######|
\####\ | / \/ \/ \|/#######|
|######\| |#########|
|########\______/##########|
|#########\ /##########/
|##########\ |#########/\
/###########\/########/###\
/################\######/########\
/##################\###/###########\
/###################\#/##############\
/####################/#################\
/###################/####################\
</pre>
</body></html>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment