diff options
author | Xavier Ducrohet <xav@android.com> | 2009-12-01 13:02:31 -0800 |
---|---|---|
committer | Xavier Ducrohet <xav@android.com> | 2009-12-01 13:02:31 -0800 |
commit | aa970a49713734c20fdde6e50e9de6ef33308901 (patch) | |
tree | 3dd3aed54e8ec739ea8e750e3f9ea12e49bd0ffb /scripts/app_engine_server | |
parent | f200375d590efd084142d60058ce6c9543a1b9cd (diff) | |
download | sdk-aa970a49713734c20fdde6e50e9de6ef33308901.zip sdk-aa970a49713734c20fdde6e50e9de6ef33308901.tar.gz sdk-aa970a49713734c20fdde6e50e9de6ef33308901.tar.bz2 |
Clean up the content of scripts/ folder.
- move back some scripts into development/scripts/
- templates for Ant project go in sdk/templates/
- files that are packaged with the sdk go in sdk/files/
Diffstat (limited to 'scripts/app_engine_server')
-rw-r--r-- | scripts/app_engine_server/LICENSE | 202 | ||||
-rwxr-xr-x | scripts/app_engine_server/app.yaml | 16 | ||||
-rw-r--r-- | scripts/app_engine_server/gae_shell/README | 17 | ||||
-rw-r--r-- | scripts/app_engine_server/gae_shell/__init__.py | 0 | ||||
-rw-r--r-- | scripts/app_engine_server/gae_shell/__init__.pyc | bin | 216 -> 0 bytes | |||
-rwxr-xr-x | scripts/app_engine_server/gae_shell/shell.py | 308 | ||||
-rw-r--r-- | scripts/app_engine_server/gae_shell/static/shell.js | 195 | ||||
-rw-r--r-- | scripts/app_engine_server/gae_shell/static/spinner.gif | bin | 1514 -> 0 bytes | |||
-rw-r--r-- | scripts/app_engine_server/gae_shell/templates/shell.html | 122 | ||||
-rw-r--r-- | scripts/app_engine_server/index.yaml | 12 | ||||
-rw-r--r-- | scripts/app_engine_server/memcache_zipserve.py | 573 |
11 files changed, 0 insertions, 1445 deletions
diff --git a/scripts/app_engine_server/LICENSE b/scripts/app_engine_server/LICENSE deleted file mode 100644 index d645695..0000000 --- a/scripts/app_engine_server/LICENSE +++ /dev/null @@ -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. diff --git a/scripts/app_engine_server/app.yaml b/scripts/app_engine_server/app.yaml deleted file mode 100755 index 1fb50c7..0000000 --- a/scripts/app_engine_server/app.yaml +++ /dev/null @@ -1,16 +0,0 @@ -application: androidappdocs-staging -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 diff --git a/scripts/app_engine_server/gae_shell/README b/scripts/app_engine_server/gae_shell/README deleted file mode 100644 index 5b0089f..0000000 --- a/scripts/app_engine_server/gae_shell/README +++ /dev/null @@ -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. diff --git a/scripts/app_engine_server/gae_shell/__init__.py b/scripts/app_engine_server/gae_shell/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/scripts/app_engine_server/gae_shell/__init__.py +++ /dev/null diff --git a/scripts/app_engine_server/gae_shell/__init__.pyc b/scripts/app_engine_server/gae_shell/__init__.pyc Binary files differdeleted file mode 100644 index 84951e9..0000000 --- a/scripts/app_engine_server/gae_shell/__init__.pyc +++ /dev/null diff --git a/scripts/app_engine_server/gae_shell/shell.py b/scripts/app_engine_server/gae_shell/shell.py deleted file mode 100755 index df2fb17..0000000 --- a/scripts/app_engine_server/gae_shell/shell.py +++ /dev/null @@ -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() diff --git a/scripts/app_engine_server/gae_shell/static/shell.js b/scripts/app_engine_server/gae_shell/static/shell.js deleted file mode 100644 index 4aa1583..0000000 --- a/scripts/app_engine_server/gae_shell/static/shell.js +++ /dev/null @@ -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; -}; diff --git a/scripts/app_engine_server/gae_shell/static/spinner.gif b/scripts/app_engine_server/gae_shell/static/spinner.gif Binary files differdeleted file mode 100644 index 3e58d6e..0000000 --- a/scripts/app_engine_server/gae_shell/static/spinner.gif +++ /dev/null diff --git a/scripts/app_engine_server/gae_shell/templates/shell.html b/scripts/app_engine_server/gae_shell/templates/shell.html deleted file mode 100644 index 123b200..0000000 --- a/scripts/app_engine_server/gae_shell/templates/shell.html +++ /dev/null @@ -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()" - >>>></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> - diff --git a/scripts/app_engine_server/index.yaml b/scripts/app_engine_server/index.yaml deleted file mode 100644 index 8e6046d..0000000 --- a/scripts/app_engine_server/index.yaml +++ /dev/null @@ -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. - diff --git a/scripts/app_engine_server/memcache_zipserve.py b/scripts/app_engine_server/memcache_zipserve.py deleted file mode 100644 index 43826b0..0000000 --- a/scripts/app_engine_server/memcache_zipserve.py +++ /dev/null @@ -1,573 +0,0 @@ -#!/usr/bin/env python -# -# 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 time -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 = 600 # max client-side cache lifetime - 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 - intlString = 'intl/' - validLangs = ['en', 'de', 'es', 'fr','it','ja','zh-CN','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 - - # 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]', reqUri, langName) - - # 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. - name = self.PreprocessUrl(reqUri, langName) - if name: - # 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 = name.split("/", 2) - contentUri = 0 - isIntl = len(sections) > 1 and (sections[0] == "intl") - if isIntl: - isValidIntl = sections[1] in self.validLangs - if isValidIntl: - urlLangName = sections[1] - contentUri = sections[2] - if (langName != urlLangName): - # if the lang code in the request is different from that in - # the cookie, reset the cookie to the url lang value. - langName = urlLangName - resetLangCookie = True - #logging.info('INTL PREP resetting langName to urlLangName [%s]', langName) - #else: - # logging.info('INTL PREP no need to reset langName') - - # Send for processing - if self.isCleanUrl(name, langName, isValidIntl): - # handle a 'clean' request. - # Try to form a response using the actual request url. - if not self.CreateResponse(name, 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(name, 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(name, self.intlString, langName) - - def isCleanUrl(self, name, langName, isValidIntl): - """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 - """ - if (langName == 'en') 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 interpretting 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: - False if the request was redirected to '/index.html', or - The processed URL, otherwise - """ - # 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 False - else: - return name - - def RedirToIntl(self, name, intlString, langName): - """Redirect an incoming request to the appropriate intl uri. - - Builds the intl/lang string from a base (en) string - and redirects (302) the request to look for a version - of the file in the language that matches the client- - supplied cookie value. - - Args: - name: The incoming, preprocessed URL - - Returns: - The lang-specific URL - """ - builtIntlLangUri = ''.join([intlString, langName, '/', name, '?', self.request.query_string]) - 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) - 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) - - # 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() |