Skip to content

Commit

Permalink
Initial import into SVN
Browse files Browse the repository at this point in the history
git-svn-id: https://urly.googlecode.com/svn/trunk@3 4bb6f403-2351-0410-a0e4-d75a30d34d26
  • Loading branch information
adam@stilesoft.com committed Jul 4, 2008
1 parent 33475b9 commit 13b4f66
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 0 deletions.
12 changes: 12 additions & 0 deletions app.yaml
@@ -0,0 +1,12 @@
application: urly2
version: 1
runtime: python
api_version: 1

handlers:
- url: /static
static_dir: static

- url: .*
script: main.py

19 changes: 19 additions & 0 deletions index.yaml
@@ -0,0 +1,19 @@
indexes:

#
#- kind: Urly
# properties:
# - name: href
# Creating a composite index failed: ascending single-property indexes are not necessary
#


# AUTOGENERATED

# This index.yaml is automatically updated whenever the dev_appserver
# detects that a new type of query is run. If you want to manage the
# index.yaml file manually, remove the above marker line (the line
# saying "# AUTOGENERATED"). If you want to manage some indexes
# manually, move them above the marker line. The index.yaml file is
# automatically uploaded to the admin console when you next deploy
# your application using appcfg.py.
41 changes: 41 additions & 0 deletions main.html
@@ -0,0 +1,41 @@
{% comment %}
// Variables for this template:
// urly - this is an urly instance to show (we use code and href properties)
// error_404 - an error message to show the user
// default_href - default href to dump into text area. Why do we have this?
// error_href - set when we have an improperly formatted href
{% endcomment %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head><title>ur.ly - dang short urls</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="static/urly.js"></script>
</head>
<body style="font-family: verdana, arial; font-size: 80%; text-align: center">
<div style="margin: 0 auto; width: 600px; border: 1px solid #cccccc; padding: 10px; text-align: left;">

<span style="float: right"><a href="http://code.google.com/appengine/"><img border="0" src="http://code.google.com/appengine/images/appengine-noborder-120x30.gif" /></a></span>
<a href="/"><img border="0" style="padding-top: 3px;" src="static/urly.gif" alt=" - dang short urls" /></a>

{% if error_404 or urly or error_href %}
<div style="margin-top: 5px; margin-bottom: 15px; padding: 10px; background-color: #e6e6ff; border: 1px dashed #cccccc;">
{% if error_404 %}Oops - we couldn't find that urly.{% endif %}
{% if error_href %}Oops - that address doesn't look right... we can't create a dang short url from it.{% endif %}
{% if urly %}
{{ urly.href }} is now dang short, and <b>its already on your clipboard</b>.<br />
<input id="href" name="href" type="text" style="margin-top: 5px; padding: 3px; width: 300px; border: 1px solid #cccccc;" value="http://ur.ly/{{ urly.code }}" />
<script type="text/javascript" language="javascript">window.onload = copy("http://ur.ly/{{ urly.code }}");</script>
<script type="text/javascript" language="javascript">window.onload = focus("href");</script>
&nbsp;&nbsp;<a href="http://ur.ly/{{ urly.code }}">Try It</a>
{% endif %}
</div>
{% endif %}

<div style="padding-bottom: 3px;">Enter a long URL below and we'll make it dang short:</div>
<form action="new.html" method="get" accept-charset="utf-8">
<textarea style="margin-bottom: 5px; width: 100%; border: 1px solid #cccccc;" name="href" id="href" rows="4">{{ default_href }}</textarea>
<input type="submit" value="Create" />
</form>
<div style="margin-top: 10px; padding-top: 5px; border-top: 1px solid #dddddd;"><span style="float: right"><a href="http://code.google.com/p/urly/">Project</a> <a href="http://code.google.com/p/urly/wiki/APIDocumentation">API</a></span>Drag this link to your browser toolbar: <a href="javascript:location.href='http://ur.ly/new.html?href='+encodeURIComponent(location.href)">ur.ly!</a></div>
</div>
</div></body></html>
71 changes: 71 additions & 0 deletions main.py
@@ -0,0 +1,71 @@
#!/usr/bin/env python
#
# Copyright 2008 Adam Stiles
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
# by applicable law or agreed to in writing, software distributed under the
# License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#

"""A url-shortener built on Google App Engine."""
__author__ = 'Adam Stiles'

"""
All Urly records in the database have an id and an href. We base62 that
integer id to create a short code that represents that Urly.
/{code} Redirect user to urly with this code
/{code}(.json|.xml|.html) Show user formatted urly with this code
/new(.json|.xml|.html)?href={href} Create a new urly with this href or
return existing one if it already exists
Note special handling for 'new' code
when we have a href GET parameter 'cause
'new' by itself looks like a code
"""
import wsgiref.handlers
import re, os, logging
from google.appengine.ext import webapp
from google.appengine.ext import db
from urly import Urly
from view import MainView

class MainHandler(webapp.RequestHandler):
"""All non-static requests go through this handler.
The code and format parameters are pre-populated by
our routing regex... see main() below.
"""
def get(self, code, format):
if (code is None):
MainView.render(self, 200, None, format)
return

href = self.request.get('href').strip()
if (code == 'new') and (href is not None):
try:
u = Urly.find_or_create_by_href(href)
if u is not None:
MainView.render(self, 200, u, format)
else:
logging.error("Error creating urly by href: %s", str(href))
MainView.render(self, 400, None, format, href)
except db.BadValueError:
# href parameter is bad
MainView.render(self, 400, None, format, href)
else:
u = Urly.find_by_code(str(code))
if u is not None:
MainView.render(self, 200, u, format)
else:
MainView.render(self, 404, None, format)

def main():
application = webapp.WSGIApplication([
('/([a-zA-Z0-9]{1,6})?(.xml|.json|.html)?', MainHandler)
], debug=True)
wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
main()
Binary file added static/clipboard.swf
Binary file not shown.
Binary file added static/urly.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions static/urly.js
@@ -0,0 +1,29 @@
//-----------------------------------------------------------------------------
function copy(text2copy) {
if (window.clipboardData) {
window.clipboardData.setData("Text",text2copy);
} else {
var flashcopier = 'flashcopier';
if(!document.getElementById(flashcopier)) {
var divholder = document.createElement('div');
divholder.id = flashcopier;
document.body.appendChild(divholder);
}
document.getElementById(flashcopier).innerHTML = '';
var divinfo = '<embed src="static/clipboard.swf" FlashVars="clipboard='+escape(text2copy)+'" width="0" height="0" type="application/x-shockwave-flash"></embed>';
document.getElementById(flashcopier).innerHTML = divinfo;
}
}

//-----------------------------------------------------------------------------
function focus(id)
{
var el = document.getElementById(id);
if (el != null) {
el.focus();
el.select();
}
}

//-----------------------------------------------------------------------------

88 changes: 88 additions & 0 deletions urly.py
@@ -0,0 +1,88 @@
# Copyright 2008 Adam Stiles
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
# by applicable law or agreed to in writing, software distributed under the
# License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

from google.appengine.ext import db
from google.appengine.api import memcache
import logging

class Urly(db.Model):
"""Our one-and-only model"""
href = db.LinkProperty(required=True)
created_at = db.DateTimeProperty(auto_now_add=True)

KEY_BASE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
BASE = 62

def code(self):
"""Return our code, our base-62 encoded id"""
if not self.is_saved():
return None
nid = self.key().id()
s = []
while nid:
nid, c = divmod(nid, Urly.BASE)
s.append(Urly.KEY_BASE[c])
s.reverse()
return "".join(s)

def to_json(self):
"""JSON is so simple that we won't worry about a template at this point"""
return "{\"code\":\"%s\",\"href\":%s\"}\n" % (self.code(), self.href);

def to_xml(self):
"""Like JSON, XML is simple enough that we won't template now"""
msg = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
msg += "<urly code=\"%s\" href=\"%s\" />\n" % (self.code(), self.href)
return msg

def save_in_cache(self):
"""We don't really care if this fails"""
memcache.set(self.code(), self)

@staticmethod
def find_or_create_by_href(href):
query = db.Query(Urly)
query.filter('href =', href)
u = query.get()
if not u:
u = Urly(href=href)
u.put()
u.save_in_cache()
return u

@staticmethod
def code_to_id(code):
aid = 0L
for c in code:
aid *= Urly.BASE
aid += Urly.KEY_BASE.index(c)
return aid

@staticmethod
def find_by_code(code):
try:
u = memcache.get(code)
except:
# http://code.google.com/p/googleappengine/issues/detail?id=417
logging.error("Urly.find_by_code() memcached error")
u = None

if u is not None:
logging.info("Urly.find_by_code() cache HIT: %s", str(code))
return u

logging.info("Urly.find_by_code() cache MISS: %s", str(code))
aid = Urly.code_to_id(code)
try:
u = Urly.get_by_id(int(aid))
if u is not None:
u.save_in_cache()
return u
except db.BadValueError:
return None
47 changes: 47 additions & 0 deletions view.py
@@ -0,0 +1,47 @@
# Copyright 2008 Adam Stiles
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
# by applicable law or agreed to in writing, software distributed under the
# License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

import os
from google.appengine.ext.webapp import template

class MainView():
"""Helper method for our one-and-only template. All display goes through here"""
@staticmethod
def render(handler, status, urly, format, href=None):
"""Lovin my delphi-like inner functions"""
def render_raw(handler, content_type, body):
handler.response.headers["Content-Type"] = content_type
handler.response.out.write(body)

def render_main(handler, values=None):
path = os.path.join(os.path.dirname(__file__), 'main.html')
handler.response.out.write(template.render(path, values))

""" We never have an error if we have an urly to show """
if (urly is not None):
if (format is None):
handler.redirect(urly.href)
elif (format == '.json'):
render_raw(handler, "application/json", urly.to_json())
elif (format == '.xml'):
render_raw(handler, "application/xml", urly.to_xml())
else:
render_main(handler, { 'urly': urly })
elif (status == 400):
handler.error(status)
if (format != '.json') and (format != '.xml'):
vals = { 'error_href': True, 'default_href': href }
render_main(handler, vals)
elif (status == 404):
handler.error(404)
if (format != '.json') and (format != '.xml'):
vals = { 'error_404': True }
render_main(handler, vals)
else:
render_main(handler)

0 comments on commit 13b4f66

Please sign in to comment.