schilder.py 12.1 KB
Newer Older
Robin Sonnabend's avatar
Robin Sonnabend committed
1
#!/usr/bin/env python3
Dave Kliczbor's avatar
Dave Kliczbor committed
2

Robin Sonnabend's avatar
Robin Sonnabend committed
3 4
from flask import Flask, flash, session, redirect, url_for, escape, request, Response, Markup, render_template
from jinja2 import Environment, PackageLoader
5 6 7 8
import sys
import os
import os.path
import glob
9
import werkzeug
Dave Kliczbor's avatar
Dave Kliczbor committed
10 11
from werkzeug.utils import secure_filename
from collections import defaultdict
12
from docutils.core import publish_parts
Robin Sonnabend's avatar
Robin Sonnabend committed
13 14
from PIL import Image
from pdf2image import convert_from_path
Dave Kliczbor's avatar
Dave Kliczbor committed
15 16
import warnings
import shutil
Robin Sonnabend's avatar
Robin Sonnabend committed
17
import subprocess as sp
Dave Kliczbor's avatar
Dave Kliczbor committed
18 19 20 21 22
import json
import tempfile
import config

app = Flask(__name__)
Dave Kliczbor's avatar
Dave Kliczbor committed
23 24 25
app.config.update(
    UPLOAD_FOLDER = config.uploaddir,
    PROPAGATE_EXCEPTIONS = True,
Robin Sonnabend's avatar
Robin Sonnabend committed
26
    MAX_CONTENT_LENGTH = 8388608
Dave Kliczbor's avatar
Dave Kliczbor committed
27
)
Dave Kliczbor's avatar
Dave Kliczbor committed
28
app.secret_key = config.app_secret
Robin Sonnabend's avatar
Robin Sonnabend committed
29

Dave Kliczbor's avatar
Dave Kliczbor committed
30 31

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

34
def load_data(filename):
35 36
    with open(os.path.join(config.datadir, filename), 'r') as infile:
        formdata = defaultdict(str, json.load(infile))
37
        formdata['filename'] = filename
38 39
        if len(formdata['markup']) < 1:
            formdata['markup'] = 'latex'
40 41 42 43 44 45
        return formdata
        
def save_data(formdata, outfilename):
    with open(os.path.join(config.datadir, outfilename), 'w') as outfile:
        json.dump(formdata, outfile)
    
46
def run_pdflatex(context, outputfilename, overwrite=True):
Robin Sonnabend's avatar
Robin Sonnabend committed
47
    if 'textemplate' not in context:
48
        context['textemplate'] = "text-image-quer.tex"
Robin Sonnabend's avatar
Robin Sonnabend committed
49 50 51 52 53 54 55 56
    texenv = Environment(loader=PackageLoader("schilder", config.textemplatedir))
    texenv.variable_start_string = "${"
    texenv.variable_end_string = "}"
    texenv.block_start_string = "${{{{{"
    texenv.block_end_string = "}}}}}"
    template = texenv.get_template(context['textemplate'])
    #if not overwrite and os.path.isfile(outputfilename) and os.path.getmtime(template.filepath) < os.path.getmtime(outputfilename):
    #    return
57 58
    if context['markup'] == 'rst':
        context['text'] = publish_parts(context['text'], writer_name='latex')['body']
59
        #context['headline'] = publish_parts(context['headline'], writer_name='latex')['body']
60
    tmpdir = tempfile.mkdtemp(dir=config.tmpdir)
Robin Sonnabend's avatar
Robin Sonnabend committed
61
    if 'img' in context and context['img'] and context['img'] != '__none':
62
        try:
Dave Kliczbor's avatar
Dave Kliczbor committed
63 64
            shutil.copy(os.path.join(config.imagedir, context['img']), 
                        os.path.join(tmpdir, context['img']))
65 66 67 68 69 70 71 72
        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:
Robin Sonnabend's avatar
Robin Sonnabend committed
73
        texfile.write(template.render(form=context))
74
    os.symlink(config.texsupportdir, os.path.join(tmpdir, 'support'))
Dave Kliczbor's avatar
Dave Kliczbor committed
75
    try:
Robin Sonnabend's avatar
Robin Sonnabend committed
76 77
        texlog = sp.check_output(['pdflatex', '--halt-on-error', tmptexfile], stderr=sp.STDOUT, cwd=tmpdir, universal_newlines=True)
    except sp.CalledProcessError as e:
78
        if overwrite:
79 80 81
            try:
                flash(Markup("<p>PDFLaTeX Output:</p><pre>%s</pre>" % e.output), 'log')
            except:
Robin Sonnabend's avatar
Robin Sonnabend committed
82
                print((e.output))
83
        raise SyntaxWarning("PDFLaTeX bailed out")
84
    if overwrite:
85 86 87 88
        try:
            flash(Markup("<p>PDFLaTeX Output:</p><pre>%s</pre>" % texlog), 'log')
        except:
            print(texlog)
89 90 91
    shutil.copy(tmppdffile, outputfilename)
    shutil.rmtree(tmpdir)

Dave Kliczbor's avatar
Dave Kliczbor committed
92
def save_and_convert_image_upload(inputname):
93 94 95
    imgfile = request.files[inputname]
    if imgfile:
        if not allowed_file(imgfile.filename):
96 97 98
            raise UserWarning(
                "Uploaded image is not in the list of allowed file types.")
        filename = os.path.join(
99 100
            config.uploaddir, secure_filename(imgfile.filename))
        imgfile.save(filename)
Robin Sonnabend's avatar
Robin Sonnabend committed
101
        img = Image.open(filename)
102
        imgname = os.path.splitext(secure_filename(imgfile.filename))[
103 104
            0].replace('.', '_') + '.png'
        savedfilename = os.path.join(config.imagedir, imgname)
Robin Sonnabend's avatar
Robin Sonnabend committed
105
        img.save(savedfilename)
106 107 108 109
        os.remove(filename)
        return imgname
    return None

110
def make_thumb(filename, maxgeometry):
Robin Sonnabend's avatar
Robin Sonnabend committed
111
    thumbpath = (filename.replace(config.imagedir, config.cachedir) + '_' + str(maxgeometry)).replace(".", "_") + ".png"
112
    if not os.path.exists(thumbpath) or os.path.getmtime(filename) > os.path.getmtime(thumbpath):
Robin Sonnabend's avatar
Robin Sonnabend committed
113 114 115
        img = Image.open(str(filename))
        img = img.resize((maxgeometry, maxgeometry))
        img.save(thumbpath)
116 117 118 119 120 121 122 123 124
    return thumbpath



@app.route('/')
def index(**kwargs):
    data = defaultdict(str)
    data.update(**kwargs)
    filelist = glob.glob(config.datadir + '/*.schild')
Robin Sonnabend's avatar
Robin Sonnabend committed
125
    data['files'] = [str(os.path.basename(f)) for f in sorted(filelist)]
Robin Sonnabend's avatar
Robin Sonnabend committed
126
    return render_template('index.html', **data)
127 128 129 130 131 132


@app.route('/edit')
def edit(**kwargs):
    data = defaultdict(str)
    data.update(**kwargs)
Dave Kliczbor's avatar
Dave Kliczbor committed
133
    imagelist = sorted(glob.glob(config.imagedir + '/*.png'))
134 135
    data['images'] = [os.path.basename(f) for f in imagelist]
    templatelist = glob.glob(config.textemplatedir + '/*.tex')
Robin Sonnabend's avatar
Robin Sonnabend committed
136
    data['templates'] = [str(os.path.basename(f))
137
                         for f in sorted(templatelist)]
Dave Kliczbor's avatar
Dave Kliczbor committed
138
    data['imageextensions'] = config.allowed_extensions
Robin Sonnabend's avatar
Robin Sonnabend committed
139
    return render_template('edit.html', **data)
140 141 142 143 144 145


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

Dave Kliczbor's avatar
Dave Kliczbor committed
146 147 148

@app.route('/create', methods=['POST'])
def create():
149 150 151
    if request.method == 'POST':
        formdata = defaultdict(str, request.form.to_dict(flat=True))
        for a in ('headline', 'text'):
Robin Sonnabend's avatar
Robin Sonnabend committed
152
            formdata[a] = str(formdata[a])
153 154 155 156 157 158
        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'
159 160
            if formdata['reusefilename']:
                outfilename = secure_filename(formdata['filename'])
161
            outpdfname = outfilename + '.pdf'
162
            formdata['filename'] = outfilename
163
            formdata['pdfname'] = outpdfname
164
            save_data(formdata, outfilename)
165
            run_pdflatex(formdata, os.path.join(config.pdfdir, outpdfname))
166
            try:
Robin Sonnabend's avatar
Robin Sonnabend committed
167
                flash(Markup("""PDF created and data saved. You might create another one. Here's a preview. Click to print.<br/>
168
                                <a href="%s"><img src="%s"/></a>""" %
169 170 171
                         (url_for('schild', filename=outfilename), url_for(
                             'pdfthumbnail', pdfname=outpdfname, maxgeometry=200))
                         ))
172
            except:
Robin Sonnabend's avatar
Robin Sonnabend committed
173
                print(("%s created" % outpdfname))
174
        except Exception as e:
175
            try:
Robin Sonnabend's avatar
Robin Sonnabend committed
176
                flash("Could not create pdf or save data: %s" % str(e), 'error')
177
            except:
Robin Sonnabend's avatar
Robin Sonnabend committed
178
                print(("Could not create pdf or save data: %s" % str(e)))
179 180 181 182 183 184

        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)]
185 186 187 188 189 190 191 192 193
        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
194 195 196 197


@app.route('/schild/<filename>')
def schild(filename):
Robin Sonnabend's avatar
Robin Sonnabend committed
198
    return render_template('schild.html', filename=filename, printer=[str(f) for f in sorted(config.printers.keys())])
199

Dave Kliczbor's avatar
Dave Kliczbor committed
200 201 202

@app.route('/printout', methods=['POST'])
def printout():
203 204 205 206 207 208
    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:
Robin Sonnabend's avatar
Robin Sonnabend committed
209 210
            lprout = sp.check_output(['lpr', '-H', str(config.printserver), '-P', str(
                printer), '-#', str(copies)] + config.lproptions + [filename], stderr=sp.STDOUT)
Robin Sonnabend's avatar
Robin Sonnabend committed
211
            flash('Schild wurde zum Drucker geschickt!')
Robin Sonnabend's avatar
Robin Sonnabend committed
212
        except sp.CalledProcessError as e:
213 214
            flash(Markup("<p>Could not print:</p><pre>%s</pre>" % e.output), 'error')
    else:
Robin Sonnabend's avatar
Robin Sonnabend committed
215
        flash('Ungültige Anzahl Kopien!')
216 217
    return redirect(url_for('index'))

Dave Kliczbor's avatar
Dave Kliczbor committed
218
def delete_file(filename):
219 220 221 222
    try:
        os.unlink(os.path.join(config.datadir, filename))
        for f in glob.glob(os.path.join(config.pdfdir, filename + '.pdf*')):
            os.unlink(f)
Robin Sonnabend's avatar
Robin Sonnabend committed
223
        flash("Schild %s wurde gelöscht" % filename)
224 225
        return redirect(url_for('index'))
    except:
Robin Sonnabend's avatar
Robin Sonnabend committed
226
        flash("Schild %s konnte nicht gelöscht werden." % filename, 'error')
227 228
        return redirect(url_for('schild', filename=filename))

Dave Kliczbor's avatar
Dave Kliczbor committed
229

Dave Kliczbor's avatar
Dave Kliczbor committed
230 231 232 233 234 235 236 237 238 239
@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
240 241
@app.route('/image/<imgname>')
def image(imgname):
242 243 244
    imgpath = os.path.join(config.imagedir, secure_filename(imgname))
    # print(imgpath)
    if os.path.exists(imgpath):
Robin Sonnabend's avatar
Robin Sonnabend committed
245
        with open(imgpath, 'rb') as imgfile:
246 247 248 249
            return Response(imgfile.read(), mimetype="image/png")
    else:
        return "Meh"  # redirect(url_for('index'))

Dave Kliczbor's avatar
Dave Kliczbor committed
250 251 252

@app.route('/thumbnail/<imgname>/<int:maxgeometry>')
def thumbnail(imgname, maxgeometry):
253 254
    imgpath = os.path.join(config.imagedir, secure_filename(imgname))
    thumbpath = make_thumb(imgpath, maxgeometry)
Robin Sonnabend's avatar
Robin Sonnabend committed
255
    with open(thumbpath, 'rb') as imgfile:
256 257
        return Response(imgfile.read(), mimetype="image/png")

Dave Kliczbor's avatar
Dave Kliczbor committed
258 259 260

@app.route('/pdfthumb/<pdfname>/<int:maxgeometry>')
def pdfthumbnail(pdfname, maxgeometry):
261
    pdfpath = os.path.join(config.pdfdir, secure_filename(pdfname))
Robin Sonnabend's avatar
Robin Sonnabend committed
262 263 264 265 266
    pngpath = pdfpath.replace(".", "_") + ".png"
    img = convert_from_path(pdfpath)[0]
    img.save(pngpath) 
    thumbpath = make_thumb(pngpath, maxgeometry)
    with open(thumbpath, 'rb') as imgfile:
267 268
        return Response(imgfile.read(), mimetype="image/png")

Dave Kliczbor's avatar
Dave Kliczbor committed
269

270 271
@app.route('/tplthumb/<tplname>/<int:maxgeometry>')
def tplthumbnail(tplname, maxgeometry):
272 273 274 275 276
    pdfpath = os.path.join(config.cachedir, secure_filename(tplname) + '.pdf')
    try:
        run_pdflatex(
            {'textemplate': secure_filename(tplname),
             'img': 'pictograms-nps-misc-camera.png',
Robin Sonnabend's avatar
Robin Sonnabend committed
277 278
             'headline': 'Überschrift',
             'text': 'Dies ist der Text, der in der UI als Text bezeichnet ist.',
279
             'markup': 'latex',
280 281 282
             }, pdfpath, overwrite=False
        )
    except Exception as e:
Robin Sonnabend's avatar
Robin Sonnabend committed
283
        raise e
284 285
        return str(e)
    else:
Robin Sonnabend's avatar
Robin Sonnabend committed
286 287 288 289 290
        pngpath = pdfpath.replace(".", "_") + ".png"
        img = convert_from_path(pdfpath)[0]
        img.save(pngpath) 
        thumbpath = make_thumb(pngpath, maxgeometry)
        with open(thumbpath, 'rb') as imgfile:
291 292
            return Response(imgfile.read(), mimetype="image/png")

293

Dave Kliczbor's avatar
Dave Kliczbor committed
294 295
@app.route('/pdfdownload/<pdfname>')
def pdfdownload(pdfname):
296
    pdfpath = os.path.join(config.pdfdir, secure_filename(pdfname))
Robin Sonnabend's avatar
Robin Sonnabend committed
297
    with open(pdfpath, 'rb') as pdffile:
298
        return Response(pdffile.read(), mimetype="application/pdf")
Dave Kliczbor's avatar
Dave Kliczbor committed
299

300

301 302 303 304 305 306
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)
Robin Sonnabend's avatar
Robin Sonnabend committed
307
            print(("Deleted %s" % filename))
308
        except Exception as e:
Robin Sonnabend's avatar
Robin Sonnabend committed
309
            print(("Could not delete %s: %s" % (filename, str(e))))
310 311 312
    for filename in glob.glob(os.path.join(config.datadir, '*.schild')):
        data = load_data(filename)
        pdfname = os.path.join(config.pdfdir, data['pdfname'])
Robin Sonnabend's avatar
Robin Sonnabend committed
313
        print(("Recreating %s" % pdfname))
314
        run_pdflatex(data, pdfname)
315

Dave Kliczbor's avatar
Dave Kliczbor committed
316
if __name__ == '__main__':
317 318 319 320 321
    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)