comparison etc/lp.lua @ 0:4b915342e2a8

LuaSocket 2.0.2 + CMake build description.
author Eric Wing <ewing . public |-at-| gmail . com>
date Tue, 26 Aug 2008 18:40:01 -0700
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4b915342e2a8
1 -----------------------------------------------------------------------------
2 -- LPD support for the Lua language
3 -- LuaSocket toolkit.
4 -- Author: David Burgess
5 -- Modified by Diego Nehab, but David is in charge
6 -- RCS ID: $Id: lp.lua,v 1.14 2005/11/21 07:04:44 diego Exp $
7 -----------------------------------------------------------------------------
8 --[[
9 if you have any questions: RFC 1179
10 ]]
11 -- make sure LuaSocket is loaded
12 local io = require("io")
13 local base = _G
14 local os = require("os")
15 local math = require("math")
16 local string = require("string")
17 local socket = require("socket")
18 local ltn12 = require("ltn12")
19 module("socket.lp")
20
21 -- default port
22 PORT = 515
23 SERVER = os.getenv("SERVER_NAME") or os.getenv("COMPUTERNAME") or "localhost"
24 PRINTER = os.getenv("PRINTER") or "printer"
25
26 local function connect(localhost, option)
27 local host = option.host or SERVER
28 local port = option.port or PORT
29 local skt
30 local try = socket.newtry(function() if skt then skt:close() end end)
31 if option.localbind then
32 -- bind to a local port (if we can)
33 local localport = 721
34 local done, err
35 repeat
36 skt = socket.try(socket.tcp())
37 try(skt:settimeout(30))
38 done, err = skt:bind(localhost, localport)
39 if not done then
40 localport = localport + 1
41 skt:close()
42 skt = nil
43 else break end
44 until localport > 731
45 socket.try(skt, err)
46 else skt = socket.try(socket.tcp()) end
47 try(skt:connect(host, port))
48 return { skt = skt, try = try }
49 end
50
51 --[[
52 RFC 1179
53 5.3 03 - Send queue state (short)
54
55 +----+-------+----+------+----+
56 | 03 | Queue | SP | List | LF |
57 +----+-------+----+------+----+
58 Command code - 3
59 Operand 1 - Printer queue name
60 Other operands - User names or job numbers
61
62 If the user names or job numbers or both are supplied then only those
63 jobs for those users or with those numbers will be sent.
64
65 The response is an ASCII stream which describes the printer queue.
66 The stream continues until the connection closes. Ends of lines are
67 indicated with ASCII LF control characters. The lines may also
68 contain ASCII HT control characters.
69
70 5.4 04 - Send queue state (long)
71
72 +----+-------+----+------+----+
73 | 04 | Queue | SP | List | LF |
74 +----+-------+----+------+----+
75 Command code - 4
76 Operand 1 - Printer queue name
77 Other operands - User names or job numbers
78
79 If the user names or job numbers or both are supplied then only those
80 jobs for those users or with those numbers will be sent.
81
82 The response is an ASCII stream which describes the printer queue.
83 The stream continues until the connection closes. Ends of lines are
84 indicated with ASCII LF control characters. The lines may also
85 contain ASCII HT control characters.
86 ]]
87
88 -- gets server acknowledement
89 local function recv_ack(con)
90 local ack = con.skt:receive(1)
91 con.try(string.char(0) == ack, "failed to receive server acknowledgement")
92 end
93
94 -- sends client acknowledement
95 local function send_ack(con)
96 local sent = con.skt:send(string.char(0))
97 con.try(sent == 1, "failed to send acknowledgement")
98 end
99
100 -- sends queue request
101 -- 5.2 02 - Receive a printer job
102 --
103 -- +----+-------+----+
104 -- | 02 | Queue | LF |
105 -- +----+-------+----+
106 -- Command code - 2
107 -- Operand - Printer queue name
108 --
109 -- Receiving a job is controlled by a second level of commands. The
110 -- daemon is given commands by sending them over the same connection.
111 -- The commands are described in the next section (6).
112 --
113 -- After this command is sent, the client must read an acknowledgement
114 -- octet from the daemon. A positive acknowledgement is an octet of
115 -- zero bits. A negative acknowledgement is an octet of any other
116 -- pattern.
117 local function send_queue(con, queue)
118 queue = queue or PRINTER
119 local str = string.format("\2%s\10", queue)
120 local sent = con.skt:send(str)
121 con.try(sent == string.len(str), "failed to send print request")
122 recv_ack(con)
123 end
124
125 -- sends control file
126 -- 6.2 02 - Receive control file
127 --
128 -- +----+-------+----+------+----+
129 -- | 02 | Count | SP | Name | LF |
130 -- +----+-------+----+------+----+
131 -- Command code - 2
132 -- Operand 1 - Number of bytes in control file
133 -- Operand 2 - Name of control file
134 --
135 -- The control file must be an ASCII stream with the ends of lines
136 -- indicated by ASCII LF. The total number of bytes in the stream is
137 -- sent as the first operand. The name of the control file is sent as
138 -- the second. It should start with ASCII "cfA", followed by a three
139 -- digit job number, followed by the host name which has constructed the
140 -- control file. Acknowledgement processing must occur as usual after
141 -- the command is sent.
142 --
143 -- The next "Operand 1" octets over the same TCP connection are the
144 -- intended contents of the control file. Once all of the contents have
145 -- been delivered, an octet of zero bits is sent as an indication that
146 -- the file being sent is complete. A second level of acknowledgement
147 -- processing must occur at this point.
148
149 -- sends data file
150 -- 6.3 03 - Receive data file
151 --
152 -- +----+-------+----+------+----+
153 -- | 03 | Count | SP | Name | LF |
154 -- +----+-------+----+------+----+
155 -- Command code - 3
156 -- Operand 1 - Number of bytes in data file
157 -- Operand 2 - Name of data file
158 --
159 -- The data file may contain any 8 bit values at all. The total number
160 -- of bytes in the stream may be sent as the first operand, otherwise
161 -- the field should be cleared to 0. The name of the data file should
162 -- start with ASCII "dfA". This should be followed by a three digit job
163 -- number. The job number should be followed by the host name which has
164 -- constructed the data file. Interpretation of the contents of the
165 -- data file is determined by the contents of the corresponding control
166 -- file. If a data file length has been specified, the next "Operand 1"
167 -- octets over the same TCP connection are the intended contents of the
168 -- data file. In this case, once all of the contents have been
169 -- delivered, an octet of zero bits is sent as an indication that the
170 -- file being sent is complete. A second level of acknowledgement
171 -- processing must occur at this point.
172
173
174 local function send_hdr(con, control)
175 local sent = con.skt:send(control)
176 con.try(sent and sent >= 1 , "failed to send header file")
177 recv_ack(con)
178 end
179
180 local function send_control(con, control)
181 local sent = con.skt:send(control)
182 con.try(sent and sent >= 1, "failed to send control file")
183 send_ack(con)
184 end
185
186 local function send_data(con,fh,size)
187 local buf
188 while size > 0 do
189 buf,message = fh:read(8192)
190 if buf then
191 st = con.try(con.skt:send(buf))
192 size = size - st
193 else
194 con.try(size == 0, "file size mismatch")
195 end
196 end
197 recv_ack(con) -- note the double acknowledgement
198 send_ack(con)
199 recv_ack(con)
200 return size
201 end
202
203
204 --[[
205 local control_dflt = {
206 "H"..string.sub(socket.hostname,1,31).."\10", -- host
207 "C"..string.sub(socket.hostname,1,31).."\10", -- class
208 "J"..string.sub(filename,1,99).."\10", -- jobname
209 "L"..string.sub(user,1,31).."\10", -- print banner page
210 "I"..tonumber(indent).."\10", -- indent column count ('f' only)
211 "M"..string.sub(mail,1,128).."\10", -- mail when printed user@host
212 "N"..string.sub(filename,1,131).."\10", -- name of source file
213 "P"..string.sub(user,1,31).."\10", -- user name
214 "T"..string.sub(title,1,79).."\10", -- title for banner ('p' only)
215 "W"..tonumber(width or 132).."\10", -- width of print f,l,p only
216
217 "f"..file.."\10", -- formatted print (remove control chars)
218 "l"..file.."\10", -- print
219 "o"..file.."\10", -- postscript
220 "p"..file.."\10", -- pr format - requires T, L
221 "r"..file.."\10", -- fortran format
222 "U"..file.."\10", -- Unlink (data file only)
223 }
224 ]]
225
226 -- generate a varying job number
227 local seq = 0
228 local function newjob(connection)
229 seq = seq + 1
230 return math.floor(socket.gettime() * 1000 + seq)%1000
231 end
232
233
234 local format_codes = {
235 binary = 'l',
236 text = 'f',
237 ps = 'o',
238 pr = 'p',
239 fortran = 'r',
240 l = 'l',
241 r = 'r',
242 o = 'o',
243 p = 'p',
244 f = 'f'
245 }
246
247 -- lp.send{option}
248 -- requires option.file
249
250 send = socket.protect(function(option)
251 socket.try(option and base.type(option) == "table", "invalid options")
252 local file = option.file
253 socket.try(file, "invalid file name")
254 local fh = socket.try(io.open(file,"rb"))
255 local datafile_size = fh:seek("end") -- get total size
256 fh:seek("set") -- go back to start of file
257 local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME")
258 or "localhost"
259 local con = connect(localhost, option)
260 -- format the control file
261 local jobno = newjob()
262 local localip = socket.dns.toip(localhost)
263 localhost = string.sub(localhost,1,31)
264 local user = string.sub(option.user or os.getenv("LPRUSER") or
265 os.getenv("USERNAME") or os.getenv("USER") or "anonymous", 1,31)
266 local lpfile = string.format("dfA%3.3d%-s", jobno, localhost);
267 local fmt = format_codes[option.format] or 'l'
268 local class = string.sub(option.class or localip or localhost,1,31)
269 local _,_,ctlfn = string.find(file,".*[%/%\\](.*)")
270 ctlfn = string.sub(ctlfn or file,1,131)
271 local cfile =
272 string.format("H%-s\nC%-s\nJ%-s\nP%-s\n%.1s%-s\nU%-s\nN%-s\n",
273 localhost,
274 class,
275 option.job or "LuaSocket",
276 user,
277 fmt, lpfile,
278 lpfile,
279 ctlfn); -- mandatory part of ctl file
280 if (option.banner) then cfile = cfile .. 'L'..user..'\10' end
281 if (option.indent) then cfile = cfile .. 'I'..base.tonumber(option.indent)..'\10' end
282 if (option.mail) then cfile = cfile .. 'M'..string.sub((option.mail),1,128)..'\10' end
283 if (fmt == 'p' and option.title) then cfile = cfile .. 'T'..string.sub((option.title),1,79)..'\10' end
284 if ((fmt == 'p' or fmt == 'l' or fmt == 'f') and option.width) then
285 cfile = cfile .. 'W'..base.tonumber(option,width)..'\10'
286 end
287
288 con.skt:settimeout(option.timeout or 65)
289 -- send the queue header
290 send_queue(con, option.queue)
291 -- send the control file header
292 local cfilecmd = string.format("\2%d cfA%3.3d%-s\n",string.len(cfile), jobno, localhost);
293 send_hdr(con,cfilecmd)
294
295 -- send the control file
296 send_control(con,cfile)
297
298 -- send the data file header
299 local dfilecmd = string.format("\3%d dfA%3.3d%-s\n",datafile_size, jobno, localhost);
300 send_hdr(con,dfilecmd)
301
302 -- send the data file
303 send_data(con,fh,datafile_size)
304 fh:close()
305 con.skt:close();
306 return jobno, datafile_size
307 end)
308
309 --
310 -- lp.query({host=,queue=printer|'*', format='l'|'s', list=})
311 --
312 query = socket.protect(function(p)
313 p = p or {}
314 local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME")
315 or "localhost"
316 local con = connect(localhost,p)
317 local fmt
318 if string.sub(p.format or 's',1,1) == 's' then fmt = 3 else fmt = 4 end
319 con.try(con.skt:send(string.format("%c%s %s\n", fmt, p.queue or "*",
320 p.list or "")))
321 local data = con.try(con.skt:receive("*a"))
322 con.skt:close()
323 return data
324 end)