From 6c8f3dd807790500d2552161df5bb5c77061119e Mon Sep 17 00:00:00 2001 From: Thorsten Behrens Date: Wed, 10 Apr 2013 03:13:12 +0200 Subject: Make upload really work, have templates for user-facing pages. split up stuff into /api/* and /*/ user-facing pages. --- convert_deck.sh | 19 +++++ slideapi.py | 250 ++++++++++++++++++++++++++++++++++++++++--------------- views/decks.tpl | 9 ++ views/revs.tpl | 9 ++ views/slides.tpl | 17 ++++ views/tags.tpl | 11 +++ views/users.tpl | 13 +++ 7 files changed, 260 insertions(+), 68 deletions(-) create mode 100755 convert_deck.sh create mode 100644 views/decks.tpl create mode 100644 views/revs.tpl create mode 100644 views/slides.tpl create mode 100644 views/tags.tpl create mode 100644 views/users.tpl diff --git a/convert_deck.sh b/convert_deck.sh new file mode 100755 index 0000000..e410b94 --- /dev/null +++ b/convert_deck.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +soffice --headless --convert-to pdf --outdir $1 $1/deck.odp +convert -resize $2 $1/deck.pdf $1/deck.png +rm $1/deck.pdf +for i in $1/deck-*.png; do + rev1=${i/*deck-/} + rev2=${rev1/.png/} + mkdir -p $1/$rev2 + mv $i $1/$rev2/$rev2.png +done + diff --git a/slideapi.py b/slideapi.py index bc26c83..b212fdd 100755 --- a/slideapi.py +++ b/slideapi.py @@ -7,47 +7,20 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # -import os, json -from bottle import get, post, auth_basic, run, static_file, request, debug +import os, json, time +from bottle import get, post, auth_basic, run, static_file, request, debug, view, HTTPError from subprocess import call - -# ----- - -@get('/api/users/') -def users(): - return "User Test" - -@get('/api/topics/') -def topics(): - return "Topic Test" - -@get('/api/users/test/') -def test(): - return "deck1
deck2" - -@get('/api/users/test/deck1/') -def deck1(): - return "revision 1
revision 2" - -@get('/api/users/test///') -def list_deck(deck, rev): - return "checkin data
checkin comment
deck.fodp
slide 1" - -@get('/api/users/test///checkinDate') -def send_checkinDate(deck,rev): - return '2013-04-01 for '+deck+', rev '+str(rev) - -@get('/api/users/test///checkinComment') -def send_checkinComment(deck,rev): - return 'Riiiiight on for '+deck+', rev '+str(rev) - -@get('/api/users/test////keywords.json') -def send_keywords(deck, rev, slide): - return {'Author': 'Joe User', - 'Title': 'The great slideshow'} - +# config +# +# we understand: +# local_conf["users"], yielding {: {"password": , "description": }, ... } +# local_conf["thumbnails"], yielding {"soffice": , "convert": , "thumbnail_size": <128x128>} +# local_conf["tags"], yielding [ , ... ] +# local_conf=json.loads(open('local.json').read()) if os.path.exists('local.json') else {} +root='filestore' + def validate_auth(user, password): if "users" in local_conf: user_dict = local_conf["users"] @@ -55,55 +28,196 @@ def validate_auth(user, password): return password == user_dict[user]['password'] return False +# ------------------------------------------------------------------- + +def get_users(): + return {user: data['description'] for user, data in local_conf["users"].items()} + +@get('/api/users/') +def users(): + return get_users() + +@get('/users/') +@view('users') +def users(): + return dict(users=get_users()) + +# ------------------------------------------------------------------- + +def get_tags(): + return local_conf["tags"] + +@get('/api/tags/') +def tags(): + return json.dumps(get_tags()) + +@get('/tags/') +@view('tags') +def tags(): + return dict(tags=get_tags()) + +# ------------------------------------------------------------------- + +def get_decks(user): + path = "%s/%s/" % (root,user) + if not os.path.isdir(path): + return [] + return [deck for deck in os.listdir(path) if os.path.isdir(path+deck)] + +def get_lastrev(user,deck): + path = "%s/%s/%s/" % (root,user,deck) + if not os.path.isdir(path): + return 0 + # get last revision + return int(sorted([rev for rev in os.listdir(path) if os.path.isdir(path+rev)], key=int)[-1]) + +@get('/api/users//') +def list_decks(user): + return json.dumps(get_decks(user)) + +@get('/users//') +@view('decks') +def list_decks(user): + return dict(decks=get_decks(user)) + +@get('/users///thumbnail.png') +@get('/api/users///thumbnail.png') +def get_thumbnail(user, deck): + path = "%s/%s/%s/" % (root,user,deck) + last_rev = get_lastrev(user,deck) + # serve thumbnail from that rev, first slide + return static_file('0.png', root=path+str(last_rev)+'/0/', mimetype='image/png') + +# ------------------------------------------------------------------- + +def get_revs(user,deck): + path = "%s/%s/%s/" % (root,user,deck) + if not os.path.isdir(path): + return [] + return sorted([rev for rev in os.listdir(path) if os.path.isdir(path+rev)], key=int) + +@get('/api/users///') +def list_revs(user,deck): + return json.dumps(get_revs(user,deck)) + +@get('/users///') +@view('revs') +def list_revs(user,deck): + return dict(revs=get_revs(user,deck)) + # eventually use ssl here - http://dgtool.blogspot.de/2011/12/ssl-encryption-in-python-bottle.html -@post('/api/upload') +@post('/users//') +@post('/api/users//') @auth_basic(validate_auth, realm='upload') -def upload_file(): - upload_path = '/tmp' - soffice = 'soffice' - convert = 'convert' - thumbnail_size = '128x128' +def upload_deck(user,deck): + path = "%s/%s/%s/" % (root,user,deck) + new_rev = get_lastrev(user,deck) + 1 + new_path = path+str(new_rev) - # override defaults by config - if "thumbnails" in local_conf: - thumbnails_dict = local_conf["thumbnails"] - upload_path = thumbnails_dict['upload_path'] if 'upload_path' in thumbnails_dict else upload_path - soffice = thumbnails_dict['soffice'] if 'soffice' in thumbnails_dict else soffice - convert = thumbnails_dict['convert'] if 'convert' in thumbnails_dict else convert - thumbnail_size = thumbnails_dict['thumbnail_size'] if 'thumbnail_size' in thumbnails_dict else thumbnail_size + if os.path.isdir(new_path): + raise HTTPError(body='inconsistent repo, bailing out') + + if request.auth[0] != user: + raise HTTPError(body='invalid user or insufficient rights, bailing out') + + os.makedirs(new_path) + upload_path = new_path # TODO: Add all required parameters here tag = request.forms.get('tag') content = request.files.get('file') # TODO: Create proper paths for uploads & Check for upload errors - out = open(upload_path+'/'+content.filename, 'wb') + out = open(upload_path+'/deck.odp', 'wb') while True: bits = content.file.read(10240) if not bits: break out.write(bits) out.close() - call([soffice, '--headless', '--convert-to', 'pdf', '--outdir', upload_path, upload_path+'/'+content.filename]) - call([convert, '-resize', thumbnail_size, upload_path+'/'+os.path.splitext(content.filename)[0]+'.pdf', upload_path+'/'+os.path.splitext(content.filename)[0]+'.png']) - return 'Success:'+tag+':'+content.filename -#----- + out = open(upload_path+'/meta.json', 'wb') + json.dump({'tag': tag, + 'server_version': '1', + 'upload_filename': content.filename}, + out) -@get('/api/users/test///deck.fodp') -def send_deck(deck, rev): - return static_file(deck+'.fodp', root='decks/'+deck+'/'+str(rev), mimetype='text/xml') + thumbnail_size = '128x128' -@get('/api/users/test////') -def list_slide(deck,rev,slide): - return "thumbnail
slide "+str(slide)+"
keywords" + # override defaults by config + if "thumbnails" in local_conf: + thumbnails_dict = local_conf["thumbnails"] + thumbnail_size = thumbnails_dict['thumbnail_size'] if 'thumbnail_size' in thumbnails_dict else thumbnail_size + + os.system('./convert_deck.sh '+upload_path+' '+thumbnail_size) + return 'Success:'+tag+':'+content.filename -@get('/api/users/test////slide.fodp') -def send_slide(deck, rev, slide): - return static_file(str(slide)+'.fodp', root='decks/'+deck+'/'+str(rev), mimetype='text/xml') -@get('/api/users/test////thumbnail.png') -def send_thumbnail(deck, rev, slide): - return static_file(str(slide)+'.png', root='decks/'+deck+'/'+str(rev), mimetype='image/png') +@get('/users////thumbnail.png') +@get('/api/users////thumbnail.png') +def get_thumbnail(user, deck, rev): + path = "%s/%s/%s/%d/" % (root,user,deck,rev) + # serve thumbnail from that rev, first slide + return static_file('0.png', root=path+'/0/', mimetype='image/png') + +@get('/users////deck.odp') +@get('/api/users////deck.odp') +def send_deck(user, deck, rev): + path = "%s/%s/%s/%d/" % (root,user,deck,rev) + return static_file(deck+'.odp', root=path, mimetype='text/xml') + +# ------------------------------------------------------------------- + +def get_slides(user,deck,rev): + path = "%s/%s/%s/%d/" % (root,user,deck,rev) + if not os.path.isdir(path): + return [] + return sorted([slide for slide in os.listdir(path) if os.path.isdir(path+slide)], key=int) + +def get_revmeta(user,deck,rev): + path = "%s/%s/%s/%d/" % (root,user,deck,rev) + comment = open(path+'comment').read() if os.path.exists(path+'comment') else '' + meta = json.loads(open(path+'meta.json').read()) if os.path.exists(path+'meta.json') else {} + return {'CreationDate': time.strftime("%a, %d %b %Y %H:%M:%S GMT", + time.gmtime(os.stat(path).st_ctime)), + 'CommitComment': comment, + 'Meta': meta} + +@get('/api/users////') +def list_slides(user, deck, rev): + return json.dumps(get_slides(user,deck,rev)) + +@get('/users////') +@view('slides') +def list_slides(user, deck, rev): + return dict(slides=get_slides(user,deck,rev), revmeta=get_revmeta(user,deck,rev)) + +@get('/users/////thumbnail.png') +@get('/api/users/////thumbnail.png') +def get_thumbnail(user, deck, rev, slide): + path = "%s/%s/%s/%d/%d/" % (root,user,deck,rev,slide) + return static_file(str(slide)+'.png', root=path, mimetype='image/png') + +@get('/api/users////meta.json') +def list_revmeta(user,deck,rev): + return get_revmeta(user,deck,rev) + +# ------------------------------------------------------------------- + +def get_slidemeta(user,deck,rev,slide): + path = "%s/%s/%s/%d/%d/" % (root,user,deck,rev,slide) + return json.loads(open(path+'meta.json').read()) if os.path.exists(path+'meta.json') else {} + +@get('/api/users/////meta.json') +def list_slidemeta(user, deck, rev, slide): + return get_slidemeta(user, deck, rev, slide) + +@get('/users/////slide.odp') +@get('/api/users/////slide.odp') +def send_slide(user, deck, rev, slide): + path = "%s/%s/%s/%d/%d/" % (root,user,deck,rev,slide) + return static_file(str(slide)+'.odp', root=path, mimetype='text/xml') + +# ------------------------------------------------------------------- def main(): debug(True) diff --git a/views/decks.tpl b/views/decks.tpl new file mode 100644 index 0000000..f25dc72 --- /dev/null +++ b/views/decks.tpl @@ -0,0 +1,9 @@ +%#generate HTML table of all decks + +%for deckname in decks: + + + + +%end +
{{deckname}}
diff --git a/views/revs.tpl b/views/revs.tpl new file mode 100644 index 0000000..500605d --- /dev/null +++ b/views/revs.tpl @@ -0,0 +1,9 @@ +%#generate HTML table of all revisions + +%for rev in revs: + + + + +%end +
Revision {{rev}}
diff --git a/views/slides.tpl b/views/slides.tpl new file mode 100644 index 0000000..8e2e342 --- /dev/null +++ b/views/slides.tpl @@ -0,0 +1,17 @@ +%#generate HTML table of all slides in one revision + +%for name, content in revmeta.items(): + + + + +%end +
{{name}}{{content}}
+ +%for slide in slides: + + + + +%end +
Slide {{slide}}
diff --git a/views/tags.tpl b/views/tags.tpl new file mode 100644 index 0000000..192dc6c --- /dev/null +++ b/views/tags.tpl @@ -0,0 +1,11 @@ +%#generate HTML table of all tags + + + + +%for tag in tags: + + + +%end +
Tag name
{{tag}}
diff --git a/views/users.tpl b/views/users.tpl new file mode 100644 index 0000000..b661740 --- /dev/null +++ b/views/users.tpl @@ -0,0 +1,13 @@ +%#generate HTML table of all users + + + + + +%for user, desc in users.items(): + + + + +%end +
UsernameDescription
{{user}}{{desc}}
-- cgit v1.2.3