forked from ajstiles/urly
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
9 changed files
with
307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
application: urly2 | ||
version: 1 | ||
runtime: python | ||
api_version: 1 | ||
|
||
handlers: | ||
- url: /static | ||
static_dir: static | ||
|
||
- url: .* | ||
script: main.py | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> | ||
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} | ||
|
||
//----------------------------------------------------------------------------- | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |