Files
2018-07-17 14:15:37 +02:00

380 lines
11 KiB
Python

#!/usr/bin/python
import flask
import datetime
import json
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy import text
from sqlalchemy.sql.expression import asc, desc, or_
from werkzeug.debug import DebuggedApplication
import hashlib
import os, sys
import os.path
from flask.ext.cache import Cache
from os import listdir, walk, chdir
from os.path import isdir, join
app = flask.Flask(__name__)
cache = Cache(app, config={'CACHE_TYPE':'simple'})
application = DebuggedApplication(app, True)
#app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///api.db"
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://bmssql3:4c403a47@localhost/bmssql3"
db = SQLAlchemy(app)
class Design(db.Model):
__tablename__ = "designs"
id = db.Column(db.Integer, primary_key=True)
ctime = db.Column(db.DateTime, default=datetime.datetime.now)
name = db.Column(db.String(255))
size = db.Column(db.Float)
data = db.Column(db.Text)
color = db.Column(db.String(7))
mods = db.Column(db.String(255))
views = db.Column(db.Integer, default=0)
voteTime = db.Column(db.DateTime)
upvotes = db.Column(db.Integer, default=0)
commentTime = db.Column(db.DateTime)
commentCount = db.Column(db.Integer, default=0)
author = db.Column(db.String(255))
token = db.Column(db.String(255))
description = db.Column(db.Text)
class Comment(db.Model):
__tablename__ = "comments"
id = db.Column(db.Integer, primary_key=True)
design = db.Column(db.Integer, index=True)
ctime = db.Column(db.DateTime, default=datetime.datetime.now)
author = db.Column(db.String(255))
token = db.Column(db.String(255))
content = db.Column(db.Text)
class Vote(db.Model):
__tablename__ = "votes"
token = db.Column(db.String(255), primary_key=True, autoincrement=False)
design = db.Column(db.Integer, primary_key=True, autoincrement=False)
time = db.Column(db.DateTime, default=datetime.datetime.now)
def design_spec(design):
output = {}
output["id"] = design.id
output["name"] = design.name
output["size"] = design.size
output["data"] = design.data
output["author"] = design.author
output["color"] = design.color
output["description"] = design.description
output["mods"] = design.mods
output["ctime"] = design.ctime.strftime("%Y-%m-%d %H:%M")
output["commentCount"] = design.commentCount
output["upvotes"] = design.upvotes
return output
def design_list(query, paginate=True):
#Apply paginization
if paginate:
pagestart = 0
pagelimit = 10
if "limit" in flask.request.args:
pagelimit = int(flask.request.args["limit"])
if "page" in flask.request.args:
pagestart = pagelimit * int(flask.request.args["page"])
query = query.slice(pagestart, pagestart+pagelimit+1);
#Unify in output
output = []
for d in query.all():
output.append(design_spec(d))
return output
@app.route("/design/<int:design_id>")
def design(design_id):
design = Design.query.filter_by(id=design_id).first()
if not design:
flask.abort(404)
return
output = design_spec(design)
token = flask.request.headers.get("APIToken")
if token:
output["hasUpvoted"] = Vote.query.filter_by(design=design.id, token=token).count() != 0
output["isMine"] = design.token == token
else:
output["hasUpvoted"] = False
output["isMine"] = False
clist = []
output["comments"] = clist
comments = Comment.query.filter_by(design=design.id)
for c in comments:
clist.append({
"author": c.author,
"ctime": c.ctime.strftime("%Y-%m-%d %H:%M"),
"content": c.content
})
design.views += 1
db.session.commit()
return json.dumps(output)
@app.route("/design/<int:design_id>/vote", methods=["POST"])
def design_vote(design_id):
design = Design.query.filter_by(id=design_id).first()
token = flask.request.headers.get("APIToken")
if not design or not token:
flask.abort(404)
return
if design.token == token:
flask.abort(403)
return
if Vote.query.filter_by(design=design.id, token=token).count() != 0:
flask.abort(403)
return
vote = Vote()
vote.token = token
vote.design = design.id
design.upvotes += 1
design.voteTime = datetime.datetime.now()
db.session.add(vote)
db.session.commit()
return "{}"
@app.route("/design/<int:design_id>/comment", methods=["POST"])
def design_comment(design_id):
design = Design.query.filter_by(id=design_id).first()
token = flask.request.headers.get("APIToken")
if not design or not token:
flask.abort(404)
return
comment = Comment()
comment.design = design.id
comment.author = flask.request.form.get("author", "")
comment.content = flask.request.form.get("content", "")
comment.token = token
if not comment.author or not comment.content:
flask.abort(400)
return
design.commentCount += 1
design.commentTime = datetime.datetime.now()
db.session.add(comment)
db.session.commit()
return "{}"
@app.route("/design/<int:design_id>/delete", methods=["POST"])
def design_delete(design_id):
design = Design.query.filter_by(id=design_id).first()
token = flask.request.headers.get("APIToken")
if not design or not token:
flask.abort(404)
return
if design.token != token:
flask.abort(403)
return
Vote.query.filter_by(design=design.id).delete()
Comment.query.filter_by(design=design.id).delete()
db.session.delete(design)
db.session.commit()
return "{}"
@app.route("/designs/submit", methods=["POST"])
def submit_design():
design = Design()
design.name = flask.request.form.get("name", "")
design.author = flask.request.form.get("author", "")
design.description = flask.request.form.get("description", "")
design.data = flask.request.form.get("data", "")
design.mods = flask.request.form.get("mods", "base")
design.color = flask.request.form.get("color", "#ffffff")
token = flask.request.headers.get("APIToken")
if token:
design.token = token
try:
design.size = float(flask.request.form.get("size", "0"))
except:
design.size = 0
if not design.name or not design.author or not design.data or design.size < 1:
flask.abort(400)
return
db.session.add(design)
db.session.commit()
return str(design.id)
@app.route("/designs/recent")
def designs_recent():
q = Design.query.order_by(desc(Design.ctime))
return json.dumps(design_list(q))
@app.route("/designs/toprated")
def designs_toprated():
q = Design.query.order_by(desc(Design.upvotes))
return json.dumps(design_list(q))
@app.route("/designs/search/<term>")
def designs_search(term):
q = Design.query.filter(or_(Design.name.like("%"+term+"%"), Design.author.like("%"+term+"%")))
return json.dumps(design_list(q))
@app.route("/designs/featured")
def designs_featured():
featured = []
#featured += design_list(Design.query.order_by(desc(Design.upvotes)).limit(2), paginate=False)
featured = design_list(Design.query.order_by(desc(text("(upvotes / POW((UNIX_TIMESTAMP()-UNIX_TIMESTAMP(ctime))/60/60/24+1,1.8))"))).limit(2), paginate=False)
featured += design_list(Design.query.order_by(desc(Design.voteTime)).limit(1), paginate=False)
featured += design_list(Design.query.order_by(desc(Design.commentTime)).limit(1), paginate=False)
featured += design_list(Design.query.order_by(desc(Design.ctime)).limit(6), paginate=False)
output = []
for d in featured:
if any(x["id"] == d["id"] for x in output):
continue
d["description"] = d["description"] if len(d["description"]) < 120 else d["description"][0:120]
output.append(d)
if len(output) > 6:
break
return json.dumps(output)
@app.route("/updates/version")
def up_version():
data = getVersions()
return data["latest"]
@app.route("/updates/latest")
def latest_list():
data = getVersions()
VERSIONS = data["versions"]
LATEST_VERSION = data["latest"]
NEW_HASHES = data["new_hashes"]
if LATEST_VERSION not in VERSIONS:
return "Invalid latest version: "+LATEST_VERSION
ver = VERSIONS[LATEST_VERSION]
output = ""
for relfile, filehash in ver["files"].items():
output += relfile+"\t"+filehash+"\n"
return output
@app.route("/updates/<prevhash>/<newhash>")
def get_file(prevhash, newhash):
if prevhash == newhash:
return ""
data = getVersions()
VERSIONS = data["versions"]
LATEST_VERSION = data["latest"]
NEW_HASHES = data["new_hashes"]
newfname = ""
relname = ""
oldfname = ""
oldrelname = ""
for vname, ver in VERSIONS.items():
if newhash in ver["hashes"]:
relname = ver["hashes"][newhash]
newfname = join("updates", vname, relname)
if prevhash in ver["hashes"]:
oldrelname = ver["hashes"][prevhash]
oldfname = join("updates", vname, relname)
if oldrelname != relname:
havefile = True
for vname, ver in VERSIONS.items():
if relname not in ver["files"]:
havefile = False
break
if havefile or prevhash not in NEW_HASHES:
flask.abort(404)
return "INVALID PREVIOUS HASH GIVEN"
if not os.path.exists(newfname):
flask.abort(404)
return "FILE NOT FOUND"
bsize = 65536
def generate():
with open(newfname, 'rb') as newfile:
buf = newfile.read(bsize)
while len(buf) > 0:
yield buf
buf = newfile.read(bsize)
return flask.Response(generate(), mimetype='application/octet-stream')
@cache.memoize()
def getVersions():
output = {"latest": "v1.0.0", "versions": {}, "new_hashes": set(), "new_files": {"Star Ruler 2.exe"}}
dirlist = listdir("updates")
for f in dirlist:
prefix = join("updates", f)
if not isdir(prefix):
continue
ver = {"hashes": {}, "files": {}}
output["versions"][f] = ver
for (dirpath, dirnames, filenames) in walk(prefix):
for fname in filenames:
absfile = join(dirpath, fname)
relfile = absfile[len(prefix)+1:]
filehash = hashfile(absfile)
ver["hashes"][filehash] = relfile
ver["files"][relfile] = filehash
if relfile in output["new_files"]:
output["new_hashes"].add(filehash)
with open("updates/latest") as f:
output["latest"] = f.read().strip()
if output["latest"] in output["versions"]:
latest = output["versions"][output["latest"]]
for vname, ver in output["versions"].items():
if vname == output["latest"]:
continue
for f in ver["files"]:
if f not in latest["files"]:
latest["files"][f] = "0"*32
return output
def hashfile(fname):
hasher = hashlib.md5()
bsize = 65536
with open(fname, 'rb') as f:
buf = f.read(bsize)
while len(buf) > 0:
hasher.update(buf)
buf = f.read(bsize)
return hasher.hexdigest()
if __name__ == "__main__":
#db.create_all()
#app.run(debug=True)
app.run(host="0.0.0.0", port=2041, debug=True)
# vim: ff=unix et sw=4 :