Removing old, duplicate developer.android.com server configuration

- Added MOVED-README.txt to redirect future updaters

Change-Id: I42d557559690804015ac7721beb75a0279ae9756
This commit is contained in:
Joe Fernandez
2014-05-20 16:01:18 -07:00
parent d5ec144b8c
commit 2f5c985113
16 changed files with 4 additions and 3262 deletions

View File

@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@@ -0,0 +1,4 @@
The server configuration for developer.android.com documentation site
has moved to:
../../../vendor/google/docs/app-engine-server

View File

@@ -1,20 +0,0 @@
application: androidappdocs-staging
version: 2
runtime: python
api_version: 1
handlers:
- url: /remote_api
script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py
login: admin
- url: /gae_shell/static
static_dir: gae_shell/static
expiration: 1d
- url: /gae_shell/.*
script: /gae_shell/shell.py
login: admin
- url: .*
script: main.py

View File

@@ -1,16 +0,0 @@
application: androiddevdocs
version: 1
runtime: python
api_version: 1
handlers:
- url: /gae_shell/static
static_dir: gae_shell/static
expiration: 1d
- url: /gae_shell/.*
script: /gae_shell/shell.py
login: admin
- url: .*
script: main.py

View File

@@ -1,17 +0,0 @@
An interactive, stateful AJAX shell that runs Python code on the server.
Part of http://code.google.com/p/google-app-engine-samples/.
May be run as a standalone app or in an existing app as an admin-only handler.
Can be used for system administration tasks, as an interactive way to try out
APIs, or as a debugging aid during development.
The logging, os, sys, db, and users modules are imported automatically.
Interpreter state is stored in the datastore so that variables, function
definitions, and other values in the global and local namespaces can be used
across commands.
To use the shell in your app, copy shell.py, static/*, and templates/* into
your app's source directory. Then, copy the URL handlers from app.yaml into
your app.yaml.

View File

@@ -1,308 +0,0 @@
#!/usr/bin/python
#
# Copyright 2007 Google Inc.
#
# 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.
"""
An interactive, stateful AJAX shell that runs Python code on the server.
Part of http://code.google.com/p/google-app-engine-samples/.
May be run as a standalone app or in an existing app as an admin-only handler.
Can be used for system administration tasks, as an interactive way to try out
APIs, or as a debugging aid during development.
The logging, os, sys, db, and users modules are imported automatically.
Interpreter state is stored in the datastore so that variables, function
definitions, and other values in the global and local namespaces can be used
across commands.
To use the shell in your app, copy shell.py, static/*, and templates/* into
your app's source directory. Then, copy the URL handlers from app.yaml into
your app.yaml.
TODO: unit tests!
"""
import logging
import new
import os
import pickle
import sys
import traceback
import types
import wsgiref.handlers
from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
# Set to True if stack traces should be shown in the browser, etc.
_DEBUG = True
# The entity kind for shell sessions. Feel free to rename to suit your app.
_SESSION_KIND = '_Shell_Session'
# Types that can't be pickled.
UNPICKLABLE_TYPES = (
types.ModuleType,
types.TypeType,
types.ClassType,
types.FunctionType,
)
# Unpicklable statements to seed new sessions with.
INITIAL_UNPICKLABLES = [
'import logging',
'import os',
'import sys',
'from google.appengine.ext import db',
'from google.appengine.api import users',
]
class Session(db.Model):
"""A shell session. Stores the session's globals.
Each session globals is stored in one of two places:
If the global is picklable, it's stored in the parallel globals and
global_names list properties. (They're parallel lists to work around the
unfortunate fact that the datastore can't store dictionaries natively.)
If the global is not picklable (e.g. modules, classes, and functions), or if
it was created by the same statement that created an unpicklable global,
it's not stored directly. Instead, the statement is stored in the
unpicklables list property. On each request, before executing the current
statement, the unpicklable statements are evaluated to recreate the
unpicklable globals.
The unpicklable_names property stores all of the names of globals that were
added by unpicklable statements. When we pickle and store the globals after
executing a statement, we skip the ones in unpicklable_names.
Using Text instead of string is an optimization. We don't query on any of
these properties, so they don't need to be indexed.
"""
global_names = db.ListProperty(db.Text)
globals = db.ListProperty(db.Blob)
unpicklable_names = db.ListProperty(db.Text)
unpicklables = db.ListProperty(db.Text)
def set_global(self, name, value):
"""Adds a global, or updates it if it already exists.
Also removes the global from the list of unpicklable names.
Args:
name: the name of the global to remove
value: any picklable value
"""
blob = db.Blob(pickle.dumps(value))
if name in self.global_names:
index = self.global_names.index(name)
self.globals[index] = blob
else:
self.global_names.append(db.Text(name))
self.globals.append(blob)
self.remove_unpicklable_name(name)
def remove_global(self, name):
"""Removes a global, if it exists.
Args:
name: string, the name of the global to remove
"""
if name in self.global_names:
index = self.global_names.index(name)
del self.global_names[index]
del self.globals[index]
def globals_dict(self):
"""Returns a dictionary view of the globals.
"""
return dict((name, pickle.loads(val))
for name, val in zip(self.global_names, self.globals))
def add_unpicklable(self, statement, names):
"""Adds a statement and list of names to the unpicklables.
Also removes the names from the globals.
Args:
statement: string, the statement that created new unpicklable global(s).
names: list of strings; the names of the globals created by the statement.
"""
self.unpicklables.append(db.Text(statement))
for name in names:
self.remove_global(name)
if name not in self.unpicklable_names:
self.unpicklable_names.append(db.Text(name))
def remove_unpicklable_name(self, name):
"""Removes a name from the list of unpicklable names, if it exists.
Args:
name: string, the name of the unpicklable global to remove
"""
if name in self.unpicklable_names:
self.unpicklable_names.remove(name)
class FrontPageHandler(webapp.RequestHandler):
"""Creates a new session and renders the shell.html template.
"""
def get(self):
# set up the session. TODO: garbage collect old shell sessions
session_key = self.request.get('session')
if session_key:
session = Session.get(session_key)
else:
# create a new session
session = Session()
session.unpicklables = [db.Text(line) for line in INITIAL_UNPICKLABLES]
session_key = session.put()
template_file = os.path.join(os.path.dirname(__file__), 'templates',
'shell.html')
session_url = '/?session=%s' % session_key
vars = { 'server_software': os.environ['SERVER_SOFTWARE'],
'python_version': sys.version,
'session': str(session_key),
'user': users.get_current_user(),
'login_url': users.create_login_url(session_url),
'logout_url': users.create_logout_url(session_url),
}
rendered = webapp.template.render(template_file, vars, debug=_DEBUG)
self.response.out.write(rendered)
class StatementHandler(webapp.RequestHandler):
"""Evaluates a python statement in a given session and returns the result.
"""
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
# extract the statement to be run
statement = self.request.get('statement')
if not statement:
return
# the python compiler doesn't like network line endings
statement = statement.replace('\r\n', '\n')
# add a couple newlines at the end of the statement. this makes
# single-line expressions such as 'class Foo: pass' evaluate happily.
statement += '\n\n'
# log and compile the statement up front
try:
logging.info('Compiling and evaluating:\n%s' % statement)
compiled = compile(statement, '<string>', 'single')
except:
self.response.out.write(traceback.format_exc())
return
# create a dedicated module to be used as this statement's __main__
statement_module = new.module('__main__')
# use this request's __builtin__, since it changes on each request.
# this is needed for import statements, among other things.
import __builtin__
statement_module.__builtins__ = __builtin__
# load the session from the datastore
session = Session.get(self.request.get('session'))
# swap in our custom module for __main__. then unpickle the session
# globals, run the statement, and re-pickle the session globals, all
# inside it.
old_main = sys.modules.get('__main__')
try:
sys.modules['__main__'] = statement_module
statement_module.__name__ = '__main__'
# re-evaluate the unpicklables
for code in session.unpicklables:
exec code in statement_module.__dict__
# re-initialize the globals
for name, val in session.globals_dict().items():
try:
statement_module.__dict__[name] = val
except:
msg = 'Dropping %s since it could not be unpickled.\n' % name
self.response.out.write(msg)
logging.warning(msg + traceback.format_exc())
session.remove_global(name)
# run!
old_globals = dict(statement_module.__dict__)
try:
old_stdout = sys.stdout
old_stderr = sys.stderr
try:
sys.stdout = self.response.out
sys.stderr = self.response.out
exec compiled in statement_module.__dict__
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
except:
self.response.out.write(traceback.format_exc())
return
# extract the new globals that this statement added
new_globals = {}
for name, val in statement_module.__dict__.items():
if name not in old_globals or val != old_globals[name]:
new_globals[name] = val
if True in [isinstance(val, UNPICKLABLE_TYPES)
for val in new_globals.values()]:
# this statement added an unpicklable global. store the statement and
# the names of all of the globals it added in the unpicklables.
session.add_unpicklable(statement, new_globals.keys())
logging.debug('Storing this statement as an unpicklable.')
else:
# this statement didn't add any unpicklables. pickle and store the
# new globals back into the datastore.
for name, val in new_globals.items():
if not name.startswith('__'):
session.set_global(name, val)
finally:
sys.modules['__main__'] = old_main
session.put()
def main():
application = webapp.WSGIApplication(
[('/gae_shell/', FrontPageHandler),
('/gae_shell/shell.do', StatementHandler)], debug=_DEBUG)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == '__main__':
main()

View File

@@ -1,195 +0,0 @@
// Copyright 2007 Google Inc.
//
// 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.
/**
* @fileoverview
* Javascript code for the interactive AJAX shell.
*
* Part of http://code.google.com/p/google-app-engine-samples/.
*
* Includes a function (shell.runStatement) that sends the current python
* statement in the shell prompt text box to the server, and a callback
* (shell.done) that displays the results when the XmlHttpRequest returns.
*
* Also includes cross-browser code (shell.getXmlHttpRequest) to get an
* XmlHttpRequest.
*/
/**
* Shell namespace.
* @type {Object}
*/
var shell = {}
/**
* The shell history. history is an array of strings, ordered oldest to
* newest. historyCursor is the current history element that the user is on.
*
* The last history element is the statement that the user is currently
* typing. When a statement is run, it's frozen in the history, a new history
* element is added to the end of the array for the new statement, and
* historyCursor is updated to point to the new element.
*
* @type {Array}
*/
shell.history = [''];
/**
* See {shell.history}
* @type {number}
*/
shell.historyCursor = 0;
/**
* A constant for the XmlHttpRequest 'done' state.
* @type Number
*/
shell.DONE_STATE = 4;
/**
* A cross-browser function to get an XmlHttpRequest object.
*
* @return {XmlHttpRequest?} a new XmlHttpRequest
*/
shell.getXmlHttpRequest = function() {
if (window.XMLHttpRequest) {
return new XMLHttpRequest();
} else if (window.ActiveXObject) {
try {
return new ActiveXObject('Msxml2.XMLHTTP');
} catch(e) {
return new ActiveXObject('Microsoft.XMLHTTP');
}
}
return null;
};
/**
* This is the prompt textarea's onkeypress handler. Depending on the key that
* was pressed, it will run the statement, navigate the history, or update the
* current statement in the history.
*
* @param {Event} event the keypress event
* @return {Boolean} false to tell the browser not to submit the form.
*/
shell.onPromptKeyPress = function(event) {
var statement = document.getElementById('statement');
if (this.historyCursor == this.history.length - 1) {
// we're on the current statement. update it in the history before doing
// anything.
this.history[this.historyCursor] = statement.value;
}
// should we pull something from the history?
if (event.ctrlKey && event.keyCode == 38 /* up arrow */) {
if (this.historyCursor > 0) {
statement.value = this.history[--this.historyCursor];
}
return false;
} else if (event.ctrlKey && event.keyCode == 40 /* down arrow */) {
if (this.historyCursor < this.history.length - 1) {
statement.value = this.history[++this.historyCursor];
}
return false;
} else if (!event.altKey) {
// probably changing the statement. update it in the history.
this.historyCursor = this.history.length - 1;
this.history[this.historyCursor] = statement.value;
}
// should we submit?
var ctrlEnter = (document.getElementById('submit_key').value == 'ctrl-enter');
if (event.keyCode == 13 /* enter */ && !event.altKey && !event.shiftKey &&
event.ctrlKey == ctrlEnter) {
return this.runStatement();
}
};
/**
* The XmlHttpRequest callback. If the request succeeds, it adds the command
* and its resulting output to the shell history div.
*
* @param {XmlHttpRequest} req the XmlHttpRequest we used to send the current
* statement to the server
*/
shell.done = function(req) {
if (req.readyState == this.DONE_STATE) {
var statement = document.getElementById('statement')
statement.className = 'prompt';
// add the command to the shell output
var output = document.getElementById('output');
output.value += '\n>>> ' + statement.value;
statement.value = '';
// add a new history element
this.history.push('');
this.historyCursor = this.history.length - 1;
// add the command's result
var result = req.responseText.replace(/^\s*|\s*$/g, ''); // trim whitespace
if (result != '')
output.value += '\n' + result;
// scroll to the bottom
output.scrollTop = output.scrollHeight;
if (output.createTextRange) {
var range = output.createTextRange();
range.collapse(false);
range.select();
}
}
};
/**
* This is the form's onsubmit handler. It sends the python statement to the
* server, and registers shell.done() as the callback to run when it returns.
*
* @return {Boolean} false to tell the browser not to submit the form.
*/
shell.runStatement = function() {
var form = document.getElementById('form');
// build a XmlHttpRequest
var req = this.getXmlHttpRequest();
if (!req) {
document.getElementById('ajax-status').innerHTML =
"<span class='error'>Your browser doesn't support AJAX. :(</span>";
return false;
}
req.onreadystatechange = function() { shell.done(req); };
// build the query parameter string
var params = '';
for (i = 0; i < form.elements.length; i++) {
var elem = form.elements[i];
if (elem.type != 'submit' && elem.type != 'button' && elem.id != 'caret') {
var value = escape(elem.value).replace(/\+/g, '%2B'); // escape ignores +
params += '&' + elem.name + '=' + value;
}
}
// send the request and tell the user.
document.getElementById('statement').className = 'prompt processing';
req.open(form.method, form.action + '?' + params, true);
req.setRequestHeader('Content-type',
'application/x-www-form-urlencoded;charset=UTF-8');
req.send(null);
return false;
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,122 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title> Interactive Shell </title>
<script type="text/javascript" src="/gae_shell/static/shell.js"></script>
<style type="text/css">
body {
font-family: monospace;
font-size: 10pt;
}
p {
margin: 0.5em;
}
.prompt, #output {
width: 45em;
border: 1px solid silver;
background-color: #f5f5f5;
font-size: 10pt;
margin: 0.5em;
padding: 0.5em;
padding-right: 0em;
overflow-x: hidden;
}
#toolbar {
margin-left: 0.5em;
padding-left: 0.5em;
}
#caret {
width: 2.5em;
margin-right: 0px;
padding-right: 0px;
border-right: 0px;
}
#statement {
width: 43em;
margin-left: -1em;
padding-left: 0px;
border-left: 0px;
background-position: top right;
background-repeat: no-repeat;
}
.processing {
background-image: url("/gae_shell/static/spinner.gif");
}
#ajax-status {
font-weight: bold;
}
.message {
color: #8AD;
font-weight: bold;
font-style: italic;
}
.error {
color: #F44;
}
.username {
font-weight: bold;
}
</style>
</head>
<body>
<p> Interactive server-side Python shell for
<a href="http://code.google.com/appengine/">Google App Engine</a>.
(<a href="http://code.google.com/p/google-app-engine-samples/">source</a>)
</p>
<textarea id="output" rows="22" readonly="readonly">
{{ server_software }}
Python {{ python_version }}
</textarea>
<form id="form" action="shell.do" method="get">
<nobr>
<textarea class="prompt" id="caret" readonly="readonly" rows="4"
onfocus="document.getElementById('statement').focus()"
>&gt;&gt;&gt;</textarea>
<textarea class="prompt" name="statement" id="statement" rows="4"
onkeypress="return shell.onPromptKeyPress(event);"></textarea>
</nobr>
<input type="hidden" name="session" value="{{ session }}" />
<input type="submit" style="display: none" />
</form>
<p id="ajax-status"></p>
<p id="toolbar">
{% if user %}
<span class="username">{{ user.nickname }}</span>
(<a href="{{ logout_url }}">log out</a>)
{% else %}
<a href="{{ login_url }}">log in</a>
{% endif %}
| Ctrl-Up/Down for history |
<select id="submit_key">
<option value="enter">Enter</option>
<option value="ctrl-enter" selected="selected">Ctrl-Enter</option>
</select>
<label for="submit_key">submits</label>
</p>
<script type="text/javascript">
document.getElementById('statement').focus();
</script>
</body>
</html>

View File

@@ -1,12 +0,0 @@
indexes:
# 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.

View File

@@ -1,756 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2009 Google Inc.
#
# 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 class to serve pages from zip files and use memcache for performance.
This contains a class and a function to create an anonymous instance of the
class to serve HTTP GET requests. Memcache is used to increase response speed
and lower processing cycles used in serving. Credit to Guido van Rossum and
his implementation of zipserve which served as a reference as I wrote this.
MemcachedZipHandler: Class that serves request
create_handler: method to create instance of MemcachedZipHandler
"""
__author__ = 'jmatt@google.com (Justin Mattson)'
import email.Utils
import logging
import mimetypes
import re
import sys
import time
import yaml
import zipfile
from google.appengine.api import memcache
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from time import localtime, strftime
def create_handler(zip_files, max_age=None, public=None):
"""Factory method to create a MemcachedZipHandler instance.
Args:
zip_files: A list of file names, or a list of lists of file name, first
member of file mappings. See MemcachedZipHandler documentation for
more information about using the list of lists format
max_age: The maximum client-side cache lifetime
public: Whether this should be declared public in the client-side cache
Returns:
A MemcachedZipHandler wrapped in a pretty, anonymous bow for use with App
Engine
Raises:
ValueError: if the zip_files argument is not a list
"""
# verify argument integrity. If the argument is passed in list format,
# convert it to list of lists format
if zip_files and type(zip_files).__name__ == 'list':
num_items = len(zip_files)
while num_items > 0:
if type(zip_files[num_items - 1]).__name__ != 'list':
zip_files[num_items - 1] = [zip_files[num_items-1]]
num_items -= 1
else:
raise ValueError('File name arguments must be a list')
class HandlerWrapper(MemcachedZipHandler):
"""Simple wrapper for an instance of MemcachedZipHandler.
I'm still not sure why this is needed
"""
def get(self, name):
self.zipfilenames = zip_files
self.TrueGet(name)
if max_age is not None:
MAX_AGE = max_age
if public is not None:
PUBLIC = public
return HandlerWrapper
class MemcachedZipHandler(webapp.RequestHandler):
"""Handles get requests for a given URL.
Serves a GET request from a series of zip files. As files are served they are
put into memcache, which is much faster than retreiving them from the zip
source file again. It also uses considerably fewer CPU cycles.
"""
zipfile_cache = {} # class cache of source zip files
MAX_AGE = 43200 # max client-side cache lifetime, in seconds
PUBLIC = True # public cache setting
CACHE_PREFIX = 'cache://' # memcache key prefix for actual URLs
NEG_CACHE_PREFIX = 'noncache://' # memcache key prefix for non-existant URL
REDIRECT_PREFIX = 'redirect://' # memcache key prefix for redirect data
REDIRECT_FILE = 'redirects.yaml' # Name of file that contains redirect table
REDIRECT_SRC = 'src' # Name of the 'source' attribute for a
# redirect table entry
REDIRECT_DST = 'dst' # Name of the 'destination' attribute for
# a redirect table entry
REDIRECT_TYPE = 'type' # Name of the 'type' attribute for a
# redirect table entry
REDIRECT_TYPE_PERM = 'permanent' # Redirect 'type' string indicating a 301
# redirect should be served
REDIRECT_TYPE_TEMP = 'temporary' # Redirect 'type'string indicate a 302
# Redirect should be served
intlString = 'intl/'
validLangs = ['en', 'de', 'es', 'fr','it','ja','ko','ru','zh-CN','zh-cn','zh-TW','zh-tw']
def TrueGet(self, reqUri):
"""The top-level entry point to serving requests.
Called 'True' get because it does the work when called from the wrapper
class' get method. Some logic is applied to the request to serve files
from an intl/<lang>/... directory or fall through to the default language.
Args:
name: URL requested
Returns:
None
"""
langName = 'en'
resetLangCookie = False
urlLangName = None
retry = False
isValidIntl = False
isStripped = False
# Try to retrieve the user's lang pref from the cookie. If there is no
# lang pref cookie in the request, add set-cookie to the response with the
# default value of 'en'.
try:
langName = self.request.cookies['android_developer_pref_lang']
except KeyError:
resetLangCookie = True
#logging.info('==========================EXCEPTION: NO LANG COOKIE FOUND, USING [%s]', langName)
logging.info('==========================REQ INIT name [%s] langName [%s] resetLangCookie [%s]', reqUri, langName, resetLangCookie)
# Do some prep for handling intl requests. Parse the url and validate
# the intl/lang substring, extract the url lang code (urlLangName) and the
# the uri that follows the intl/lang substring(contentUri)
sections = reqUri.split("/", 2)
isIntl = len(sections) > 2 and (sections[0] == "intl")
if isIntl:
isValidIntl = sections[1] in self.validLangs
urlLangName = sections[1]
contentUri = sections[2]
logging.info(' Content URI is [%s]...', contentUri)
if isValidIntl:
if (langName != urlLangName) or (langName == 'en'):
# if the lang code in the request is different from that in
# the cookie, or if the target lang is en, strip the
# intl/nn substring. It will later be redirected to
# the user's preferred language url.
# logging.info(' Handling a MISMATCHED intl request')
reqUri = contentUri
isStripped = True
isValidIntl = False
isIntl = False
#logging.info('INTL PREP resetting langName to urlLangName [%s]', langName)
#else:
# logging.info('INTL PREP no need to reset langName')
else:
contentUri = reqUri
# Apply manual redirects from redirects.yaml. This occurs before any
# other mutations are performed, to avoid odd redirect behavior
# (For example, a user may want to redirect a directory without having
# /index.html appended.)
did_redirect = self.ProcessManualRedirects(contentUri, langName, isIntl)
if did_redirect:
return
# Preprocess the req url. If it references a directory or the domain itself,
# append '/index.html' to the url and 302 redirect. Otherwise, continue
# processing the request below.
did_redirect = self.PreprocessUrl(reqUri, langName)
if did_redirect:
return
# Send for processing
if self.isCleanUrl(reqUri, langName, isValidIntl, isStripped):
# handle a 'clean' request.
# Try to form a response using the actual request url.
# logging.info(' Request being handled as clean: [%s]', name)
if not self.CreateResponse(reqUri, langName, isValidIntl, resetLangCookie):
# If CreateResponse returns False, there was no such document
# in the intl/lang tree. Before going to 404, see if there is an
# English-language version of the doc in the default
# default tree and return it, else go to 404.
self.CreateResponse(contentUri, langName, False, resetLangCookie)
elif isIntl:
# handle the case where we need to pass through an invalid intl req
# for processing (so as to get 404 as appropriate). This is needed
# because intl urls are passed through clean and retried in English,
# if necessary.
# logging.info(' Handling an invalid intl request...')
self.CreateResponse(reqUri, langName, isValidIntl, resetLangCookie)
else:
# handle the case where we have a non-clean url (usually a non-intl
# url) that we need to interpret in the context of any lang pref
# that is set. Prepend an intl/lang string to the request url and
# send it as a 302 redirect. After the redirect, the subsequent
# request will be handled as a clean url.
self.RedirToIntl(reqUri, self.intlString, langName)
def ProcessManualRedirects(self, contentUri, langName, isIntl):
"""Compute any manual redirects for a request and execute them.
This allows content authors to manually define a set of regex rules which,
when matched, will cause an HTTP redirect to be performed.
Redirect rules are typically stored in a file named redirects.yaml. See the
comments in that file for more information about formatting.
Redirect computations are stored in memcache for performance.
Note that international URIs are handled automatically, and are assumed to
mirror redirects for non-intl requests.
Args:
contentUri: The relative URI (without leading slash) that was requested.
This should NOT contain an intl-prefix, if otherwise present.
langName: The requested language.
isIntl: True if contentUri originally contained an intl prefix.
Results:
boolean: True if a redirect has been set, False otherwise.
"""
# Redirect data is stored in memcache for performance
memcache_key = self.REDIRECT_PREFIX + contentUri
redirect_data = memcache.get(memcache_key)
if redirect_data is None:
logging.info('Redirect cache miss. Computing new redirect data.\n'
'Memcache Key: ' + memcache_key)
redirect_data = self.ComputeManualRedirectUrl(contentUri)
memcache.set(memcache_key, redirect_data)
contentUri = redirect_data[0]
redirectType = redirect_data[1]
# If this is an international URL, prepend intl path to minimize
# number of redirects
if isIntl:
contentUri = '/%s%s%s' % (self.intlString, langName, contentUri)
if redirectType is None:
# No redirect necessary
return False
elif redirectType == self.REDIRECT_TYPE_PERM:
logging.info('Sending permanent redirect: ' + contentUri);
self.redirect(contentUri, permanent=True)
return True
elif redirectType == self.REDIRECT_TYPE_TEMP:
logging.info('Sending temporary redirect: ' + contentUri);
self.redirect(contentUri, permanent=False)
return True
else:
# Invalid redirect type
logging.error('Invalid redirect type: %s', redirectType)
raise ('Invalid redirect type: %s', redirectType)
def ComputeManualRedirectUrl(self, uri):
"""Read redirects file and evaluate redirect rules for a given URI.
Args:
uri: The relative URI (without leading slash) for which redirect data
should be computed. No special handling of intl URIs is pefromed
at this level.
Returns:
tuple: The computed redirect data. This tuple has two parts:
redirect_uri: The new URI that should be used. (If no redirect rule is
found, the original input to 'uri' will be returned.
redirect_type: Either 'permanent' for an HTTP 301 redirect, 'temporary'
for an HTTP 302 redirect, or None if no redirect should be performed.
"""
# Redircts are defined in a file named redirects.yaml.
try:
f = open(self.REDIRECT_FILE)
data = yaml.load(f)
f.close()
except IOError, e:
logging.warning('Error opening redirect file (' + self.REDIRECT_FILE +
'): ' + e.strerror)
return (uri, None)
# The incoming path is missing a leading slash. However, many parts of the
# redirect system require leading slashes to distinguish between relative
# and absolute redirects. So, to compensate for this, we'll add a leading
# slash here as well.
uri = '/' + uri
# Check to make sure we actually got an iterable list out of the YAML file
if data is None:
logging.warning('Redirect file (' + self.REDIRECT_FILE + ') not valid '
'YAML.')
elif 'redirects' not in data:
logging.warning('Redirect file (' + self.REDIRECT_FILE + ') not '
'properly formatted -- no \'redirects:\' header.')
elif hasattr(data['redirects'], '__iter__'):
# Iterate through redirect data, try to find a redirect that matches.
for redirect in data['redirects']:
# Note: re.search adds an implied '^' to the beginning of the regex
# This means that the regex must match from the beginning of the
# string.
try:
if re.match(redirect[self.REDIRECT_SRC], uri):
# Match found. Apply redirect rule.
redirect_uri = re.sub('^' + redirect[self.REDIRECT_SRC],
redirect[self.REDIRECT_DST], uri)
logging.info('Redirect rule matched.\n'
'Rule: %s\n'
'Src: %s\n'
'Dst: %s',
redirect[self.REDIRECT_SRC], uri, redirect_uri)
if self.REDIRECT_TYPE in redirect:
redirect_type = redirect[self.REDIRECT_TYPE]
else:
# Default redirect type, if unspecified
redirect_type = self.REDIRECT_TYPE_PERM
return (redirect_uri, redirect_type)
except:
e = sys.exc_info()[1]
raise ('Error while processing redirect rule.\n'
'Rule: %s\n'
'Error: %s' % (redirect[self.REDIRECT_SRC], e))
# No redirect found, return URL unchanged
return (uri, None)
def isCleanUrl(self, name, langName, isValidIntl, isStripped):
"""Determine whether to pass an incoming url straight to processing.
Args:
name: The incoming URL
Returns:
boolean: Whether the URL should be sent straight to processing
"""
# logging.info(' >>>> isCleanUrl name [%s] langName [%s] isValidIntl [%s]', name, langName, isValidIntl)
if (langName == 'en' and not isStripped) or isValidIntl or not ('.html' in name) or (not isValidIntl and not langName):
return True
def PreprocessUrl(self, name, langName):
"""Any preprocessing work on the URL when it comes in.
Put any work related to interpreting the incoming URL here. For example,
this is used to redirect requests for a directory to the index.html file
in that directory. Subclasses should override this method to do different
preprocessing.
Args:
name: The incoming URL
Returns:
True if the request was redirected to '/index.html'.
Otherewise False.
"""
# determine if this is a request for a directory
final_path_segment = name
final_slash_offset = name.rfind('/')
if final_slash_offset != len(name) - 1:
final_path_segment = name[final_slash_offset + 1:]
if final_path_segment.find('.') == -1:
name = ''.join([name, '/'])
# if this is a directory or the domain itself, redirect to /index.html
if not name or (name[len(name) - 1:] == '/'):
uri = ''.join(['/', name, 'index.html'])
# logging.info('--->PREPROCESSING REDIRECT [%s] to [%s] with langName [%s]', name, uri, langName)
self.redirect(uri, False)
return True
else:
return False
def RedirToIntl(self, name, intlString, langName):
"""Redirect an incoming request to the appropriate intl uri.
For non-en langName, builds the intl/lang string from a
base (en) string and redirects (302) the request to look for
a version of the file in langName. For en langName, simply
redirects a stripped uri string (intl/nn removed).
Args:
name: The incoming, preprocessed URL
Returns:
The lang-specific URL
"""
if not (langName == 'en'):
builtIntlLangUri = ''.join([intlString, langName, '/', name, '?', self.request.query_string])
else:
builtIntlLangUri = name
uri = ''.join(['/', builtIntlLangUri])
logging.info('-->REDIRECTING %s to %s', name, uri)
self.redirect(uri, False)
return uri
def CreateResponse(self, name, langName, isValidIntl, resetLangCookie):
"""Process the url and form a response, if appropriate.
Attempts to retrieve the requested file (name) from cache,
negative cache, or store (zip) and form the response.
For intl requests that are not found (in the localized tree),
returns False rather than forming a response, so that
the request can be retried with the base url (this is the
fallthrough to default language).
For requests that are found, forms the headers and
adds the content to the response entity. If the request was
for an intl (localized) url, also resets the language cookie
to the language specified in the url if needed, to ensure that
the client language and response data remain harmonious.
Args:
name: The incoming, preprocessed URL
langName: The language id. Used as necessary to reset the
language cookie in the response.
isValidIntl: If present, indicates whether the request is
for a language-specific url
resetLangCookie: Whether the response should reset the
language cookie to 'langName'
Returns:
True: A response was successfully created for the request
False: No response was created.
"""
# see if we have the page in the memcache
logging.info('PROCESSING %s langName [%s] isValidIntl [%s] resetLang [%s]',
name, langName, isValidIntl, resetLangCookie)
resp_data = self.GetFromCache(name)
if resp_data is None:
logging.info(' Cache miss for %s', name)
resp_data = self.GetFromNegativeCache(name)
if resp_data is None:
resp_data = self.GetFromStore(name)
# IF we have the file, put it in the memcache
# ELSE put it in the negative cache
if resp_data is not None:
self.StoreOrUpdateInCache(name, resp_data)
elif isValidIntl:
# couldn't find the intl doc. Try to fall through to English.
#logging.info(' Retrying with base uri...')
return False
else:
logging.info(' Adding %s to negative cache, serving 404', name)
self.StoreInNegativeCache(name)
self.Write404Error()
return True
else:
# found it in negative cache
self.Write404Error()
return True
# found content from cache or store
logging.info('FOUND CLEAN')
if resetLangCookie:
logging.info(' Resetting android_developer_pref_lang cookie to [%s]',
langName)
expireDate = time.mktime(localtime()) + 60 * 60 * 24 * 365 * 10
self.response.headers.add_header('Set-Cookie',
'android_developer_pref_lang=%s; path=/; expires=%s' %
(langName, strftime("%a, %d %b %Y %H:%M:%S", localtime(expireDate))))
mustRevalidate = False
if ('.html' in name):
# revalidate html files -- workaround for cache inconsistencies for
# negotiated responses
mustRevalidate = True
#logging.info(' Adding [Vary: Cookie] to response...')
self.response.headers.add_header('Vary', 'Cookie')
content_type, encoding = mimetypes.guess_type(name)
if content_type:
self.response.headers['Content-Type'] = content_type
self.SetCachingHeaders(mustRevalidate)
self.response.out.write(resp_data)
elif (name == 'favicon.ico'):
self.response.headers['Content-Type'] = 'image/x-icon'
self.SetCachingHeaders(mustRevalidate)
self.response.out.write(resp_data)
elif name.endswith('.psd'):
self.response.headers['Content-Type'] = 'application/octet-stream'
self.SetCachingHeaders(mustRevalidate)
self.response.out.write(resp_data)
elif name.endswith('.svg'):
self.response.headers['Content-Type'] = 'image/svg+xml'
self.SetCachingHeaders(mustRevalidate)
self.response.out.write(resp_data)
elif name.endswith('.mp4'):
self.response.headers['Content-Type'] = 'video/mp4'
self.SetCachingHeaders(mustRevalidate)
self.response.out.write(resp_data)
elif name.endswith('.webm'):
self.response.headers['Content-Type'] = 'video/webm'
self.SetCachingHeaders(mustRevalidate)
self.response.out.write(resp_data)
elif name.endswith('.ogv'):
self.response.headers['Content-Type'] = 'video/ogg'
self.SetCachingHeaders(mustRevalidate)
self.response.out.write(resp_data)
return True
def GetFromStore(self, file_path):
"""Retrieve file from zip files.
Get the file from the source, it must not have been in the memcache. If
possible, we'll use the zip file index to quickly locate where the file
should be found. (See MapToFileArchive documentation for assumptions about
file ordering.) If we don't have an index or don't find the file where the
index says we should, look through all the zip files to find it.
Args:
file_path: the file that we're looking for
Returns:
The contents of the requested file
"""
resp_data = None
file_itr = iter(self.zipfilenames)
# decode any escape characters in the URI
# Note: We are currenty just looking for '@' (%40)
file_path = file_path.replace('%40', '@')
# check the index, if we have one, to see what archive the file is in
archive_name = self.MapFileToArchive(file_path)
if not archive_name:
archive_name = file_itr.next()[0]
while resp_data is None and archive_name:
zip_archive = self.LoadZipFile(archive_name)
if zip_archive:
# we expect some lookups will fail, and that's okay, 404s will deal
# with that
try:
resp_data = zip_archive.read(file_path)
except (KeyError, RuntimeError), err:
# no op
x = False
if resp_data is not None:
logging.info('%s read from %s', file_path, archive_name)
try:
archive_name = file_itr.next()[0]
except (StopIteration), err:
archive_name = False
return resp_data
def LoadZipFile(self, zipfilename):
"""Convenience method to load zip file.
Just a convenience method to load the zip file from the data store. This is
useful if we ever want to change data stores and also as a means of
dependency injection for testing. This method will look at our file cache
first, and then load and cache the file if there's a cache miss
Args:
zipfilename: the name of the zip file to load
Returns:
The zip file requested, or None if there is an I/O error
"""
zip_archive = None
zip_archive = self.zipfile_cache.get(zipfilename)
if zip_archive is None:
try:
zip_archive = zipfile.ZipFile(zipfilename)
self.zipfile_cache[zipfilename] = zip_archive
except (IOError, RuntimeError), err:
logging.error('Can\'t open zipfile %s, cause: %s' % (zipfilename,
err))
return zip_archive
def MapFileToArchive(self, file_path):
"""Given a file name, determine what archive it should be in.
This method makes two critical assumptions.
(1) The zip files passed as an argument to the handler, if concatenated
in that same order, would result in a total ordering
of all the files. See (2) for ordering type.
(2) Upper case letters before lower case letters. The traversal of a
directory tree is depth first. A parent directory's files are added
before the files of any child directories
Args:
file_path: the file to be mapped to an archive
Returns:
The name of the archive where we expect the file to be
"""
num_archives = len(self.zipfilenames)
while num_archives > 0:
target = self.zipfilenames[num_archives - 1]
if len(target) > 1:
if self.CompareFilenames(target[1], file_path) >= 0:
return target[0]
num_archives -= 1
return None
def CompareFilenames(self, file1, file2):
"""Determines whether file1 is lexigraphically 'before' file2.
WARNING: This method assumes that paths are output in a depth-first,
with parent directories' files stored before childs'
We say that file1 is lexigraphically before file2 if the last non-matching
path segment of file1 is alphabetically before file2.
Args:
file1: the first file path
file2: the second file path
Returns:
A positive number if file1 is before file2
A negative number if file2 is before file1
0 if filenames are the same
"""
f1_segments = file1.split('/')
f2_segments = file2.split('/')
segment_ptr = 0
while (segment_ptr < len(f1_segments) and
segment_ptr < len(f2_segments) and
f1_segments[segment_ptr] == f2_segments[segment_ptr]):
segment_ptr += 1
if len(f1_segments) == len(f2_segments):
# we fell off the end, the paths much be the same
if segment_ptr == len(f1_segments):
return 0
# we didn't fall of the end, compare the segments where they differ
if f1_segments[segment_ptr] < f2_segments[segment_ptr]:
return 1
elif f1_segments[segment_ptr] > f2_segments[segment_ptr]:
return -1
else:
return 0
# the number of segments differs, we either mismatched comparing
# directories, or comparing a file to a directory
else:
# IF we were looking at the last segment of one of the paths,
# the one with fewer segments is first because files come before
# directories
# ELSE we just need to compare directory names
if (segment_ptr + 1 == len(f1_segments) or
segment_ptr + 1 == len(f2_segments)):
return len(f2_segments) - len(f1_segments)
else:
if f1_segments[segment_ptr] < f2_segments[segment_ptr]:
return 1
elif f1_segments[segment_ptr] > f2_segments[segment_ptr]:
return -1
else:
return 0
def SetCachingHeaders(self, revalidate):
"""Set caching headers for the request."""
max_age = self.MAX_AGE
#self.response.headers['Expires'] = email.Utils.formatdate(
# time.time() + max_age, usegmt=True)
cache_control = []
if self.PUBLIC:
cache_control.append('public')
cache_control.append('max-age=%d' % max_age)
if revalidate:
cache_control.append('must-revalidate')
self.response.headers['Cache-Control'] = ', '.join(cache_control)
def GetFromCache(self, filename):
"""Get file from memcache, if available.
Args:
filename: The URL of the file to return
Returns:
The content of the file
"""
return memcache.get('%s%s' % (self.CACHE_PREFIX, filename))
def StoreOrUpdateInCache(self, filename, data):
"""Store data in the cache.
Store a piece of data in the memcache. Memcache has a maximum item size of
1*10^6 bytes. If the data is too large, fail, but log the failure. Future
work will consider compressing the data before storing or chunking it
Args:
filename: the name of the file to store
data: the data of the file
Returns:
None
"""
try:
if not memcache.add('%s%s' % (self.CACHE_PREFIX, filename), data):
memcache.replace('%s%s' % (self.CACHE_PREFIX, filename), data)
except (ValueError), err:
logging.warning('Data size too large to cache\n%s' % err)
def Write404Error(self):
"""Ouptut a simple 404 response."""
self.error(404)
self.response.out.write(
''.join(['<html><head><title>404: Not Found</title></head>',
'<body><b><h2>Error 404</h2><br/>',
'File not found</b></body></html>']))
def StoreInNegativeCache(self, filename):
"""If a non-existant URL is accessed, cache this result as well.
Future work should consider setting a maximum negative cache size to
prevent it from from negatively impacting the real cache.
Args:
filename: URL to add ot negative cache
Returns:
None
"""
memcache.add('%s%s' % (self.NEG_CACHE_PREFIX, filename), -1)
def GetFromNegativeCache(self, filename):
"""Retrieve from negative cache.
Args:
filename: URL to retreive
Returns:
The file contents if present in the negative cache.
"""
return memcache.get('%s%s' % (self.NEG_CACHE_PREFIX, filename))
def main():
application = webapp.WSGIApplication([('/([^/]+)/(.*)',
MemcachedZipHandler)])
util.run_wsgi_app(application)
if __name__ == '__main__':
main()

View File

@@ -1,700 +0,0 @@
# Redirect file.
# This file contains the list of rewrite rules that are applied when serving
# pages.
#
# Each redirect has four parts:
#
# - src: The path to redirect. This is a regex rule prefixed with an implied
# '^'. Unless you're doing something advanced, your path should start with
# '/' character.
#
# - dst: The path to redirect to. If the path begins with a slash,
# it is considered a relative redirect. Otherwise, it is an absolute
# redirct (and should probably begin with http: or http://). You may use
# capturing groups to preserve part of the source path. To referece a
# capturing group, use \N, where N is the (1-based) index of desired group.
#
# - type: Either 'permanent' or 'temporary', depending on whether you want an
# HTTP 301 or HTTP 302 redirect, respectiviely. See RFC 2616 for the
# difference between these:
#
# http://tools.ietf.org/html/rfc2616
#
# If you don't specify a type, 'permanent' will be used by default. Note that
# this is different from the Apache convention (which uses 'temporary' by
# default.)
#
# - comment: Currently ignored by the computer, but useful for humans.
#
# Example:
#
# redirects:
# - src: /foo
# dst: /bar
# # Redirect /foo to /bar. This will also redirect foo/ and
# # foo/test.html. Note that the redirect type is optional. This will be
# # treated as a permanent redirect.
#
# - src: /(.+droid(/.*)?)$
# dst: /droids/\1
# type: permanent
# # Redirect /android to /droids/android and /bugdroid to
# # /droids/bugdroid. However, it will not redirect /droid or
# # /bugdroids.
#
# - src: /google
# dst: http://www.google.com
# type: temporary
# # This is an example of a redirect to an absolute URI.
#
#
# WATCH OUT -- SRC LINE HAS TO START WITH A HYPHEN !!
redirects:
# new one works
- src: /sdk/android-
dst: /about/versions/android-
type: permanent
comment: Redirect sdk reference to new location
- src: /about/versions/index.html
dst: /about/index.html
type: permanent
- src: /about/versions/api-levels.html
dst: /guide/topics/manifest/uses-sdk-element.html#ApiLevels
type: permanent
# new one works
- src: /sdk/oem-usb.html
dst: /tools/extras/oem-usb.html
type: permanent
comment: Redirect sdk reference to new location
- src: /sdk/installing.html
dst: /sdk/installing/index.html
type: permanent
comment: Redirect sdk reference to new location
- src: /sdk/compatibility-library.html
dst: /tools/support-library/index.html
type: permanent
- src: /tools/extras/support-library.html
dst: /tools/support-library/index.html
type: permanent
- src: /training/basics/fragments/support-lib.html
dst: /tools/support-library/setup.html
type: permanent
- src: /training/id-auth/.*
dst: /google/play-services/auth.html
type: permanent
# new one works
- src: /sdk/eclipse-adt.html
dst: /tools/sdk/eclipse-adt.html
type: permanent
comment: Redirect sdk reference to new location
# new one works
- src: /sdk/tools-notes.html
dst: /tools/sdk/tools-notes.html
type: permanent
comment: Redirect sdk reference to new location
# new one works
- src: /sdk/adding-components.html
dst: /sdk/exploring.html
type: permanent
comment: Redirect sdk reference to new location
- src: /sdk/ndk/overview.html
dst: /tools/sdk/ndk/index.html
type: permanent
- src: /sdk/ndk/
dst: /tools/sdk/ndk/
type: permanent
comment: Redirect sdk reference to new location
- src: /tools/sdk/win-usb.html
dst: /sdk/win-usb.html
type: permanent
- src: /tools/sdk/index.html
dst: /sdk/index.html
type: permanent
- src: /tools/sdk/installing.html
dst: /sdk/installing/bundle.html
type: permanent
#new one works
- src: /sdk/requirements.html
dst: /sdk/index.html
type: permanent
comment: Redirect sdk reference to new location
- src: /sdk/installing/next.html
dst: /training/basics/firstapp/index.html
type: permanent
comment: Next steps doc was lame and everybody should go to first class
#- src: /sdk/(?!index.html|installing/|exploring)
# dst: /tools/sdk/
# type: permanent
# comment: Redirect sdk reference to new location
#- src: /sdk/compatibility
# dst: /tools/sdk/support-package.html
# type: permanent
# comment: Redirect to new location
# new one
- src: /guide/market/
dst: /google/play/
type: permanent
comment: redirect billing to new loc
- src: /guide/google/gcm/client-javadoc/.*
dst: /reference/com/google/android/gcm/package-summary.html
type: permanent
comment: redirect to new loc
- src: /guide/google/gcm/server-javadoc/.*
dst: /reference/com/google/android/gcm/server/package-summary.html
type: permanent
comment: redirect to new loc
- src: /guide/google/play/services.html
dst: /google/play-services/index.html
type: permanent
comment: redirect to new loc
- src: /guide/google/
dst: /google/
type: permanent
comment: redirect to new loc
- src: /guide/publishing/licensing.html
dst: /google/play/licensing/index.html
type: permanent
comment: Redirect Licensing docs to new location
# new one
- src: /google/play/billing/billing_about.html
dst: /google/play/billing/index.html
type: permanent
comment: Redirect Licensing docs to new location
- src: /guide/developing/tools/
dst: /tools/help/
type: permanent
comment: Redirect to new location
- src: /guide/developing/
dst: /tools/
type: permanent
comment: Redirect to new location
- src: /tools/aidl.html
dst: /guide/components/aidl.html
type: permanent
- src: /guide/market/publishing/multiple-apks.html
dst: /google/play/publishing/multiple-apks.html
type: permanent
comment: Redirect to new location
- src: /guide/publishing/publishing.html
dst: /distribute/googleplay/publish/preparing.html
type: permanent
comment: Redirect to new location
- src: /guide/publishing/
dst: /tools/publishing/
type: permanent
comment: Redirect to new location
- src: /guide/topics/fundamentals.html
dst: /guide/components/fundamentals.html
type: permanent
- src: /guide/topics/intents/intents-filters.html
dst: /guide/components/intents-filters.html
type: permanent
- src: /guide/topics/fundamentals/
dst: /guide/components/
type: permanent
comment: Redirect to new location
- src: /guide/topics/clipboard/copy-paste.html
dst: /guide/topics/text/copy-paste.html
type: permanent
comment: Redirect to new location
- src: /guide/topics/ui/notifiers/index.html
dst: /guide/topics/ui/notifiers/notifications.html
type: permanent
comment: Flatten side nav to make Notifications and Toasts separate
# new one
- src: /guide/topics/wireless/
dst: /guide/topics/connectivity/
type: permanent
comment: Redirect to new location
# new one
- src: /guide/topics/drawing/.*
dst: /guide/topics/graphics/opengl.html
type: permanent
comment: Redirect to new location
- src: /guide/topics/connectivity/usb/adk.html
dst: /tools/adk/index.html
type: permanent
- src: /tools/workflow/publishing/versioning.html
dst: /tools/publishing/versioning.html
type: permanent
- src: /tools/workflow/publishing/publishing.html
dst: /tools/publishing/publishing_overview.html
type: permanent
- src: /tools/workflow/publishing_overview.html
dst: /tools/publishing/publishing_overview.html
type: permanent
- src: /tools/workflow/publishing/publishing_overview.html
dst: /tools/publishing/publishing_overview.html
type: permanent
- src: /tools/workflow/app-signing.html
dst: /tools/publishing/app-signing.html
type: permanent
- src: /tools/adk/aoa.html
dst: http://source.android.com/tech/accessories/aoap/aoa.html
type: permanent
comment: Open Accessory Protocol content has moved to source.android.com.
- src: /tools/adk/aoa2.html
dst: http://source.android.com/tech/accessories/aoap/aoa2.html
type: permanent
comment: Open Accessory Protocol content has moved to source.android.com.
- src: /guide/topics/usb
dst: /guide/topics/connectivity/usb
type: permanent
comment: Redirect to new location
# new one
- src: /guide/appendix/api-levels.html
dst: /guide/topics/manifest/uses-sdk-element.html#ApiLevels
type: permanent
comment: Redirect to new location
- src: /guide/appendix/install-location.html
dst: /guide/topics/data/install-location.html
type: permanent
- src: /guide/appendix/g-app-intents.html
dst: /guide/components/intents-common.html
type: permanent
comment: Redirect to new location
# new one
- src: /guide/basics/.*
dst: /about/index.html
type: permanent
comment: Redirect to new location
- src: /guide/topics/security/security.html
dst: /training/articles/security-tips.html
type: permanent
comment: Redirect to new location
- src: /guide/topics/security/index.html
dst: /training/articles/security-tips.html
type: permanent
comment: Redirect to new location
# new one
- src: /guide/appendix/market-filters.html
dst: /google/play/filters.html
type: permanent
comment: Redirect to new location
- src: /guide/topics/testing/
dst: /tools/testing/
type: permanent
- src: /guide/topics/graphics/animation.html
dst: /guide/topics/graphics/overview.html
type: permanent
comment: Redirect to new location
- src: /guide/topics/graphics/renderscript/(compute.html|index.html|reference.html)
dst: /guide/topics/renderscript/index.html
type: permanent
comment: Redirect to new location
- src: /guide/topics/graphics/renderscript.html
dst: /guide/topics/renderscript/index.html
type: permanent
comment: Redirect to new location
- src: /guide/topics/location/obtaining-user-location.html
dst: /guide/topics/location/strategies.html
type: permanent
comment: Redirect to new location
# new one
- src: /guide/topics/nfc/
dst: /guide/topics/connectivity/nfc/
type: permanent
comment: Redirect to new location
# new one
- src: /guide/topics/wireless/
dst: /guide/topics/connectivity/
type: permanent
comment: Redirect to new location
# new one
- src: /guide/topics/network/
dst: /guide/topics/connectivity/
type: permanent
comment: Redirect to new location
# new one
- src: /resources/articles/creating-input-method.html
dst: /guide/topics/text/creating-input-method.html
type: permanent
# new one
- src: /resources/articles/spell-checker-framework.html
dst: /guide/topics/text/spell-checker-framework.html
type: permanent
# new one
- src: /resources/tutorials/notepad/
dst: /training/notepad/
type: permanent
comment: this is only for external links, until we update this documentation
# new one
- src: /resources/faq/
dst: /guide/faq/
type: permanent
comment: FAQ still needs a permanent home
# new one
- src: /resources/tutorials/hello-world.html
dst: /training/basics/firstapp/index.html
type: permanent
comment: Redirect to new location
# add the rest of the tutorials here
- src: /guide/practices/design/
dst: /guide/practices/
type: permanent
comment: Redirect to new location
- src: /guide/practices/accessibility.html
dst: /guide/topics/ui/accessibility/index.html
type: permanent
# move best practices to training
- src: /guide/practices/app-design/performance.html
dst: /training/articles/perf-tips.html
type: permanent
- src: /guide/practices/performance.html
dst: /training/articles/perf-tips.html
type: permanent
- src: /guide/practices/app-design/responsiveness.html
dst: /training/articles/perf-anr.html
type: permanent
- src: /guide/practices/responsiveness.html
dst: /training/articles/perf-anr.html
type: permanent
- src: /guide/practices/security.html
dst: /training/articles/security-tips.html
type: permanent
- src: /guide/practices/jni.html
dst: /training/articles/perf-jni.html
type: permanent
# move ui docs to design
- src: /guide/practices/ui_guidelines/index.html
dst: /design/index.html
type: permanent
- src: /guide/practices/ui_guidelines/icon_design.*
dst: /design/style/iconography.html
type: permanent
- src: /guide/practices/ui_guidelines/activity_task_design.html
dst: /design/patterns/app-structure.html
type: permanent
- src: /guide/practices/ui_guidelines/menu_design.html
dst: /design/patterns/actionbar.html
type: permanent
# new one
- src: /resources/dashboard/.*
dst: /about/dashboards/index.html
type: permanent
comment: Redirect to new location
- src: /resources/community-groups.html
dst: /support.html
type: permanent
comment: Redirect to new location
- src: /guide/tutorials/
dst: /resources/tutorials/
type: permanent
- src: /resources/tutorials/views/hello-linearlayout.html
dst: /guide/topics/ui/layout/linear.html
type: permanent
comment: Redirect to new location
- src: /resources/tutorials/views/hello-relativelayout.html
dst: /guide/topics/ui/layout/relative.html
type: permanent
comment: Redirect to new location
- src: /resources/tutorials/views/hello-listview.html
dst: /guide/topics/ui/layout/listview.html
type: permanent
comment: Redirect to new location
- src: /resources/tutorials/views/hello-gridview.html
dst: /guide/topics/ui/layout/gridview.html
type: permanent
comment: Redirect to new location
- src: /guide/webapps/overview.html
dst: /guide/webapps/index.html
type: permanent
- src: /resources/tutorials/views/hello-webview.html
dst: /guide/webapps/webview.html
type: permanent
comment: Redirect to new location
- src: /resources/tutorials/views/hello-formstuff.html
dst: /guide/topics/ui/controls.html
type: permanent
comment: Redirect to new location
- src: /resources/tutorials/views/hello-datepicker.html
dst: /guide/topics/ui/controls/pickers.html
type: permanent
comment: Redirect to new location
- src: /resources/tutorials/views/hello-timepicker.html
dst: /guide/topics/ui/controls/pickers.html
type: permanent
comment: Redirect to new location
- src: /resources/tutorials/views/hello-autocomplete.html
dst: /guide/topics/ui/controls/text.html
type: permanent
comment: Redirect to new location
- src: /resources/tutorials/views/hello-spinner.html
dst: /guide/topics/ui/controls/spinner.html
type: permanent
comment: Redirect to new location
- src: /resources/tutorials/opengl/opengl-es10.html
dst: /training/graphics/opengl/index.html
type: permanent
- src: /resources/tutorials/opengl/opengl-es20.html
dst: /training/graphics/opengl/index.html
type: permanent
- src: /resources/tutorials/views/hello-mapview.html
dst: https://developers.google.com/maps/documentation/android/hello-mapview
type: permanent
- src: /resources/tutorials/views/.*
dst: /guide/topics/ui/declaring-layout.html#CommonLayouts
type: permanent
- src: /guide/topics/ui/layout-objects.html
dst: /guide/topics/ui/declaring-layout.html#CommonLayouts
type: permanent
- src: /resources/tutorials/localization/.*
dst: /training/basics/supporting-devices/languages.html
type: permanent
- src: /resources/samples/.*
dst: /samples/index.html
type: permanent
- src: /tools/samples/index.html
dst: /samples/index.html
type: permanent
comment: Redirect to new location
- src: /resources/(?!articles)
dst: /training/
type: permanent
comment: Redirect to new location
- src: /guide/publishing/publishing.html#BuildaButton
dst: /distribute/googleplay/promote/badges.html
type: permanent
comment: Redirect to new location
# ------------------- TRAINING -------------------
- src: /guide/topics/ui/layout/tabs.html
dst: /training/implementing-navigation/lateral.html
type: permanent
- src: /training/cloudsync/aesync.html
dst: /google/gcm/index.html
type: permanent
comment: Syncing with App Engine was removed because it's obsolete.
- src: /training/basics/location/
dst: /training/location/
type: permanent
# -------------------- MISC ----------------------
- src: /shareables/
dst: http://commondatastorage.googleapis.com/androiddevelopers/shareables/
type: permanent
comment: Redirect to new location
- src: /downloads/
dst: http://commondatastorage.googleapis.com/androiddevelopers/
type: permanent
comment: Redirect to new location
- src: /search.html
dst: /index.html
type: permanent
comment: Redirect to new location
- src: /videos/index.html
dst: /develop/index.html
type: permanent
comment: Redirect to new location
- src: /live/index.html
dst: https://developers.google.com/live/
type: permanent
comment: Redirect to new location
- src: /intl/zh-CN/...
dst: /intl/zh-cn/...
type: permanent
- src: /intl/zh-TW/...
dst: /intl/zh-tw/...
type: permanent
# -------------------- EASTER EGG REDIRECTS ----------------------
# ---------- PLATFORM VERSIONS ----------------
- src: /4.2
dst: /about/versions/android-4.2.html
type: permanent
- src: /4.1
dst: /about/versions/android-4.1.html
type: permanent
- src: /4.0
dst: /about/versions/android-4.0.html
type: permanent
- src: /(k|kk|kitkat)/?$
dst: /about/versions/kitkat.html
type: permanent
- src: /(j|jb|jellybean)/?$
dst: /about/versions/jelly-bean.html
type: permanent
- src: /(i|ics|icecreamsandwich)/?$
dst: /about/versions/android-4.0-highlights.html
type: permanent
- src: /(h|hc|honeycomb)/?$
dst: /about/versions/android-3.0-highlights.html
type: permanent
- src: /(g|gb|gingerbread)/?$
dst: /about/versions/android-2.3-highlights.html
type: permanent
# ---------- MISC -----------------
- src: /%2B/?$
dst: https://plus.google.com/108967384991768947849/posts
type: permanent
comment: Redirect /+ and /+/ to Google+
- src: /blog
dst: http://android-developers.blogspot.com/
type: permanent
- src: /stats
dst: /about/dashboards/index.html
type: permanent
- src: /youtube
dst: http://www.youtube.com/user/androiddevelopers
type: permanent
- src: /playbadge/?$
dst: http://developer.android.com/distribute/googleplay/promote/badges.html
type: permanent
- src: /deviceart/?$
dst: http://developer.android.com/distribute/promote/device-art.html
type: permanent
- src: /edu/signup/?$
dst: https://services.google.com/fb/forms/playedu
type: permanent
- src: /edu/?$
dst: /distribute/googleplay/edu/index.html
type: permanent
- src: /edu/signup
dst: https://services.google.com/fb/forms/playedu
type: permanent