Permalink
Browse files

Beta signups, user info caching, error handling, daemonizing

  • Loading branch information...
1 parent e2d4e4e commit 0b31db60079b2ea10c8181daa726072c3f4072c4 @mdirolf mdirolf committed Oct 13, 2011
Showing with 289 additions and 42 deletions.
  1. +195 −0 daemon.py
  2. +20 −14 db.py
  3. +15 −8 github.py
  4. +1 −1 static/c/main.css
  5. +1 −1 templates/beta_index.html
  6. +0 −1 templates/create.html
  7. +0 −1 templates/index_logged_in.html
  8. +0 −1 templates/repo.html
  9. +57 −15 www.py
View
195 daemon.py
@@ -0,0 +1,195 @@
+# Based on http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/
+
+import atexit
+import errno
+import logging
+import logging.handlers
+import os
+from signal import SIGTERM
+import sys
+import time
+
+
+def go(daemon):
+ if len(sys.argv) > 1:
+ if "start" == sys.argv[1]:
+ daemon.start()
+ elif "stop" == sys.argv[1]:
+ daemon.stop()
+ elif "restart" == sys.argv[1]:
+ daemon.restart()
+ elif "status" == sys.argv[1]:
+ sys.exit(daemon.status())
+ else:
+ print "Unknown command"
+ sys.exit(2)
+ sys.exit(0)
+ else:
+ print "usage: %s start|stop|restart" % sys.argv[0]
+ sys.exit(2)
+
+
+class Daemon:
+ """
+ A generic daemon class.
+
+ Usage: subclass the Daemon class and override the run() method
+ """
+ def __init__(self, pidfile, logfile=None):
+ self.pidfile = pidfile
+ self.logfile = logfile
+
+ def daemonize(self):
+ """
+ do the UNIX double-fork magic, see Stevens' "Advanced
+ Programming in the UNIX Environment" for details (ISBN 0201563177)
+ http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC16
+ """
+ try:
+ pid = os.fork()
+ if pid > 0:
+ # exit first parent
+ sys.exit(0)
+ except OSError, e:
+ sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
+ sys.exit(1)
+
+ # decouple from parent environment
+ os.chdir("/")
+ os.setsid()
+ os.umask(0)
+
+ # do second fork
+ try:
+ pid = os.fork()
+ if pid > 0:
+ # exit from second parent
+ sys.exit(0)
+ except OSError, e:
+ sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
+ sys.exit(1)
+
+ # redirect standard file descriptors
+ sys.stdout.flush()
+ sys.stderr.flush()
+ null = os.open("/dev/null", os.O_RDWR)
+ for i in range(3):
+ try:
+ os.dup2(null, i)
+ except OSError, e:
+ if e.errno != errno.EBADF:
+ raise
+ os.close(null)
+
+ # write pidfile
+ atexit.register(self.delpid)
+ pid = str(os.getpid())
+ file(self.pidfile,'w+').write("%s\n" % pid)
+
+ def setup_logging(self):
+ """Configure the logging module"""
+ log = logging.getLogger()
+ log.setLevel(logging.INFO)
+ handler = logging.handlers.RotatingFileHandler(self.logfile,
+ maxBytes=1024*1024,
+ backupCount=1)
+ handler.setFormatter(logging.Formatter(
+ "%(asctime)s %(process)d %(levelname)s %(message)s"))
+ log.addHandler(handler)
+
+ def delpid(self):
+ os.remove(self.pidfile)
+
+ def start(self):
+ """
+ Start the daemon
+ """
+ # Check for a pidfile to see if the daemon already runs
+ try:
+ pf = file(self.pidfile,'r')
+ pid = int(pf.read().strip())
+ pf.close()
+ except IOError:
+ pid = None
+
+ if pid:
+ message = "pidfile %s already exist. Daemon already running?\n"
+ sys.stderr.write(message % self.pidfile)
+ sys.exit(1)
+
+ # Start the daemon
+ self.daemonize()
+ if self.logfile:
+ self.setup_logging()
+ self.run()
+
+ def stop(self):
+ """
+ Stop the daemon
+ """
+ # Get the pid from the pidfile
+ try:
+ pf = file(self.pidfile,'r')
+ pid = int(pf.read().strip())
+ pf.close()
+ except IOError:
+ pid = None
+
+ if not pid:
+ message = "pidfile %s does not exist. Daemon not running?\n"
+ sys.stderr.write(message % self.pidfile)
+ return # not an error in a restart
+
+ # Try killing the daemon process
+ try:
+ while 1:
+ os.kill(pid, SIGTERM)
+ time.sleep(0.1)
+ except OSError, err:
+ err = str(err)
+ if err.find("No such process") > 0:
+ if os.path.exists(self.pidfile):
+ os.remove(self.pidfile)
+ else:
+ print str(err)
+ sys.exit(1)
+
+ def status(self):
+ try:
+ pf = file(self.pidfile, "r")
+ pid = int(pf.read().strip())
+ pf.close()
+ except IOError:
+ pid = None
+
+ if not pid:
+ sys.stderr.write("Daemon not running.\n")
+ return 3
+
+ # see if it's running?
+ try:
+ os.getpgid(pid)
+ sys.stderr.write("Daemon (PID %s) is running...\n" % pid)
+ return 0
+ except OSError, err:
+ err = str(err)
+ if err.find("No such process") > 0:
+ sys.stderr.write("Daemon not running but pidfile exists\n")
+ return 1
+ else:
+ print str(err)
+ return 4
+
+ def restart(self):
+ """
+ Restart the daemon
+ """
+ self.stop()
+ self.start()
+
+ def run(self):
+ """
+ You should override this method when you subclass Daemon. It
+ will be called after the process has been daemonized by
+ start() or restart().
+ """
View
34 db.py
@@ -2,26 +2,32 @@
db = Connection(replicaset="fiesta", tz_aware=True)["gitlists"]
-db.gh_memo.create_index("u")
+db.memo.create_index("u")
+# Caching arbitrary URIs
def memoized(uri):
- doc = db.gh_memo.find_one({"u": uri})
+ doc = db.memo.find_one({"u": uri})
if doc:
return doc["d"]
return doc
def memoize(uri, data):
- db.gh_memo.save({"u": uri, "d": data})
-
-
-def pending_create(creds, github_id, usernames, addresses, repo, org):
- doc = {"_id": creds["oauth_token"],
- "secret": creds["oauth_token_secret"],
- "creator_id": github_id,
- "usernames": usernames,
- "addresses": addresses,
- "repo": repo,
- "org": org}
- db.pending.save(doc, safe=True)
+ db.memo.save({"u": uri, "d": data})
+
+
+# Beta signups
+def beta(username):
+ db.beta.save({"gh": username}, safe=True)
+
+
+# Memoizing github user data
+def save_user(username, email_address, display_name):
+ db.users.save({"_id": username,
+ "email": email_address,
+ "name": display_name}, safe=True)
+
+
+def user(username):
+ return db.users.find_one({"_id": username})
View
@@ -71,15 +71,22 @@ def make_request(u, big=False):
return json.loads(data)
-def user_info(username=None):
- if not username:
- return make_request("/user")
+def current_user():
+ data = make_request("/user")
+ if data:
+ db.save_user(data["login"], data["email"], data["name"])
+ return data
+
+
+def user_info(username):
+ existing = db.user(username)
+ if existing:
+ return existing
+
u = "http://github.com/api/v2/json/user/show/" + username
- data = db.memoized(u)
- if not data:
- data = urllib.urlopen(u).read()
- db.memoize(u, data)
- return json.loads(data)
+ data = json.loads(urllib.urlopen(u).read())["user"]
+ db.save_user(username, data["email"], data["name"])
+ return data
def repos(org=None):
View
@@ -7,7 +7,7 @@ ul li {
margin-top: 0.75em;
}
#f {
- padding-top: 1em;
+ padding-top: 3em;
}
#copy {
float: right;
@@ -7,7 +7,7 @@
<p>Apologies for being coy.</p>
-<p>Sign up below to get on the list, or just bug us <a href="http://twitter.com/gitlists">on twitter</a>.</p>
+<p>Sign up below to get on the list, or just bug us on <a href="http://twitter.com/gitlists">twitter</a>.</p>
<form method="POST" action="/beta">
<label>
View
@@ -2,7 +2,6 @@
{% block content %}
<h1><a href="/">Home</a> > <a href="/repo/{{ org and org + "/" or "" }}{{ repo }}">{{ repo }}@gitlists.com</a> > Create</h1>
-<hr>
<form id="create" method="POST" action="/create">
<p class="top">Configure your gitlist and click <strong>Create</strong>.</p>
<p>When it's created a welcome message will be sent out to all of the list members.</p>
@@ -3,7 +3,6 @@
{% block content %}
<h1>Hi, <span class="handle">{{ user.login }}</span>!</h1>
<h2>Which repository do you want to create a gitlist for?</h2>
-<hr>
<h3>Your repositories</h3>
<ul>
{% for repo in repos %}
View
@@ -3,7 +3,6 @@
{% block content %}
<h1><a href="/">Home</a> > {{ repo.name }}@gitlists.com</h1>
<h2>Who should be a member of the gitlist?</h2>
-<hr>
<form name="create" method="GET" action="/create">
{% if collaborators or contributors or org_members or forkers or watchers %}
<div class="yui3-g">
Oops, something went wrong.

0 comments on commit 0b31db6

Please sign in to comment.