70062049e40e4865a5d1ca0ac2dd1d3b5a4706f9
David Blume first commit

David Blume authored 7 years ago

1) #!/usr/bin/env python
2) # index.py for a rudimentary key/value server by David Blume
3) import os
4) import sys
5) import time
6) import datetime
7) import cgi, cgitb
8) import filelock
9) import tempfile
David Blume Improve the documentation a...

David Blume authored 7 years ago

10) import yaml  # You may need to "pip install pyyaml"
David Blume first commit

David Blume authored 7 years ago

11) import texttime
12) 
13) cgitb.enable(display=0, logdir="/tmp")
14) store_name = 'store.txt'
15) 
16) 
17) def read_file(full_pathname):
David Blume Improve the documentation a...

David Blume authored 7 years ago

18)     """ The file must be a YAML file containing a dict. """
David Blume first commit

David Blume authored 7 years ago

19)     store = dict()
20)     if os.path.isfile(full_pathname):
21)         try:
22)             with filelock.FileLock(full_pathname):
23)                 with open(full_pathname, 'r') as f:
24)                     store = yaml.load(f)
25)         except filelock.FileLockException as e:
26)             raise
27)     return store
28) 
29) 
30) def write_file(full_pathname, data):
David Blume Improve the documentation a...

David Blume authored 7 years ago

31)     """ See http://david.dlma.com/blog/dads-project-in-the-garage """
David Blume first commit

David Blume authored 7 years ago

32)     try:
33)         with filelock.FileLock(full_pathname):
34)             with tempfile.NamedTemporaryFile(mode='w',
35)                     dir=os.path.dirname(full_pathname),
36)                     delete=False) as f:
37)                 yaml.dump(data, f)
38)                 # exiting this "with" flushes, but does not sync
39)                 f.flush()
40)                 os.fdatasync(f.fileno())  # Faster, Unix only
41)                 tempname = f.name
42)             os.rename(tempname, full_pathname)  # Atomic on Unix, not Windows
43)     except filelock.FileLockException as e:
44)         raise
45) 
46) 
47) def map_ip_to_location(ip):
David Blume Minor refactor for the inde...

David Blume authored 7 years ago

48)     """ Use a meaningful location instead of an IP address where possible."""
David Blume first commit

David Blume authored 7 years ago

49)     if ip == '67.188.28.83':
50)         return 'home'.ljust(13)
51)     elif ip in ('50.224.7.233', '50.224.7.248'):
52)         return 'work'.ljust(13)
53)     return ip
54) 
55) 
David Blume Minor refactor for the inde...

David Blume authored 7 years ago

56) device_prefixes = { '1G': '(Au)',
57)                     '5S': '(Br)',
58)                     'YW': '(FW)',
59)                     'YY': '(Da)',
60)                     '2N': '(Li)',
61)                     '1R': '(Ty)',
62)                     'YU': '(Lf)',
63)                     'YP': '(C4)',
64)                   }
65) 
66) def map_key_to_prefix(k):
67)     """ Return a hint about the key if we can."""
68)     try:
69)         return "%s " % device_prefixes[k[:2]]
70)     except KeyError:
71)         pass
72)     return ""
73) 
David Blume Refactor table printing int...

David Blume authored 7 years ago

74) 
75) def print_as_table(rows, underline_header = True):
76)     """ Prints rows with consistent column widths. """
77)     cols = zip(*rows)
78) 
79)     # Compute column widths by taking maximum length of values per column
80)     col_widths = [max(len(value) for value in col) for col in cols]
81) 
82)     # Create a suitable format string for nice table output
83)     fmt = "\t".join(["{:<%d}" % i for i in col_widths])
84) 
85)     # Print each row using the computed format
86)     if underline_header:
87)         print fmt.format(*rows.pop(0))
88)         print "\t".join([('-' * i) for i in col_widths])  # column header separator
89)     for row in rows:
90)         print fmt.format(*row)
91) 
92) 
93) def print_footer():
94)     print
95)     print 'Tip:'
96)     print 'sync_ip () { export ENV_VAR=$(curl -s "%s?k=YP008G581095,2N00FG504225"); }' % os.environ['SCRIPT_URI']
97)     print 'curl --data "`hostname -s`=`hostname -i`&auth=$AUTH" "%s"' % os.environ['SCRIPT_URI']
98)     print 'Source code at http://git.dlma.com/kvs.git/'
99) 
100) 
David Blume first commit

David Blume authored 7 years ago

101) if __name__ == '__main__':
102)     localdir = os.path.abspath(os.path.dirname(sys.argv[0]))
103)     args = cgi.FieldStorage()
104)     key = None
105)     auth = None
106)     get_key = False
107)     auth_in_get = False
108)     doing_post = False
109) 
110)     # Find the parameters that are GET parameters
111)     # Could also check os.environ['REQUEST_METHOD'] == 'POST',
112)     # but that doesn't say which way individual params were sent.
113)     if os.environ['QUERY_STRING']:
114)         qs = os.environ['QUERY_STRING'].split('&')
115)         for pair in qs:
116)             k, v = pair.split('=')
117)             if k == 'k':
118)                 get_key = True
119)             if k == 'auth':
120)                 auth_in_get = True
121) 
122)     # Could be either GET or POST. Need to check QUERY_STRING above.
123)     if "k" in args:
124)         key = args["k"].value
125) 
126)     if os.environ['REQUEST_METHOD'] == 'POST':
127)         doing_post = True
128)         if not auth_in_get and "auth" in args:
129)             auth = args["auth"].value
130) 
131)     store = read_file(os.path.join(localdir, store_name))
132)     if get_key:
133)         keys = key.split(',')
David Blume Use list comprehension.

David Blume authored 7 years ago

134)         matches = sorted([(store[x]['time'], store[x]['value']) for x in keys if x in store], reverse=True)
135)         if matches:
David Blume first commit

David Blume authored 7 years ago

136)             print "Content-type: text/plain; charset=utf-8\n"
David Blume Use list comprehension.

David Blume authored 7 years ago

137)             sys.stdout.write(matches[0][1])
David Blume first commit

David Blume authored 7 years ago

138)         else:
139)             print 'Status: 404 Not Found\n'
140)     else:
141)         wrote_response = False
142)         with open('auth.txt', 'r') as f:
143)             authorization = f.read().strip()
144)         if doing_post and auth == authorization:
145)             for new_key in args:
David Blume Use list comprehension.

David Blume authored 7 years ago

146)                 if new_key not in ('auth', ) and \
David Blume first commit

David Blume authored 7 years ago

147)                     len(new_key) < 80 and len(args[new_key].value) < 140:
148)                     if new_key in store and 'created' in store[new_key]:
149)                         created_time = store[new_key]['created']
150)                     else:
151)                         created_time = int(time.time())
152)                     store[new_key] = {'value': args[new_key].value,
153)                                       'time': int(time.time()),
154)                                       'created': created_time,
155)                                       'origin': os.environ['REMOTE_ADDR']}
156)                     write_file(os.path.join(localdir, store_name), store)
157)                     wrote_response = True
158)                     print "Content-type: text/plain; charset=utf-8\n"
159)                     print "OK"
160)                     break
161)         elif not os.environ['QUERY_STRING'] and not doing_post:
162)             # just the home page, then
163)             print "Content-type: text/plain; charset=UTF-8\n"
164)             wrote_response = True
David Blume Print the index page with d...

David Blume authored 7 years ago

165) 
166)             rows = [('key', 'value', 'created', 'updated', 'origin', 'notes'),]
David Blume first commit

David Blume authored 7 years ago

167)             for key in sorted(store, key=lambda k: store[k]['time'], reverse=True):
168)                 d = store[key]
169)                 td = texttime.stringify(datetime.timedelta(seconds=time.time()-d['time']))
David Blume Minor refactor for the inde...

David Blume authored 7 years ago

170)                 created = time.strftime('%Y-%m-%d', time.localtime(d['created']))
171)                 origin = map_ip_to_location(d['origin'])
David Blume Print the index page with d...

David Blume authored 7 years ago

172)                 rows.append((key, d['value'], created, str(d['time']), origin,
173)                             "%supdated %s ago" % (map_key_to_prefix(key), td)))
174) 
David Blume Refactor table printing int...

David Blume authored 7 years ago

175)             print_as_table(rows)
176)             print_footer()