cablegates/pandora/backend/views.py

728 lines
24 KiB
Python

# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
from __future__ import division
import os.path
import re
from datetime import datetime
from urllib2 import unquote
import json
import mimetypes
from django import forms
from django.core.paginator import Paginator
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.db.models import Q, Avg, Count
from django.http import HttpResponse, Http404
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404
from django.template import RequestContext
from django.conf import settings
try:
import simplejson as json
except ImportError:
from django.utils import simplejson as json
from oxdjango.decorators import login_required_json
from oxdjango.shortcuts import render_to_json_response, get_object_or_404_json, json_response
from oxdjango.http import HttpFileResponse
import oxlib
import models
import utils
from daemon import send_bg_message
from oxuser.models import getUserJSON
from oxuser.views import api_login, api_logout, api_register, api_contact, api_recover, api_preferences
def api(request):
if request.META['REQUEST_METHOD'] == "OPTIONS":
response = HttpResponse('')
#response = render_to_json_response({'status': {'code': 200, 'text': 'please use POST'}})
response['Access-Control-Allow-Origin'] = '*'
return response
if not 'action' in request.POST:
return apidoc(request)
function = request.POST['action']
#FIXME: possible to do this in f
#data = json.loads(request.POST['data'])
#FIXME: security considerations, web facing api should not call anything in globals!!!
f = globals().get('api_'+function, None)
if f:
response = f(request)
else:
response = render_to_json_response(json_response(status=400,
text='Unknown function %s' % function))
#response['Access-Control-Allow-Origin'] = '*'
return response
def api_hello(request):
'''
return {'status': {'code': int, 'text': string},
'data': {user: object}}
'''
response = json_response({})
if request.user.is_authenticated():
response['data']['user'] = getUserJSON(request.user)
else:
response['data']['user'] = {'name': 'Guest', 'group': 'guest', 'preferences': {}}
return render_to_json_response(response)
def api_error(request):
'''
trows 503 error
'''
success = error_is_success
return render_to_json_response({})
def _order_query(qs, s, prefix='sort__'):
order_by = []
for e in s.split(','):
o = e.split(':')
if len(o) == 1: o.append('asc')
order = {'id': 'movieId'}.get(o[0], o[0])
order = '%s%s' % (prefix, order)
if o[1] == 'desc':
order = '-%s' % order
order_by.append(order)
if order_by:
qs = qs.order_by(*order_by)
return qs
def _parse_query(request):
get = json.loads(request.POST['data'])
query = {}
query['i'] = 0
query['o'] = 100
query['s'] = 'title:asc'
def parse_dict(s):
d = s.split(",")
return [i.strip() for i in d]
_dicts = ['p', ]
_ints = ['n', ]
for key in ('s', 'p', 'g', 'l', 'n'):
if key in get:
if key in _ints:
query[key] = int(get[key])
elif key in _dicts:
query[key] = parse_dict(get[key])
else:
query[key] = get[key]
query['q'] = models.Movie.objects.find(request)
if 'r' in get:
r = get['r'].split(':')
if len(r) == 1: r.append(0)
if r[0] == '': r[0] = 0
if r[1] == '': r[0] = -1
query['i'] = int(r[0])
query['o'] = int(r[1])
#group by only allows sorting by name or number of itmes
return query
def api_find(request):
'''
param data
{'q': query, 's': sort, 'r': range}
q: query string, can contain field:search more on query syntax at
http://wiki.0xdb.org/wiki/QuerySyntax
s: comma seperated list of field:order, default: director:asc,year:desc
r: result ragne, from:to or from
p: properties to return, if obmited stats are returned
g: group elements by, country, genre, director...
with p, items is list of dicts with requested properties:
return {'status': {'code': int, 'text': string},
'data': {items: array}}
with g, items contains list of {'title': string, 'items': int}:
return {'status': {'code': int, 'text': string},
'data': {items: array}}
with g + n=1, return number of items in given query
return {'status': {'code': int, 'text': string},
'data': {items: int}}
without p or g, return stats about query:
return {'status': {'code': int, 'text': string},
'data': {items=int, files=int, pixels=int, size=int, duration=int}}
'''
query = _parse_query(request)
response = json_response({})
if 'p' in query:
response['data']['items'] = []
qs = _order_query(query['q'], query['s'])
if 'n' in query:
response = {'items': qs.count()}
else:
_p = query['p']
def only_p(m):
r = {}
if m:
m = json.loads(m)
for p in _p:
r[p] = m[p]
return r
qs = qs[query['i']:query['o']]
response['data']['items'] = [only_p(m['json']) for m in qs.values('json')]
elif 'g' in query:
if query['s'].split(':')[0] not in ('name', 'items'):
query['s'] = 'name'
#FIXME: also filter lists here
response['data']['items'] = []
name = 'name'
items = 'movies'
movie_qs = query['q']
_objects = {
'country': models.Country.objects,
'genre': models.Genre.objects,
'language': models.Language.objects,
'director': models.Person.objects.filter(cast__role='directors'),
}
if query['g'] in _objects:
qs = _objects[query['g']].filter(movies__id__in=movie_qs).values('name').annotate(movies=Count('movies'))
elif query['g'] == "year":
qs = movie_qs.values('imdb__year').annotate(movies=Count('id'))
name='imdb__year'
if 'n' in query:
response['data']['items'] = qs.count()
else:
#replace normalized items/name sort with actual db value
order_by = query['s'].split(":")
if len(order_by) == 1:
order_by.append('desc')
if order_by[0] == 'name':
order_by = "%s:%s" % (name, order_by[1])
else:
order_by = "%s:%s" % (items, order_by[1])
qs = _order_query(qs, order_by, '')
qs = qs[query['i']:query['o']]
response['data']['items'] = [{'title': i[name], 'items': i[items]} for i in qs]
else:
#FIXME: also filter lists here
movies = models.Movie.objects.filter(available=True)
files = models.File.objects.all()
response['data']['items'] = movies.count()
response['data']['files'] = files.count()
r = files.aggregate(Count('size'), Count('pixels'), Count('duration'))
response['data']['pixels'] = r['pixels__count']
response['data']['size'] = r['size__count']
response['data']['duration'] = r['duration__count']
return render_to_json_response(response)
def api_getItem(request):
'''
param data
string id
return item array
'''
response = json_response({})
itemId = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Movie, movieId=itemId)
#FIXME: check permissions
response['data'] = {'item': item.json}
return render_to_json_response(response)
@login_required_json
def api_editItem(request):
'''
param data
{id: string, key: value,..}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
data = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Movie, movieId=data['id'])
if item.editable(request.user):
response = json_response(status=501, text='not implemented')
item.edit(data)
else:
response = json_response(status=403, text='permissino denied')
return render_to_json_response(response)
@login_required_json
def api_removeItem(request):
'''
param data
string id
return {'status': {'code': int, 'text': string}}
'''
response = json_response({})
itemId = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Movie, movieId=itemId)
if item.editable(request.user):
response = json_response(status=501, text='not implemented')
else:
response = json_response(status=403, text='permissino denied')
return render_to_json_response(response)
@login_required_json
def api_addLayer(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = {'status': {'code': 501, 'text': 'not implemented'}}
return render_to_json_response(response)
@login_required_json
def api_removeLayer(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = {'status': {'code': 501, 'text': 'not implemented'}}
return render_to_json_response(response)
@login_required_json
def api_editLayer(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = json_response({})
data = json.loads(request.POST['data'])
layer = get_object_or_404_json(models.Layer, pk=data['id'])
if layer.editable(request.user):
response = json_response(status=501, text='not implemented')
else:
response = json_response(status=403, text='permission denied')
return render_to_json_response(response)
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
@login_required_json
def api_addListItem(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
@login_required_json
def api_removeListItem(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
@login_required_json
def api_addList(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
@login_required_json
def api_editList(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
def api_removeList(request):
'''
param data
{key: value}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
@login_required_json
def api_addArchive(request):
'''
ARCHIVE API NEEDS CLEANUP
param data
{name: string}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
data = json.loads(request.POST['data'])
try:
archive = models.Archive.objects.get(name=data['name'])
response = {'status': {'code': 401, 'text': 'archive with this name exists'}}
except models.Archive.DoesNotExist:
archive = models.Archive(name=data['name'])
archive.save()
archive.users.add(request.user)
response = json_response({})
response['status']['text'] = 'archive created'
return render_to_json_response(response)
@login_required_json
def api_editArchive(request):
'''
ARCHIVE API NEEDS CLEANUP
param data
{id: string, key: value,..}
return {'status': {'code': int, 'text': string},
'data': {}}
'''
data = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Archive, name=data['name'])
if item.editable(request.user):
response = json_response(status=501, text='not implemented')
item.edit(data)
else:
response = json_response(status=403, text='permission denied')
return render_to_json_response(response)
@login_required_json
def api_removeArchive(request):
'''
ARCHIVE API NEEDS CLEANUP
param data
string id
return {'status': {'code': int, 'text': string}}
'''
response = json_response({})
itemId = json.loads(request.POST['data'])
item = get_object_or_404_json(models.Archive, movieId=itemId)
if item.editable(request.user):
response = json_response(status=501, text='not implemented')
else:
response = json_response(status=403, text='permission denied')
return render_to_json_response(response)
#@login_required_json
def api_update(request):
'''
param data
{archive: string, files: json}
return {'status': {'code': int, 'text': string},
'data': {info: object, rename: object}}
'''
data = json.loads(request.POST['data'])
archive = data['archive']
files = data['files']
archive = get_object_or_404_json(models.Archive, name=archive)
if archive.editable(request.user):
needs_data = []
rename = {}
for oshash in files:
data = files[oshash]
q = models.ArchiveFile.objects.filter(archive=archive, file__oshash=oshash)
if q.count() == 0:
#print "adding file", oshash, data['path']
f = models.ArchiveFile.get_or_create(archive, oshash)
f.update(data)
if not f.file.movie:
send_bg_message({'findMovie': f.file.id})
#FIXME: only add if it was not in File
else:
f = q[0]
if data['path'] != f.path:
f.path = data['path']
f.save()
if f.file.needs_data:
needs_data.append(oshash)
if f.path != f.file.path:
rename[oshash] = f.file.path
#print "processed files for", archive.name
#remove all files not in files.keys() from ArchiveFile
response = json_response({'info': needs_data, 'rename': rename})
else:
response = json_response(status=403, text='permission denied')
return render_to_json_response(response)
def api_encodingSettings(request):
'''
returns Firefogg encoding settings as specified by site
return {'status': {'code': int, 'text': string},
'data': {'options': {'videoQuality':...}}}
'''
response = json_response({'options': settings.VIDEO_ENCODING[settings.VIDEO_PROFILE]})
return render_to_json_response(response)
class UploadForm(forms.Form):
data = forms.TextInput()
file = forms.FileField()
class VideoChunkForm(forms.Form):
chunk = forms.FileField()
done = forms.IntegerField(required=False)
@login_required_json
def api_upload(request): #video, timeline, frame
'''
upload video, timeline or frame
param data
param file
return {'status': {'code': int, 'text': string},
'data': {}}
'''
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
data = json.loads(request.POST['data'])
oshash = data['oshash']
f = get_object_or_404(models.File, oshash=oshash)
if data['item'] == 'frame':
ff = form.cleaned_data['file']
position = data['position']
frame, created = models.Frame.objects.get_or_create(file=f, position=position)
if not created and frame.frame:
frame.frame.delete()
frame.frame.save(ff.name, ff)
frame.save()
response = json_response({'url': frame.frame.url})
return render_to_json_response(response)
if data['item'] == 'timeline':
pass
#print "not implemented"
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
@login_required_json
def firefogg_upload(request):
#handle video upload
if request.method == 'POST':
#init upload
if 'oshash' in request.POST:
#FIXME: what to do if requested oshash is not in db?
#FIXME: should existing data be reset here? or better, should this fail if an upload was there
f = get_object_or_404(models.File, oshash=request.POST['oshash'])
stream = getattr(f, 'stream_%s'%settings.VIDEO_UPLOAD)
if stream:
stream.delete()
f.available = False
f.save()
response = {
'uploadUrl': request.build_absolute_uri('/api/upload/?oshash=%s' % f.oshash),
'result': 1
}
return render_to_json_response(response)
#post next chunk
if 'chunk' in request.FILES and 'oshash' in request.GET:
print "all chunk now"
f = get_object_or_404(models.File, oshash=request.GET['oshash'])
form = VideoChunkForm(request.POST, request.FILES)
#FIXME:
if form.is_valid() and f.editable(request.user):
c = form.cleaned_data['chunk']
response = {
'result': 1,
'resultUrl': request.build_absolute_uri('/')
}
if not f.save_chunk(c, c.name):
response['result'] = -1
elif form.cleaned_data['done']:
#FIXME: send message to encode deamon to create derivates instead
f.available = True
f.save()
response['result'] = 1
response['done'] = 1
return render_to_json_response(response)
print request.GET, request.POST
response = json_response(status=400, text='this request requires POST')
return render_to_json_response(response)
@login_required_json
def api_editFile(request): #FIXME: should this be file.files. or part of update
'''
change file / imdb link
'''
response = json_response(status=501, text='not implemented')
return render_to_json_response(response)
def api_parse(request): #parse path and return info
'''
param data
{path: string}
return {'status': {'code': int, 'text': string},
data: {imdb: string}}
'''
path = json.loads(request.POST['data'])['path']
response = json_response(utils.parsePath(path))
return render_to_json_response(response)
def api_getImdbId(request):
'''
param data
{title: string, director: string, year: string}
return {'status': {'code': int, 'text': string},
'data': {imdbId:string }}
'''
imdbId = oxweb.imdb.guess(search_title, r['director'], timeout=-1)
if imdbId:
response = json_response({'imdbId': imdbId})
else:
response = json_response(status=404, text='not found')
return render_to_json_response(response)
def api_fileInfo(request):
'''
param data
oshash string
return {'status': {'code': int, 'text': string},
'data': {imdbId:string }}
'''
if 'data' in request.POST:
oshash = json.loads(request.POST['data'])
elif 'oshash' in request.GET:
oshash = request.GET['oshash']
f = models.MovieFile.objects.get(oshash=oshash)
response = {'data': f.json()}
return render_to_json_response(response)
def api_subtitles(request):
'''
param data
oshash string
language string
subtitle string
return
if no language is provided:
{data: {languages: array}}
if language is set:
{data: {subtitle: string}}
if subtitle is set:
saves subtitle for given language
'''
if 'data' in request.POST:
data = json.loads(request.POST['data'])
oshash = data['oshash']
language = data.get('language', None)
srt = data.get('subtitle', None)
if srt:
user = request.user
sub = models.Subtitles.objects.get_or_create(user, oshash, language)
sub.srt = srt
sub.save()
else:
response = json_response({})
if language:
q = models.Subtitles.objects.filter(movie_file__oshash=oshash, language=language)
if q.count() > 0:
response['data']['subtitle'] = q[0].srt
return render_to_json_response(response)
l = models.Subtitles.objects.filter(movie_file__oshash=oshash).values('language')
response['data']['languages'] = [f['language'] for f in l]
return render_to_json_response(response)
def video(request, id, quality):
movie = get_object_or_404(models.Movie, movieId=id)
if quality not in settings.VIDEO_ENCODING:
raise Http404
stream = getattr(movie, 'stream_'+quality)
response = HttpFileResponse(stream.path, content_type='video/ogg')
#FIXME: movie needs duration field
#response['Content-Duration'] = movie.duration
return response
def frame(request, id, position, size):
movie = get_object_or_404(models.Movie, movieId=id)
position = oxlib.time2ms(position)/1000
frame = movie.frame(position, int(size))
if not frame:
raise Http404
return HttpFileResponse(frame, content_type='image/jpeg')
'''
GET list
> {
"files": {
"a41cde31c581e11d": {"path": "E/Example, The/An Example.avi", "size":1646274},
}
}
'''
@login_required_json
def list_files(request):
response = {}
response['files'] = {}
qs = models.UserFile.filter(user=request.user)
p = Paginator(qs, 1000)
for i in p.page_range:
page = p.page(i)
for f in page.object_list:
response['files'][f.movie_file.oshash] = {'path': f.path, 'size': f.movie_file.size}
return render_to_json_response(response)
def find_files(request):
response = {}
query = _parse_query(request)
response['files'] = {}
qs = models.UserFile.filter(user=request.user).filter(movie_file__movie__id__in=query['q'])
p = Paginator(qs, 1000)
for i in p.page_range:
page = p.page(i)
for f in page.object_list:
response['files'][f.movie_file.oshash] = {'path': f.path, 'size': f.movie_file.size}
return render_to_json_response(response)
def apidoc(request):
'''
this is used for online documentation at http://127.0.0.1:8000/api/
'''
import sys
def trim(docstring):
if not docstring:
return ''
# Convert tabs to spaces (following the normal Python rules)
# and split into a list of lines:
lines = docstring.expandtabs().splitlines()
# Determine minimum indentation (first line doesn't count):
indent = sys.maxint
for line in lines[1:]:
stripped = line.lstrip()
if stripped:
indent = min(indent, len(line) - len(stripped))
# Remove indentation (first line is special):
trimmed = [lines[0].strip()]
if indent < sys.maxint:
for line in lines[1:]:
trimmed.append(line[indent:].rstrip())
# Strip off trailing and leading blank lines:
while trimmed and not trimmed[-1]:
trimmed.pop()
while trimmed and not trimmed[0]:
trimmed.pop(0)
# Return a single string:
return '\n'.join(trimmed)
functions = filter(lambda x: x.startswith('api_'), globals().keys())
api = []
for f in sorted(functions):
api.append({
'name': f[4:],
'doc': trim(globals()[f].__doc__).replace('\n', '<br>\n')
})
context = RequestContext(request, {'api': api,
'sitename': settings.SITENAME,})
return render_to_response('api.html', context)