brain/brain.py

732 lines
23 KiB
Python

import os, sys
from git import *
import bottle
from bottle import default_app, route, run, request, template, static_file, redirect
from os.path import isdir
from string import lower, split
from urllib import unquote
import shutil
from datetime import datetime
import re
import Image
from markdown2 import markdown
import MySQLdb
try:
conn = MySQLdb.connect( host = "localhost",
user = "brain",
passwd = "horsebatteries",
db = "brain")
cursor = conn.cursor()
except:
print 'Cannot connect to database.'
conf = {}
conf['repo'] = '/srv/git/documents'
conf['ext_bundles'] = ['.pages', '.sparsebundle']
conf['ext_render'] = ['.md','.txt','.jpg','.gif','.png']
conf['ext_edit'] = ['.md','.txt','.rb','.py','.pl','.sh']
def sanitize_path(path):
return path.lstrip('./')
def render_file(name,public=False):
path = os.path.join(conf['repo'], name)
filename, extension = os.path.splitext(name)
filename = os.path.basename(name)
if isdir(path) and lower(extension) in conf['ext_bundles']:
d = datetime.now()
basedir = re.sub(filename+extension, '', path)
rootdir = filename+extension
z = shutil.make_archive('/tmp/' + unquote(filename) + '_' + \
d.strftime("%Y%M%d%H%M%S"), "zip", basedir, rootdir )
return static_file(os.path.basename(z), root='/tmp')
if lower(extension) in conf['ext_render']:
try:
img = Image.open(path)
width, height = img.size
if width > 1024 or height > 1024:
if height > width:
h = 1024
w = round(1024 * (float(width)/float(height)))
else:
if height < width:
h = round(1024 * (float(height)/float(width)))
w = 1024
else:
h = 1024
w = 1024
else:
h = height
w = width
content = '<img height="'+str(h)+'" width="'+str(w)+'" src="/raw/'+sanitize_path(name)+'" />'
except:
try:
o = open(path,'r')
content = markdown(o.read())
o.close()
content += '<p><a href="/edit/'+sanitize_path(name)+'">Edit</a> - '
content += '<a href="/raw/'+sanitize_path(name)+'">Raw</a></p>'
except:
content = "<p>This cannot be rendered.</p>"
if sanitize_path(os.path.dirname(name)):
content += '<p>Back to <a href="/repo/'+sanitize_path(os.path.dirname(name))\
+'">'+os.path.dirname(name)+'</a></p>'
return template('templates/repo', content = content, \
title=filename)
else:
return static_file(unquote(name), root=conf['repo'])
# Return the static assets.
@route('/static/<name:path>')
def static(name=''):
return static_file(name, root='./static/')
# Traverse the repository
@route('/repo')
@route('/repo/')
@route('/repo/<name:path>')
def public(name=''):
repo = Repo(conf['repo'])
repo.git.pull()
tree = []
filename, extension = os.path.splitext(name)
filename = os.path.basename(filename)
path = os.path.join(conf['repo'], sanitize_path(name))
if os.path.exists(path):
if isdir(path) and not lower(extension) in conf['ext_bundles']:
tree.append('<ul>')
print "This is a regular dir: " + path
if not name == '':
n = name.rstrip('/')
l = split(n, '/')
tree.append('<li>')
if len(l) > 1:
tree.append('<a href="/repo/'+'/'.join(l[0:-1])+'">..</a>')
else:
tree.append('<a href="/repo/">..</a>')
tree.append('</li>')
dirs = []
files = []
for f in sorted(os.listdir(path)):
if not f.startswith('.') and not f.startswith('_'):
if isdir(os.path.join(path,f)):
dirs.append('<li>')
dirs.append('<a href="/repo/'+os.path.join(sanitize_path(name),f)+'">'+f+'</a>')
dirs.append('<a href="/move/'+os.path.join(sanitize_path(name),f)+'">(move)</a>')
dirs.append('<a href="/delete/'+os.path.join(sanitize_path(name),f)+'">(delete)</a>')
dirs.append('</li>')
else:
files.append('<li>')
files.append('<a href="/repo/'+os.path.join(sanitize_path(name),f)+'">'+f+'</a>')
False,e = os.path.splitext(f)
if e in conf['ext_edit']:
files.append('<a href="/edit/'+os.path.join(sanitize_path(name),f)+'">(edit)</a>')
files.append('<a href="/move/'+os.path.join(sanitize_path(name),f)+'">(move)</a>')
files.append('<a href="/delete/'+os.path.join(sanitize_path(name),f)+'">(delete)</a>')
files.append('</li>')
for d in dirs: tree.append(d)
for f in files: tree.append(f)
tree.append('</ul>')
tree.append('<p><a href="/upload/'+sanitize_path(name)+'">Upload</a> ')
tree.append('<a href="/new/'+sanitize_path(name)+'">New File</a> ')
tree.append('<a href="/mkdir/'+sanitize_path(name)+'">New Dir</a></p>')
return template('templates/repo', content="\n".join(tree), title="Files in "+sanitize_path(name))
else:
return render_file(name)
else:
return 'File does not exist.'
# Raw access to files.
@route('/raw/<path:path>')
def raw(path=False):
try:
return static_file(unquote(path),root=conf['repo'])
except:
return 'No such file'
# Uploading files.
@route('/upload/:path', method="GET")
@route('/upload/', method="GET")
@route('/upload', method="GET")
def upload(path=''):
path = sanitize_path(path)
body = []
body.append('<style type="text/css">\
td.r { text-align: left; }\
td.c { text-align: center; }\
td.l { text-align: right; }\
</style>')
body.append('<h1>Upload a new file</h1>')
body.append('<form style="width:500px;" method="post" action="/upload" enctype="multipart/form-data">')
body.append('<table>')
body.append('<tr><td class="l"><label for="uploaded">Upload:</label></td>')
body.append('<td class="r"><input type="file" name="uploaded" /></td></tr>')
body.append('<tr><td class="l"><label for="path">Destination:</label></td>')
body.append('<td class="r"><input type="text" name="path" style="width:250px" ')
if not path == '':
body.append('value="'+path+'" ')
body.append('/></td></tr>')
body.append('<tr><td class="l"><label for="message">Commit Message</label></td>')
body.append('<td class="r"><textarea name="message" cols="50" rows="7"></textarea></td></tr>')
body.append('<tr><td class="c"><input type="submit" name="submit"></td></tr>')
body.append('</table>')
body.append('</form>')
return template('templates/repo', content='\n'.join(body), \
title='Add')
@route('/upload', method="POST")
def do_upload():
repo = Repo(conf['repo'])
repo.git.pull()
path = request.forms.path
path = sanitize_path(path)
message = request.forms.message
u = request.files.uploaded
filename = u.filename
new_file = os.path.join(conf['repo'],path,filename)
if not os.path.exists(os.path.join(conf['repo'],path)):
os.mkdir(os.path.join(conf['repo'],path))
overwritten = False
if os.path.exists(new_file):
overwritten = True
w = open(new_file,'wb')
w.write(u.file.read())
w.close()
content = ['<p><a href="/repo/'+path+'/'+filename+'">' + filename + '</a> uploaded to\
<a href="/repo/'+path+'">/' + path + '</a>']
if overwritten:
content.append(' (original overwritten).</p>')
else:
content.append('.</p>')
if not overwritten or (overwritten and not repo.git.diff(new_file) == ""):
repo.git.add(new_file)
content.append("<p>Git: ")
commit_result = repo.git.commit(new_file,m=message)
repo.git.push()
content.append(commit_result)
content.append('</p>')
else:
content.append('<p>Git: file is not different.</p>')
content.append('<p><a href="/upload">Upload another.</a></p>')
return template('templates/repo', content='\n'.join(content), title='"'+filename+'" uploaded.')
# Deleting files.
@route('/delete', method="GET")
@route('/delete/<path:path>', method="GET")
def delete(path = ''):
body = []
path = sanitize_path(path)
body.append('<style type="text/css">\
td.r { text-align: left; }\
td.c { text-align: center; }\
td.l { text-align: right; }\
</style>')
body.append('<h1>Delete a file</h1>')
body.append('<form style="width:500px;" method="post" action="/delete" enctype="multipart/form-data">')
body.append('<table>')
body.append('<tr><td class="l"><label for="path">File to be deleted:</label></td>')
body.append('<td class="r"><input type="text" name="path" style="width:250px" ')
if path: body.append('value="'+path+'" ')
body.append('/></td></tr>')
body.append('<tr><td class="l"><label for="message">Commit Message</label></td>')
body.append('<td class="r"><textarea name="message" cols="50" rows="7"></textarea></td></tr>')
body.append('<tr><td><label for="delete">Do you really want to delete?</label></td>')
body.append('<td class="c"><input type="submit" name="delete" value="Yes."></td></tr>')
body.append('</table>')
body.append('</form>')
return template('templates/repo', content='\n'.join(body), title='Delete')
@route('/delete', method='POST')
def delete():
body = []
repo = Repo(conf['repo'])
path = request.forms.path
path = sanitize_path(path)
directory,filename = os.path.split(path)
directory = '/'+directory
message = request.forms.message
result = False
repo.git.pull()
if os.path.exists(os.path.join(conf['repo'],path)):
try:
repo.git.rm(path, r=True)
result = repo.git.commit(path, m=message)
repo.git.push()
except:
if not isdir(os.path.join(conf['repo'],path)):
os.remove(os.path.join(conf['repo'],path))
body.append('<p>' + path + ' deleted from <a href="/repo'+directory+'">'+directory+'</a>.</p>')
if result: body.append('<p>Git: '+result+'</p>')
else:
body.append('<p>'+path+' does not exist.</p>')
return template('templates/repo', content='\n'.join(body), title=path+' deleted.')
# And if we want to edit a file?
@route('/edit/<path:path>', method='GET')
@route('/edit', method="GET")
def edit(path=''):
if not path and request.query.path:
path = request.query.path
path = sanitize_path(path)
body = []
if path:
full = os.path.join(conf['repo'],path)
False, ext = os.path.splitext(path)
if os.path.exists(full) and not isdir(full) and ext in conf['ext_edit']:
body.append('<style type="text/css">\
td.r { text-align: left; vertical-align:top;}\
td.c { text-align: center; }\
td.l { text-align: right; vertical-align:top;}\
</style>')
body.append('<h1>Editing '+ path +'</h1>')
body.append('<form style="width:500px;" method="post" action="/edit" enctype="multipart/form-data">')
body.append('<input type="hidden" name="path" value="'+path+'" />')
body.append('<table>')
body.append('<tr><td class="l"><label for="content">Content:</label></td>')
body.append('<td class="c" style="width:100%"><textarea name="content" cols="80" rows="80">')
with open(full,'r') as f: body.append(f.read())
body.append('</textarea></td></tr>')
body.append('<tr><td class="l"><label for="message">Commit Message</label></td>')
body.append('<td class="r"><textarea name="message" cols="50" rows="7"></textarea></td></tr>')
body.append('<tr><td class="c"><input type="submit" name="submit"></td></tr>')
body.append('</table>')
body.append('</form>')
else:
body.append('<p>Path does not exist or is not editable.')
else:
body.append('<h1>Edit a file</h1>')
body.append('<form style="width:500px;" method="get" action="/edit">')
body.append('<table>')
body.append('<tr><td><label for="path">File:</label></td>')
body.append('<td><input type="text" name="path" style="width:250px;" /></td></tr>')
body.append('<tr><td><input type="submit" /></td></tr>')
body.append('</table>')
body.append('</form>')
return template('templates/repo', content='\n'.join(body), \
title='New')
@route('/edit', method='POST')
def edit():
repo = Repo(conf['repo'])
path = request.forms.path
content = request.forms.content
message = request.forms.message
full_path = os.path.join(conf['repo'],path)
repo.git.pull()
body = []
f = open(full_path, 'w')
f.write(content)
f.close()
if not repo.git.diff(path) == '':
commit = repo.git.commit(path,m=message)
repo.git.push()
body.append('<p><a href="/repo/'+path+'">' + path + '</a> edited.</p>')
body.append('<p>Git: ' + commit + '</p>')
else:
body.append('<p>' + path + ' not changed.</p>')
return template('templates/repo', content='\n'.join(body),\
title=path+' edited.')
# Let's add some functionality to make new files.
@route('/new', method='GET')
@route('/new/<path:path>', method='GET')
def new(path = ''):
path = sanitize_path(path)
body = []
body.append('<style type="text/css">\
td.r { text-align: left; vertical-align:top;}\
td.c { text-align: center; }\
td.l { text-align: right; vertical-align:top;}\
</style>')
body.append('<h1>Create a new file</h1>')
body.append('<form style="width:500px;" method="post" action="/new" enctype="multipart/form-data">')
body.append('<table>')
body.append('<tr><td class="l"><label for="name">Filename:</label></td>')
body.append('<td class="r"><input type="text" name="name" style="width:250px"/></td></tr>')
body.append('<tr><td class="l"><label for="path">Destination:</label></td>')
body.append('<td class="r"><input type="text" name="path" style="width:250px"')
if path: body.append('value="'+path+'" ')
body.append('/></td></tr>')
body.append('<tr><td class="l"><label for="content">Content:</label></td>')
body.append('<td class="c" style="width:100%"><textarea name="content" cols="80" rows="80"></textarea></td></tr>')
body.append('<tr><td class="l"><label for="message">Commit Message</label></td>')
body.append('<td class="r"><textarea name="message" cols="50" rows="7"></textarea></td></tr>')
body.append('<tr><td class="c"><input type="submit" name="submit"></td></tr>')
body.append('</table>')
body.append('</form>')
return template('templates/repo', content='\n'.join(body), \
title='New')
@route('/new', method="POST")
def new():
filename = request.forms.name
directory = sanitize_path(request.forms.path)
content = request.forms.content
message = request.forms.message
full_path = os.path.join(conf['repo'], directory, filename)
w = open(full_path, 'w')
w.write(content)
w.close()
repo = Repo(conf['repo'])
repo.git.pull()
commit = False
overwritten = False
if os.path.exists(os.path.join(directory,filename)):
overwritten = True
diff = repo.git.diff(os.path.join(directory,filename))
if not overwritten or (overwritten and not diff == ''):
repo.git.add(os.path.join(directory, filename))
commit = repo.git.commit(os.path.join(directory, filename),m=message)
repo.git.push()
body = []
body.append('<p>'+filename+' added to <a href="/repo/'+directory+'">'+(directory if not directory == '' else '/')+'</a></p>')
if commit:
body.append('<p>Git: '+commit+'</p>')
return template('templates/repo', content='\n'.join(body), \
title=filename + ' created')
# Let's add in some functionality to move files.
@route('/move/<path:path>', method="GET")
@route('/move', method="GET")
def rename(path = ''):
body = []
path = sanitize_path(path)
body.append('<style type="text/css">\
td.r { text-align: left; vertical-align:top;}\
td.c { text-align: center; }\
td.l { text-align: right; vertical-align:top;}\
</style>')
body.append('<h1>Move</h1>')
body.append('<form style="width:500px;" method="post" action="/move" enctype="multipart/form-data">')
body.append('<table>')
body.append('<tr><td class="l"><label for="name">Current:</label></td>')
body.append('<td class="r"><input type="text" name="current" style="width:250px"')
if path: body.append(' value="'+path+'" ')
body.append('/></td></tr>')
body.append('<tr><td class="l"><label for="path">Destination:</label></td>')
body.append('<td class="r"><input type="text" name="destination" style="width:250px"/></td></tr>')
body.append('<tr><td class="l"><label for="message">Commit Message</label></td>')
body.append('<td class="r"><textarea name="message" cols="50" rows="7"></textarea></td></tr>')
body.append('<tr><td class="c"><input type="submit" name="submit"></td></tr>')
body.append('</table>')
body.append('</form>')
return template('templates/repo', content='\n'.join(body),\
title='move')
@route('/move', method="POST")
def rename():
current = os.path.join(conf['repo'],sanitize_path(request.forms.current))
new = os.path.join(conf['repo'],sanitize_path(request.forms.destination))
message = request.forms.message
body = []
if current and new:
if os.path.exists(current):
if not os.path.exists(new):
repo = Repo(conf['repo'])
repo.git.pull()
repo.git.mv(current,new)
if not message: message = datetime.now().isoformat()
commit = repo.git.commit(current,new,m=message)
repo.git.push()
body.append('<p>'+request.forms.current+' moved to '\
+request.forms.destination+'.</p>')
body.append('<p>Git: '+commit+'</p>')
else:
body.append('<p>'+request.forms.destination+' already exists.</p>')
else:
body.append('<p>'+request.forms.current+' does not exist.</p>')
else:
body.append('<p>Must have both a current and new name.</p>')
return template('templates/repo', content='\n'.join(body),\
title=request.forms.current+' moved to '+request.forms.destination)
# What if we want to make a new directory?
@route('/mkdir/<path:path>', method="GET")
@route('/mkdir', method="GET")
def mkdir(path=''):
body = []
path = sanitize_path(path)
body.append('<style type="text/css">\
td.r { text-align: left; vertical-align:top;}\
td.c { text-align: center; }\
td.l { text-align: right; vertical-align:top;}\
</style>')
body.append('<h1>Create Directory</h1>')
body.append('<form style="width:500px;" method="post" action="/mkdir" enctype="multipart/form-data">')
body.append('<table>')
body.append('<tr><td class="l"><label for="name">Name:</label></td>')
body.append('<td class="r"><input type="text" name="path" style="width:250px"')
if path: body.append(' value="'+path+'" ')
body.append('/></td></tr>')
body.append('<tr><td class="c"><input type="submit" name="submit"></td></tr>')
body.append('</table>')
body.append('</form>')
return template('templates/repo', content='\n'.join(body),\
title='Create Directory')
@route('/mkdir', method="POST")
def mkdir():
body = []
path = request.forms.path
path = sanitize_path(path)
if not os.path.exists(os.path.join(conf['repo'],path)):
os.mkdir(os.path.join(conf['repo'],path))
if isdir(os.path.join(conf['repo'],path)):
body.append('<p><a href="/repo/'+path+'">' + path + '</a> created.</p>')
else:
body.append('<p>Directory could not be created</p>')
else:
body.append('<p>Path exists.</p>')
return template('templates/repo', content='\n'.join(body),\
title='Create Directory')
# Let's build in the url shortening functionality.
@route('/short/<short>')
@route('/short/')
@route('/short')
def short(short = ''):
if short:
query = 'SELECT url,public FROM short WHERE `key` = "'+short+'" LIMIT 1;'
cursor.execute(query)
url = cursor.fetchone()
if url:
redirect(url[0])
else:
body = ['Short url "'+short+'" does not exist.']
return template('templates/public', content='\n'.join(body),\
title='Error')
else:
body = ['You must provide a short url.']
return template('templates/public', content='\n'.join(body),\
title='Error')
# Let's make a place to store my secrets
@route('/secrets/<path>')
@route('/secrets/')
@route('/secrets')
def secrets(path = ''):
if path and not path == "list":
query = 'SELECT username,password FROM `secrets` WHERE base_url LIKE "%' + path + '";'
cursor.execute(query)
secrets = cursor.fetchall()
body = []
for s in secrets:
body.append(s[0]+' | '+ s[1]+'<br/>')
return template('templates/secret', content = '\n'.join(body), title = 'Secrets')
if path == "list":
query = 'SELECT base_url,username,password FROM `secrets`;'
cursor.execute(query)
secrets = cursor.fetchall()
body = []
for s in secrets:
body.append(s[0]+': '+s[1]+' | '+s[2]+'<br/>')
return template('templates/secret', content = '\n'.join(body), title = 'Secrets')
# An API will be needed for bookmarklets and whatnot.
@route('/api')
def api():
return 'Someday there will be an API here.'
# Do something with the home page.
@route('/')
def index():
redirect('/repo')
application = bottle.default_app()
if __name__ == "__main__":
bottle.debug(True)
conf['repo'] = os.path.expanduser('~/dev/repo')
run(host='0.0.0.0', port=8000, reloader=True)