manage number of copies and PDF files
This commit is contained in:
parent
ad2051e0e0
commit
b318b9c46a
5 changed files with 94 additions and 17 deletions
12
README.md
12
README.md
|
@ -5,6 +5,11 @@ A very simple web interface to upload and print files.
|
|||
|
||||
## Install and run
|
||||
|
||||
Dependencies:
|
||||
* Python 3
|
||||
* **pdfinfo** executable; usually found in the **poppler-utils** package
|
||||
|
||||
|
||||
To install it:
|
||||
``` bash
|
||||
wget https://bootstrap.pypa.io/get-pip.py
|
||||
|
@ -21,7 +26,12 @@ Now you can **point your browser to [http://localhost:7777/](http://localhost:77
|
|||
|
||||
You can also **run the server in https**, putting in the *ssl* directory two files named *httprint_key.pem* and *httprint_cert.pem*
|
||||
|
||||
By default the **--print-with-code** argument is true, and the uploaded files are just scheduled for priting. To actually print them, you should supply the generated code, for example: `curl -X POST http://localhost:7777/api/print/1234`
|
||||
By default:
|
||||
|
||||
* the **--print-with-code** argument is true, and the uploaded files are just scheduled for priting. To actually print them, you should supply the generated code, for example: `curl -X POST http://localhost:7777/api/print/1234`
|
||||
* **--max-pages** is set to 10, limiting the number of allowed copies
|
||||
* **--pdf-only** is true, meaning that only PDF files are allowed
|
||||
* **--check-pdf-pages** is true, and the number of pages of a PDF are taken into consideration, calculating the maximum number of pages to print
|
||||
|
||||
|
||||
# License and copyright
|
||||
|
|
0
archive/.gitkeep
Normal file
0
archive/.gitkeep
Normal file
8
dist/index.html
vendored
8
dist/index.html
vendored
|
@ -11,8 +11,12 @@
|
|||
<body>
|
||||
<h1>Upload a file to print</h1>
|
||||
|
||||
<input id="upload-file" type="file" />
|
||||
|
||||
File to print: <input id="upload-file" type="file" />
|
||||
<br>
|
||||
<br>
|
||||
Number of copies: <input id="copies" type="number" min="1" max="10" value="1" />
|
||||
<br>
|
||||
<br>
|
||||
<button onClick="uploadFile();">print</button>
|
||||
</body>
|
||||
</html>
|
||||
|
|
8
dist/static/js/httprint.js
vendored
8
dist/static/js/httprint.js
vendored
|
@ -1,9 +1,11 @@
|
|||
function uploadFile() {
|
||||
let photo = document.getElementById("upload-file").files[0];
|
||||
let copies = document.getElementById('copies').value;
|
||||
let formData = new FormData();
|
||||
|
||||
formData.append("file", photo);
|
||||
var uploadFile = document.getElementById('upload-file');
|
||||
formData.append("copies", copies);
|
||||
var uploadField = document.getElementById('upload-file');
|
||||
fetch("/api/upload", {method: "POST", body: formData})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
|
@ -26,7 +28,7 @@ function uploadFile() {
|
|||
layout: 2
|
||||
});
|
||||
}
|
||||
uploadFile.value = null;
|
||||
uploadField.value = null;
|
||||
})
|
||||
.catch(function(err) {
|
||||
iziToast.error({
|
||||
|
@ -35,6 +37,6 @@ function uploadFile() {
|
|||
position: 'topCenter',
|
||||
layout: 2
|
||||
});
|
||||
uploadFile.value = null;
|
||||
uploadField.value = null;
|
||||
});
|
||||
}
|
||||
|
|
83
httprint.py
83
httprint.py
|
@ -37,13 +37,16 @@ API_VERSION = '1.0'
|
|||
QUEUE_DIR = 'queue'
|
||||
ARCHIVE = True
|
||||
ARCHIVE_DIR = 'archive'
|
||||
PRINT_CMD = ['lp']
|
||||
PRINT_CMD = ['lp', '-n', '%(copies)s']
|
||||
CODE_DIGITS = 4
|
||||
MAX_PAGES = 10
|
||||
PRINT_WITH_CODE = True
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
re_pages = re.compile('^Pages:\s+(\d+)$', re.M | re.I)
|
||||
|
||||
|
||||
class HTTPrintBaseException(Exception):
|
||||
"""Base class for httprint custom exceptions.
|
||||
|
@ -109,26 +112,40 @@ class BaseHandler(tornado.web.RequestHandler):
|
|||
self.set_status(status)
|
||||
self.write({'error': False, 'message': message})
|
||||
|
||||
def _run(self, cmd):
|
||||
def _run(self, cmd, fname):
|
||||
p = subprocess.Popen(cmd, close_fds=True)
|
||||
p.communicate()
|
||||
if self.cfg.archive:
|
||||
if not os.path.isdir(self.cfg.archive_dir):
|
||||
os.makedirs(self.cfg.archive_dir)
|
||||
for fn in glob.glob(fname + '*'):
|
||||
shutil.move(fn, self.cfg.archive_dir)
|
||||
for fn in glob.glob(fname + '*'):
|
||||
try:
|
||||
os.unlink(fn)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def run_subprocess(self, cmd):
|
||||
def run_subprocess(self, cmd, fname):
|
||||
"""Execute the given action.
|
||||
|
||||
:param cmd: the command to be run with its command line arguments
|
||||
:type cmd: list
|
||||
"""
|
||||
p = mp.Process(target=self._run, args=(cmd,))
|
||||
p = mp.Process(target=self._run, args=(cmd, fname))
|
||||
p.start()
|
||||
|
||||
def print_file(self, fname):
|
||||
cmd = PRINT_CMD + [fname]
|
||||
self.run_subprocess(cmd)
|
||||
if self.cfg.archive:
|
||||
if not os.path.isdir(self.cfg.archive_dir):
|
||||
os.makedirs(self.cfg.archive_dir)
|
||||
shutil.move(fname, self.cfg.archive_dir)
|
||||
copies = 1
|
||||
try:
|
||||
with open(fname + '.copies', 'r') as fd:
|
||||
copies = int(fd.read())
|
||||
if copies < 1:
|
||||
copies = 1
|
||||
except Exception:
|
||||
pass
|
||||
cmd = [x % {'copies': copies} for x in PRINT_CMD] + [fname]
|
||||
self.run_subprocess(cmd, fname)
|
||||
|
||||
|
||||
class PrintHandler(BaseHandler):
|
||||
|
@ -138,7 +155,8 @@ class PrintHandler(BaseHandler):
|
|||
if not code:
|
||||
self.build_error("empty code")
|
||||
return
|
||||
files = glob.glob(self.cfg.queue_dir + '/%s-*' % code)
|
||||
files = [x for x in sorted(glob.glob(self.cfg.queue_dir + '/%s-*' % code))
|
||||
if not x.endswith('.info') and not x.endswith('.pages')]
|
||||
if not files:
|
||||
self.build_error("no matching files")
|
||||
return
|
||||
|
@ -172,6 +190,16 @@ class UploadHandler(BaseHandler):
|
|||
if not self.request.files.get('file'):
|
||||
self.build_error("no file uploaded")
|
||||
return
|
||||
copies = 1
|
||||
try:
|
||||
copies = int(self.get_argument('copies'))
|
||||
if copies < 1:
|
||||
copies = 1
|
||||
except Exception:
|
||||
pass
|
||||
if copies > self.cfg.max_pages:
|
||||
self.build_error('you have asked too many copies')
|
||||
return
|
||||
fileinfo = self.request.files['file'][0]
|
||||
webFname = fileinfo['filename']
|
||||
extension = ''
|
||||
|
@ -195,8 +223,38 @@ class UploadHandler(BaseHandler):
|
|||
with open(pname + '.info', 'w') as fd:
|
||||
fd.write('original file name: %s\n' % webFname)
|
||||
fd.write('uploaded on: %s\n' % now)
|
||||
fd.write('copies: %d\n' % copies)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
with open(pname + '.copies', 'w') as fd:
|
||||
fd.write('%d' % copies)
|
||||
except Exception:
|
||||
pass
|
||||
failure = False
|
||||
if self.cfg.check_pdf_pages or self.cfg.pdf_only:
|
||||
try:
|
||||
p = subprocess.Popen(['pdfinfo', pname], stdout=subprocess.PIPE)
|
||||
out, _ = p.communicate()
|
||||
if p.returncode != 0 and self.cfg.pdf_only:
|
||||
self.build_error('the uploaded file does not seem to be a PDF')
|
||||
failure = True
|
||||
out = out.decode('utf-8', errors='ignore')
|
||||
pages = int(re_pages.findall(out)[0])
|
||||
if pages * copies > self.cfg.max_pages and self.cfg.check_pdf_pages:
|
||||
self.build_error('too many pages to print (%d)' % (pages * copies))
|
||||
failure = True
|
||||
except Exception:
|
||||
self.build_error('unable to get PDF information')
|
||||
failure = True
|
||||
pass
|
||||
if failure:
|
||||
for fn in glob.glob(pname + '*'):
|
||||
try:
|
||||
os.unlink(fn)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
if self.cfg.print_with_code:
|
||||
self.build_success("go to the printer and enter this code: %s" % code)
|
||||
else:
|
||||
|
@ -225,10 +283,13 @@ def serve():
|
|||
define('ssl_key', default=os.path.join(os.path.dirname(__file__), 'ssl', 'httprint_key.pem'),
|
||||
help='specify the SSL private key to use for secure connections')
|
||||
define('code-digits', default=CODE_DIGITS, help='number of digits of the code', type=int)
|
||||
define('max-pages', default=MAX_PAGES, help='maximum number of pages to print', type=int)
|
||||
define('queue-dir', default=QUEUE_DIR, help='directory to store files before they are printed', type=str)
|
||||
define('archive', default=True, help='archive printed files', type=bool)
|
||||
define('archive-dir', default=ARCHIVE_DIR, help='directory to archive printed files', type=str)
|
||||
define('print-with-code', default=True, help='a code must be entered for printing', type=bool)
|
||||
define('pdf-only', default=True, help='only print PDF files', type=bool)
|
||||
define('check-pdf-pages', default=True, help='check that the number of pages of PDF files do not exeed --max-pages', type=bool)
|
||||
define('debug', default=False, help='run in debug mode', type=bool)
|
||||
tornado.options.parse_command_line()
|
||||
|
||||
|
|
Loading…
Reference in a new issue