/
oneimage.py
executable file
·276 lines (223 loc) · 8.47 KB
/
oneimage.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#!/usr/bin/python
"""
backend service that does operations on a single image
uses 'x-foaf-agent' header for permissions
POST /viewPerm?value=public&img=http://photo.bigast... -> {msg: "public"}
GET /viewPerm?img=http://photo.bigast... -> { public: true }
GET /facts?img=http://photo.bigast... -> rendered table
GET /ariDateAge?img=http://photo... -> for ari photos
the fetching of the resized images is still over in serve
"""
from __future__ import division
import boot
import json, time, urllib, datetime, itertools, cgi, sys
from dateutil.tz import tzlocal
from klein import run, route
from rdflib import URIRef, Variable, Literal
import auth
from xml.utils import iso8601
import shove_leveldb.store
from tagging import getTagLabels
import access, webuser
from oneimagequery import photoCreated
from ns import PHO, FOAF, EXIF, SCOT, DC, RDF, DCTERMS
from urls import localSite
from alternates import findAltRoot
import db
import networking
log = boot.log
if sys.argv[0].endswith('oneimage.py'):
_photoCreatedCache = shove_leveldb.store.LevelDBStore('leveldb:///tmp/photoCreatedCache') # uri : datetime
else:
_photoCreatedCache = None
@route('/viewPerm', methods=['GET'])
def GET_viewPerm(request):
uri = URIRef(request.args['img'][0])
request.setHeader('Content-type', 'application/json')
return json.dumps({"viewableBy" :
"public" if access.isPublic(graph, uri) else "superuser"})
@route('/viewPerm', methods=['POST'])
def POST_viewPerm(request):
"""
moved to /aclChange
"""
raise NotImplementedError
@route('/facts')
def GET_facts(request):
request.setHeader("content-type", "application/json")
img = URIRef(request.args['uri'][0])
# check security
ret = {}
lines = []
now = time.time()
try:
created = photoCreated(graph, img, _cache=_photoCreatedCache)
ret['created'] = created.isoformat()
sec = time.mktime(created.timetuple())
except ValueError, e:
log.warn("no created time for %s" % img)
import traceback
traceback.print_exc()
created = sec = None
if sec is not None:
ago = int((now - sec) / 86400)
if ago < 365:
ago = '; %s days ago' % ago
else:
ago = ''
lines.append("Picture taken %s%s" % (created.isoformat(' '), ago))
allDepicts = [row['who'] for row in
graph.queryd(
"SELECT DISTINCT ?who WHERE { ?img foaf:depicts ?who }",
initBindings={"img" : img})]
allTags = getTagLabels(graph, "todo", img)
if created is not None:
for who, tag, birthday in [
(URIRef("http://photo.bigasterisk.com/2008/person/apollo"),
'apollo',
'2008-07-22'),
] + auth.birthdays:
try:
if (who in allDepicts or Literal(tag) in allTags):
name = graph.value(
who, FOAF.name, default=graph.label(
who, default=tag))
lines.append("%s is %s old. " % (
name, personAgeString(birthday, created.isoformat())))
except Exception, e:
log.error("%s birthday failed: %s" % (who, e))
ret['factLines'] = [dict(line=x) for x in lines]
# 'used in this blog entry'
return json.dumps(ret)
@route('/links')
def GET_links(request):
"""images and other things related to this one"""
img = URIRef(request.args['uri'][0])
links = {}
def relQuery(rel):
rows = graph.queryd("""
SELECT DISTINCT ?d ?label WHERE {
?img ?rel ?d .
OPTIONAL { ?d rdfs:label ?label }
}""", initBindings={Variable("rel") : rel,
Variable("img") : img})
for r in rows:
if 'label' not in r:
r['label'] = r['d']
yield r
def setUrl(**params):
params['current'] = img
return ('/set?' + urllib.urlencode(params))
for row in relQuery(FOAF.depicts):
try:
links.setdefault('depicting', []).append(
{'uri' : localSite(row['d']), 'label' : row['label']})
except ValueError, e:
log.warn("error in FOAF.depicts: %s %s" % (vars(), e))
pass
for row in relQuery(PHO.inDirectory):
links.setdefault('inDirectory', []).append(
{'uri' : setUrl(dir=row['d']),
'label' : row['d'].split('/')[-2]})
for row in relQuery(DC.date):
links.setdefault('takenOn', []).append(
{'uri' : setUrl(date=row['d']),
'label' : row['d']})
# photos from email may have only the email's date
for row in relQuery(SCOT.hasTag):
links.setdefault('withTag', []).append(
{'uri' : setUrl(tag=row['label'].encode('utf8')),
'label' : row['label']})
alts = findAltRoot(graph, img)
if alts:
links['alternates'] = [{'uri' : setUrl(alt=alts[0]),
'label' : alts[1]}]
# taken near xxxxx
return json.dumps({'links' : links.items()})
from tagging import getTags, saveTags
@route('/tags', methods=['GET'])
def GET_tags(request):
"""description too, though you can get that separately if you want"""
img = URIRef(request.args['uri'][0])
user = webuser.getUserKlein(request)
request.setHeader("Content-Type", "text/json")
return json.dumps(getTags(graph, user, img))
@route('/tags', methods=['PUT'])
def PUT_tags(request):
img = URIRef(request.args['uri'][0])
user = webuser.getUserKlein(request)
log.info('user %r', user)
body = cgi.parse_qs(request.content.read())
saveTags(graph,
foafUser=user,
img=img,
tagString=body.get('tags', [''])[0],
desc=body.get('desc', [''])[0])
request.setHeader("Content-Type", "text/json")
return json.dumps(getTags(graph, user, img))
@route('/stats')
def GET_stats(request):
uri = URIRef(request.args['img'][0])
request.setHeader('Content-type', 'application/json')
# check security
# this will be all the stuff in render_facts
d = graph.value(uri, EXIF.dateTime)
return json.dumps({"date" : d,
})
# GET should tell you about the alts for the image
@route('/alt', methods=['POST'])
def POST_alt(request):
uri = URIRef(request.args['uri'][0])
desc = json.load(request.content)
if desc['source'] != str(uri):
# sometimes this happens on a : vs %3A disagreement, which
# is something I think I workaround elsewhere. Drop
# 'source' attr entirely? figure out where this new :
# escaping is happening?
raise ValueError("source %r != %r" % (desc['source'], str(uri)))
newAltUri = pickNewUri(uri, desc['tag'])
ctx = URIRef(newAltUri + "#create")
now = Literal(datetime.datetime.now(tzlocal()))
creator = webuser.getUserKlein(request)
if not creator:
raise ValueError("missing creator")
stmts = [
(uri, PHO.alternate, newAltUri),
(newAltUri, RDF.type, FOAF.Image),
(newAltUri, DCTERMS.creator, creator),
(newAltUri, DCTERMS.created, now),
]
for k, v in desc.items():
if k in ['source']:
continue
if k == 'types':
for typeUri in v:
stmts.append((newAltUri, RDF.type, URIRef(typeUri)))
else:
# this handles basic json types at most. consider
# JSON-LD or just n3 as better input formats, but make
# sure all their statements are about the uri, not
# arbitrary data that could change security
# permissions and stuff
stmts.append((newAltUri, PHO[k], Literal(v)))
graph.add(stmts, context=ctx)
return "added %s statements to context %s" % (len(stmts), ctx)
def pickNewUri(self, uri, tag):
for suffix in itertools.count(1):
proposed = URIRef("%s/alt/%s%s" % (uri, tag, suffix))
if not graph.contains((proposed, None, None)):
return proposed
def personAgeString(isoBirthday, photoDate):
try:
sec = iso8601.parse(str(photoDate))
except Exception:
sec = iso8601.parse(str(photoDate) + '-0700')
birth = iso8601.parse(isoBirthday)
days = (sec - birth) / 86400
if days / 30 < 12:
return "%.1f months" % (days / 30)
else:
return "%.1f years" % (days / 365)
if __name__ == '__main__':
graph = db.getGraph()
run('0.0.0.0', networking.oneImageServer()[1])