d23585cee1fc — Nolan Prescott 4 years ago
Commit working implementation of async glue boy

 - could probably use a real 404, rather than the current behavior
6 files changed, 147 insertions(+), 0 deletions(-)

A => .gitignore
A => README.org
A => cleanup.sh
A => glue_boy.py
A => index.html
A => schema.sql
A => .gitignore +4 -0
@@ 0,0 1,4 @@ 
+*.sqlite
+__pycache__
+*.pyc
+*.pid

          
A => README.org +27 -0
@@ 0,0 1,27 @@ 
+* What the fork?
+  This is a fork (of a fork) of the [[https://github.com/NPrescott/glue-boy][original glue-boy]], a stupidly simple
+  pastebin. This is an async implementation using ~SQLite~ rather than the
+  filesystem.
+
+** installation
+   this iteration of ~glue-boy~ is built on [[https://github.com/twisted/klein][Klein]] and Python 3.6 (for `async def`
+   and `f`-strings)
+
+   #+BEGIN_EXAMPLE
+   pip install -r requirements.txt
+   #+END_EXAMPLE
+
+   and then  and you're off and running
+
+   To initialize the database (required):
+
+   #+BEGIN_EXAMPLE
+   sqlite3 glues.sqlite <schema.sql
+   #+END_EXAMPLE
+
+** cleanup
+   ~cleanup.sh~ is intended to delete all pastes not accessed in more than a
+   week - throw that on a crontab.
+
+   N.B. ~cleanup.sh~ relies on being in the same directory as the
+   ~glues.sqlite~ file

          
A => cleanup.sh +4 -0
@@ 0,0 1,4 @@ 
+#!/bin/sh
+
+echo "DELETE FROM glues WHERE julianday('now') - julianday(last_accessed) > 7;" | sqlite3 glues.sqlite
+echo "vacuum;" | sqlite3 glues.sqlite

          
A => glue_boy.py +71 -0
@@ 0,0 1,71 @@ 
+#!/usr/bin/env python3
+'''
+A stupidly simple pastebin.
+
+requires current directory be on PYTHONPATH, e.g.
+  $ export PYTHONPATH=$(pwd)
+  $ twistd -n web --class=glue_boy.resource
+'''
+
+import datetime
+from uuid import uuid4
+
+from klein import Klein
+from twisted.enterprise import adbapi
+from twisted.internet.defer import ensureDeferred
+from twisted.web.static import File
+
+
+class GluesDatabase():
+    db_pool = adbapi.ConnectionPool('sqlite3', 'glues.sqlite',
+                                    check_same_thread=False)
+    table = 'glues'
+
+    async def get_paste(self, paste_id):
+        '''
+        Return a string from the database, if matching id is not found return the
+        empty string
+        '''
+        await self.db_pool.runOperation("UPDATE glues SET last_accessed = ? "
+                                        "WHERE id = ?",
+                                        (datetime.datetime.now(), paste_id))
+        content = await self.db_pool.runQuery("SELECT content FROM glues "
+                                              "WHERE id = ?", (paste_id,))
+        if content:
+            return content[0][0]
+        return ""
+
+    async def write_paste(self, content):
+        '''
+        Write string content to the database, return the id it was written with.
+        '''
+        paste_id = uuid4().hex[:6]
+        await self.db_pool.runOperation("INSERT INTO glues (id, content) VALUES (?, ?)",
+                                        (paste_id, content))
+        return paste_id
+
+
+class WebApp():
+    app = Klein()
+    db = GluesDatabase()
+
+    @app.route('/', methods=['GET', 'POST'])
+    def new_paste(self, request):
+        if request.method == b'GET':
+            # hack to get around: https://github.com/twisted/klein/issues/41
+            f = File('./')
+            f.indexNames = ['index.html']
+            return f
+        elif request.method == b'POST':
+            content, *_ = request.args[b'content']
+            d = ensureDeferred(self.db.write_paste(content))
+            d.addCallback(
+                lambda paste_id: request.redirect(f'./content/{paste_id}'))
+            return d
+
+    @app.route('/content/<paste_id>', methods=['GET'])
+    def existing_paste(self, request, paste_id):
+        request.setHeader('Content-Type', 'text/plain')
+        return self.db.get_paste(paste_id)
+
+resource = WebApp().app.resource

          
A => index.html +34 -0
@@ 0,0 1,34 @@ 
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>glue-boy</title>
+  </head>
+  <body style="font-family:monospace">
+    <div>
+      <form action="" method="POST">
+        <textarea name="content"  
+                  rows="15" 
+                  cols="79"
+                  style="max-width:100%;"
+                  placeholder="paste your content here" 
+                  autofocus required></textarea>
+        <p>
+          <input name="btn" type="submit" value="glue!"/>
+        </p>
+      </form>
+      <p>
+        glue-boy is command line friendly, upload a file with:
+      <p>
+        <code>$ curl -L -F &#34;content=&lt;filename.extension&#34; -w &#34;%{url_effective}\n&#34; http://glue.nprescott.com
+        </code>
+      </p>
+
+      <p>or paste STDOUT:</p>
+      <p>
+        <code>$ fortune | curl -L -F &#34;content=&lt;-&#34; -w &#34;%{url_effective}\n&#34; http://glue.nprescott.com</code>
+      </p>
+      <a href="http://github.com/NPrescott/glue-boy">glue-boy</a>
+      </p>
+    </div>
+  </body>
+</html>

          
A => schema.sql +7 -0
@@ 0,0 1,7 @@ 
+DROP TABLE if EXISTS glues;
+
+CREATE TABLE glues (
+       id      STRING,
+       content TEXT,
+       last_accessed DATETIME DEFAULT (DATETIME('now'))
+);