first commit
David Blume

David Blume commited on 2016-09-08 23:30:02
Showing 6 changed files, with 400 additions and 0 deletions.

... ...
@@ -0,0 +1,5 @@
1
+RewriteEngine On
2
+RewriteCond %{HTTPS} off
3
+RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
4
+DirectoryIndex index.php index.html index.py
5
+AddCharset UTF-8 .txt
... ...
@@ -0,0 +1,33 @@
1
+# kvs
2
+
3
+kvs is a rudimentary key/value store written in Python.
4
+
5
+# Getting the project
6
+
7
+You can get a copy of this project by clicking on the
8
+[ZIP](http://git.dlma.com/kvs.git/zipball/master)
9
+or [TAR](http://git.dlma.com/kvs.git/tarball/master) buttons
10
+near the top right of the GitList web page.
11
+
12
+If you're me, and you want to contribute to the repo, then you can clone it like so:
13
+
14
+    git clone ssh://USERNAME@dlma.com/~/git/kvs.git
15
+
16
+# Building it
17
+
18
+1. Enable Python pages at your web server.
19
+2. Move auth\_sample.txt to auth.txt.
20
+
21
+# Is it any good?
22
+
23
+[Yes](https://news.ycombinator.com/item?id=3067434).
24
+
25
+# To Do
26
+
27
+1. Better document it.
28
+2. PEP-8
29
+3. Productize it.
30
+
31
+# Licence
32
+
33
+This software uses the [MIT license](http://git.dlma.com/roku_ip_tagger.git/blob/master/LICENSE.txt).
... ...
@@ -0,0 +1 @@
1
+yourauthorizationhere
... ...
@@ -0,0 +1,85 @@
1
+#!/usr/bin/python
2
+#!C:/Python26/python.exe
3
+#
4
+# http://www.evanfosmark.com/2009/01/cross-platform-file-locking-support-in-python/
5
+
6
+import os
7
+import time
8
+import errno
9
+
10
+class FileLockException(Exception):
11
+    pass
12
+
13
+class FileLock(object):
14
+    """ A file locking mechanism that has context-manager support so
15
+        you can use it in a with statement. This should be relatively cross
16
+        compatible as it doesn't rely on msvcrt or fcntl for the locking.
17
+    """
18
+
19
+    def __init__(self, file_name, timeout=10, delay=.05):
20
+        """ Prepare the file locker. Specify the file to lock and optionally
21
+            the maximum timeout and the delay between each attempt to lock.
22
+        """
23
+        self.is_locked = False
24
+        self.lockfile = "%s.lock" % file_name
25
+        self.timeout = timeout
26
+        self.delay = delay
27
+
28
+
29
+    def acquire(self):
30
+        """ Acquire the lock, if possible. If the lock is in use, it check again
31
+            every `wait` seconds. It does this until it either gets the lock or
32
+            exceeds `timeout` number of seconds, in which case it throws
33
+            an exception.
34
+        """
35
+        if self.is_locked:
36
+            return
37
+        start_time = time.time()
38
+        while True:
39
+            try:
40
+                self.fd = os.open(self.lockfile, os.O_CREAT|os.O_EXCL|os.O_RDWR)
41
+                os.write(self.fd, "%d" % os.getpid())
42
+                break;
43
+            except OSError, e:
44
+                if e.errno != errno.EEXIST:
45
+                    raise
46
+                if (time.time() - start_time) >= self.timeout:
47
+                    raise FileLockException("Timeout occured.")
48
+                time.sleep(self.delay)
49
+        self.is_locked = True
50
+
51
+
52
+    def release(self):
53
+        """ Get rid of the lock by deleting the lockfile.
54
+            When working in a `with` statement, this gets automatically
55
+            called at the end.
56
+        """
57
+        if self.is_locked:
58
+            os.close(self.fd)
59
+            os.unlink(self.lockfile)
60
+            self.is_locked = False
61
+
62
+
63
+    def __enter__(self):
64
+        """ Activated when used in the with statement.
65
+            Should automatically acquire a lock to be used in the with block.
66
+        """
67
+        if not self.is_locked:
68
+            self.acquire()
69
+        return self
70
+
71
+
72
+    def __exit__(self, type, value, traceback):
73
+        """ Activated at the end of the with statement.
74
+            It automatically releases the lock if it isn't locked.
75
+        """
76
+        if self.is_locked:
77
+            self.release()
78
+
79
+
80
+    def __del__(self):
81
+        """ Make sure that the FileLock instance doesn't leave a lockfile
82
+            lying around.
83
+        """
84
+        self.release()
85
+
... ...
@@ -0,0 +1,169 @@
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
10
+import yaml
11
+import texttime
12
+
13
+cgitb.enable(display=0, logdir="/tmp")
14
+store_name = 'store.txt'
15
+
16
+
17
+def read_file(full_pathname):
18
+    store = dict()
19
+    if os.path.isfile(full_pathname):
20
+        try:
21
+            with filelock.FileLock(full_pathname):
22
+                with open(full_pathname, 'r') as f:
23
+                    store = yaml.load(f)
24
+        except filelock.FileLockException as e:
25
+            raise
26
+    return store
27
+
28
+
29
+def write_file(full_pathname, data):
30
+    ''' See http://david.dlma.com/blog/dads-project-in-the-garage '''
31
+    try:
32
+        with filelock.FileLock(full_pathname):
33
+            with tempfile.NamedTemporaryFile(mode='w',
34
+                    dir=os.path.dirname(full_pathname),
35
+                    delete=False) as f:
36
+                yaml.dump(data, f)
37
+                # exiting this "with" flushes, but does not sync
38
+                f.flush()
39
+                os.fdatasync(f.fileno())  # Faster, Unix only
40
+                tempname = f.name
41
+            os.rename(tempname, full_pathname)  # Atomic on Unix, not Windows
42
+    except filelock.FileLockException as e:
43
+        raise
44
+
45
+
46
+def map_ip_to_location(ip):
47
+    if ip == '67.188.28.83':
48
+        return 'home'.ljust(13)
49
+    elif ip in ('50.224.7.233', '50.224.7.248'):
50
+        return 'work'.ljust(13)
51
+    return ip
52
+
53
+
54
+if __name__ == '__main__':
55
+    localdir = os.path.abspath(os.path.dirname(sys.argv[0]))
56
+    args = cgi.FieldStorage()
57
+    key = None
58
+    extra = None
59
+    auth = None
60
+    get_key = False
61
+    auth_in_get = False
62
+    doing_post = False
63
+
64
+    # Find the parameters that are GET parameters
65
+    # Could also check os.environ['REQUEST_METHOD'] == 'POST',
66
+    # but that doesn't say which way individual params were sent.
67
+    if os.environ['QUERY_STRING']:
68
+        qs = os.environ['QUERY_STRING'].split('&')
69
+        for pair in qs:
70
+            k, v = pair.split('=')
71
+            if k == 'k':
72
+                get_key = True
73
+            if k == 'auth':
74
+                auth_in_get = True
75
+
76
+    # Could be either GET or POST. Need to check QUERY_STRING above.
77
+    if "k" in args:
78
+        key = args["k"].value
79
+
80
+    if os.environ['REQUEST_METHOD'] == 'POST':
81
+        doing_post = True
82
+        if not auth_in_get and "auth" in args:
83
+            auth = args["auth"].value
84
+
85
+    if "extra" in args:
86
+        extra = args["extra"].value
87
+
88
+    store = read_file(os.path.join(localdir, store_name))
89
+    if get_key:
90
+        keys = key.split(',')
91
+        if len(keys) > 1:
92
+            devices = set()
93
+            for k in store:
94
+                d = store[k]
95
+                if k in keys:
96
+                    devices.add((d['time'], key, d['value']))
97
+            if devices:  # not empty
98
+                print "Content-type: text/plain; charset=utf-8\n"
99
+                sys.stdout.write(sorted(devices, reverse=True)[0][2])
100
+            else:
101
+                print 'Status: 404 Not Found\n'
102
+        elif key in store:
103
+            print "Content-type: text/plain; charset=utf-8\n"
104
+            sys.stdout.write(store[key]['value'])
105
+            if extra is not None:
106
+                sys.stdout.write('\n%s' % (store[key]['time'],))
107
+        else:
108
+            print 'Status: 404 Not Found\n'
109
+    else:
110
+        wrote_response = False
111
+        with open('auth.txt', 'r') as f:
112
+            authorization = f.read().strip()
113
+        if doing_post and auth == authorization:
114
+            for new_key in args:
115
+                if new_key not in ('auth', 'extra') and \
116
+                    len(new_key) < 80 and len(args[new_key].value) < 140:
117
+                    if new_key in store and 'created' in store[new_key]:
118
+                        created_time = store[new_key]['created']
119
+                    else:
120
+                        created_time = int(time.time())
121
+                    store[new_key] = {'value': args[new_key].value,
122
+                                      'time': int(time.time()),
123
+                                      'created': created_time,
124
+                                      'origin': os.environ['REMOTE_ADDR']}
125
+                    write_file(os.path.join(localdir, store_name), store)
126
+                    wrote_response = True
127
+                    print "Content-type: text/plain; charset=utf-8\n"
128
+                    print "OK"
129
+                    break
130
+        elif not os.environ['QUERY_STRING'] and not doing_post:
131
+            # just the home page, then
132
+            print "Content-type: text/plain; charset=UTF-8\n"
133
+            wrote_response = True
134
+            print "key          \tvalue         \tcreated \tupdated \torigin      \tNotes"
135
+            print "------------ \t------------- \t----------\t----------\t-------------\t---------------------"
136
+            devices = { '1G': '(Au)',
137
+                        '5S': '(Br)',
138
+                        'YW': '(FW)',
139
+                        'YY': '(Da)',
140
+                        '2N': '(Li)',
141
+                        '1R': '(Ty)',
142
+                        '1R': '(Ty)',
143
+                        'YU': '(Lf)',
144
+                        'YP': '(C4)',
145
+                      }
146
+            for key in sorted(store, key=lambda k: store[k]['time'], reverse=True):
147
+                if key[:2] in devices:
148
+                    device = devices[key[:2]] + ' '
149
+                else:
150
+                    device = ''
151
+                d = store[key]
152
+                td = texttime.stringify(datetime.timedelta(seconds=time.time()-d['time']))
153
+                if 'created' in d:
154
+                    created = time.strftime('%Y-%m-%d', time.localtime(d['created']))
155
+                else:
156
+                    created = "          "
157
+                if 'origin' in d:
158
+                    origin = map_ip_to_location(d['origin'])
159
+                else:
160
+                    origin = "               "
161
+                print '\t'.join((key.ljust(12), d['value'].ljust(13), created, str(d['time']), origin, "%supdated %s ago" % (device, td)))
162
+            print
163
+            print 'Tip:'
164
+            print 'sync_ip () { export ENV_VAR=$(curl -s "%s?k=2FN50L002036,2FN44N010911"); }' % os.environ['SCRIPT_URI']
165
+            print 'curl --data "`hostname -s`=`hostname -i`&auth=$AUTH" "%s"' % os.environ['SCRIPT_URI']
166
+
167
+        if not wrote_response:
168
+            print 'Status: 400 Bad Request\n'
169
+
... ...
@@ -0,0 +1,107 @@
1
+from datetime import timedelta
2
+
3
+# Set this to the language you want to use.
4
+LANG = "en"
5
+
6
+# Singular and plural forms of time units in your language.
7
+unit_names = dict(
8
+                  en = {"year" : ("year", "years"),
9
+                        "month" : ("month", "months"),
10
+                        "week" : ("week", "weeks"),
11
+                        "day" : ("day", "days"),
12
+                        "hour" : ("hour", "hours"),
13
+                        "minute" : ("minute", "minutes"),
14
+                        "second" : ("second", "seconds")})
15
+                  
16
+num_repr = dict(
17
+                en = {1 : "a",
18
+                      2 : "two",
19
+                      3 : "three",
20
+                      4 : "four",
21
+                      5 : "five",
22
+                      6 : "six",
23
+                      7 : "seven",
24
+                      8 : "eight",
25
+                      9 : "nine",
26
+                      10 : "ten",
27
+                      11 : "eleven",
28
+                      12 : "twelve"})
29
+
30
+def amount_to_str(amount, unit_name):
31
+    if amount == 1 and unit_name == "hour" and LANG == "en":
32
+        return "an"
33
+    if amount in num_repr[LANG]:
34
+        return num_repr[LANG][amount]
35
+    return str(amount)
36
+
37
+def seconds_in_units(seconds):
38
+    """
39
+    Returns a tuple containing the most appropriate unit for the
40
+    number of seconds supplied and the value in that units form.
41
+
42
+        >>> seconds_in_units(7700)
43
+        (2, 'hour')
44
+    """
45
+    unit_limits = [("year", 365 * 24 * 3600),
46
+                   ("month", 30 * 24 * 3600),
47
+                   ("week", 7 * 24 * 3600),
48
+                   ("day", 24 * 3600),
49
+                   ("hour", 3600),
50
+                   ("minute", 60)]
51
+    for unit_name, limit in unit_limits:
52
+        if seconds >= limit * 0.94:
53
+            amount = int(round(float(seconds) / limit))
54
+            return amount, unit_name
55
+    return seconds, "second"
56
+
57
+def stringify(td):
58
+    """
59
+    Converts a timedelta into a nicely readable string.
60
+
61
+        >>> td = timedelta(days = 77, seconds = 5)
62
+        >>> print readable_timedelta(td)
63
+        two months
64
+    """
65
+    seconds = td.days * 3600 * 24 + td.seconds
66
+    amount, unit_name = seconds_in_units(seconds)
67
+
68
+    # Localize it.
69
+    i18n_amount = amount_to_str(amount, unit_name)
70
+    i18n_unit = unit_names[LANG][unit_name][1]
71
+    if amount == 1:
72
+        i18n_unit = unit_names[LANG][unit_name][0]
73
+    return "%s %s" % (i18n_amount, i18n_unit)
74
+    
75
+def test(td):
76
+    if td.days > 100:
77
+        fmt = "In %s, it's a long time. (%s)"
78
+    elif td.days > 4:
79
+        fmt = "I've only got %s to finish the project. (%s)"
80
+    elif td.days > 0:
81
+        fmt = "The party was %s ago. (%s)"
82
+    elif td.seconds > 3600:
83
+        fmt = "Something weird happened %s ago. (%s)"
84
+    elif td.seconds > 60:
85
+        fmt = "The train arrives in %s. (%s)"
86
+    else:
87
+        fmt = "%s passes fast. (%s)"
88
+    print fmt % (stringify(td), str(td))
89
+
90
+def main():
91
+    global LANG
92
+    LANG = "en"
93
+    test(timedelta(weeks = 7, days = 3))
94
+    test(timedelta(weeks = 1))
95
+    test(timedelta(days = 1000))
96
+    test(timedelta(days = 400))
97
+    test(timedelta(days = 4))
98
+    test(timedelta(seconds = 2000))
99
+    test(timedelta(seconds = 9888))
100
+    test(timedelta(seconds = 999888))
101
+    test(timedelta(seconds = 999))
102
+    test(timedelta(seconds = 99))
103
+    test(timedelta(seconds = 45))
104
+    test(timedelta(seconds = 3))
105
+
106
+if __name__ == "__main__":
107
+    main()
0 108