schilder.py 13.3 KB
Newer Older
1
#!/usr/bin/env python2
Dave Kliczbor's avatar
Dave Kliczbor committed
2 3 4
# -*- encoding: utf8 -*-

from flask import Flask, flash, session, redirect, url_for, escape, request, Response, Markup
5 6 7 8
import sys
import os
import os.path
import glob
Dave Kliczbor's avatar
Dave Kliczbor committed
9 10 11 12 13
from genshi.template import TemplateLoader
from genshi.template.text import NewTextTemplate
from flaskext.genshi import Genshi, render_response
from werkzeug.utils import secure_filename
from collections import defaultdict
14
from docutils.core import publish_parts
Dave Kliczbor's avatar
Dave Kliczbor committed
15 16 17 18 19 20 21 22 23 24
import warnings
import shutil
import subprocess
from subprocess import CalledProcessError, STDOUT
import PythonMagick
import json
import tempfile
import config

app = Flask(__name__)
Dave Kliczbor's avatar
Dave Kliczbor committed
25 26 27 28 29
app.config.update(
    UPLOAD_FOLDER = config.uploaddir,
    PROPAGATE_EXCEPTIONS = True,
    MAX_CONTENT_LENGTH = 8388608L
)
Dave Kliczbor's avatar
Dave Kliczbor committed
30 31 32 33
app.secret_key = config.app_secret
genshi = Genshi(app)
genshi.extensions['html'] = 'html5'

34

Dave Kliczbor's avatar
Dave Kliczbor committed
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
def check_output(*popenargs, **kwargs):
    # Copied from py2.7s subprocess module
    r"""Run command with arguments and return its output as a byte string.

    If the exit code was non-zero it raises a CalledProcessError.  The
    CalledProcessError object will have the return code in the returncode
    attribute and output in the output attribute.

    The arguments are the same as for the Popen constructor.  Example:

    >>> check_output(["ls", "-l", "/dev/null"])
    'crw-rw-rw- 1 root root 1, 3 Oct 18  2007 /dev/null\n'

    The stdout argument is not allowed as it is used internally.
    To capture standard error in the result, use stderr=STDOUT.

    >>> check_output(["/bin/sh", "-c",
    ...               "ls -l non_existent_file ; exit 0"],
    ...              stderr=STDOUT)
    'ls: non_existent_file: No such file or directory\n'
    """
    if 'stdout' in kwargs:
        raise ValueError('stdout argument not allowed, it will be overridden.')
    process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
    output, unused_err = process.communicate()
    retcode = process.poll()
    if retcode != 0:
        cmd = kwargs.get("args")
        if cmd is None:
            cmd = popenargs[0]
        raise CalledProcessError(retcode, cmd, output=output)
        #raise Exception(output)
    return output

def allowed_file(filename):
70 71
    return '.' in filename and filename.rsplit('.', 1)[1] in config.allowed_extensions

72
def load_data(filename):
73 74
    with open(os.path.join(config.datadir, filename), 'r') as infile:
        formdata = defaultdict(str, json.load(infile))
75
        formdata['filename'] = filename
76 77
        if len(formdata['markup']) < 1:
            formdata['markup'] = 'latex'
78 79 80 81 82 83
        return formdata
        
def save_data(formdata, outfilename):
    with open(os.path.join(config.datadir, outfilename), 'w') as outfile:
        json.dump(formdata, outfile)
    
84
def run_pdflatex(context, outputfilename, overwrite=True):
85 86 87 88 89 90 91 92 93
    if not context.has_key('textemplate'):
        context['textemplate'] = "text-image-quer.tex"
    genshitex = TemplateLoader([config.textemplatedir])
    template = genshitex.load(
        context['textemplate'], cls=NewTextTemplate, encoding='utf8')
    if not overwrite and os.path.isfile(outputfilename) and os.path.getmtime(template.filepath) < os.path.getmtime(outputfilename):
        return
    if context['markup'] == 'rst':
        context['text'] = publish_parts(context['text'], writer_name='latex')['body']
94
        #context['headline'] = publish_parts(context['headline'], writer_name='latex')['body']
95 96 97
    tmpdir = tempfile.mkdtemp(dir=config.tmpdir)
    if context.has_key('img') and context['img'] and context['img'] != '__none':
        try:
Dave Kliczbor's avatar
Dave Kliczbor committed
98 99
            shutil.copy(os.path.join(config.imagedir, context['img']), 
                        os.path.join(tmpdir, context['img']))
100 101 102 103 104 105 106 107 108 109 110 111
        except:
            raise IOError("COULD NOT COPY")
    else:
        # print "MEH No image"
        pass
    tmptexfile = os.path.join(tmpdir, 'output.tex')
    tmppdffile = os.path.join(tmpdir, 'output.pdf')
    with open(tmptexfile, 'w') as texfile:
        texfile.write(template.generate(form=context).render(encoding='utf8'))
    cwd = os.getcwd()
    os.chdir(tmpdir)
    os.symlink(config.texsupportdir, os.path.join(tmpdir, 'support'))
Dave Kliczbor's avatar
Dave Kliczbor committed
112
    try:
113 114 115 116
        texlog = check_output(
            ['pdflatex', '--halt-on-error', tmptexfile], stderr=STDOUT)
    except CalledProcessError as e:
        if overwrite:
117 118 119 120
            try:
                flash(Markup("<p>PDFLaTeX Output:</p><pre>%s</pre>" % e.output), 'log')
            except:
                print(e.output)
121 122 123
        raise SyntaxWarning("PDFLaTeX bailed out")
    finally:
        os.chdir(cwd)
124
    if overwrite:
125 126 127 128
        try:
            flash(Markup("<p>PDFLaTeX Output:</p><pre>%s</pre>" % texlog), 'log')
        except:
            print(texlog)
129 130 131
    shutil.copy(tmppdffile, outputfilename)
    shutil.rmtree(tmpdir)

Dave Kliczbor's avatar
Dave Kliczbor committed
132
def save_and_convert_image_upload(inputname):
133 134 135
    imgfile = request.files[inputname]
    if imgfile:
        if not allowed_file(imgfile.filename):
136 137 138
            raise UserWarning(
                "Uploaded image is not in the list of allowed file types.")
        filename = os.path.join(
139 140
            config.uploaddir, secure_filename(imgfile.filename))
        imgfile.save(filename)
141
        img = PythonMagick.Image(filename)
142
        imgname = os.path.splitext(secure_filename(imgfile.filename))[
143 144 145 146 147 148 149
            0].replace('.', '_') + '.png'
        savedfilename = os.path.join(config.imagedir, imgname)
        img.write(savedfilename)
        os.remove(filename)
        return imgname
    return None

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
def make_thumb(filename, maxgeometry):
    thumbpath = filename + '.' + str(maxgeometry)
    if not os.path.exists(thumbpath) or os.path.getmtime(filename) > os.path.getmtime(thumbpath):
        img = PythonMagick.Image(str(filename))
        img.transform("%sx%s" % (maxgeometry, maxgeometry))
        img.quality(90)
        img.write(str("png:%s" % thumbpath))
    return thumbpath



@app.route('/')
def index(**kwargs):
    data = defaultdict(str)
    data.update(**kwargs)
    filelist = glob.glob(config.datadir + '/*.schild')
    data['files'] = [unicode(os.path.basename(f)) for f in sorted(filelist)]
    return render_response('index.html', data)


@app.route('/edit')
def edit(**kwargs):
    data = defaultdict(str)
    data.update(**kwargs)
Dave Kliczbor's avatar
Dave Kliczbor committed
174
    imagelist = sorted(glob.glob(config.imagedir + '/*.png'))
175 176 177 178
    data['images'] = [os.path.basename(f) for f in imagelist]
    templatelist = glob.glob(config.textemplatedir + '/*.tex')
    data['templates'] = [unicode(os.path.basename(f))
                         for f in sorted(templatelist)]
Dave Kliczbor's avatar
Dave Kliczbor committed
179
    data['imageextensions'] = config.allowed_extensions
180 181 182 183 184 185 186
    return render_response('edit.html', data)


@app.route('/edit/<filename>')
def edit_one(filename):
    return edit(form=load_data(filename))

Dave Kliczbor's avatar
Dave Kliczbor committed
187 188 189

@app.route('/create', methods=['POST'])
def create():
190 191 192 193 194 195 196 197 198 199
    if request.method == 'POST':
        formdata = defaultdict(str, request.form.to_dict(flat=True))
        for a in ('headline', 'text'):
            formdata[a] = unicode(formdata[a])
        try:
            imgpath = save_and_convert_image_upload('imgupload')
            if imgpath is not None:
                formdata['img'] = imgpath
            outfilename = secure_filename(formdata['headline'][:16]) + str(hash(formdata['headline'] + formdata[
                'text'] + os.path.splitext(formdata['textemplate'])[0] + os.path.splitext(formdata['img'])[0])) + '.schild'
200 201
            if formdata['reusefilename']:
                outfilename = secure_filename(formdata['filename'])
202
            outpdfname = outfilename + '.pdf'
203
            formdata['filename'] = outfilename
204
            formdata['pdfname'] = outpdfname
205
            save_data(formdata, outfilename)
206
            run_pdflatex(formdata, os.path.join(config.pdfdir, outpdfname))
207 208 209
            try:
                flash(Markup(u"""PDF created and data saved. You might create another one. Here's a preview. Click to print.<br/>
                                <a href="%s"><img src="%s"/></a>""" %
210 211 212
                         (url_for('schild', filename=outfilename), url_for(
                             'pdfthumbnail', pdfname=outpdfname, maxgeometry=200))
                         ))
213 214
            except:
                print("%s created" % outpdfname)
215
        except Exception as e:
216 217 218 219
            try:
                flash(u"Could not create pdf or save data: %s" % str(e), 'error')
            except:
                print("Could not create pdf or save data: %s" % str(e))
220 221 222 223 224 225

        data = {'form': formdata}
        imagelist = glob.glob(config.imagedir + '/*.png')
        data['images'] = [os.path.basename(f) for f in imagelist]
        templatelist = glob.glob(config.textemplatedir + '/*.tex')
        data['templates'] = [os.path.basename(f) for f in sorted(templatelist)]
226 227 228 229 230 231 232 233 234
        try:
            return redirect(url_for('edit_one', filename=outfilename))
        except:
            pass
    try:
        flash("No POST data. You've been redirected to the edit page.", 'warning')
        return redirect(url_for('edit'))
    except:
        pass
Dave Kliczbor's avatar
Dave Kliczbor committed
235 236 237 238


@app.route('/schild/<filename>')
def schild(filename):
239 240
    return render_response('schild.html', {'filename': filename, 'printer': [unicode(f) for f in sorted(config.printers.keys())]})

Dave Kliczbor's avatar
Dave Kliczbor committed
241 242 243

@app.route('/printout', methods=['POST'])
def printout():
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
    filename = os.path.join(
        config.pdfdir, secure_filename(request.form['filename']))
    printer = config.printers[request.form['printer']]
    copies = int(request.form['copies']) or 0
    if copies > 0 and copies <= 6:
        try:
            lprout = check_output(['lpr', '-H', str(config.printserver), '-P', str(
                printer), '-#', str(copies)] + config.lproptions + [filename], stderr=STDOUT)
            flash(u'Schild wurde zum Drucker geschickt!')
        except CalledProcessError as e:
            flash(Markup("<p>Could not print:</p><pre>%s</pre>" % e.output), 'error')
    else:
        flash(u'Ungültige Anzahl Kopien!')
    return redirect(url_for('index'))

259
def delete_file(filename):
260 261 262 263 264 265 266 267 268 269
    try:
        os.unlink(os.path.join(config.datadir, filename))
        for f in glob.glob(os.path.join(config.pdfdir, filename + '.pdf*')):
            os.unlink(f)
        flash(u"Schild %s wurde gelöscht" % filename)
        return redirect(url_for('index'))
    except:
        flash(u"Schild %s konnte nicht gelöscht werden." % filename, 'error')
        return redirect(url_for('schild', filename=filename))

Dave Kliczbor's avatar
Dave Kliczbor committed
270

271 272 273 274 275 276 277 278 279 280
@app.route('/delete', methods=['POST'])
def delete():
    return delete_file(secure_filename(request.form['filename']))

@app.route('/deletelist', methods=['POST'])
def deletelist():
    for filename in request.form.getlist('filenames'):
        delete_file(secure_filename(filename))
    return redirect(url_for('index'))

Dave Kliczbor's avatar
Dave Kliczbor committed
281 282
@app.route('/image/<imgname>')
def image(imgname):
283 284 285 286 287 288 289 290
    imgpath = os.path.join(config.imagedir, secure_filename(imgname))
    # print(imgpath)
    if os.path.exists(imgpath):
        with open(imgpath, 'r') as imgfile:
            return Response(imgfile.read(), mimetype="image/png")
    else:
        return "Meh"  # redirect(url_for('index'))

Dave Kliczbor's avatar
Dave Kliczbor committed
291 292 293

@app.route('/thumbnail/<imgname>/<int:maxgeometry>')
def thumbnail(imgname, maxgeometry):
294 295 296 297 298
    imgpath = os.path.join(config.imagedir, secure_filename(imgname))
    thumbpath = make_thumb(imgpath, maxgeometry)
    with open(thumbpath, 'r') as imgfile:
        return Response(imgfile.read(), mimetype="image/png")

Dave Kliczbor's avatar
Dave Kliczbor committed
299 300 301

@app.route('/pdfthumb/<pdfname>/<int:maxgeometry>')
def pdfthumbnail(pdfname, maxgeometry):
302 303 304 305 306
    pdfpath = os.path.join(config.pdfdir, secure_filename(pdfname))
    thumbpath = make_thumb(pdfpath, maxgeometry)
    with open(thumbpath, 'r') as imgfile:
        return Response(imgfile.read(), mimetype="image/png")

Dave Kliczbor's avatar
Dave Kliczbor committed
307

308 309
@app.route('/tplthumb/<tplname>/<int:maxgeometry>')
def tplthumbnail(tplname, maxgeometry):
310 311 312 313 314 315 316
    pdfpath = os.path.join(config.cachedir, secure_filename(tplname) + '.pdf')
    try:
        run_pdflatex(
            {'textemplate': secure_filename(tplname),
             'img': 'pictograms-nps-misc-camera.png',
             'headline': u'Überschrift',
             'text': u'Dies ist der Text, der in der UI als Text bezeichnet ist.',
317
             'markup': 'latex',
318 319 320 321 322 323 324 325 326
             }, pdfpath, overwrite=False
        )
    except Exception as e:
        return str(e)
    else:
        thumbpath = make_thumb(pdfpath, maxgeometry)
        with open(thumbpath, 'r') as imgfile:
            return Response(imgfile.read(), mimetype="image/png")

327

Dave Kliczbor's avatar
Dave Kliczbor committed
328 329
@app.route('/pdfdownload/<pdfname>')
def pdfdownload(pdfname):
330 331 332
    pdfpath = os.path.join(config.pdfdir, secure_filename(pdfname))
    with open(pdfpath, 'r') as pdffile:
        return Response(pdffile.read(), mimetype="application/pdf")
Dave Kliczbor's avatar
Dave Kliczbor committed
333

334

335 336 337 338 339 340 341 342 343 344 345 346 347 348
def recreate_cache():
    for filename in (glob.glob(os.path.join(config.pdfdir, '*.pdf*')) +
             glob.glob(os.path.join(config.cachedir, '*.pdf*')) +
             glob.glob(os.path.join(config.imagedir, '*.png.*'))):
        try:
            os.unlink(filename)
            print("Deleted %s" % filename)
        except Exception as e:
            print("Could not delete %s: %s" % (filename, str(e)))
    for filename in glob.glob(os.path.join(config.datadir, '*.schild')):
        data = load_data(filename)
        pdfname = os.path.join(config.pdfdir, data['pdfname'])
        print("Recreating %s" % pdfname)
        run_pdflatex(data, pdfname)
349

Dave Kliczbor's avatar
Dave Kliczbor committed
350
if __name__ == '__main__':
351 352 353 354 355
    if len(sys.argv) > 1 and sys.argv[1] == '--recreate-cache':
        recreate_cache()
    else:
        app.debug = True
        app.run(host=config.listen, port=config.port)