2016-09-18 08:33:46 +00:00
|
|
|
import distutils.util
|
|
|
|
import flask
|
|
|
|
from flask import request
|
2016-10-10 03:50:13 +00:00
|
|
|
import functools
|
2016-09-18 08:33:46 +00:00
|
|
|
import json
|
2016-10-10 03:50:13 +00:00
|
|
|
import math
|
2016-09-18 08:33:46 +00:00
|
|
|
import mimetypes
|
|
|
|
import os
|
|
|
|
import random
|
|
|
|
import re
|
|
|
|
import requests
|
2016-10-10 03:50:13 +00:00
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import uuid
|
2016-09-18 08:33:46 +00:00
|
|
|
import warnings
|
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
import phototagger
|
|
|
|
sys.path.append('C:\\git\\else\\Bytestring'); import bytestring
|
|
|
|
|
2016-09-18 08:33:46 +00:00
|
|
|
site = flask.Flask(__name__)
|
|
|
|
site.config.update(
|
|
|
|
SEND_FILE_MAX_AGE_DEFAULT=180,
|
|
|
|
TEMPLATES_AUTO_RELOAD=True,
|
|
|
|
)
|
2016-10-10 03:50:13 +00:00
|
|
|
site.jinja_env.add_extension('jinja2.ext.do')
|
2016-09-18 08:33:46 +00:00
|
|
|
|
|
|
|
P = phototagger.PhotoDB()
|
|
|
|
|
|
|
|
FILE_READ_CHUNK = 2 ** 20
|
|
|
|
|
|
|
|
MOTD_STRINGS = [
|
|
|
|
'Good morning, Paul. What will your first sequence of the day be?',
|
|
|
|
#'Buckle up, it\'s time to:',
|
|
|
|
]
|
|
|
|
|
2016-10-30 01:46:23 +00:00
|
|
|
THUMBDIR = phototagger.DEFAULT_THUMBDIR
|
2016-09-18 08:33:46 +00:00
|
|
|
ERROR_INVALID_ACTION = 'Invalid action'
|
|
|
|
ERROR_NO_TAG_GIVEN = 'No tag name supplied'
|
|
|
|
ERROR_TAG_TOO_SHORT = 'Not enough valid chars'
|
|
|
|
ERROR_SYNONYM_ITSELF = 'Cant apply synonym to itself'
|
|
|
|
ERROR_NO_SUCH_TAG = 'Doesn\'t exist'
|
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
####################################################################################################
|
|
|
|
####################################################################################################
|
|
|
|
####################################################################################################
|
|
|
|
####################################################################################################
|
|
|
|
|
|
|
|
def give_session_token(function):
|
|
|
|
@functools.wraps(function)
|
|
|
|
def wrapped(*args, **kwargs):
|
|
|
|
# Inject new token so the function doesn't know the difference
|
|
|
|
token = request.cookies.get('etiquette_session', None)
|
|
|
|
if not token:
|
|
|
|
token = generate_session_token()
|
|
|
|
request.cookies = dict(request.cookies)
|
|
|
|
request.cookies['etiquette_session'] = token
|
|
|
|
|
|
|
|
ret = function(*args, **kwargs)
|
|
|
|
|
|
|
|
# Send the token back to the client
|
|
|
|
if not isinstance(ret, flask.Response):
|
|
|
|
ret = flask.Response(ret)
|
|
|
|
ret.set_cookie('etiquette_session', value=token, max_age=60)
|
|
|
|
|
|
|
|
return ret
|
|
|
|
return wrapped
|
|
|
|
|
|
|
|
def _helper_comma_split(s):
|
|
|
|
if s is None:
|
|
|
|
return s
|
|
|
|
s = s.replace(' ', ',')
|
|
|
|
s = [x.strip() for x in s.split(',')]
|
|
|
|
s = [x for x in s if x]
|
|
|
|
return s
|
|
|
|
|
2016-10-18 05:13:12 +00:00
|
|
|
def create_tag(easybake_string):
|
|
|
|
notes = P.easybake(easybake_string)
|
|
|
|
notes = [{'action': action, 'tagname': tagname} for (action, tagname) in notes]
|
|
|
|
return notes
|
|
|
|
|
|
|
|
def delete_tag(tag):
|
|
|
|
tag = tag.split('.')[-1].split('+')[0]
|
|
|
|
tag = P.get_tag(tag)
|
|
|
|
|
|
|
|
tag.delete()
|
|
|
|
return {'action': 'delete_tag', 'tagname': tag.name}
|
|
|
|
|
|
|
|
def delete_synonym(synonym):
|
|
|
|
synonym = synonym.split('+')[-1].split('.')[-1]
|
|
|
|
synonym = phototagger.normalize_tagname(synonym)
|
|
|
|
try:
|
|
|
|
master_tag = P.get_tag(synonym)
|
|
|
|
except phototagger.NoSuchTag:
|
|
|
|
flask.abort(404, 'That synonym doesnt exist')
|
|
|
|
|
|
|
|
if synonym not in master_tag.synonyms():
|
|
|
|
flask.abort(400, 'That name is not a synonym')
|
|
|
|
|
|
|
|
master_tag.remove_synonym(synonym)
|
|
|
|
return {'action':'delete_synonym', 'synonym': synonym}
|
|
|
|
|
2016-09-18 08:33:46 +00:00
|
|
|
def edit_params(original, modifications):
|
|
|
|
new_params = original.to_dict()
|
|
|
|
new_params.update(modifications)
|
|
|
|
if not new_params:
|
|
|
|
return ''
|
|
|
|
new_params = ['%s=%s' % (k, v) for (k, v) in new_params.items() if v]
|
|
|
|
new_params = '&'.join(new_params)
|
|
|
|
new_params = '?' + new_params
|
|
|
|
return new_params
|
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
def generate_session_token():
|
|
|
|
token = str(uuid.uuid4())
|
|
|
|
#print('MAKE SESSION', token)
|
|
|
|
return token
|
|
|
|
|
|
|
|
def make_json_response(j, *args, **kwargs):
|
|
|
|
dumped = json.dumps(j)
|
|
|
|
response = flask.Response(dumped, *args, **kwargs)
|
|
|
|
response.headers['Content-Type'] = 'application/json;charset=utf-8'
|
|
|
|
return response
|
|
|
|
|
2016-09-18 08:33:46 +00:00
|
|
|
def P_album(albumid):
|
|
|
|
try:
|
|
|
|
return P.get_album(albumid)
|
|
|
|
except phototagger.NoSuchAlbum:
|
|
|
|
flask.abort(404, 'That tag doesnt exist')
|
|
|
|
|
|
|
|
def P_photo(photoid):
|
|
|
|
try:
|
|
|
|
return P.get_photo(photoid)
|
|
|
|
except phototagger.NoSuchPhoto:
|
|
|
|
flask.abort(404, 'That photo doesnt exist')
|
|
|
|
|
|
|
|
def P_tag(tagname):
|
|
|
|
try:
|
|
|
|
return P.get_tag(tagname)
|
|
|
|
except phototagger.NoSuchTag as e:
|
|
|
|
flask.abort(404, 'That tag doesnt exist: %s' % e)
|
|
|
|
|
|
|
|
def read_filebytes(filepath, range_min, range_max):
|
|
|
|
range_span = range_max - range_min
|
|
|
|
|
|
|
|
#print('read span', range_min, range_max, range_span)
|
|
|
|
f = open(filepath, 'rb')
|
|
|
|
f.seek(range_min)
|
|
|
|
sent_amount = 0
|
|
|
|
with f:
|
|
|
|
while sent_amount < range_span:
|
2016-10-18 05:13:12 +00:00
|
|
|
#print(sent_amount)
|
2016-09-18 08:33:46 +00:00
|
|
|
chunk = f.read(FILE_READ_CHUNK)
|
|
|
|
if len(chunk) == 0:
|
|
|
|
break
|
|
|
|
|
|
|
|
yield chunk
|
|
|
|
sent_amount += len(chunk)
|
|
|
|
|
2016-10-18 05:13:12 +00:00
|
|
|
def seconds_to_hms(seconds):
|
|
|
|
seconds = math.ceil(seconds)
|
|
|
|
(minutes, seconds) = divmod(seconds, 60)
|
|
|
|
(hours, minutes) = divmod(minutes, 60)
|
|
|
|
parts = []
|
|
|
|
if hours: parts.append(hours)
|
|
|
|
if minutes: parts.append(minutes)
|
|
|
|
parts.append(seconds)
|
|
|
|
hms = ':'.join('%02d' % part for part in parts)
|
|
|
|
return hms
|
|
|
|
|
2016-09-18 08:33:46 +00:00
|
|
|
def send_file(filepath):
|
|
|
|
'''
|
|
|
|
Range-enabled file sending.
|
|
|
|
'''
|
2016-10-10 03:50:13 +00:00
|
|
|
try:
|
|
|
|
file_size = os.path.getsize(filepath)
|
|
|
|
except FileNotFoundError:
|
|
|
|
flask.abort(404)
|
|
|
|
|
2016-09-18 08:33:46 +00:00
|
|
|
outgoing_headers = {}
|
|
|
|
mimetype = mimetypes.guess_type(filepath)[0]
|
|
|
|
if mimetype is not None:
|
|
|
|
if 'text/' in mimetype:
|
|
|
|
mimetype += '; charset=utf-8'
|
|
|
|
outgoing_headers['Content-Type'] = mimetype
|
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
if 'range' in request.headers:
|
|
|
|
desired_range = request.headers['range'].lower()
|
|
|
|
desired_range = desired_range.split('bytes=')[-1]
|
2016-09-18 08:33:46 +00:00
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
int_helper = lambda x: int(x) if x.isdigit() else None
|
|
|
|
if '-' in desired_range:
|
|
|
|
(desired_min, desired_max) = desired_range.split('-')
|
|
|
|
range_min = int_helper(desired_min)
|
|
|
|
range_max = int_helper(desired_max)
|
|
|
|
else:
|
|
|
|
range_min = int_helper(desired_range)
|
|
|
|
|
|
|
|
if range_min is None:
|
|
|
|
range_min = 0
|
|
|
|
if range_max is None:
|
|
|
|
range_max = file_size
|
|
|
|
|
|
|
|
# because ranges are 0-indexed
|
|
|
|
range_max = min(range_max, file_size - 1)
|
|
|
|
range_min = max(range_min, 0)
|
|
|
|
|
|
|
|
range_header = 'bytes {min}-{max}/{outof}'.format(
|
|
|
|
min=range_min,
|
|
|
|
max=range_max,
|
|
|
|
outof=file_size,
|
|
|
|
)
|
|
|
|
outgoing_headers['Content-Range'] = range_header
|
|
|
|
status = 206
|
2016-09-18 08:33:46 +00:00
|
|
|
else:
|
2016-10-10 03:50:13 +00:00
|
|
|
range_max = file_size - 1
|
2016-09-18 08:33:46 +00:00
|
|
|
range_min = 0
|
2016-10-10 03:50:13 +00:00
|
|
|
status = 200
|
2016-09-18 08:33:46 +00:00
|
|
|
|
|
|
|
outgoing_headers['Accept-Ranges'] = 'bytes'
|
|
|
|
outgoing_headers['Content-Length'] = (range_max - range_min) + 1
|
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
if request.method == 'HEAD':
|
|
|
|
outgoing_data = bytes()
|
|
|
|
else:
|
|
|
|
outgoing_data = read_filebytes(filepath, range_min=range_min, range_max=range_max)
|
|
|
|
|
2016-09-18 08:33:46 +00:00
|
|
|
response = flask.Response(
|
|
|
|
outgoing_data,
|
2016-10-10 03:50:13 +00:00
|
|
|
status=status,
|
2016-09-18 08:33:46 +00:00
|
|
|
headers=outgoing_headers,
|
|
|
|
)
|
|
|
|
return response
|
|
|
|
|
2016-10-18 05:13:12 +00:00
|
|
|
def truthystring(s):
|
|
|
|
if isinstance(s, (bool, int)) or s is None:
|
|
|
|
return s
|
|
|
|
s = s.lower()
|
|
|
|
if s in {'1', 'true', 't', 'yes', 'y', 'on'}:
|
|
|
|
return True
|
|
|
|
if s in {'null', 'none'}:
|
|
|
|
return None
|
|
|
|
return False
|
2016-10-10 03:50:13 +00:00
|
|
|
|
|
|
|
####################################################################################################
|
|
|
|
####################################################################################################
|
|
|
|
####################################################################################################
|
|
|
|
####################################################################################################
|
|
|
|
|
|
|
|
def jsonify_album(album, minimal=False):
|
|
|
|
j = {
|
|
|
|
'id': album.id,
|
|
|
|
'description': album.description,
|
|
|
|
'title': album.title,
|
|
|
|
}
|
|
|
|
if minimal is False:
|
|
|
|
j['photos'] = [jsonify_photo(photo) for photo in album.photos()]
|
|
|
|
j['parent'] = album.parent()
|
|
|
|
j['sub_albums'] = [child.id for child in album.children()]
|
|
|
|
|
|
|
|
return j
|
|
|
|
|
|
|
|
def jsonify_photo(photo):
|
|
|
|
tags = photo.tags()
|
|
|
|
tags.sort(key=lambda x: x.name)
|
|
|
|
j = {
|
|
|
|
'id': photo.id,
|
|
|
|
'extension': photo.extension,
|
|
|
|
'width': photo.width,
|
|
|
|
'height': photo.height,
|
|
|
|
'ratio': photo.ratio,
|
|
|
|
'area': photo.area,
|
|
|
|
'bytes': photo.bytes,
|
2016-10-18 05:13:12 +00:00
|
|
|
'duration': seconds_to_hms(photo.duration) if photo.duration is not None else None,
|
2016-10-10 03:50:13 +00:00
|
|
|
'duration_int': photo.duration,
|
|
|
|
'bytestring': photo.bytestring(),
|
|
|
|
'has_thumbnail': bool(photo.thumbnail),
|
|
|
|
'created': photo.created,
|
|
|
|
'filename': photo.basename,
|
|
|
|
'mimetype': photo.mimetype(),
|
|
|
|
'albums': [jsonify_album(album, minimal=True) for album in photo.albums()],
|
|
|
|
'tags': [jsonify_tag(tag) for tag in tags],
|
|
|
|
}
|
|
|
|
return j
|
|
|
|
|
|
|
|
def jsonify_tag(tag):
|
|
|
|
j = {
|
|
|
|
'id': tag.id,
|
|
|
|
'name': tag.name,
|
|
|
|
'qualified_name': tag.qualified_name(),
|
|
|
|
}
|
|
|
|
return j
|
|
|
|
|
|
|
|
####################################################################################################
|
|
|
|
####################################################################################################
|
|
|
|
####################################################################################################
|
|
|
|
####################################################################################################
|
2016-09-18 08:33:46 +00:00
|
|
|
|
|
|
|
@site.route('/')
|
2016-10-10 03:50:13 +00:00
|
|
|
@give_session_token
|
2016-09-18 08:33:46 +00:00
|
|
|
def root():
|
|
|
|
motd = random.choice(MOTD_STRINGS)
|
|
|
|
return flask.render_template('root.html', motd=motd)
|
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
@site.route('/favicon.ico')
|
|
|
|
@site.route('/favicon.png')
|
|
|
|
def favicon():
|
|
|
|
filename = os.path.join('static', 'favicon.png')
|
|
|
|
return flask.send_file(filename)
|
|
|
|
|
|
|
|
def get_album_core(albumid):
|
2016-09-18 08:33:46 +00:00
|
|
|
album = P_album(albumid)
|
2016-10-10 03:50:13 +00:00
|
|
|
album = jsonify_album(album)
|
|
|
|
return album
|
|
|
|
|
|
|
|
@site.route('/album/<albumid>')
|
|
|
|
@give_session_token
|
|
|
|
def get_album_html(albumid):
|
|
|
|
album = get_album_core(albumid)
|
2016-09-18 08:33:46 +00:00
|
|
|
response = flask.render_template(
|
|
|
|
'album.html',
|
|
|
|
album=album,
|
2016-10-30 01:46:23 +00:00
|
|
|
child_albums=[jsonify_album(P_album(x)) for x in album['sub_albums']],
|
2016-10-10 03:50:13 +00:00
|
|
|
photos=album['photos'],
|
2016-09-18 08:33:46 +00:00
|
|
|
)
|
|
|
|
return response
|
|
|
|
|
2016-10-21 03:15:24 +00:00
|
|
|
@site.route('/album/<albumid>.json')
|
2016-10-10 03:50:13 +00:00
|
|
|
@give_session_token
|
|
|
|
def get_album_json(albumid):
|
|
|
|
album = get_album_core(albumid)
|
|
|
|
return make_json_response(album)
|
|
|
|
|
|
|
|
@site.route('/albums')
|
|
|
|
@give_session_token
|
2016-10-21 03:15:24 +00:00
|
|
|
def get_albums_html():
|
2016-10-10 03:50:13 +00:00
|
|
|
albums = P.get_albums()
|
|
|
|
albums = [a for a in albums if a.parent() is None]
|
|
|
|
return flask.render_template('albums.html', albums=albums)
|
|
|
|
|
2016-10-21 03:15:24 +00:00
|
|
|
@site.route('/albums.json')
|
|
|
|
@give_session_token
|
|
|
|
def get_albums_json():
|
|
|
|
albums = P.get_albums()
|
|
|
|
albums = [a for a in albums if a.parent() is None]
|
|
|
|
albums = [jsonify_album(album, minimal=True) for album in albums]
|
|
|
|
return make_json_response(albums)
|
|
|
|
|
2016-09-18 08:33:46 +00:00
|
|
|
@site.route('/file/<photoid>')
|
|
|
|
def get_file(photoid):
|
|
|
|
requested_photoid = photoid
|
|
|
|
photoid = photoid.split('.')[0]
|
|
|
|
photo = P.get_photo(photoid)
|
|
|
|
|
|
|
|
do_download = request.args.get('download', False)
|
|
|
|
do_download = truthystring(do_download)
|
|
|
|
|
|
|
|
use_original_filename = request.args.get('original_filename', False)
|
|
|
|
use_original_filename = truthystring(use_original_filename)
|
|
|
|
|
|
|
|
if do_download:
|
|
|
|
if use_original_filename:
|
|
|
|
download_as = photo.basename
|
|
|
|
else:
|
|
|
|
download_as = photo.id + '.' + photo.extension
|
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
## Sorry, but otherwise the attachment filename gets terminated
|
2016-09-18 08:33:46 +00:00
|
|
|
#download_as = download_as.replace(';', '-')
|
|
|
|
download_as = download_as.replace('"', '\\"')
|
|
|
|
response = flask.make_response(send_file(photo.real_filepath))
|
|
|
|
response.headers['Content-Disposition'] = 'attachment; filename="%s"' % download_as
|
|
|
|
return response
|
|
|
|
else:
|
|
|
|
return send_file(photo.real_filepath)
|
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
def get_photo_core(photoid):
|
2016-09-18 08:33:46 +00:00
|
|
|
photo = P_photo(photoid)
|
2016-10-10 03:50:13 +00:00
|
|
|
photo = jsonify_photo(photo)
|
|
|
|
return photo
|
2016-09-18 08:33:46 +00:00
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
@site.route('/photo/<photoid>', methods=['GET'])
|
|
|
|
@give_session_token
|
|
|
|
def get_photo_html(photoid):
|
|
|
|
photo = get_photo_core(photoid)
|
|
|
|
photo['tags'].sort(key=lambda x: x['qualified_name'])
|
|
|
|
return flask.render_template('photo.html', photo=photo)
|
|
|
|
|
|
|
|
@site.route('/photo/<photoid>.json', methods=['GET'])
|
|
|
|
@give_session_token
|
|
|
|
def get_photo_json(photoid):
|
|
|
|
photo = get_photo_core(photoid)
|
|
|
|
photo = make_json_response(photo)
|
|
|
|
return photo
|
|
|
|
|
|
|
|
def get_search_core():
|
2016-09-18 08:33:46 +00:00
|
|
|
print(request.args)
|
|
|
|
|
2016-10-30 01:46:23 +00:00
|
|
|
# FILENAME & EXTENSION
|
|
|
|
filename_terms = request.args.get('filename', None)
|
2016-10-10 03:50:13 +00:00
|
|
|
extension_string = request.args.get('extension', None)
|
|
|
|
extension_not_string = request.args.get('extension_not', None)
|
|
|
|
mimetype_string = request.args.get('mimetype', None)
|
2016-09-18 08:33:46 +00:00
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
extension_list = _helper_comma_split(extension_string)
|
|
|
|
extension_not_list = _helper_comma_split(extension_not_string)
|
|
|
|
mimetype_list = _helper_comma_split(mimetype_string)
|
2016-09-18 08:33:46 +00:00
|
|
|
|
|
|
|
# LIMIT
|
|
|
|
limit = request.args.get('limit', '')
|
|
|
|
if limit.isdigit():
|
|
|
|
limit = int(limit)
|
|
|
|
limit = min(100, limit)
|
|
|
|
else:
|
|
|
|
limit = 50
|
|
|
|
|
|
|
|
# OFFSET
|
|
|
|
offset = request.args.get('offset', None)
|
|
|
|
if offset:
|
|
|
|
offset = int(offset)
|
|
|
|
else:
|
|
|
|
offset = None
|
|
|
|
|
|
|
|
# MUSTS, MAYS, FORBIDS
|
|
|
|
qualname_map = P.export_tags(exporter=phototagger.tag_export_qualname_map)
|
|
|
|
tag_musts = request.args.get('tag_musts', '').split(',')
|
|
|
|
tag_mays = request.args.get('tag_mays', '').split(',')
|
|
|
|
tag_forbids = request.args.get('tag_forbids', '').split(',')
|
|
|
|
tag_expression = request.args.get('tag_expression', None)
|
|
|
|
|
|
|
|
tag_musts = [qualname_map.get(tag, tag) for tag in tag_musts if tag != '']
|
|
|
|
tag_mays = [qualname_map.get(tag, tag) for tag in tag_mays if tag != '']
|
|
|
|
tag_forbids = [qualname_map.get(tag, tag) for tag in tag_forbids if tag != '']
|
|
|
|
|
|
|
|
# ORDERBY
|
|
|
|
orderby = request.args.get('orderby', None)
|
|
|
|
if orderby:
|
|
|
|
orderby = orderby.replace('-', ' ')
|
|
|
|
orderby = orderby.replace('_', ' ')
|
|
|
|
orderby = orderby.split(',')
|
|
|
|
else:
|
|
|
|
orderby = None
|
|
|
|
|
|
|
|
# HAS_TAGS
|
|
|
|
has_tags = request.args.get('has_tags', '')
|
|
|
|
if has_tags == '':
|
|
|
|
has_tags = None
|
|
|
|
else:
|
|
|
|
has_tags = truthystring(has_tags)
|
|
|
|
|
|
|
|
# MINMAXERS
|
|
|
|
area = request.args.get('area', None)
|
|
|
|
width = request.args.get('width', None)
|
|
|
|
height = request.args.get('height', None)
|
|
|
|
ratio = request.args.get('ratio', None)
|
|
|
|
bytes = request.args.get('bytes', None)
|
2016-10-10 03:50:13 +00:00
|
|
|
duration = request.args.get('duration', None)
|
2016-09-18 08:33:46 +00:00
|
|
|
created = request.args.get('created', None)
|
|
|
|
|
|
|
|
# These are in a dictionary so I can pass them to the page template.
|
|
|
|
search_kwargs = {
|
|
|
|
'area': area,
|
|
|
|
'width': width,
|
|
|
|
'height': height,
|
|
|
|
'ratio': ratio,
|
|
|
|
'bytes': bytes,
|
2016-10-10 03:50:13 +00:00
|
|
|
'duration': duration,
|
2016-09-18 08:33:46 +00:00
|
|
|
|
|
|
|
'created': created,
|
|
|
|
'extension': extension_list,
|
|
|
|
'extension_not': extension_not_list,
|
2016-10-30 01:46:23 +00:00
|
|
|
'filename': filename_terms,
|
2016-09-18 08:33:46 +00:00
|
|
|
'has_tags': has_tags,
|
|
|
|
'mimetype': mimetype_list,
|
|
|
|
'tag_musts': tag_musts,
|
|
|
|
'tag_mays': tag_mays,
|
|
|
|
'tag_forbids': tag_forbids,
|
|
|
|
'tag_expression': tag_expression,
|
|
|
|
|
|
|
|
'limit': limit,
|
|
|
|
'offset': offset,
|
|
|
|
'orderby': orderby,
|
|
|
|
|
|
|
|
'warn_bad_tags': True,
|
|
|
|
}
|
|
|
|
print(search_kwargs)
|
|
|
|
with warnings.catch_warnings(record=True) as catcher:
|
|
|
|
photos = list(P.search(**search_kwargs))
|
2016-10-10 03:50:13 +00:00
|
|
|
photos = [jsonify_photo(photo) for photo in photos]
|
2016-09-18 08:33:46 +00:00
|
|
|
warns = [str(warning.message) for warning in catcher]
|
2016-10-18 05:13:12 +00:00
|
|
|
#print(warns)
|
2016-10-10 03:50:13 +00:00
|
|
|
|
|
|
|
# TAGS ON THIS PAGE
|
2016-09-18 08:33:46 +00:00
|
|
|
total_tags = set()
|
|
|
|
for photo in photos:
|
2016-10-10 03:50:13 +00:00
|
|
|
for tag in photo['tags']:
|
|
|
|
total_tags.add(tag['qualified_name'])
|
|
|
|
total_tags = sorted(total_tags)
|
2016-09-18 08:33:46 +00:00
|
|
|
|
|
|
|
# PREV-NEXT PAGE URLS
|
|
|
|
offset = offset or 0
|
|
|
|
if len(photos) == limit:
|
|
|
|
next_params = edit_params(request.args, {'offset': offset + limit})
|
|
|
|
next_page_url = '/search' + next_params
|
|
|
|
else:
|
|
|
|
next_page_url = None
|
|
|
|
if offset > 0:
|
|
|
|
prev_params = edit_params(request.args, {'offset': max(0, offset - limit)})
|
|
|
|
prev_page_url = '/search' + prev_params
|
|
|
|
else:
|
|
|
|
prev_page_url = None
|
|
|
|
|
|
|
|
search_kwargs['extension'] = extension_string
|
|
|
|
search_kwargs['extension_not'] = extension_not_string
|
|
|
|
search_kwargs['mimetype'] = mimetype_string
|
2016-10-10 03:50:13 +00:00
|
|
|
|
|
|
|
final_results = {
|
|
|
|
'next_page_url': next_page_url,
|
|
|
|
'prev_page_url': prev_page_url,
|
|
|
|
'photos': photos,
|
|
|
|
'total_tags': total_tags,
|
|
|
|
'warns': warns,
|
|
|
|
'search_kwargs': search_kwargs,
|
|
|
|
'qualname_map': qualname_map,
|
|
|
|
}
|
|
|
|
return final_results
|
|
|
|
|
|
|
|
@site.route('/search')
|
|
|
|
@give_session_token
|
|
|
|
def get_search_html():
|
|
|
|
search_results = get_search_core()
|
|
|
|
search_kwargs = search_results['search_kwargs']
|
|
|
|
qualname_map = search_results['qualname_map']
|
2016-09-18 08:33:46 +00:00
|
|
|
response = flask.render_template(
|
|
|
|
'search.html',
|
2016-10-10 03:50:13 +00:00
|
|
|
next_page_url=search_results['next_page_url'],
|
|
|
|
prev_page_url=search_results['prev_page_url'],
|
|
|
|
photos=search_results['photos'],
|
2016-09-18 08:33:46 +00:00
|
|
|
qualname_map=json.dumps(qualname_map),
|
2016-10-10 03:50:13 +00:00
|
|
|
search_kwargs=search_kwargs,
|
|
|
|
total_tags=search_results['total_tags'],
|
|
|
|
warns=search_results['warns'],
|
2016-09-18 08:33:46 +00:00
|
|
|
)
|
|
|
|
return response
|
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
@site.route('/search.json')
|
|
|
|
@give_session_token
|
|
|
|
def get_search_json():
|
|
|
|
search_results = get_search_core()
|
|
|
|
search_kwargs = search_results['search_kwargs']
|
|
|
|
qualname_map = search_results['qualname_map']
|
|
|
|
include_qualname_map = request.args.get('include_map', False)
|
|
|
|
include_qualname_map = truthystring(include_qualname_map)
|
|
|
|
if not include_qualname_map:
|
|
|
|
search_results.pop('qualname_map')
|
|
|
|
return make_json_response(j)
|
|
|
|
|
2016-09-18 08:33:46 +00:00
|
|
|
@site.route('/static/<filename>')
|
2016-10-10 03:50:13 +00:00
|
|
|
def get_static(filename):
|
|
|
|
filename = filename.replace('\\', os.sep)
|
|
|
|
filename = filename.replace('/', os.sep)
|
|
|
|
filename = os.path.join('static', filename)
|
|
|
|
return flask.send_file(filename)
|
|
|
|
|
|
|
|
@site.route('/tags')
|
|
|
|
@site.route('/tags/<specific_tag>')
|
|
|
|
@give_session_token
|
|
|
|
def get_tags(specific_tag=None):
|
|
|
|
try:
|
|
|
|
tags = P.export_tags(phototagger.tag_export_easybake, specific_tag=specific_tag)
|
|
|
|
except phototagger.NoSuchTag:
|
|
|
|
flask.abort(404, 'That tag doesnt exist')
|
|
|
|
|
|
|
|
tags = tags.split('\n')
|
|
|
|
tags = [t for t in tags if t != '']
|
|
|
|
tags = [(t, t.split('.')[-1].split('+')[0]) for t in tags]
|
|
|
|
return flask.render_template('tags.html', tags=tags)
|
|
|
|
|
|
|
|
@site.route('/thumbnail/<photoid>')
|
|
|
|
def get_thumbnail(photoid):
|
|
|
|
photoid = photoid.split('.')[0]
|
|
|
|
photo = P_photo(photoid)
|
|
|
|
if photo.thumbnail:
|
|
|
|
path = photo.thumbnail
|
|
|
|
else:
|
|
|
|
flask.abort(404, 'That file doesnt have a thumbnail')
|
|
|
|
return send_file(path)
|
|
|
|
|
2016-10-18 05:13:12 +00:00
|
|
|
@site.route('/album/<albumid>', methods=['POST'])
|
2016-10-21 03:15:24 +00:00
|
|
|
@site.route('/album/<albumid>.json', methods=['POST'])
|
2016-10-18 05:13:12 +00:00
|
|
|
@give_session_token
|
|
|
|
def post_edit_album(albumid):
|
|
|
|
'''
|
|
|
|
Edit the album's title and description.
|
|
|
|
Apply a tag to every photo in the album.
|
|
|
|
'''
|
2016-10-21 03:15:24 +00:00
|
|
|
response = {}
|
|
|
|
album = P_album(albumid)
|
|
|
|
|
|
|
|
if 'add_tag' in request.form:
|
|
|
|
action = 'add_tag'
|
|
|
|
|
|
|
|
tag = request.form[action].strip()
|
|
|
|
try:
|
|
|
|
tag = P_tag(tag)
|
|
|
|
except phototagger.NoSuchTag:
|
|
|
|
response = {'error': 'That tag doesnt exist', 'tagname': tag}
|
|
|
|
return make_json_response(response, status=404)
|
|
|
|
recursive = request.form.get('recursive', False)
|
|
|
|
recursive = truthystring(recursive)
|
|
|
|
album.add_tag_to_all(tag, nested_children=recursive)
|
|
|
|
response['action'] = action
|
|
|
|
response['tagname'] = tag.name
|
|
|
|
return make_json_response(response)
|
2016-10-18 05:13:12 +00:00
|
|
|
|
2016-10-10 03:50:13 +00:00
|
|
|
@site.route('/photo/<photoid>', methods=['POST'])
|
2016-10-21 03:15:24 +00:00
|
|
|
@site.route('/photo/<photoid>.json', methods=['POST'])
|
2016-10-10 03:50:13 +00:00
|
|
|
@give_session_token
|
|
|
|
def post_edit_photo(photoid):
|
2016-10-18 05:13:12 +00:00
|
|
|
'''
|
|
|
|
Add and remove tags from photos.
|
|
|
|
'''
|
2016-10-10 03:50:13 +00:00
|
|
|
response = {}
|
|
|
|
photo = P_photo(photoid)
|
|
|
|
|
|
|
|
if 'add_tag' in request.form:
|
|
|
|
action = 'add_tag'
|
|
|
|
method = photo.add_tag
|
|
|
|
elif 'remove_tag' in request.form:
|
|
|
|
action = 'remove_tag'
|
|
|
|
method = photo.remove_tag
|
|
|
|
else:
|
|
|
|
flask.abort(400, 'Invalid action')
|
|
|
|
|
|
|
|
tag = request.form[action].strip()
|
|
|
|
if tag == '':
|
|
|
|
flask.abort(400, 'No tag supplied')
|
|
|
|
|
|
|
|
try:
|
|
|
|
tag = P.get_tag(tag)
|
|
|
|
except phototagger.NoSuchTag:
|
2016-10-18 05:13:12 +00:00
|
|
|
response = {'error': 'That tag doesnt exist', 'tagname': tag}
|
|
|
|
return make_json_response(response, status=404)
|
2016-10-10 03:50:13 +00:00
|
|
|
|
|
|
|
method(tag)
|
|
|
|
response['action'] = action
|
2016-10-18 05:13:12 +00:00
|
|
|
#response['tagid'] = tag.id
|
2016-10-10 03:50:13 +00:00
|
|
|
response['tagname'] = tag.name
|
2016-10-18 05:13:12 +00:00
|
|
|
return make_json_response(response)
|
2016-10-10 03:50:13 +00:00
|
|
|
|
|
|
|
@site.route('/tags', methods=['POST'])
|
|
|
|
@give_session_token
|
|
|
|
def post_edit_tags():
|
2016-10-18 05:13:12 +00:00
|
|
|
'''
|
|
|
|
Create and delete tags and synonyms.
|
|
|
|
'''
|
2016-10-10 03:50:13 +00:00
|
|
|
print(request.form)
|
|
|
|
status = 200
|
|
|
|
if 'create_tag' in request.form:
|
|
|
|
action = 'create_tag'
|
|
|
|
method = create_tag
|
|
|
|
elif 'delete_tag_synonym' in request.form:
|
|
|
|
action = 'delete_tag_synonym'
|
|
|
|
method = delete_synonym
|
|
|
|
elif 'delete_tag' in request.form:
|
|
|
|
action = 'delete_tag'
|
|
|
|
method = delete_tag
|
|
|
|
else:
|
2016-10-18 05:13:12 +00:00
|
|
|
status = 400
|
2016-10-10 03:50:13 +00:00
|
|
|
response = {'error': ERROR_INVALID_ACTION}
|
|
|
|
|
|
|
|
if status == 200:
|
|
|
|
tag = request.form[action].strip()
|
|
|
|
if tag == '':
|
|
|
|
response = {'error': ERROR_NO_TAG_GIVEN}
|
2016-10-18 05:13:12 +00:00
|
|
|
status = 400
|
|
|
|
|
|
|
|
if status == 200:
|
|
|
|
# expect the worst
|
|
|
|
status = 400
|
2016-10-10 03:50:13 +00:00
|
|
|
try:
|
|
|
|
response = method(tag)
|
|
|
|
except phototagger.TagTooShort:
|
|
|
|
response = {'error': ERROR_TAG_TOO_SHORT, 'tagname': tag}
|
|
|
|
except phototagger.CantSynonymSelf:
|
|
|
|
response = {'error': ERROR_SYNONYM_ITSELF, 'tagname': tag}
|
|
|
|
except phototagger.NoSuchTag as e:
|
|
|
|
response = {'error': ERROR_NO_SUCH_TAG, 'tagname': tag}
|
|
|
|
except ValueError as e:
|
|
|
|
response = {'error': e.args[0], 'tagname': tag}
|
|
|
|
else:
|
|
|
|
status = 200
|
|
|
|
|
|
|
|
response = json.dumps(response)
|
|
|
|
response = flask.Response(response, status=status)
|
|
|
|
return response
|
2016-09-18 08:33:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2016-10-10 03:50:13 +00:00
|
|
|
site.run(threaded=True)
|