diff options
author | jsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68> | 2013-01-03 07:41:53 +0000 |
---|---|---|
committer | jsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68> | 2013-01-03 07:41:53 +0000 |
commit | d7069b5a2e7859ab14c5a909d5e5fc6bc84b80cb (patch) | |
tree | d8027520fb22c176f54e860c0d2ebd000b1c457f /sca-cpp/trunk/hosting | |
parent | 9e1b9e73145e00ea591bd1e0e9777625bad66dc9 (diff) |
Improve app hosting management app, restructure UI and refactor REST services and data model to use an SQL database.
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1428193 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to '')
140 files changed, 6611 insertions, 2295 deletions
diff --git a/sca-cpp/trunk/hosting/server/Makefile.am b/sca-cpp/trunk/hosting/server/Makefile.am index d6e15f819b..226c69b75a 100644 --- a/sca-cpp/trunk/hosting/server/Makefile.am +++ b/sca-cpp/trunk/hosting/server/Makefile.am @@ -18,31 +18,42 @@ if WANT_PYTHON moddir = $(prefix)/hosting/server -dist_mod_SCRIPTS = start stop ssl-start mkapplinks config-backup data-backup put-auth get-auth delete-auth +dist_mod_SCRIPTS = start stop ssl-start mkapplinks config-backup data-backup put-auth get-auth delete-auth create-tables drop-tables load-tables clean-tables load-authn pgsql imapd-start imapd-stop -not_minified = htdocs/public/iframe.html htdocs/create/index.html htdocs/page/index.html htdocs/login/index.html htdocs/public/notfound/index.html htdocs/public/oops/index.html htdocs/proxy/public/oops/index.html htdocs/graph/index.html htdocs/public/notauth/index.html htdocs/account/index.html htdocs/home/index.html htdocs/index.html htdocs/public/notyet/index.html htdocs/clone/index.html htdocs/delete/index.html htdocs/stats/index.html htdocs/app/index.html htdocs/store/index.html htdocs/config.js htdocs/public/config.js htdocs/cache-template.cmf htdocs/app/cache-template.cmf htdocs/cache-template.cmf htdocs/app/cache-template.cmf +not_minified = htdocs/create/index.html htdocs/page/index.html htdocs/login/index.html htdocs/public/cache/index.html htdocs/public/notfound/index.html htdocs/public/oops/index.html htdocs/proxy/public/oops/index.html htdocs/proxy/public/cache/index.html htdocs/graph/index.html htdocs/public/notauth/index.html htdocs/account/index.html htdocs/home/index.html htdocs/index.html htdocs/cache/index.html htdocs/public/notyet/index.html htdocs/clone/index.html htdocs/delete/index.html htdocs/rate/index.html htdocs/search/index.html htdocs/info/index.html htdocs/app/index.html htdocs/store/index.html htdocs/config.js htdocs/public/config.js htdocs/cache/cache-template.cmf htdocs/app/cache/cache-template.cmf htdocs/public/cache/cache-template.cmf htdocs/proxy/public/cache/cache-template.cmf -minified = htdocs/public/iframe-min.html htdocs/create/index-min.html htdocs/page/index-min.html htdocs/login/index-min.html htdocs/public/notfound/index-min.html htdocs/public/oops/index-min.html htdocs/proxy/public/oops/index-min.html htdocs/graph/index-min.html htdocs/public/notauth/index-min.html htdocs/account/index-min.html htdocs/home/index-min.html htdocs/index-min.html htdocs/public/notyet/index-min.html htdocs/clone/index-min.html htdocs/delete/index-min.html htdocs/stats/index-min.html htdocs/app/index-min.html htdocs/store/index-min.html htdocs/config-min.js htdocs/public/config-min.js +minified = htdocs/create/index-min.html htdocs/page/index-min.html htdocs/login/index-min.html htdocs/public/cache/index-min.html htdocs/public/notfound/index-min.html htdocs/public/oops/index-min.html htdocs/proxy/public/oops/index-min.html htdocs/proxy/public/cache/index-min.html htdocs/graph/index-min.html htdocs/public/notauth/index-min.html htdocs/account/index-min.html htdocs/home/index-min.html htdocs/index-min.html htdocs/cache/index-min.html htdocs/public/notyet/index-min.html htdocs/clone/index-min.html htdocs/delete/index-min.html htdocs/rate/index-min.html htdocs/search/index-min.html htdocs/info/index-min.html htdocs/app/index-min.html htdocs/store/index-min.html htdocs/config-min.js htdocs/public/config-min.js js_minified = ../../modules/js/htdocs/all-min.js ../../modules/js/htdocs/ui-min.css -resources = server.composite *.py data/palettes/*/palette.composite data/accounts/*/*.account data/apps/*/app.composite data/apps/*/app.stats data/apps/*/htdocs/app.html data/dashboards/*/user.apps data/store/*/store.apps htdocs/cache-manifest.cmf htdocs/app/cache-manifest.cmf htdocs/*.ico htdocs/home/*.png htdocs/home/*.b64 htdocs/*.txt htdocs/public/*.png htdocs/public/*.b64 ${minified} +b64images = htdocs/public/app.b64 htdocs/public/img.b64 htdocs/public/rate.b64 htdocs/public/ratings.b64 htdocs/public/search.b64 htdocs/public/user.b64 + +resources = server.composite *.py data/palettes/*/palette.composite data/accounts/*/*.account data/apps/*/app.composite data/apps/*/app.info data/apps/*/htdocs/app.html data/dashboards/*/user.apps data/store/*/store.apps htdocs/cache/cache-manifest.cmf htdocs/app/cache/cache-manifest.cmf htdocs/public/cache/cache-manifest.cmf htdocs/proxy/public/cache/cache-manifest.cmf htdocs/*.ico htdocs/*.txt htdocs/public/*.png ${b64images} ${minified} nobase_dist_mod_DATA = ${resources} EXTRA_DIST = ${resources} ${not_minified} -SUFFIXES = -min.html -min.js +SUFFIXES = -min.html -min.js b64 .html-min.html: ../../modules/http/minify-html $< $@ .js-min.js: ../../modules/http/minify-js $< $@ -htdocs/cache-manifest.cmf: htdocs/cache-template.cmf ${minified} ${js_minified} - ../../modules/http/cache-manifest htdocs $^ +.png.b64: + ../../modules/http/base64-encode $< $@ + +htdocs/cache/cache-manifest.cmf: htdocs/cache/cache-template.cmf ${minified} ${js_minified} ${b64images} + ../../modules/http/cache-manifest htdocs/cache $^ + +htdocs/app/cache/cache-manifest.cmf: htdocs/app/cache/cache-template.cmf ${minified} ${js_minified} ${b64images} + ../../modules/http/cache-manifest htdocs/app/cache $^ -htdocs/app/cache-manifest.cmf: htdocs/app/cache-template.cmf ${minified} ${js_minified} - ../../modules/http/cache-manifest htdocs/app $^ +htdocs/public/cache/cache-manifest.cmf: htdocs/public/cache/cache-template.cmf ${minified} ${js_minified} ${b64images} + ../../modules/http/cache-manifest htdocs/public/cache $^ + +htdocs/proxy/public/cache/cache-manifest.cmf: htdocs/proxy/public/cache/cache-template.cmf ${minified} ${js_minified} ${b64images} + ../../modules/http/cache-manifest htdocs/proxy/public/cache $^ nuvem: ln -s "../../../nuvem/nuvem-parallel/nuvem" "nuvem" @@ -54,13 +65,16 @@ install-data-hook: cd $(moddir); rm -f nuvem; ln -s "../../../nuvem/nuvem-parallel/nuvem" "nuvem" cd $(moddir); rm -f lib; ln -s "../../components" "lib" -CLEANFILES = ${minified} nuvem lib htdocs/cache-manifest.cmf htdocs/app/cache-manifest.cmf +CLEANFILES = ${minified} nuvem lib htdocs/cache/cache-manifest.cmf htdocs/app/cache/cache-manifest.cmf htdocs/public/cache/cache-manifest.cmf htdocs/proxy/public/cache/cache-manifest.cmf client_test_SOURCES = client-test.cpp -client_test_LDFLAGS = -lxml2 -lcurl -lmozjs +client_test_LDFLAGS = -lxml2 -lcurl -ljansson + +patch_test_SOURCES = patch-test.cpp +patch_test_LDFLAGS = -lxml2 -lcurl -ljansson dist_noinst_SCRIPTS = logic-test server-test test.py -noinst_PROGRAMS = client-test -TESTS = logic-test +noinst_PROGRAMS = client-test patch-test +TESTS = logic-test patch-test endif diff --git a/sca-cpp/trunk/hosting/server/accounts.py b/sca-cpp/trunk/hosting/server/accounts.py index 3587f5fb65..99655620f1 100644 --- a/sca-cpp/trunk/hosting/server/accounts.py +++ b/sca-cpp/trunk/hosting/server/accounts.py @@ -16,8 +16,9 @@ # under the License. # Accounts collection implementation -from time import strftime from util import * +from atomutil import * +from sys import debug # Convert a particular user id to an account id def accountid(user): @@ -25,12 +26,17 @@ def accountid(user): # Get the current user's account def get(id, user, cache): + debug('accounts.py::get::id', id) account = cache.get(accountid(user)) - if isNil(account) or account is None: - return (("'entry", ("'title", user.get(())), ("'id", user.get(())), ("'updated", strftime('%b %d, %Y'))),) + if isNil(account): + return mkentry(user.get(()), user.get(()), user.get(()), now(), ()) return account # Update the user's account def put(id, account, user, cache): - return cache.put(accountid(user), account) + debug('accounts.py::put::id', id) + debug('accounts.py::put::account', account) + + accountentry = mkentry(title(account), user.get(()), user.get(()), now(), content(account)) + return cache.put(accountid(user), accountentry) diff --git a/sca-cpp/trunk/hosting/server/apps.py b/sca-cpp/trunk/hosting/server/apps.py index 064701a7df..5b0c1b8831 100644 --- a/sca-cpp/trunk/hosting/server/apps.py +++ b/sca-cpp/trunk/hosting/server/apps.py @@ -16,96 +16,103 @@ # under the License. # App collection implementation -from time import strftime from util import * +from atomutil import * from sys import debug # Convert an id to an app id def appid(id): - return ("apps", car(id), "app.stats") + return ("apps", car(id), "app.info") # Put an app into the apps db -def put(id, app, user, cache, dashboard, store, composites, pages): +def put(id, app, user, cache, dashboard, store, composites, pages, icons): debug('apps.py::put::id', id) debug('apps.py::put::app', app) # Update an app - eid = cadr(assoc("'id", car(app))) + eid = entryid(app) if car(id) == eid: # Check app author - eapp = cache.get(appid(id)); - if (not (isNil(eapp) or eapp is None)) and (cadr(assoc("'author", car(eapp))) != user.get(())): - debug('apps.py::put', 'different author', cadr(assoc("'author", car(eapp)))) + eapp = cache.get(appid(id)) + if (not isNil(eapp)) and (author(eapp) != user.get(())): + debug('apps.py::put', 'different author', author(eapp)) return False # Update the app in the apps db - appentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y')), assoc("'content", car(app))),) + appentry = mkentry(title(app), car(id), user.get(()), now(), content(app)) debug('apps.py::put::appentry', appentry) cache.put(appid(id), appentry) dashboard.put(id, appentry) # Create new page and composite if necessary - if isNil(eapp) or eapp is None: - comp = (("'entry", ("'title", car(id)), ("'id", car(id))),) - composites.put(id, comp); - page = (("'entry", ("'title", car(id)), ("'id", car(id))),) - pages.put(id, comp); + if isNil(eapp): + comp = mkentry(car(id), car(id), user.get(()), now(), ()) + composites.put(id, comp) + page = mkentry(car(id), car(id), user.get(()), now(), ()) + pages.put(id, page) + icon = mkentry(car(id), car(id), user.get(()), now(), ()) + icons.put(id, icon) return True return True # Check app author - eapp = cache.get(appid(id)); - if (not (isNil(eapp) or eapp is None)) and (cadr(assoc("'author", car(eapp))) != user.get(())): - debug('apps.py::put', 'different author', cadr(assoc("'author", car(eapp)))) + eapp = cache.get(appid(id)) + if (not isNil(eapp)) and (author(eapp) != user.get(())): + debug('apps.py::put', 'different author', author(eapp)) return False - # Clone an app - appentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y')), assoc("'content", car(app))),) + # Get app to clone + capp = cache.get(appid((eid,))) + if isNil(capp): + debug('apps.py::put', 'cloned app not found', (eid,)) + return False + + # Clone app + appentry = mkentry(title(app), car(id), user.get(()), now(), content(app)) debug('apps.py::put::appentry', appentry) cache.put(appid(id), appentry) composites.put(id, composites.get((eid,))) pages.put(id, pages.get((eid,))) + icons.put(id, icons.get((eid,))) dashboard.put(id, appentry) return True # Get an app from the apps db -def get(id, user, cache, dashboard, store, composites, pages): +def get(id, user, cache, dashboard, store, composites, pages, icons): debug('apps.py::get::id', id) if isNil(id): return (("'feed", ("'title", "Apps"), ("'id", "apps")),) # Get the requested app - app = cache.get(appid(id)); - if isNil(app) or app is None: + app = cache.get(appid(id)) + if isNil(app): debug('apps.py::get', 'app not found', id) - - # Return a default new app - return (("'entry", ("'title", car(id)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y')), ("'content", ("'stats", ("'description", '')))),) + return None # Return the app debug('apps.py::get::app', app) return app # Delete an app from the apps db -def delete(id, user, cache, dashboard, store, composites, pages): +def delete(id, user, cache, dashboard, store, composites, pages, icons): debug('apps.py::delete::id', id) # Get the requested app - app = cache.get(appid(id)); - if isNil(app) or app is None: + app = cache.get(appid(id)) + if isNil(app): debug('apps.py::delete', 'app not found', id) return False # Check app author - author = cadr(assoc("'author", car(app))) - if author != user.get(()): - debug('apps.py::delete', 'different author', author) + if author(app) != user.get(()): + debug('apps.py::delete', 'different author', author(app)) return False # Delete the app, its composite and page dashboard.delete(id) composites.delete(id) pages.delete(id) + icons.delete(id) cache.delete(appid(id)) return True diff --git a/sca-cpp/trunk/hosting/server/atomutil.py b/sca-cpp/trunk/hosting/server/atomutil.py new file mode 100644 index 0000000000..8f01f81d3a --- /dev/null +++ b/sca-cpp/trunk/hosting/server/atomutil.py @@ -0,0 +1,68 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Simple ATOM handling functions +from util import * +from time import strftime, gmtime + +# Make an ATOM entry +def mkentry(title, id, author, updated, content): + return (("'entry", ("'title", title), ("'id", id), ("'author", author), ("'updated", updated), ("'content",) if isNil(content) else ("'content", content)),) + +# Make an ATOM feed +def mkfeed(title, id, author, updated, entries): + return (("'entry", ("'title", title), ("'id", id), ("'author", author), ("'updated", updated), ("'content",) if isNil(content) else ("'content", content)),) + +# Return ATOM attributes +def title(e): + if isNil(e): + return () + t = assoc("'title", car(e)) + return None if isNil(t) else cadr(t) + +def entryid(e): + if isNil(e): + return () + id = assoc("'id", car(e)) + return None if isNil(id) else cadr(id) + +def author(e): + if isNil(e): + return () + a = assoc("'author", car(e)) + return None if isNil(a) else cadr(a) + +def updated(e): + if isNil(e): + return () + u = assoc("'updated", car(e)) + return None if isNil(u) else cadr(u) + +def content(e): + if isNil(e): + return () + c = assoc("'content", car(e)) + return () if isNil(c) or isNil(cdr(c)) else c[len(c) - 1] + +# Return the current time +def now(): + return strftime('%Y-%m-%dT%H:%M:%S+00:00', gmtime()) + +# Return an (updated now) assoc +def updatedNow(): + return ("'updated", strftime('%Y-%m-%dT%H:%M:%S+00:00', gmtime())) + diff --git a/sca-cpp/trunk/hosting/server/authn.py b/sca-cpp/trunk/hosting/server/authn.py index 4d4f34b9fb..02c24a2852 100644 --- a/sca-cpp/trunk/hosting/server/authn.py +++ b/sca-cpp/trunk/hosting/server/authn.py @@ -16,7 +16,6 @@ # under the License. # User authenticator implementation -from time import strftime from util import * # Convert a particular user id to an authentication id @@ -26,7 +25,7 @@ def authnid(id): # Get a user's authentication def get(id, cache): authn = cache.get(authnid(id)) - if isNil(authn) or authn is None: + if isNil(authn): return None return authn diff --git a/sca-cpp/trunk/hosting/server/clean-tables b/sca-cpp/trunk/hosting/server/clean-tables new file mode 100755 index 0000000000..76a59921f3 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/clean-tables @@ -0,0 +1,37 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` + +tmp=$1 +if [ "$tmp" = "" ]; then + tmp="$here/tmp" +fi +host=$2 +if [ "$host" = "" ]; then + host="localhost" +fi + +# Cleanup tables +cat >$tmp/sqldb/clean-tables.sql <<EOF +delete from data; +EOF + +$here/../../components/sqldb/pgsql $host 6432 <$tmp/sqldb/clean-tables.sql + diff --git a/sca-cpp/trunk/hosting/server/composites.py b/sca-cpp/trunk/hosting/server/composites.py index 970bc98a5c..9e4b40b669 100644 --- a/sca-cpp/trunk/hosting/server/composites.py +++ b/sca-cpp/trunk/hosting/server/composites.py @@ -16,8 +16,8 @@ # under the License. # App composites collection implementation -from time import strftime from util import * +from atomutil import * from sys import debug # Convert an id to a composite id @@ -30,19 +30,18 @@ def put(id, comp, user, cache, apps): debug('composites.py::put::comp', comp) # Get the requested app - app = apps.get(id); - if isNil(app) or app is None: + app = apps.get(id) + if isNil(app): debug('composites.py::put', 'app not found', id) return False # Check app author - author = cadr(assoc("'author", car(app))) - if author != user.get(()): - debug('composites.py::put', 'different author', author) + if author(app) != user.get(()): + debug('composites.py::put', 'different author', author(app)) return False # Update the composite in the composite db - compentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y')), assoc("'content", car(comp))),) + compentry = mkentry(title(app), car(id), user.get(()), now(), content(comp)) debug('composites.py::put::compentry', compentry) return cache.put(compid(id), compentry) @@ -54,25 +53,22 @@ def get(id, user, cache, apps): # Get the requested app app = apps.get(id) - if isNil(app) or app is None: + if isNil(app): debug('composites.py::get', 'app not found', id) # Return a default new composite - return (("'entry", ("'title", car(id)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y'))),) + return mkentry(car(id), car(id), user.get(()), now(), ()) # Get the requested composite - comp = cache.get(compid(id)); - if isNil(comp) or comp is None: + comp = cache.get(compid(id)) + if isNil(comp): debug('composites.py::get', 'composite not found', id) # Return a default new composite - return (("'entry", ("'title", car(id)), ("'id", car(id)), assoc("'author", car(app)), assoc("'updated", car(app))),) + return mkentry(title(app), car(id), author(app), now(), ()) # Return the composite - def updated(u): - return assoc("'updated", car(app)) if isNil(u) or u is None else u - - compentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), assoc("'author", car(app)), updated(assoc("'updated", car(comp))), assoc("'content", car(comp))),) + compentry = mkentry(title(app), car(id), author(app), updated(comp), content(comp)) debug('composites.py::get::compentry', compentry) return compentry @@ -81,15 +77,14 @@ def delete(id, user, cache, apps): debug('composites.py::delete::id', id) # Get the requested app - app = apps.get(id); - if isNil(app) or app is None: + app = apps.get(id) + if isNil(app): debug('composites.py::delete', 'app not found', id) return False # Check app author - author = cadr(assoc("'author", car(app))) - if author != user.get(()): - debug('composites.py::delete', 'different author', author) + if author(app) != user.get(()): + debug('composites.py::delete', 'different author', author(app)) return False # Delete the composite diff --git a/sca-cpp/trunk/hosting/server/config-backup b/sca-cpp/trunk/hosting/server/config-backup index 5e09008fb0..f0f38f8c15 100755 --- a/sca-cpp/trunk/hosting/server/config-backup +++ b/sca-cpp/trunk/hosting/server/config-backup @@ -18,5 +18,5 @@ # under the License. cd ../../ -tar czf ../config-backup.tar.gz hosting/server/*start hosting/server/*stop hosting/server/htdocs/*.js hosting/server/htdocs/public/*.js hosting/server/htdocs/home/*.b64 hosting/server/htdocs/home/*.png +tar czf ../config-backup.tar.gz hosting/server/*start hosting/server/*stop hosting/server/htdocs/*.js hosting/server/htdocs/public/*.js diff --git a/sca-cpp/trunk/hosting/server/create-tables b/sca-cpp/trunk/hosting/server/create-tables new file mode 100755 index 0000000000..65f681f957 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/create-tables @@ -0,0 +1,38 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` + +tmp=$1 +if [ "$tmp" = "" ]; then + tmp="$here/tmp" +fi +host=$2 +if [ "$host" = "" ]; then + host="localhost" +fi + +# Create tables +cat >$tmp/sqldb/create-tables.sql <<EOF +create table data(key text, value text); +create index data_index on data(key); +EOF + +$here/../../components/sqldb/pgsql $host 6432 <tmp/sqldb/create-tables.sql + diff --git a/sca-cpp/trunk/hosting/server/dashboards.py b/sca-cpp/trunk/hosting/server/dashboards.py index d6281d0454..a87f1fa819 100644 --- a/sca-cpp/trunk/hosting/server/dashboards.py +++ b/sca-cpp/trunk/hosting/server/dashboards.py @@ -17,6 +17,7 @@ # Dashboards collection implementation from util import * +from atomutil import * from sys import debug # Convert a particular user id to a dashboard id @@ -27,7 +28,7 @@ def dashboardid(user): def getdashboard(id, cache): debug('dashboards.py::getdashboard::id', id) val = cache.get(id) - if isNil(val) or val is None: + if isNil(val): return () dashboard = cdddr(car(val)) if not isNil(dashboard) and isList(car(cadr(car(dashboard)))): @@ -47,36 +48,57 @@ def putdashboard(id, dashboard, cache): return cache.put(id, val) # Put an app into the user's dashboard -def put(id, app, user, cache, apps): +def put(id, app, user, cache, apps, ratings): debug('dashboards.py::put::id', id) debug('dashboards.py::put::app', app) def putapp(id, app, dashboard): if isNil(dashboard): return app - if car(id) == cadr(assoc("'id", car(dashboard))): + if car(id) == entryid(dashboard): return cons(car(app), cdr(dashboard)) return cons(car(dashboard), putapp(id, app, cdr(dashboard))) - appentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), ("'author", user.get(())), assoc("'updated", car(app)), assoc("'content", car(app))),) + appentry = mkentry(title(app), car(id), user.get(()), now(), content(app)) debug('dashboards.py::put::appentry', appentry) dashboard = putapp(id, appentry, getdashboard(dashboardid(user), cache)) return putdashboard(dashboardid(user), dashboard, cache) +# Merge app info and ratings into a list of apps +def mergeapps(entries, apps, ratings): + debug('store.py::mergeapps::entries', entries) + + def mergeapp(entry): + debug('store.py::mergeapp::entry', entry) + id = (entryid(entry),) + app = apps.get(id) + if isNil(app): + return ((),) + info = content(app) + rating = ratings.get(id) + rates = content(rating) + mergedentry = mkentry(title(app), car(id), author(app), updated(app), ("'info",) + (() if isNil(info) else cdr(info)) + (() if isNil(rates) else cdr(rates))) + return mergedentry + + mergedentries = tuple(filter(lambda e: not isNil(e), map(lambda e: car(mergeapp((e,))), entries))) + debug('store.py::mergeapps::mergedentries', mergedentries) + return mergedentries + # Get apps from the user's dashboard -def get(id, user, cache, apps): +def get(id, user, cache, apps, ratings): debug('dashboards.py::get::id', id) def findapp(id, dashboard): if isNil(dashboard): return None - if car(id) == cadr(assoc("'id", car(dashboard))): + if car(id) == entryid(dashboard): return (car(dashboard),) return findapp(id, cdr(dashboard)) if isNil(id): - dashboard = ((("'feed", ("'title", "Your Apps"), ("'id", user.get(()))) + getdashboard(dashboardid(user), cache)),) + dashboardapps = mergeapps(getdashboard(dashboardid(user), cache), apps, ratings) + dashboard = ((("'feed", ("'title", "Your Apps"), ("'id", user.get(()))) + dashboardapps),) debug('dashboards.py::get::dashboard', dashboard) return dashboard @@ -85,7 +107,7 @@ def get(id, user, cache, apps): return app # Delete apps from the user's dashboard -def delete(id, user, cache, apps): +def delete(id, user, cache, apps, ratings): debug('dashboards.py::delete::id', id) if isNil(id): return cache.delete(dashboardid(user)) @@ -93,7 +115,7 @@ def delete(id, user, cache, apps): def deleteapp(id, dashboard): if isNil(dashboard): return () - if car(id) == cadr(assoc("'id", car(dashboard))): + if car(id) == entryid(dashboard): return cdr(dashboard) return cons(car(dashboard), deleteapp(id, cdr(dashboard))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/me360/app.info b/sca-cpp/trunk/hosting/server/data/apps/me360/app.info new file mode 100644 index 0000000000..febbd7daec --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/me360/app.info @@ -0,0 +1 @@ +((entry (title "Check my public social data") (id "me360") (author "admin@example.com") (updated "Apr 28, 2012") (content (info (description "Sample app")))))
\ No newline at end of file diff --git a/sca-cpp/trunk/hosting/server/data/apps/me360/app.stats b/sca-cpp/trunk/hosting/server/data/apps/me360/app.stats deleted file mode 100644 index 31695e59d7..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/me360/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Check my public social data") (id "me360") (author "admin@example.com") (updated "Apr 28, 2012") (content (stats (description "Sample app")))))
\ No newline at end of file diff --git a/sca-cpp/trunk/hosting/server/data/apps/nearme/app.info b/sca-cpp/trunk/hosting/server/data/apps/nearme/app.info new file mode 100644 index 0000000000..07777a07b0 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/nearme/app.info @@ -0,0 +1 @@ +((entry (title "nearme") (id "nearme") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/nearme/app.stats b/sca-cpp/trunk/hosting/server/data/apps/nearme/app.stats deleted file mode 100644 index 5bc3a2ed3c..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/nearme/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "nearme") (id "nearme") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/nearme2/app.info b/sca-cpp/trunk/hosting/server/data/apps/nearme2/app.info new file mode 100644 index 0000000000..e637ebab14 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/nearme2/app.info @@ -0,0 +1 @@ +((entry (title "nearme2") (id "nearme2") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/nearme2/app.stats b/sca-cpp/trunk/hosting/server/data/apps/nearme2/app.stats deleted file mode 100644 index c6d99481ae..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/nearme2/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "nearme2") (id "nearme2") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/new/app.info b/sca-cpp/trunk/hosting/server/data/apps/new/app.info new file mode 100644 index 0000000000..04ab8b8571 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/new/app.info @@ -0,0 +1 @@ +((entry (title "An empty app template") (id "new") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/new/app.stats b/sca-cpp/trunk/hosting/server/data/apps/new/app.stats deleted file mode 100644 index 7c0571bf2b..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/new/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "An empty app template") (id "new") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/ourphotos/app.info b/sca-cpp/trunk/hosting/server/data/apps/ourphotos/app.info new file mode 100644 index 0000000000..afc57e86da --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/ourphotos/app.info @@ -0,0 +1 @@ +((entry (title "Our photos of an event") (id "ourphotos") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/ourphotos/app.stats b/sca-cpp/trunk/hosting/server/data/apps/ourphotos/app.stats deleted file mode 100644 index 6986fbea1b..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/ourphotos/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Our photos of an event") (id "ourphotos") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/shoppingcart/app.info b/sca-cpp/trunk/hosting/server/data/apps/shoppingcart/app.info new file mode 100644 index 0000000000..3b77112bff --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/shoppingcart/app.info @@ -0,0 +1 @@ +((entry (title "My online store") (id "shoppingcart") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/shoppingcart/app.stats b/sca-cpp/trunk/hosting/server/data/apps/shoppingcart/app.stats deleted file mode 100644 index b4c696fbe1..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/shoppingcart/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "My online store") (id "shoppingcart") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/slice/app.info b/sca-cpp/trunk/hosting/server/data/apps/slice/app.info new file mode 100644 index 0000000000..76685cd580 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/slice/app.info @@ -0,0 +1 @@ +((entry (title "Slice") (id "slice") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/slice/app.stats b/sca-cpp/trunk/hosting/server/data/apps/slice/app.stats deleted file mode 100644 index bebfcbbaf9..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/slice/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Slice") (id "slice") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/test/app.info b/sca-cpp/trunk/hosting/server/data/apps/test/app.info new file mode 100644 index 0000000000..b6ea414f7a --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/test/app.info @@ -0,0 +1 @@ +((entry (title "An empty test app") (id "test") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/test/app.stats b/sca-cpp/trunk/hosting/server/data/apps/test/app.stats deleted file mode 100644 index 8c9b3792ed..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/test/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "An empty test app") (id "test") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testanimation/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testanimation/app.info index 0b6f8bda73..a6759e4df8 100644 --- a/sca-cpp/trunk/hosting/server/data/apps/testanimation/app.stats +++ b/sca-cpp/trunk/hosting/server/data/apps/testanimation/app.info @@ -1 +1 @@ -((entry (title "Test animation components") (id "testanimation") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) +((entry (title "Test animation components") (id "testanimation") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testdb/app.info b/sca-cpp/trunk/hosting/server/data/apps/testdb/app.info new file mode 100644 index 0000000000..3f5b3deadc --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/testdb/app.info @@ -0,0 +1 @@ +((entry (title "Test database components") (id "testdb") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testdb/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testdb/app.stats deleted file mode 100644 index e33dc221a5..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/testdb/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Test database components") (id "testdb") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testevents/app.info b/sca-cpp/trunk/hosting/server/data/apps/testevents/app.info new file mode 100644 index 0000000000..20191d663c --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/testevents/app.info @@ -0,0 +1 @@ +((entry (title "Test event components") (id "testevents") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testevents/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testevents/app.stats deleted file mode 100644 index 9c14040cd0..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/testevents/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Test event components") (id "testevents") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testhttp/app.info b/sca-cpp/trunk/hosting/server/data/apps/testhttp/app.info new file mode 100644 index 0000000000..ef2afff46f --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/testhttp/app.info @@ -0,0 +1 @@ +((entry (title "Test HTTP components") (id "testhttp") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testhttp/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testhttp/app.stats deleted file mode 100644 index f55f07105b..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/testhttp/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Test HTTP components") (id "testhttp") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testlogic/app.info b/sca-cpp/trunk/hosting/server/data/apps/testlogic/app.info new file mode 100644 index 0000000000..b4d3d9d492 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/testlogic/app.info @@ -0,0 +1 @@ +((entry (title "Test logic components") (id "testlogic") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testlogic/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testlogic/app.stats deleted file mode 100644 index 018a42a91f..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/testlogic/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Test logic components") (id "testlogic") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testsearch/app.info b/sca-cpp/trunk/hosting/server/data/apps/testsearch/app.info new file mode 100644 index 0000000000..6c959dc3c4 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/testsearch/app.info @@ -0,0 +1 @@ +((entry (title "Test search components") (id "testsearch") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testsearch/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testsearch/app.stats deleted file mode 100644 index 23679affa8..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/testsearch/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Test search components") (id "testsearch") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testsms/app.info b/sca-cpp/trunk/hosting/server/data/apps/testsms/app.info new file mode 100644 index 0000000000..ef2afff46f --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/testsms/app.info @@ -0,0 +1 @@ +((entry (title "Test HTTP components") (id "testhttp") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testsms/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testsms/app.stats deleted file mode 100644 index f55f07105b..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/testsms/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Test HTTP components") (id "testhttp") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testsocial/app.info b/sca-cpp/trunk/hosting/server/data/apps/testsocial/app.info new file mode 100644 index 0000000000..16190d4fe8 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/testsocial/app.info @@ -0,0 +1 @@ +((entry (title "Test social components") (id "testsocial") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testsocial/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testsocial/app.stats deleted file mode 100644 index e386c0528c..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/testsocial/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Test social components") (id "testsocial") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testtext/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testtext/app.info index 4b06f1dbba..e717015880 100644 --- a/sca-cpp/trunk/hosting/server/data/apps/testtext/app.stats +++ b/sca-cpp/trunk/hosting/server/data/apps/testtext/app.info @@ -1 +1 @@ -((entry (title "Test text processing components") (id "testtext") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) +((entry (title "Test text processing components") (id "testtext") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testurl/app.info b/sca-cpp/trunk/hosting/server/data/apps/testurl/app.info new file mode 100644 index 0000000000..37d89b57e0 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/testurl/app.info @@ -0,0 +1 @@ +((entry (title "Test URL components") (id "testurl") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testurl/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testurl/app.stats deleted file mode 100644 index 5683bd4a2a..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/testurl/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Test URL components") (id "testurl") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testvalues/app.info b/sca-cpp/trunk/hosting/server/data/apps/testvalues/app.info new file mode 100644 index 0000000000..42ed01b53d --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/testvalues/app.info @@ -0,0 +1 @@ +((entry (title "Test values and lists") (id "testvalues") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testvalues/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testvalues/app.stats deleted file mode 100644 index 88f63235f8..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/testvalues/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Test values and lists") (id "testvalues") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testwidgets/app.info b/sca-cpp/trunk/hosting/server/data/apps/testwidgets/app.info new file mode 100644 index 0000000000..3d8e7be195 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/testwidgets/app.info @@ -0,0 +1 @@ +((entry (title "Test widgets") (id "testwidgets") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testwidgets/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testwidgets/app.stats deleted file mode 100644 index f9a11815a5..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/testwidgets/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Test widgets") (id "testwidgets") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testwidgets2/app.info b/sca-cpp/trunk/hosting/server/data/apps/testwidgets2/app.info new file mode 100644 index 0000000000..2ba25712f9 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/testwidgets2/app.info @@ -0,0 +1 @@ +((entry (title "Test more widgets") (id "testwidgets2") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testwidgets2/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testwidgets2/app.stats deleted file mode 100644 index e375415f6f..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/testwidgets2/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "Test more widgets") (id "testwidgets2") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/testwidgets3/app.stats b/sca-cpp/trunk/hosting/server/data/apps/testwidgets3/app.info index d08847ca3d..3d01141f07 100644 --- a/sca-cpp/trunk/hosting/server/data/apps/testwidgets3/app.stats +++ b/sca-cpp/trunk/hosting/server/data/apps/testwidgets3/app.info @@ -1 +1 @@ -((entry (title "Test HTML generator components") (id "testwidgets3") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) +((entry (title "Test HTML generator components") (id "testwidgets3") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/twsms/app.info b/sca-cpp/trunk/hosting/server/data/apps/twsms/app.info new file mode 100644 index 0000000000..d870caf3e2 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/data/apps/twsms/app.info @@ -0,0 +1 @@ +((entry (title "SMS send service") (id "twsms") (author "admin@example.com") (updated "Jan 01, 2012") (content (info (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/data/apps/twsms/app.stats b/sca-cpp/trunk/hosting/server/data/apps/twsms/app.stats deleted file mode 100644 index 4a0584174c..0000000000 --- a/sca-cpp/trunk/hosting/server/data/apps/twsms/app.stats +++ /dev/null @@ -1 +0,0 @@ -((entry (title "SMS send service") (id "twsms") (author "admin@example.com") (updated "Jan 01, 2012") (content (stats (description "Sample app"))))) diff --git a/sca-cpp/trunk/hosting/server/drop-tables b/sca-cpp/trunk/hosting/server/drop-tables new file mode 100755 index 0000000000..3628fd3d34 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/drop-tables @@ -0,0 +1,38 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` + +tmp=$1 +if [ "$tmp" = "" ]; then + tmp="$here/tmp" +fi +host=$2 +if [ "$host" = "" ]; then + host="localhost" +fi + +# Drop tables +cat >$tmp/sqldb/drop-tables.sql <<EOF +drop index data_index; +drop table data; +EOF + +$here/../../components/sqldb/pgsql $host 6432 <tmp/sqldb/drop-tables.sql + diff --git a/sca-cpp/trunk/hosting/server/htdocs/account/index.html b/sca-cpp/trunk/hosting/server/htdocs/account/index.html index a0c2e78c31..47c0ea0e9c 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/account/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/account/index.html @@ -19,19 +19,25 @@ --> <div id="bodydiv" class="body"> -<div class="viewform"> +<div id="viewform" class="viewform"> <form id="userForm"> <table style="width: 100%;"> -<tr><tr><td><b>Photo:</b></td></tr> -<tr><td><img id="userimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Name:</b></td></tr> -<tr><td><input type="text" id="userTitle" class="flatentry" size="30" placeholder="Enter your name" style="width: 300px;"/></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>About Me:</b></td></tr> -<tr><td><textarea id="userDescription" class="flatentry" cols="40" rows="3" placeholder="Enter a short description of yourself" style="width: 300px;"></textarea></td></tr> +<tr><tr><td class="label">Username:</td></tr> +<tr><td><input type="text" id="userName" class="readentry" size="30" readonly="readonly" placeholder="Your username" style="width: 300px;"/></td></tr> +<tr><tr><td class="label">Email:</td></tr> +<tr><td><input type="text" id="userEmail" class="readentry" size="30" readonly="readonly" placeholder="Your email address" style="width: 300px;"/></td></tr> +<tr><tr><td class="label">Picture:</td></tr> +<tr><td><img id="userPicture" style="width: 50px; height: 50px; vertical-align: top;"/><input id="uploadPicture" type="button" class="lightbutton" value="Upload"/><input id="uploadFile" type="file" accept="image/*" style="display:none;"/><span id="refreshingPicture" class="refreshing" style="display:none;"/></td></tr> +<tr><tr><td class="label">Name:</td></tr> +<tr><td><input type="text" id="userFullname" class="flatentry" size="30" placeholder="Your name" style="width: 300px;"/></td></tr> +<tr><tr><td class="label">Bio:</td></tr> +<tr><td><textarea id="userDescription" class="flatentry" cols="40" rows="3" placeholder="About yourself, in fewer than 120 characters" style="width: 300px;"></textarea></td></tr> </table> - <br/> + +<!-- +TODO Disabled for now <table style="width: 100%;"> <tr> <th class="thl thr" style="padding-top: 4px; padding-bottom: 4px; padding-left: 2px; padding-right: 2px; ">Calendar</th> @@ -66,60 +72,73 @@ <tr><td style="padding-right: 2px;"><input type="text" id="name10" class="flatentry" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value10" class="flatentry" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr> </table> </form> +<br/> +--> </div> <script type="text/javascript"> -(function() { +(function accountbody() { /** - * Init service references. + * Setup page layout. */ -var editorComp = sca.component("Editor"); -var user= sca.defun(sca.reference(editorComp, "user")); -var accounts = sca.reference(editorComp, "accounts"); +(function layout() { + document.title = config.windowtitle() + ' - Account'; + $('viewhead').innerHTML = '<span class="cmenu">' + username + '</span>' + + '<input type="button" class="redbutton plusminus" style="position: absolute; top: 4px; left: 5px;" id="deleteUser" value="-" title="Delete your account" disabled="true"/>'; + if (!ui.isMobile()) + $('viewform').className = 'viewform flatscrollbars'; + $('userName').value = username; +})(); /** - * Set page titles. + * Set images. */ -document.title = config.windowtitle() + ' - Account'; -$('viewhead').innerHTML = '<span class="cmenu">' + username + '</span>'; +$('userPicture').src = ui.b64png(appcache.get('/public/user.b64')); /** - * Set images. + * Initialize service references. */ -$('userimg').src = ui.b64img(appcache.get('/public/user.b64')); +var editorComp = sca.component("Editor"); +var user= sca.defun(sca.reference(editorComp, "user")); +var accounts = sca.reference(editorComp, "accounts"); +var pictures = sca.reference(editorComp, "pictures"); /** * The current account entry and corresponding saved XML content. */ -var accountentry; -var savedaccountentryxml = ''; +var savedacctxml = ''; +var savedpicxml = ''; /** * Get and display the user's account. */ -function getaccount() { - showStatus('Loading'); +(function getacct() { + workingstatus(true); + showstatus('Loading'); return accounts.get('', function(doc) { // Stop now if we didn't get an account if (doc == null) { - showError('Account info not available'); + errorstatus('Account info not available'); + workingstatus(false); return false; } - showOnlineStatus(); - accountentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); - $('userTitle').value = cadr(assoc("'title", cdr(accountentry))); + var acctentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); + $('userFullname').value = cadr(assoc("'title", acctentry)); + + var acct = cadr(assoc("'content", acctentry)); - var content = cadr(assoc("'content", cdr(accountentry))); - var acct = isNil(content)? mklist() : cdr(content); + var email = assoc("'email", acct); + $('userEmail').value = isNil(email) || isNil(cdr(email))? '' : cadr(email); var desc = assoc("'description", acct); $('userDescription').innerHTML = isNil(desc) || isNil(cdr(desc))? '' : cadr(desc); + /* TODO disabled for now var cal = assoc("'calendar", acct); reduce(function(i, evt) { var sched = assoc("'@schedule", evt); @@ -137,27 +156,126 @@ function getaccount() { $('value' + i).value = isNil(kv)? '' : cadr(kv); return i + 1; }, 1, isNil(keys)? mklist() : cadr(cadr(keys))); + */ + + savedacctxml = car(atom.writeATOMEntry(valuesToElements(mklist(acctentry)))); + return true; + }); + return true; +})(); + +/** + * Get and display the user's picture. + */ +(function getpic() { + workingstatus(true); + showstatus('Loading'); - savedaccountentryxml = car(atom.writeATOMEntry(valuesToElements(mklist(accountentry)))); + return pictures.get('', function(doc) { + // Stop now if we didn't get a picture + if (doc == null) { + errorstatus('Picture not available'); + workingstatus(false); + return false; + } + + var picentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); + savedpicxml = car(atom.writeATOMEntry(valuesToElements(mklist(picentry)))); + var content = assoc("'content", picentry); + var picture = assoc("'picture", content); + var img = assoc("'image", picture); + if (!isNil(img)) + $('userPicture').src = cadr(img); + + onlinestatus(); + workingstatus(false); return true; }); + return true; +})(); + +/** + * Refresh picture. + */ +var refreshingpic = false; +function refreshpic() { + if (!refreshingpic) + return false; + $('refreshingPicture').style.display = 'inline-block'; + return pictures.get('', function(doc) { + if (doc == null) { + errorstatus('Picture not available'); + $('refreshingPicture').style.display = 'none'; + refreshingpic = false; + return false; + } + + var picentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); + var content = assoc("'content", picentry); + var picture = assoc("'picture", content); + var token = assoc("'token", picture); + + // Update picture + if (isNil(token)) { + var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(picentry)))); + savedpicxml = entryxml; + var img = assoc("'image", picture); + if (!isNil(img)) + $('userPicture').src = cadr(img); + $('refreshingPicture').style.display = 'none'; + refreshingpic = false; + return true; + } + + // Refresh in 2 secs + return ui.delay(refreshpic, 2000); + }, 'remote'); + return true; } /** * Save the user's account. */ -function save(entryxml) { +function saveacct(entryxml) { + if (isNil(username)) + return false; + workingstatus(true); + showstatus('Saving'); + + savedacctxml = entryxml; + accounts.put('', savedacctxml, function(e) { + if (e) { + showstatus('Local copy'); + workingstatus(false); + return false; + } + + showstatus('Saved'); + workingstatus(false); + return true; + }); + return true; +} + +/** + * Save the user's picture. + */ +function savepic(entryxml) { if (isNil(username)) return false; - showStatus('Saving'); - savedaccountentryxml = entryxml; - accounts.put('', savedaccountentryxml, function(e) { + workingstatus(true); + showstatus('Uploading'); + + savedpicxml = entryxml; + pictures.put('', savedpicxml, function(e) { if (e) { - showStatus('Local copy'); + showstatus('Local copy'); + workingstatus(false); return false; } - showStatus('Saved'); + showstatus('Uploaded'); + workingstatus(false); return true; }); return true; @@ -167,8 +285,27 @@ function save(entryxml) { * Handle a change event */ function onaccountchange() { - var title = $('userTitle').value; - var desc = $('userDescription').value; + + // Validate user input + var title = $('userFullname').value; + if (title.length == 0) { + errorstatus('Name cannot be empty'); + return false; + } + if (title.length > 40) { + errorstatus('Name cannot be longer than 40 characters'); + return false; + } + + var email = $('userEmail').value; + + var description = $('userDescription').value; + if (description.length > 120) { + errorstatus('Bio cannot be longer than 120 characters'); + return false; + } + + /* TODO disabled for now var cal = map(function(i) { var sched = $('sched' + i).value; var svc = $('service' + i).value; @@ -180,18 +317,24 @@ function onaccountchange() { return mklist("'key", mklist("'@name", kn), mklist("'@value", kv)); }, range(1, 11)); - var accountentry = mklist("'entry", mklist("'title", title != ''? title : username), mklist("'id", username), - mklist("'content", mklist("'account", mklist("'description", desc), cons("'keys", keys), cons("'calendar", cal)))); - var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(accountentry)))); - if (savedaccountentryxml == entryxml) + var acctentry = mklist("'entry", mklist("'title", title != ''? title : username), mklist("'id", username), + mklist("'content", mklist("'account", mklist("'email", email), mklist("'description", description), cons("'keys", keys), cons("'calendar", cal)))); + */ + + var acctentry = mklist("'entry", mklist("'title", title != ''? title : username), mklist("'id", username), + mklist("'content", mklist("'account", mklist("'email", email), mklist("'description", description)))); + var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(acctentry)))); + if (savedacctxml == entryxml) return false; - showStatus('Modified'); - return save(entryxml); + showstatus('Modified'); + return saveacct(entryxml); } -$('userTitle').onchange = onaccountchange; +$('userFullname').onchange = onaccountchange; $('userDescription').onchange = onaccountchange; + +/* TODO disabled for now map(function(i) { $('sched' + i).onchange = onaccountchange; $('service' + i).onchange = onaccountchange; @@ -202,6 +345,19 @@ map(function(i) { $('value' + i).onchange = onaccountchange; return true; }, range(1, 11)); +*/ + +/** + * Handle a key event. + */ +var lastkeyup = null; +$('userFullname').onkeyup = $('userDescription').onkeyup = function() { + var t = new Date().getTime(); + lastkeyup = t; + ui.delay(function() { + return t == lastkeyup? onaccountchange() : true; + }, 2000); +}; /** * Handle a form submit event. @@ -212,9 +368,96 @@ $('userForm').onsubmit = function() { }; /** - * Get the user's account. + * Read and upload icon file. + */ +function readpic(files) { + if (!files || files.length == 0) + return false; + if (!files[0].type.match('image.*')) { + errorstatus('Please select an image'); + return false; + } + workingstatus(true); + showstatus('Loading'); + + // Read the selected file into a 50x50 image + return ui.readimage(files[0], + function(e) { + errorstatus('Couldn\'t read the file'); + workingstatus(false); + }, + function(p) { + showstatus('Loading ' + p + '%'); + }, + function(url) { + // Update the user picture + $('userPicture').src = url; + showstatus('Loaded'); + + // Now upload it + ui.delay(function() { + var picentry = mklist("'entry", mklist("'title", username), mklist("'id", username), mklist("'author", username), mklist("'content", mklist("'picture", mklist("'image", url)))); + var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(picentry)))); + if (savedpicxml == entryxml) { + onlinestatus(); + workingstatus(false); + return false; + } + return savepic(entryxml); + }); + }, 50, 50); +} + +/** + * Upload a picture in an email. */ -getaccount(); +function emailpicture() { + + // Generate and put a picture email upload token + workingstatus(true); + showstatus('Uploading'); + var token = uuid4(); + var picentry = mklist("'entry", mklist("'title", username), mklist("'id", username), mklist("'author", username), mklist("'content", mklist("'picture", mklist("'token", token)))); + var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(picentry)))); + pictures.put('', entryxml, function(e) { + if (e) { + showstatus('Local copy'); + workingstatus(false); + return false; + } + workingstatus(false); + + // Open the email app + var mailto = safeb64encode('p/' + username + '/' + token); + ui.navigate('mailto:' + mailto + '@' + topdomainname(window.location.hostname) + '?subject=Email to upload&body=Paste picture here', '_self'); + + // Refresh app icon + refreshingpic = true; + return ui.delay(refreshpic, 500); + }, 'remote'); +} + +/** + * Handle picture upload events. + */ +$('uploadPicture').onclick = function() { + if (ui.isMobile()) + return emailpicture(); + return $('uploadFile').click(); +}; +$('uploadFile').onchange = function(e) { + return readpic(e.target.files); +}; +$('userPicture').ondrag = function(e) { + e.stopPropagation(); + e.preventDefault(); + e.dataTransfer.dropEffect = 'copy'; +}; +$('userPicture').ondrop = function(e) { + e.stopPropagation(); + e.preventDefault(); + return readpic(e.dataTransfer.files); +}; })(); </script> diff --git a/sca-cpp/trunk/hosting/server/htdocs/app/cache-template.cmf b/sca-cpp/trunk/hosting/server/htdocs/app/cache/cache-template.cmf index 5881cf83dd..40da327179 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/app/cache-template.cmf +++ b/sca-cpp/trunk/hosting/server/htdocs/app/cache/cache-template.cmf @@ -4,7 +4,7 @@ CACHE MANIFEST # App resources /favicon.ico -/public/iframe-min.html +/login/ /public/img.png /public/notauth/ /public/notfound/ diff --git a/sca-cpp/trunk/hosting/server/htdocs/app/index.html b/sca-cpp/trunk/hosting/server/htdocs/app/index.html index cddf4fb477..cd033118a3 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/app/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/app/index.html @@ -17,37 +17,51 @@ * specific language governing permissions and limitations * under the License. --> -<html manifest="cache-manifest.cmf"> +<html manifest="cache/cache-manifest.cmf"> <head> +<!-- Firebug inspector --> +<!-- +<script type="text/javascript" src="https://getfirebug.com/releases/lite/1.3/firebug-lite.js"></script> +--> +<!-- Weinre inspector --> +<!-- +<script src="http://www.example.com:9998/target/target-script-min.js#anonymous"></script> +--> <title></title> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"/> +<!-- <meta name="apple-mobile-web-app-capable" content="yes"/> <meta name="apple-mobile-web-app-status-bar-style" content="black"/> -<link rel="apple-touch-icon" href="/public/touchicon.png"/> +--> +<link rel="apple-touch-icon-precomposed" href="/public/touchicon.png"/> <base href="/"/> <script type="text/javascript"> -(function() { +try { + +(function apphead() { window.appcache = {}; /** * Get and cache a resource. */ -appcache.get = function(uri) { +appcache.get = function(uri, mode) { var h = uri.indexOf('#'); var u = h == -1? uri : uri.substring(0, h); // Get resource from local storage first var ls = window.lstorage || localStorage; var item = null; - try { item = ls.getItem(u); } catch(e) {} + try { item = ls.getItem('ui.r.' + u); } catch(e) {} if (item != null && item != '') return item; // Get resource from network var http = new XMLHttpRequest(); - http.open("GET", u, false); + http.open("GET", mode == 'remote'? (u + '?t=' + new Date().getTime() + '&r=' + Math.random()) : u, false); http.setRequestHeader("Accept", "*/*"); + if (mode == 'remote') + http.setRequestHeader("If-Modified-Since", "Thu, 1 Jan 1970 00:00:00 GMT"); http.send(null); if (http.status == 200) { if (http.getResponseHeader("X-Login") != null) { @@ -59,7 +73,7 @@ appcache.get = function(uri) { if (window.debug) debug('http error', u, 'No-Content'); return null; } - try { ls.setItem(u, http.responseText); } catch(e) {} + try { ls.setItem('ui.r.' + u, http.responseText); } catch(e) {} return http.responseText; } if (window.debug) debug('http error', u, http.status, http.statusText); @@ -74,46 +88,32 @@ appcache.get = function(uri) { /** * Load Javascript and CSS. */ -(function() { +(function appboot() { var bootjs = document.createElement('script'); bootjs.type = 'text/javascript'; -bootjs.text = appcache.get('/all-min.js'); -document.head.appendChild(bootjs); -document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); - -})(); - -/** - * Redirect to login page if not signed in. - */ -(function() { - -if (document.location.protocol == 'https:' && !hasauthcookie()) - document.location = '/login/'; +bootjs.text = 'try {\n' + appcache.get('/all-min.js') + '\n' + appcache.get('/config-min.js') + '\n} catch(e) { console.log(e.stack); throw e; }\n'; +var head = document.getElementsByTagName('head')[0]; +head.appendChild(bootjs); +head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); })(); +} catch(e) { + if (window.debug) debug(e.stack); + throw e; +} </script> </head> <body class="delayed"> -<div id="mainbodydiv" class="mainbodydiv"> - -<div id="headdiv" class="hsection"> -<script type="text/javascript"> -(function() { - -$('headdiv').appendChild(ui.declareScript(appcache.get('/config-min.js'))); - -})(); -</script> -</div> <div id="content"> </div> <script type="text/javascript"> -(function() { +try { + +(function appbody() { /** * Get the app name @@ -186,7 +186,7 @@ applicationCache.addEventListener('updateready', function(e) { applicationCache.addEventListener('cached', function(e) { //debug('appcache cached', e); map(function(res) { - appcache.get(res[0]); + appcache.get(res[0], 'remote'); }, appresources); }, false); @@ -316,7 +316,8 @@ function setwidgetvalue(e, dv) { var nesheet = document.createElement('style'); nesheet.id = 'style_' + e.id; nesheet.type = 'text/css'; - document.head.appendChild(nesheet); + var head = document.getElementsByTagName('head')[0]; + head.appendChild(nesheet); nesheet.innerHTML = s; } else { esheet.innerHTML = s; @@ -331,9 +332,9 @@ function setwidgetvalue(e, dv) { // Restart current animation if necessary if (!isNil(aname) && ce.style.webkitAnimationName == aname) { ce.style.webkitAnimationName = ''; - setTimeout(function() { + ui.async(function restartanimation() { ce.style.webkitAnimationName = aname; - }, 0); + }); } return a; } @@ -930,10 +931,7 @@ function setupLocationHandler() { */ document.body.onorientationchange = function(e) { //debug('onorientationchange'); - - // Scroll to the top and hide the address bar - window.scrollTo(0, 0); - + ui.onorientationchange(e); return true; }; @@ -980,14 +978,9 @@ function getappcomposite(appname) { /** * Initialize the document. */ -function onload() { +window.onload = function() { //debug('onload'); - - // Scroll to the top and hide the address bar - window.scrollTo(0, 0); - - // Show the page - document.body.style.visibility = 'visible'; + ui.onload(); // Initialize the app composite getappcomposite(appname); @@ -996,17 +989,16 @@ function onload() { getapppage(appname); return true; -} - -onload(); +}; })(); -</script> -<div id="footdiv" class="fsection"> -</div> +} catch(e) { + debug(e.stack); + throw e; +} +</script> -</div> </body> </html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/cache-template.cmf b/sca-cpp/trunk/hosting/server/htdocs/cache/cache-template.cmf index 8d9aa26f7d..8347c5553a 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/cache-template.cmf +++ b/sca-cpp/trunk/hosting/server/htdocs/cache/cache-template.cmf @@ -5,7 +5,7 @@ CACHE MANIFEST # App resources / /favicon.ico -/public/iframe-min.html +/login/ /public/img.png /public/notauth/ /public/notfound/ diff --git a/sca-cpp/trunk/hosting/server/htdocs/cache/index.html b/sca-cpp/trunk/hosting/server/htdocs/cache/index.html new file mode 100644 index 0000000000..f5c81791f2 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/cache/index.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<!-- + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. +--> +<html manifest="/cache/cache-manifest.cmf"> +<head> +<script type="text/javascript"> +applicationCache.addEventListener('checking', function(e) { + return window.parent.onappcachechecking(e); +}, false); +applicationCache.addEventListener('error', function(e) { + return window.parent.onappcacheerror(e); +}, false); +applicationCache.addEventListener('noupdate', function(e) { + return window.parent.onappcachenoupdate(e); +}, false); +applicationCache.addEventListener('downloading', function(e) { + return window.parent.onappcachedownloading(e); +}, false); +applicationCache.addEventListener('progress', function(e) { + return window.parent.onappcacheprogress(e); +}, false); +applicationCache.addEventListener('updateready', function(e) { + return window.parent.onappcacheupdateready(e); +}, false); +applicationCache.addEventListener('cached', function(e) { + return window.parent.onappcachecached(e); +}, false); +window.onload = function() { + window.parent.onloadappcache(); +}; +</script> +</head> +<body> +</body> +</html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/clone/index.html b/sca-cpp/trunk/hosting/server/htdocs/clone/index.html index 0a2f7733bc..c6a9658ce0 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/clone/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/clone/index.html @@ -19,29 +19,24 @@ --> <div id="bodydiv" class="body"> -<div class="viewform"> +<div id="viewform" class="viewform"> <form id="cloneAppForm"> <table style="width: 100%;"> -<tr><td><b>New App Name:</b></td></tr> -<tr><td><input type="text" id="appName" class="flatentry" size="15" autocapitalize="off" placeholder="Your app name"/></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Icon:</b></td></tr> -<tr><td><img id="appimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Title:</b></td></tr> -<tr><td><input type="text" id="appTitle" class="flatentry" size="30" placeholder="Enter the title of your app" style="width: 300px;"/></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Description:</b></td></tr> -<tr><td><textarea id="appDescription" class="flatentry" cols="40" rows="3" placeholder="Enter a short description of your app" style="width: 300px;"></textarea></td></tr> -<tr><td> -<input id="cloneAppOKButton" type="submit" class="graybutton bluebutton" style="font-weight: bold;" value="Clone" title="Clone the app"/> +<tr><td class="label">New URL:</td></tr> +<tr><td><span id="hostname" class="readentry"></span><input type="text" id="appName" class="flatentry" size="18" autocapitalize="off" placeholder="New app name"/></td></tr> +<tr><td style="padding-top: 20px;"> +<input id="cloneAppOKButton" type="submit" class="bluebutton" style="font-weight: bold;" value="Clone" title="Clone this app"/> <input id="cloneAppCancelButton" type="button" class="graybutton" value="Cancel"/> </td></tr> </table> </form> +<br/> </div> <script type="text/javascript"> -(function() { +(function clonebody() { /** * Get the app name. @@ -49,20 +44,20 @@ var appname = ui.fragmentParams(location)['app']; /** - * Set page titles. + * Setup page layout. */ -document.title = config.windowtitle() + ' - ' + config.clone() + ' - ' + appname; -$('viewhead').innerHTML = '<span class="smenu">' + config.clone() + ' ' + appname + '</span>'; -$('cloneAppOKButton').value = config.clone(); -$('cloneAppOKButton').title = config.clone() + ' this app'; - -/** - * Set images. - */ -$('appimg').src = ui.b64img(appcache.get('/public/app.b64')); +(function layout() { + document.title = config.windowtitle() + ' - ' + config.clone() + ' - ' + appname; + $('viewhead').innerHTML = '<span class="smenu">' + config.clone() + ' ' + appname + '</span>'; + if (!ui.isMobile()) + $('viewform').className = 'viewform flatscrollbars'; + $('hostname').innerHTML = window.location.hostname + '/'; + $('cloneAppOKButton').value = config.clone(); + $('cloneAppOKButton').title = config.clone() + ' this app'; +})(); /** - * Init service references. + * Initialize service references. */ var editorComp = sca.component("Editor"); var apps = sca.reference(editorComp, "apps"); @@ -71,50 +66,60 @@ var apps = sca.reference(editorComp, "apps"); * The current app entry and corresponding saved XML content. */ var appentry; -var savedappentryxml = ''; +var savedappxml = ''; /** - * Get and display an app. + * Get and display the requested app. */ -function getapp(name) { - if (isNil(name)) +(function getapp() { + if (isNil(appname)) return false; - showStatus('Loading'); + workingstatus(true); + showstatus('Loading'); - return apps.get(name, function(doc) { + return apps.get(appname, function(doc) { // Stop now if we didn't get the app if (doc == null) { - showError('App not available'); + errorstatus('Couldn\'t get the app info'); + workingstatus(false); return false; } - showOnlineStatus(); - - appentry = doc != null? car(elementsToValues(atom.readATOMEntry(mklist(doc)))) : mklist("'entry", mklist("'title", ''), mklist("'id", name)); - $('appTitle').value = cadr(assoc("'title", cdr(appentry))); - var content = cadr(assoc("'content", cdr(appentry))); - var description = assoc("'description", content); - $('appDescription').value = isNil(description) || isNil(cadr(description))? '' : cadr(description); - savedappentryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry)))); + + appentry = doc != null? car(elementsToValues(atom.readATOMEntry(mklist(doc)))) : mklist("'entry", mklist("'title", ''), mklist("'id", appname)); + var content = cadr(assoc("'content", appentry)); + savedappxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry)))); + + onlinestatus(); + workingstatus(false); return true; }); -} +})(); /** * Save an app. */ -function save(name, entryxml) { - showStatus('Saving'); - savedappentryxml = entryxml; - apps.put(name, savedappentryxml, function(e) { +function saveapp(name, entryxml) { + workingstatus(true); + showstatus('Saving'); + + savedappxml = entryxml; + apps.put(name, savedappxml, function(e) { if (e) { - showStatus('Local copy'); + if (e.code && e.code == 404) { + errorstatus('App name is taken, please pick another name'); + workingstatus(false); + return false; + } + showstatus('Local copy'); + workingstatus(false); return false; } - showStatus('Saved'); + showstatus('Saved'); + workingstatus(false); - // Open it in the page editor - ui.navigate('/#view=page&app=' + name, '_view'); + // Open the app in the page editor + ui.navigate('/#view=info&app=' + name, '_view'); return false; }); return false; @@ -124,19 +129,36 @@ function save(name, entryxml) { * Clone an app. */ $('cloneAppForm').onsubmit = function() { + + // Validate app name var name = $('appName').value; - if (name == '') { - showError('Missing app name'); + if (name.length < 3 || name.length > 10) { + errorstatus('App name must be between 3 and 10 characters'); + return false; + } + name = name.toLowerCase(); + var anum = name.split('').reduce(function(p, c, i, a) { return p && ((c >= 'a' && c <= 'z') || (c >= '0' && c<= '9')); }, true); + if (!anum) { + errorstatus('App name is taken, please pick another name'); + return false; + } + if (name.charAt(0) < 'a' || name.charAt(0) > 'z') { + errorstatus('App name must start with a letter'); + return false; + } + + // Check reserved app names + var reserved = mklist('account', 'app', 'cache', 'clone', 'create', 'delete', 'graph', 'home', 'login', 'new', 'page', 'proxy', 'public', 'private', 'info', 'store'); + if (!isNil(assoc(name, map(function(r) { return mklist(r, r); }, reserved)))) { + errorstatus('App name is taken, please pick another name'); return false; } - showStatus('Saving'); // Clone the app - var title = $('appTitle').value; - var description = $('appDescription').value; - appentry = mklist("'entry", mklist("'title", title != ''? title : name), mklist("'id", appname), mklist("'content", mklist("'stats", mklist("'description", description)))); + showstatus('Modified'); + appentry = mklist("'entry", mklist("'title", name), mklist("'id", appname), mklist("'author", username)); var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry)))); - return save(name, entryxml); + return saveapp(name, entryxml); }; /** @@ -146,11 +168,6 @@ $('cloneAppCancelButton').onclick = function() { history.back(); }; -/** - * Get the current app. - */ -getapp(appname); - })(); </script> diff --git a/sca-cpp/trunk/hosting/server/htdocs/config.js b/sca-cpp/trunk/hosting/server/htdocs/config.js index 70d3ea1195..355174e6f7 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/config.js +++ b/sca-cpp/trunk/hosting/server/htdocs/config.js @@ -32,7 +32,7 @@ config.pagetitle = function() { }; config.hometitle = function() { - return '<br/><span style="font-weight: bold;">Create SCA Composite Apps</span><br/><br/>'; + return '<br/><span style="font-weight: bold;">Create SCA Apps</span><br/><br/>'; }; config.clone = function() { diff --git a/sca-cpp/trunk/hosting/server/htdocs/create/index.html b/sca-cpp/trunk/hosting/server/htdocs/create/index.html index d8d2b30f3c..a11c0958f5 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/create/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/create/index.html @@ -19,43 +19,38 @@ --> <div id="bodydiv" class="body"> -<div class="viewform"> +<div id="viewform" class="viewform"> <form id="createAppForm"> <table style="width: 100%;"> -<tr><td><b>App Name:</b></td></tr> -<tr><td><input type="text" id="appName" class="flatentry" size="15" autocapitalize="off" placeholder="Your app name"/></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>App Icon:</b></td></tr> -<tr><td><img id="appimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>App Title:</b></td></tr> -<tr><td><input type="text" id="appTitle" class="flatentry" size="30" placeholder="Enter the title of your app" style="width: 300px;"/></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Description:</b></td></tr> -<tr><td><textarea id="appDescription" class="flatentry" cols="40" rows="3" placeholder="Enter a short description of your app" style="width: 300px;"></textarea></td></tr> -<tr><td> -<input id="createAppOKButton" type="submit" class="graybutton bluebutton" style="font-weight: bold;" value="Create" title="Create the app"/> +<tr><td class="label">URL:</td></tr> +<tr><td><span id="hostname" class="readentry"></span><input type="text" id="appName" class="flatentry" size="18" autocapitalize="off" placeholder="New app name"/></td></tr> +<tr><td style="padding-top: 20px;"> +<input id="createAppOKButton" type="submit" class="bluebutton" style="font-weight: bold;" value="Create" title="Create the app"/> <input id="createAppCancelButton" type="button" class="graybutton" value="Cancel"/> </td></tr> </table> </form> +<br/> </div> <script type="text/javascript"> -(function() { +(function createbody() { /** - * Set page titles. + * Setup page layout. */ -document.title = config.windowtitle() + ' - Create App'; -$('viewhead').innerHTML = '<span class="smenu">Create an App</span>'; - -/** - * Set images. - */ -$('appimg').src = ui.b64img(appcache.get('/public/app.b64')); +(function layout() { + document.title = config.windowtitle() + ' - New App'; + $('viewhead').innerHTML = '<span class="smenu">New App</span>'; + if (!ui.isMobile()) + $('viewform').className = 'viewform flatscrollbars'; + $('hostname').innerHTML = window.location.hostname + '/'; +})(); /** - * Init service references. + * Initialize service references. */ var editorComp = sca.component("Editor"); var apps = sca.reference(editorComp, "apps"); @@ -64,23 +59,32 @@ var apps = sca.reference(editorComp, "apps"); * The current app entry and corresponding saved XML content. */ var appentry; -var savedappentryxml = ''; +var savedappxml = ''; /** * Save an app. */ -function save(name, entryxml) { - showStatus('Saving'); - savedappentryxml = entryxml; - apps.put(name, savedappentryxml, function(e) { +function saveapp(name, entryxml) { + workingstatus(true); + showstatus('Saving'); + + savedappxml = entryxml; + apps.put(name, savedappxml, function(e) { if (e) { - showStatus('Local copy'); + if (e.code && e.code == 404) { + errorstatus('App name is taken, please pick another name'); + workingstatus(false); + return false; + } + showstatus('Local copy'); + workingstatus(false); return false; } - showStatus('Saved'); + showstatus('Saved'); + workingstatus(false); - // Open it in the page editor - ui.navigate('/#view=page&app=' + name, '_view'); + // Open the app in the page editor + ui.navigate('/#view=info&app=' + name, '_view'); return false; }); return false; @@ -90,19 +94,36 @@ function save(name, entryxml) { * Create an app. */ $('createAppForm').onsubmit = function() { + + // Validate app name var name = $('appName').value; - if (name == '') { - showError('Missing app name'); + if (name.length < 3 || name.length > 10) { + errorstatus('App name must be between 3 and 10 characters'); + return false; + } + name = name.toLowerCase(); + var anum = name.split('').reduce(function(p, c, i, a) { return p && ((c >= 'a' && c <= 'z') || (c >= '0' && c<= '9')); }, true); + if (!anum) { + errorstatus('App name can only use numbers and letters'); + return false; + } + if (name.charAt(0) < 'a' || name.charAt(0) > 'z') { + errorstatus('App name must start with a letter'); + return false; + } + + // Check reserved app names + var reserved = mklist('account', 'app', 'cache', 'clone', 'create', 'delete', 'graph', 'home', 'login', 'new', 'page', 'proxy', 'public', 'private', 'info', 'store'); + if (!isNil(assoc(name, map(function(r) { return mklist(r, r); }, reserved)))) { + errorstatus('App name is taken, please pick another name'); return false; } - showStatus('Modified'); // Clone the 'new' app template - var title = $('appTitle').value; - var description = $('appDescription').value; - appentry = mklist("'entry", mklist("'title", title != ''? title : name), mklist("'id", 'new'), mklist("'content", mklist("'stats", mklist("'description", description)))); + showstatus('Modified'); + appentry = mklist("'entry", mklist("'title", name), mklist("'id", 'new'), mklist("'author", username)); var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry)))); - return save(name, entryxml); + return saveapp(name, entryxml); }; /** @@ -115,7 +136,7 @@ $('createAppCancelButton').onclick = function() { /** * Show the status. */ -showOnlineStatus(); +onlinestatus(); })(); </script> diff --git a/sca-cpp/trunk/hosting/server/htdocs/delete/index.html b/sca-cpp/trunk/hosting/server/htdocs/delete/index.html index 5a668af401..81cfa0b625 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/delete/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/delete/index.html @@ -19,31 +19,32 @@ --> <div id="bodydiv" class="body"> -<div class="viewform"> +<div id="viewform" class="viewform"> <form id="deleteAppForm"> <table style="width: 100%;"> -<tr><tr><td style="padding-top: 6px;"><b>App Icon:</b></td></tr> -<tr><td><img id="appimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>App Title:</b></td></tr> -<tr><td><input type="text" id="appTitle" class="flatentry" size="30" readonly="readonly" style="width: 300px;"/></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Author:</b></td></tr> -<tr><td><span id="appAuthor"></span></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Updated:</b></td></tr> -<tr><td><span id="appUpdated"></span></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Description:</b></td></tr> -<tr><td><textarea id="appDescription" class="flatentry" cols="40" rows="3" readonly="readonly" style="width: 300px;"></textarea></td></tr> +<tr><tr><td class="label">URL:</td></tr> +<tr><td><input type="text" id="appURL" class="readentry" size="30" readonly="readonly" placeholder="App URL" style="width: 300px;"/></td></tr> +<tr><tr><td class="label">Icon:</td></tr> +<tr><td><img id="appIcon" style="width: 50px; height: 50px; vertical-align: top;"></td></tr> +<tr><tr><td class="label">Author:</td></tr> +<tr><td><img id="authorPhoto" style="width: 50px; height: 50px; vertical-align: middle;"><input type="text" id="appAuthor" class="readentry" size="30" readonly="readonly" placeholder="Author of the app" style="width: 248px;"/></td></tr> +<tr><tr><td class="label">Updated:</td></tr> +<tr><td><input type="text" id="appUpdated" class="readentry" size="30" readonly="readonly" placeholder="App update date" style="width: 300px;"/></td></tr> +<tr><tr><td class="label">Description:</td></tr> +<tr><td><textarea id="appDescription" class="readentry" cols="40" rows="3" readonly="readonly" placeholder="No description for this app" style="width: 300px;"></textarea></td></tr> <tr><td> -<input id="deleteAppOKButton" type="submit" class="graybutton bluebutton" style="font-weight: bold;" value="Delete" title="Delete the app"/> +<input id="deleteAppOKButton" type="submit" class="bluebutton" style="font-weight: bold;" value="Delete" title="Delete the app"/> <input id="deleteAppCancelButton" type="button" class="graybutton" value="Cancel"/> </td></tr> </table> </form> +<br/> </div> <script type="text/javascript"> -(function() { +(function deletebody() { /** * Get the app name. @@ -51,18 +52,24 @@ var appname = ui.fragmentParams(location)['app']; /** - * Set page titles. + * Setup page layout. */ -document.title = config.windowtitle() + ' - ' + 'Delete' + ' - ' + appname; -$('viewhead').innerHTML = '<span class="smenu">Delete ' + appname + '</span>'; +(function layout() { + document.title = config.windowtitle() + ' - ' + 'Delete' + ' - ' + appname; + $('viewhead').innerHTML = '<span class="smenu">Delete ' + appname + '</span>'; + if (!ui.isMobile()) + $('viewform').className = 'viewform flatscrollbars'; + $('appURL').value = window.location.hostname + '/' + appname + '/'; +})(); /** * Set images. */ -$('appimg').src = ui.b64img(appcache.get('/public/app.b64')); +$('appIcon').src = ui.b64png(appcache.get('/public/app.b64')); +$('authorPhoto').src = ui.b64png(appcache.get('/public/user.b64')); /** - * Init service references. + * Initialize service references. */ var editorComp = sca.component("Editor"); var apps = sca.reference(editorComp, "apps"); @@ -73,49 +80,56 @@ var apps = sca.reference(editorComp, "apps"); var appentry; /** - * Get and display an app. + * Get and display the requested app. */ -function getapp(name) { - if (isNil(name)) +(function getapp() { + if (isNil(appname)) return false; - showStatus('Loading'); + workingstatus(true); + showstatus('Loading'); - return apps.get(name, function(doc) { + return apps.get(appname, function(doc) { // Stop now if we didn't get the app if (doc == null) { - showError('App not available'); + errorstatus('Couldn\'t get the app info'); + workingstatus(false); return false; } - showOnlineStatus(); - appentry = doc != null? car(elementsToValues(atom.readATOMEntry(mklist(doc)))) : mklist("'entry", mklist("'title", ''), mklist("'id", name)); - $('appTitle').value = cadr(assoc("'title", cdr(appentry))); - $('appAuthor').innerHTML = cadr(assoc("'author", cdr(appentry))); - $('appUpdated').innerHTML = cadr(assoc("'updated", cdr(appentry))); - var content = cadr(assoc("'content", cdr(appentry))); + appentry = doc != null? car(elementsToValues(atom.readATOMEntry(mklist(doc)))) : mklist("'entry", mklist("'title", ''), mklist("'id", appname)); + var author = cadr(assoc("'author", appentry)); + $('appAuthor').value = author.split('@')[0]; + $('appUpdated').value = xmldatetime(cadr(assoc("'updated", appentry))).toLocaleDateString(); + var content = cadr(assoc("'content", appentry)); var description = assoc("'description", content); $('appDescription').value = isNil(description) || isNil(cadr(description))? '' : cadr(description); + + onlinestatus(); + workingstatus(false); return true; }); -} +})(); /** - * Delete an app. + * Delete the app. */ $('deleteAppForm').onsubmit = function() { - showStatus('Deleting'); + workingstatus(true); + showstatus('Deleting'); // Delete the app apps.del(appname, function(e) { if (e) { - showStatus('Local copy'); + showstatus('Local copy'); + workingstatus(false); return false; } - showOnlineStatus(); + onlinestatus(); + workingstatus(false); // Return to the app store - ui.navigate('/#view=store', '_view'); + ui.navigate('/#view=store&category=myapps&idx=5', '_view'); return false; }); return false; @@ -128,11 +142,6 @@ $('deleteAppCancelButton').onclick = function() { history.back(); }; -/** - * Get the current app. - */ -getapp(appname); - })(); </script> diff --git a/sca-cpp/trunk/hosting/server/htdocs/favicon-16.xcf b/sca-cpp/trunk/hosting/server/htdocs/favicon-16.xcf Binary files differnew file mode 100644 index 0000000000..e43dcba033 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/favicon-16.xcf diff --git a/sca-cpp/trunk/hosting/server/htdocs/favicon-32.xcf b/sca-cpp/trunk/hosting/server/htdocs/favicon-32.xcf Binary files differnew file mode 100644 index 0000000000..c1736338ec --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/favicon-32.xcf diff --git a/sca-cpp/trunk/hosting/server/htdocs/favicon.ico b/sca-cpp/trunk/hosting/server/htdocs/favicon.ico Binary files differindex a7b502b9e1..bd60462963 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/favicon.ico +++ b/sca-cpp/trunk/hosting/server/htdocs/favicon.ico diff --git a/sca-cpp/trunk/hosting/server/htdocs/graph/index.html b/sca-cpp/trunk/hosting/server/htdocs/graph/index.html index d360336375..d01bfa1c02 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/graph/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/graph/index.html @@ -19,13 +19,13 @@ --> <div id="bodydiv" class="body"> -<div id="contentdiv" class="viewcontent" style="width: 2500px;"> -<div id="graphdiv" class="graphdiv" style="top: 0px; left: -2500px; width: 5000px; height: 5000px;"></div> -<div id="playdiv" style="position: absolute; top: 0x; left: 0px; width: 2500px; height: 5000px; visibility: hidden"></div> +<div id="viewcontent" class="viewcontent" style="width: 100%;"> +<div id="graphdiv" class="graphcontent" style="top: 0px; left: -2500px; width: 5000px; height: 5000px;"></div> +<div id="playdiv" style="position: absolute; top: 0x; left: 0px; width: 2500px; height: 5000px; display: none"></div> </div> <script type="text/javascript"> -(function() { +(function graphbody() { /** * Get the current app name. @@ -49,11 +49,14 @@ document.title = config.windowtitle() + ' - ' + config.logic() + ' - ' + appname * Set header div. */ $('viewhead').innerHTML = '<span id="appTitle" class="cmenu">' + appname + '</span>' + -'<input type="button" id="deleteCompButton" title="Delete a component" class="graybutton redbutton plusminus" style="position: absolute; top: 4px; left: 5px;" disabled="true" value="-"/>' + -'<span style="position: absolute; top: 0px; left: 45px; right: 115px; padding: 0px; background: transparent;"><input id="compValue" type="text" value="" class="flatentry" title="Component value" autocapitalize="off" placeholder="Value" style="position: absolute; left: 0px; top: 4px; width: 100%; visibility: hidden;" readonly="readonly"/></span>' + +'<input type="button" id="deleteCompButton" title="Delete a component" class="redbutton plusminus" style="position: absolute; top: 4px; left: 5px;" disabled="true" value="-"/>' + +'<span style="position: absolute; top: 0px; left: 45px; right: 115px; padding: 0px; background: transparent;"><input id="compValue" type="text" value="" class="flatentry" title="Component value" autocapitalize="off" placeholder="Value" style="position: absolute; left: 0px; top: 4px; width: 100%; display: none;" readonly="readonly"/></span>' + '<input type="button" id="playCompButton" title="View component value" class="graybutton plusminus" style="position: absolute; top: 4px; right: 75px;" disabled="true" value=">"/>' + -'<input type="button" id="copyCompButton" title="Copy a component" class="graybutton bluebutton" style="position: absolute; top: 4px; right: 40px;" disabled="true" value="C"/>' + -'<input type="button" id="addCompButton" title="Add a component" class="graybutton bluebutton plusminus" style="position: absolute; top: 4px; right: 5px;" disabled="true" value="+"/>'; +'<input type="button" id="copyCompButton" title="Copy a component" class="bluebutton" style="position: absolute; top: 4px; right: 40px;" disabled="true" value="C"/>' + +'<input type="button" id="addCompButton" title="Add a component" class="bluebutton plusminus" style="position: absolute; top: 4px; right: 5px;" disabled="true" value="+"/>'; + +if (!ui.isMobile()) + $('viewcontent').className = 'viewcontent flatscrollbars'; /** * Track the current app composite, author, and saved XML content. @@ -159,8 +162,8 @@ graph.mkedit = function(graphdiv, pos, atitle, cvalue, cadd, ccopy, cdelete, onc graph.dragged = false; graph.selected = null; cvalue.readOnly = true; - cvalue.style.visibility = 'hidden'; - atitle.style.visibility = 'visible'; + cvalue.style.display = 'none'; + atitle.style.display = 'block'; ccopy.disabled = true; cdelete.disabled = true; cadd.disabled = !editable; @@ -182,6 +185,54 @@ graph.mkedit = function(graphdiv, pos, atitle, cvalue, cadd, ccopy, cdelete, onc } /** + * Render component move animation. + */ + function onmoveanimation() { + //debug('onmoveanimation'); + + // Stop animation if we're not dragging an element anymore + if (graph.dragging == null) + return true; + + // Request the next animation frame + ui.animation(onmoveanimation); + + // Nothing to do if the selected component has not moved + if (graph.moveX == graph.dragX && graph.moveY == graph.dragY) + return true; + + // Remember that the selected component has been dragged + graph.dragged = true; + + // Cut wire to the dragged component + if (graph.dragging.parentNode != graphdiv) { + var compos = scdl.composite(graphdiv.compos); + setElement(compos, graph.sortcompos(graph.cutwire(graph.dragging, compos, graphdiv))); + + // Bring component to the top + graph.bringtotop(graph.dragging, graphdiv); + } + + // Calculate new position of the dragged component + var gpos = graph.relpos(graph.dragging); + var newX = gpos.x + (graph.moveX - graph.dragX); + var newY = gpos.y + (graph.moveY - graph.dragY); + if (newX >= graph.palcx) + graph.dragX = graph.moveX + else + newX = graph.palcx; + if (newY >= 0) + graph.dragY = graph.moveY; + else + newY = 0; + + // Move it + graph.move(graph.dragging, graph.mkpath().pos(newX, newY)); + + return false; + }; + + /** * Handle a mouse down or touch start event. */ function onmousedown(e) { @@ -209,6 +260,9 @@ graph.mkedit = function(graphdiv, pos, atitle, cvalue, cadd, ccopy, cdelete, onc graph.dragX = pos.screenX; graph.dragY = pos.screenY; + // Start move animation + ui.animation(onmoveanimation); + e.preventDefault(); return true; }; @@ -393,69 +447,28 @@ graph.mkedit = function(graphdiv, pos, atitle, cvalue, cadd, ccopy, cdelete, onc /** * Handle a mouse or touch move event. */ - function onmousemove(e) { - if (graph.dragging == null) - return true; - - // Ignore duplicate mouse move events - if (graph.moveX == graph.dragX && graph.moveY == graph.dragY) - return true; - - // Remember that the component was dragged - graph.dragged = true; - - // Cut wire to component - if (graph.dragging.parentNode != graphdiv) { - var compos = scdl.composite(graphdiv.compos); - setElement(compos, graph.sortcompos(graph.cutwire(graph.dragging, compos, graphdiv))); - - // Bring component to the top - graph.bringtotop(graph.dragging, graphdiv); - } - - // Calculate new position of dragged element - var gpos = graph.relpos(graph.dragging); - var newX = gpos.x + (graph.moveX - graph.dragX); - var newY = gpos.y + (graph.moveY - graph.dragY); - if (newX >= graph.palcx) - graph.dragX = graph.moveX - else - newX = graph.palcx; - if (newY >= 0) - graph.dragY = graph.moveY; - else - newY = 0; - - // Move the dragged element - graph.move(graph.dragging, graph.mkpath().pos(newX, newY)); - - return false; - }; - if (!ui.isMobile()) { window.onmousemove = function(e) { //debug('onmousemove'); - // Remember mouse position + // Record mouse position graph.moveX = e.screenX; graph.moveY = e.screenY; - - return onmousemove(e); + if (graph.dragging == null) + return true; + return false; } } else { graphdiv.ontouchmove = function(e) { //debug('ontouchmove'); - // Remember touch position + // Record touch position var pos = e.touches[0]; - if (graph.moveX == pos.screenX && graph.moveY == pos.screenY) - return true; graph.moveX = pos.screenX; graph.moveY = pos.screenY; - if (graph.moveX == graph.dragX && graph.moveY == graph.dragY) + if (graph.dragging == null) return true; - - return onmousemove(e); + return false; } } @@ -465,7 +478,7 @@ graph.mkedit = function(graphdiv, pos, atitle, cvalue, cadd, ccopy, cdelete, onc function onvaluechange() { if (graph.selected == null) return false; - if (graphdiv.parentNode.style.visibility == 'hidden') + if (graphdiv.parentNode.style.display == 'none') return false; // Change component name and refactor references to it @@ -496,8 +509,8 @@ graph.mkedit = function(graphdiv, pos, atitle, cvalue, cadd, ccopy, cdelete, onc graph.setproperty(graph.selected.comp, cvalue.value); var hasprop = graph.hasproperty(graph.selected.comp); cvalue.readOnly = (hasprop? false : true) || !editable; - cvalue.style.visibility = hasprop? 'visible' : 'hidden'; - atitle.style.visibility = hasprop? 'hidden' : 'visible'; + cvalue.style.display = hasprop? 'block' : 'none'; + atitle.style.display = hasprop? 'none' : 'block'; cvalue.value = graph.property(graph.selected.comp); // Refresh the composite @@ -597,10 +610,11 @@ graph.mkedit = function(graphdiv, pos, atitle, cvalue, cadd, ccopy, cdelete, onc return false; }; - // Create a hidden SVG element to help compute the width - // of component and reference titles + // Create a hidden element to help compute the width of + // component and reference titles graph.offtitles = document.createElement('span'); graph.offtitles.style.visibility = 'hidden'; + graph.offtitles.style.display = 'block'; graph.offtitles.position = 'absolute'; graph.offtitles.top = -500; graph.offtitles.width = 500; @@ -792,8 +806,8 @@ graph.compselect = function(g, s, atitle, cvalue, ccopy, cdelete) { if (isNil(g) || !s) { cvalue.value = ''; cvalue.readOnly = true; - cvalue.style.visibility = 'hidden'; - atitle.style.visibility = 'visible'; + cvalue.style.display = 'none'; + atitle.style.display = 'block'; ccopy.disabled = true; cdelete.disabled = true; if (isNil(g)) @@ -806,8 +820,8 @@ graph.compselect = function(g, s, atitle, cvalue, ccopy, cdelete) { cvalue.value = graph.hasproperty(g.comp)? graph.property(g.comp) : g.id; cvalue.readOnly = false || !editable; - cvalue.style.visibility = 'visible'; - atitle.style.visibility = 'hidden'; + cvalue.style.display = 'block'; + atitle.style.display = 'none'; ccopy.disabled = false || !editable; cdelete.disabled = false || !editable; @@ -1817,13 +1831,15 @@ var bpalette = null; function getapp(name, g) { if (isNil(name)) return false; - showStatus('Loading'); + workingstatus(true); + showstatus('Loading'); return composites.get(name, function(doc) { // Stop now if we didn't get a composite if (doc == null) { - showError('App not available'); + errorstatus('Couldn\'t get the app info'); + workingstatus(false); return false; } @@ -1849,7 +1865,12 @@ function getapp(name, g) { author = elementValue(namedElementChild("'author", composentry)); editable = author == username; cadd.disabled = !editable; - showStatus(editable? onlineStatus() : 'Read only'); + if (editable) + onlinestatus(); + else + showstatus('Read only'); + + workingstatus(false); return true; }); } @@ -1912,17 +1933,21 @@ function installpalette(name, pos, g, bg, palette, gpalettes) { * Save the current composite. */ function save(savexml) { - showStatus('Saving'); + workingstatus(true); + showstatus('Saving'); + savedcomposxml = savexml; var entry = '<?xml version="1.0" encoding="UTF-8"?>\n' + '<entry xmlns="http://www.w3.org/2005/Atom">' + '<title type="text">' + appname + '</title><id>' + appname + '</id><author><email>' + author + '</email></author>' + '<content type="application/xml">' + savedcomposxml + '</content></entry>'; composites.put(appname, entry, function(e) { if (e) { - showStatus('Local copy'); + showstatus('Local copy'); + workingstatus(false); return false; } - showStatus('Saved'); + showstatus('Saved'); + workingstatus(false); return false; }); return true; @@ -1938,17 +1963,17 @@ function oncomposchange(prop) { var newxml = car(writeXML(composite, false)); if (savedcomposxml == newxml) return false; - showStatus('Modified'); + showstatus('Modified'); // Save property changes right away if (prop) return save(newxml); // Autosave other changes after 1 second - showStatus('Modified'); - setTimeout(function() { + showstatus('Modified'); + ui.delay(function autosave() { if (savedcomposxml == newxml) { - showStatus('Saved'); + showstatus('Saved'); return false; } return save(newxml); @@ -1997,7 +2022,7 @@ function showdata(gcomp) { cplay.value = '<'; gvisible = false; pdiv.innerHTML = ''; - pdiv.style.visibility = 'visible'; + pdiv.style.display = 'block'; // Get the component result data var comp = sca.component(gcomp.id, appname); @@ -2030,9 +2055,9 @@ function showdata(gcomp) { return displaydata(t, '100%'); }); - setTimeout(function() { - graphdiv.style.visibility = 'hidden' - }, 0); + ui.async(function hidegraphdiv() { + graphdiv.style.display = 'none' + }); return true; } @@ -2043,13 +2068,13 @@ function showgraph(gcomp) { if (gvisible) return true; cplay.value = '>'; - graphdiv.style.visibility = 'visible' + graphdiv.style.display = 'block' gvisible = true; graph.compselect(gcomp, true, atitle, cvalue, ccopy, cdelete); - setTimeout(function() { - pdiv.style.visibility = 'hidden'; + ui.async(function hideplaydiv() { + pdiv.style.display = 'none'; pdiv.innerHTML = ''; - }, 0); + }); return true; } diff --git a/sca-cpp/trunk/hosting/server/htdocs/home/home.b64 b/sca-cpp/trunk/hosting/server/htdocs/home/home.b64 deleted file mode 100644 index 9131135881..0000000000 --- a/sca-cpp/trunk/hosting/server/htdocs/home/home.b64 +++ /dev/null @@ -1 +0,0 @@ -iVBORw0KGgoAAAANSUhEUgAAAaoAAACRCAIAAAAdJ2t7AAAgAElEQVR42u2d+1NT1/r/99/AD2f68ZfjOOOFGXFwhoLScqgow2E+IAylGUqDCAlBwFgU6yUwMkVyKnKGD7anSvVrcWwZLziVFioUiQJCzIWQkHATwrXCEVQUBP1Uz6d+n7XXTtgJm5ug3J5n3tMuHtfaa+2tefE8a629wrxGQ0NDW5HG4CNAQ0ND/KGhoaEh/tDQ0NAQf2hoaGiIPzQ0NDTEHxoaGhriDw0NDQ3xh4aGhob4Q0NDQ0P8oaGhoSH+0NDQ0BB/aGhoaMsSfy9fvhwbGxsdHX2G9tYMHi88ZHjUS+75L92R4/NchPe4iPA3MNyhsV4pbzz1q/mr4oZM1FsSPN5yS6763qX23obBwcHnz5/T5/981DrQ90NfV9b9zmP3O1MXpWBgWX09F/vvG/kjb3nyNK/5nsJg3KczJGnrUDMUPC5FnfGMpVnf3ePwL2GoZbAxr1+r6Ffv669NWtqCW4AbaTjT36Xn3+Piwl/rv6tKzEpk07sUPHBjR3l/f//w8PDTxxX3O9MWK/UmKq2v9wYdeUnv/b1IvbkJHuD1e230eQ73lPSr9y556glwcG9/y3V6j4sLfxD32dlXYs4sa838rS2zvF2JekuCxwsPGR41PHaIAXu67va0HaVk6e9Kfdyf+nQgdXhwMQoGBsODQQIBIQa8025NUOu4j7HOkGC0JDQ0JZibUTMVPC6jBR4dEBBiwJ62O73VCZQXD9RJT/QJI4aEZ/VLW3ALcCNwO4SAXXog4NxjwPnEH+S8lH1lrcrb9zMr+5SodyB41PDASRbccrqj+SCwD8jyn1HF/40tdsEgYaiQBWdr66TVtcC+RKNF1tYpa+9CvYnaOuEBQhZsvZvdeUsK7HuiT/yzSfa6efkIbgduimTB/f2QBS8W/L18+bK88RSN+5B9C0DAhv9pb063Nqfc7zy6JNhnJ2CP9cT+O2pJVU2Ctg7ZN3cCKrQG6+39nSpJf03CMmOfnYAPdIp+1ua4EjJv+BsbG6NrHZCOTftxVdXHfRYXVNCB5JpEPYcPy33/UXts5k1KGr5qbzoE+BvoPbyQRBved/6gd5Hl4Myb3O84JquqAfzJ6oz2j7G4MNd1x1aXLYnhjXPCgbT2qvvu9IjGhWk+hew3GHYLujgGXUhrCmhhjlfepzV0qGSAv4d3x5HxoibCqpHOFjRv1uod6EmdnOIPsLMo8Dc6Okoz39/apsdfaVkQw7x3ug1JN4naktYwTHxZ2izwZ84E9oEe3j+0kPgbiHVjmBO1B2bepL87TcLiT2ow2T7DdV4Mw6yP9ErLnSMOYorSGWZDqGlhmk+u8Rv8+HomlKCLmKJjtDDHi+/VGYB9oMd2chnDQxgm43L07CjzZq3eiUYMCRR/gJ1Fgb9nz55R/JW3T/9ZLVXBg3X9vgfKx1U9yDvH0BgeSE/yhwwjnw3+fjUrKf4e9S1Y9PfHMER/8fBXm/tG+Iurb7B9hitdGca7pHXuoIkpUTKMf3hLl6ylXfqGzbeGzT/+BG4wpgQ4uCVsHvBXT/E3pI3jeGEhIDtVJJkdZd6s1TvRs3oOf4CdRYm/nuTPXN+L/+kwG8vIw13fi81PIeWOZCjv/3//Db/8wuXvM6z5p+4uZT/DxVVR/q7U9568IBk85bVRH7q6xx50rmkXW8E1PM6dVvjsX0kq1n+xIGQzw9haRRWzzis/cc41YR8Ee7/32dkUwU6dVFT2qb1CfL58ik5nPn7BTq9c5Ya3OeA9+O+BOeOvqSjwI9tD+Czzk0djijHLJyEb132+dyN1HsmP/WMSJzR/VP/JZ5zvL7lF8f/H1dz4Zdpm2zUjnrI1u37lOvoo6C/w3zNzwV9jhZttzGszSsEjOq9cZfccOhfNfsijik6vXU99W30uVtJPfnTZOZtzg/f5Chu/GNfdodTrkVMsdW6+wSuvVDaJk4e/xoDPQyFe26lt57NGojq3er2/m+367uz1WefWtSFbSdc5Fc6XdbxBScXp/1ofE25ywN/EG5kD/mIKgrnukrNF4BkqCRCvow6XU2fBIy2Vr2Y83Q16Qhbrtx4Ms/rnil1OrRB/s4r+vkwLYxhxFHChuPjv5CmGicq5tHfN/xSFEI/rBzmq5K//5QtFueqYqn43ZHyb5R9frP+COuN/UrBxIhipmZPzAYGC6tiEQBIu5Xtad/js2R1QjL16uLyKOINPxF6p/+J0PukdEsnSKhEUPkyNKtDJD8cRvnyYkSTYqQNe2VZr4j6+qEvJOUEG8FnBF4Kdznz8gjXLecOTB5A2U0R/5R3KYvNxUHnHpPgbqw+Ei8i+juyyJqmv+jFsTvpUR5zMRm916z59wd+g+OWv+wSdf1gj4FP60cGgJmtSVb43aX4zmavJeN7Uxd/M8ySk0x0cqyf3GJL5SVurNDeI/PEU0d+zh4r+LiIoTBL9tUZcIsxyO1EgqmqUlLH8yiiIrK0JzTsCZa8ik0x7wQU+u7uzRVWVgYf8ye+VqnZpLXGu2pMtqq3ZmZNIal6qo/hj1ifuLKukTo+LdZKyXMbWPCgjlmDrfI2gkwseG03+u8ll/EpMQuEhuX5olSbsm/3s9TWck4n1zUjfnvfVhMve4t9gTAnkvFtCefgTvJHJYLfH0pKoM4CgMFn013eZ/BuTpQdaaySvKgM3Mcw2mW9LZWR1Nvn9nfVD1GvNTvJXuCtgsIL8y9umCHVqhfibdfJ7rQAen3tBn/L0wTXsv4b3L5Iy4eDPLBT+UU9rHv4MUHU25fsTEA6tOalLKzYriptT4r3Ha57k1XSaESt1qHD8yzDSqrTti6+vJhX3KMvb0q6UfbqGRcnZjDWMa1Ax1zAZfrdtzkgS7LScd33SivG9wv14/B9i+CwG/STU6XczHr9gp9+Sjv7ODa9DPkXya7yX3dZ0GFSt/+pK1fFfjMcF8ffHQFLVr7GPhhVjAwe6asPcWCpRft2wUvQcuRrBMBEhA0JO3dcQIfz1RuuBR93Jj/oSTvxtYs19R1ik6rP/CmR4RAH3WDpF8nu9qTCxuhr0490Ckz6t15omnPy2lK625YZSU83Oi8XRLV0Skymy6DRwwbvIFJ4ByIuN5BCgCTiRG65tZ51bA6tM0dq66LpKry2A5NxdLIkCamnNdj8Y3I7snWkQlyXamrcHwP2uTxd0itnmq7Yw7JXbJ8mOmUDb9f1DeJ1WEWeY0GVjeDdop569IHgjEiH2Fel/vKdKBF29dfbzctVejW7a5NecDj2vKq2IHqqJGtJEZMGvsGC/FxASXvbmgr1g3yFMfudh7q85Dj7QaSo5/MqLL4iCv9LDZUlQDi84zDJrzVlu6ePYYW9mTUbSxX+9z/DNFVLUj39yrHnAVRh/XzdzP17McSWM6zmcdtCdfzE5Bx3fIq7hl1N0ys+vv4cLMjtsrej1Q34S6vTMjMcv2OmZE/yOvpx4p/a4D8BHSQcFwB8InALJ7/C+H9M28vux46+qz5Yd561jNvp3Cznv5m92GORGxi0i6D6p+Vf9AK158MxGgj8TAeXfHnCMO0SdgnEfgI+SDgqAPxAJBifir7F4FYs5FoUav89D+QMBvygHPOkxjiwQ5UQ6DHg94xKSzfJrfO1CdMKfkA7+y+yPauc7laFCTjEXx7H9CoVgFH876xyuH8nGjGHsok240GVjeDc4EX+CNxIjFPcB+CjpoAD4A4FTAH/GsG0AsmtkEaMl29Xh4uuYTcG+I2yOzEbzTJ6dd7xWiL83WPo4dhhCadf3yCpHxzESIrGf9dPNSif8HfAmgRgbZ31gi7OUV8piTxanlDjWlAvj7z07iU7G0UAMLuX6D9UXpZAb9sg3s8n192x4ZeNLSjgzaacq5+hvHH8n5YwNf86dfjvj8Qt2mseP/iZf+oCEl2Kut/0g6KdqJeAPnDcbTzjhz/Q1BGXrinRJTx8DCqUfAf50B1n8/cVOuht77TGds7OWxHSeXTZ4ddVG3qhMeOyIv9yNE6K/yZc+IOGlmJPUakF3dUrAHzjbO76aAn/hGRA9+QeU1MQ0kqiQ+Esa2ZBqv5gDgcl/T6T3xRrWaQ8JuyKLCgILK6PZyTsb/tjojIv+xpEUuAcQoxR0Unru1JoCSfLrH1oniL8NdvyRalz0xy2YhAlddmr8Cd7IxHWbRNsK7/0qotSbNwB/4FTo1YL4o1wzKKBnt24bR7qvBZVejngFhW/t4YK71eLcCvH3Jiu/V/LJbBcTEALpZAENeegM4AT8QSDGTX4djLrSnFbwEwmv1qTuKZ0R/pg14pCC5rTv88k03GdXD7Oke//r+mPlbV98KSepd/C/klX1UWRVQS76vipJzrL4w0k6dbz+x+yU3O6iNu76kKcLdjrz8U9S82N2vjKuqO3wyVT3yeb++FHeo74vDMZMIGCxJtPSZPd/weGPUGlzlfXg2EDS1YN/JfM4+fE0+nOLDWzrO2C6Sqb5jtjm/pyc3Ixe2iddfQfabrIVMqOfCuHvqS6InWSMejCw70bmxsnm/vhRnsTQkKP+BQhobMg8pb/BYdEghD/CgsidtY0SU43/Hm4xQaIi83Rr0y5EmUyhOWRCzU/VSifvVn9+LrLORCfXXA5d56bhQpQRdaZwdurQnczNZZOah0jz8DwyYef6TeUkThZkjWRIa8l1cmOEoj+XCHr9/bbrj+NP8LKyKfEneCNTL3EMaSW3b2cBARVVN+/U/NPmt5FLHwogE6f4t1TGvCghs8Ah8oBuTbT1Bx8ob0oJ5SYEFWGAPPK3Lgt85dgK8fdGG1/MsRB5+eeQ1VJV7ad03YBdT4CPuisffx+ydQoKgtbYfgdtjhNd65lQczL82ScuMmLLybbq2GD7dcS+wRDvx0WR0En16Yes88O4Hf5kMIcFO3UizkV28YS/dCvY6azGL9jpRXYVhQ2RXdn5ymNT4+/50BFa5guc3NyfNVJm6+KjWG8ZcGnvJxzp/sb5ZdmRY2MKQSdcoa3I375G+dHekN+HFWP1QLp1TskvyZcLbI03rmMnGQ9Ojb+4pnsc8ngCpw1/pWttdJDWFtiztVURia7rGWb3OfBHnE+3/6W429Zzwelir7w7V9wCKMmm+SP3XDOu0poiFoW21eQLMZM42eb+FGRRl47Q9RAB/G2xXT+tQOLYSrgv3g2ylXew+OMKgjcyNf7+1xRHy3yB08aL6HyRCyUdWds9673JdvFtu/z6LFGnIO1d50FDwr6zHtx6iGMrxN+87fub8oWHL4ub00o7js+wPreFsEOpaksrdXiN5Hip43WuFPw9+GAUN6/XkbSZv4oybadchWk7nc34BWuyTtWMN7gM9H7BZx/86AidI0/7Djx9fMTuYUm3zvRY8cfAAZIUT+60TSAeeuR4hcnf9yA1/5jxBhep3shnH/w4+WaO9pg6U0zjhJWHltZo8LcIOSdUFrgCV7Nreue0+wobu6SmKVvN8rJT3MhkG1we3pXy2Qc/Or/CYZS8Gt/TJxnSRI8Yp3+jw6EV4u+t42+WmvkLJKpaEZt9+x448Xc2BnQ/3fzWO513TVzigHAPEl6QPe6bQk9rIer9i3pgeue8a+ISB4kBDSQRHo/7lpre2mshb7K9GcI9SHhBvLhveQrxZ4OaOelAxqdXZvb6cJEqNl7+QbD4/dgTn15sPv5uOl1U+PujO/ZMdljX4+mdiL8ZvRSsLfZJOx3ZiPhD/C0E/laanPB3r+PET9VKkMGYKZT8LiI54e+0peKuTgnKUf8yXfKLmh5/Wn1u6s0boNu3swSTX8Qf4m9Z4Q/CPbrxBQQFp6WPxYw/CPfoxhcQFJyXPlCzxB+Ee3TjCwgKE5Y+EH+Iv+WCP7rtGfA3cF9B2XftLpHTxpdFiD+67Rnwt9dYT9nXalaAnDe+oGaGP7rtGfA3oNtL2SevvANy3vhiU8u3XjKRu6rC5tdEFKZ7HZW5HZX7VJdxu1teaUSFCq+ju9wyFP5WvdMhCJF5MtfklMAReuxK2c58pW9Bth+R0rf0WhRbTdqS75chc0uWef18TQyewWuB49XYmgVnw8lyikZUoPBI3uV2NMXXUClB/KGmV7H5eLX+KyBgV9uRu+YTFH/gXAwnvkyDvy7Fj3cLgIBJavUVwyWKP/L+r8CJL6jplagzXL11FgjYVZVYWPM9xV8iOe9+wlsflFYl/nS/C/cihz5M7PD+B5NfFPPaGC5z8Nm2QLOqTmGPnvD0oW/FsTuo+W9WhQL7VCkOzrxr0YYUF8bZvIb0Th2tUtVIEX+oaVTewWW7fAm/9LbI9OyhgiKPL+GX3lAz0B5LC0UeX8IvvTVLDNnj74BS/HXnsns6Pb2sGnHhLsKsbSmhfflste0+3TXhR9lTYbJs5/2NFPlw7T192OhPwh4J43JU4Zuv9Dml8Cq8LH5dE8QS1qWwRGxQrqVM7Lvsf0rhk5fuk690t20sFY1cYzfNbvfuM0b/vIvwMSNfjPhDTa9fjA7sm+zIg0WoXqsD+yY98gA1w/xXo+Ozb9IjD/Shm3iBFsWf9ay3ePvqjG8jyeY+Fm0hirChsrBCpU9pCaSiUnrgFYc/S0Syvf12Fn+WCJaPLoVFkd0VYro3cDCfbJneJAsYaZYOloVVXw7v5p0W86LIlzaH4JHiL4Q9WkYjJ/BNZkeC+EPNKAac9sCrRRsDTnfgFWp2MeC0B169NoqydnkZasT5wbzkd1xRp7YTv1hpO9dPHyajBwJ6eg2yHhVLKLHcI8TGr9eandscste11ZVSg2I137VN5NM3njtHZbG4/LmCJrli9kdmk+cqFs2uZj3O/aHmbz/gUhHi723vB7QpJm/7BPxZxHnBztN8L0r8tjF0wm71zyUxQ9fYtFfk/6o5gkzYbfd9QWYS/QBbm7Z7qK6F5rG5MxPsp09fSyfysnIDTolWsZFgEA0M6YFam2SBNiLT4JFh1nEdlVZIEH8oxB/qbeEv3wl/RhHHIMbNoHFGTB+byTIiv9Pbac7rniXj6CaT+VqNvMoadsrP0+d2+mrKQfbEBDY89PQe4nWdV8StL7ewM4MhKTvJLCT7ljGzK/AV4g+F+EO9E/yxJx0QQnnYWdbCrpAcZafhhi57sX/qnes5YeWWcSk55yP2dJGlh5G9MmV+tGYnPTOVIq8mcJO9bAwnq8zrPPpsg6FpMk236cyjbUUF8YdC/KHecvJrzR0/93QTGwOGpIdxC7KMi0zmSldLknMjXuijhzTRQ/qYkcqgEHZCsFsvfUGRxzAyuQf95hByXpZFRLezbAt2E3sy9nPzuYlCHuAGKVsZl2S5RwjbXJwdgckvCvGHemvRX7Btf1+zpHCX83a8EBLKSTW8LTLJ6TtfOH71pdi+8tssNed62BeUxba90C8qgrhlE7igzH+Qt2mGrvPaJDVke4x3LQ8Ywm3PKMQf6q3hb+ZfcRnDHoc1k5oSNjB02rEsHSFOyZs2R/yhEH+ohcIfvvOL+EP8If4Qf4g/xB/iD/GH+EP8If4Qf4i/RY+/DpVsmePPKEf8oRB/qAn409dbb+8H/D3WLNsTT0caFBR/o6OjiD8U4g9lOwhLb7xXcxLwN6hettHfk9bvKP7GxsYQfyjEH4pTQr25sb68QyX5vTr+z6ZlyL4/m+Mf9Bgo/l6+fIn4QyH+UDb8mZubmpqaNRd6q/c80ScuMwIC+550lVD2DQ4OzpFaiD/EH+JvueGvs7PTbDZ3NFf3N5x5oFM8qZOPGBKe1S9lGeUjDQrIee1xH9jz588RfyjEH8oBf4CGtrY2q9Xav3xteHh47tRC/CH+EH/LEH80NwRbfuCDm5p73If4Q/wtFvzJEH/zJ7mlhb8n7uXLl2NjY6Ojo8+WuMEtwI3Mca0D8Yf4W1z6d3fa/jtq8k3nBhPCa+5SNLXOy5bglWCIP8TfAutBT+pJtZZEf/p6hNfc9V1bB+IP8Yf4Wxp61JdaXm8E/MWrtbK2TuTXXBTf3mXo/R3xh/hD/C0NDQ+mNjU1XdDp96i1iUYLEnAu7Cvp6bUvESD+EH+IvyWAP7pPrbql9YylWaEz7NMa9upA9StR+vpEvTGh3pxgbnZWvRn+CCo4Nflcb0ytN55paFS33rPabHBwcJEsLyD+UIi/qfBH96mVmy3Z2rr9d9R0IXgFCm58f7X6pFpbXm+EiBh+K9A4rqftjvVutvX2fnqUy7TqqZT11ybNWup9/VpFf8OZ/i79PG4uQfyhEH/T4O+HBktctVpaXStZqexz0gWdHiJi+K3QbfqhYwbI43RL2lu9503Y58DBvf0t1+drazHiD4X4mwp/d9qtZOmjVpOkrUvQ1snqjFKDKa6+YQWK3Li+Pl6t3aPWVre03mso77wV11sVD/q9On5QHfdYIx3Sxk3UE51suC5+ju+WjRgSnugTHqhZAnbp5+XFMsQfCvE3Ff4g56X4w6UPTm2d8CjOWJoh54WADiKyd3l4AXQE3ZEseD6OFUD8oRB/U218odueceOLEwEVWgM9uLS/JuEdH9wC3T3QKeblUCnEHwrxN9W2Z+6lN962Z4m2MqKufWpASGuuuu8+FtHoXH8mbedR4sJc1x1bXbYkht3ixiOtKbAPbC7apzXQtY6Hd8fB9KImwjr7Y5zfoNWTOvm8HCmK+EMh/qZ66Y1O9o+/9NZYupphPAqneQcupiSTYdaHmhzrz6zt/KnOi4FRRHql5X58nRtPTNExbmBz/dYOA13QGD+23hgewjAZl6Nnh783ajViSJiXA+URfyjE32xOfGkhCPMuaZ0pKfj1Z9t2rqp0ndAdy+UtYfOAvwnf2WYhIDtVJJnl15a/Sav5+jY1xB8K8Tdz/DX6wYeVNbd/fu++foPXJQ3xm0rd1m/wyKtkQ7wKKPucy/mv9THhJl79nGJeuQJqRpedW7ueOjZ4nyceierc6vVb14ZsBZcrW4dLmYnf3213KK3tnlMsnVA5qui0/WpeeaVkGFxvzNqMUknFaXY8DvibOIA54C+mIJjrLjlbBJ6hkgDxOupwOXUWPNJS+WrG092gJ/yyfuvBMKt/rtjl1Arxh/hD/C2Mnj1U9HcRQWGy6E9cmE1wk3EhQttAcBZxDkgUXXiEfIJDciUQXhWlM8zWoMuQY26BHJNXv5VfltZecGGYVXuyRbU1O3MSwe91qS6mRMmiINY3I92/xMQL2Vj/+sTQKk3YN/uh6HFRw6+8Pe8rgpnd2aKqyqCMWILI87ciLpEKbicKRFWNMSXceOz4ExzAZLDbY2lJ1BlAUJgs+uu77AsXkaUHWmskryoDNzHMNplvS2VkdbY7+LN+iHqt2Un4vytgsCIA/r9NEerUCvGH+EP8LYyuNxUmVleDfrxbYNKn9VrTpk1+xeeBGqER7V2hn29lSRQpImXCwV32IGuS5Dc8wx8oGVhlitbWRddVem2hrQiwAqomziQSf2At/bHdP8S5clgaDCAxkqvfHhABrEyP4XVnp569IDgAiRD7ivQ/3lMlgq7eOvt5uWqvRjdt8mtOh55XlVZED9VEDWkisjwZJtjvBYSEl725YC/YdwiTX8Qf4m+RxH0APko6KAD+QCQYnIi/xuJVwJQiNjSruwoBlF9JKSRyXufPrWUY36JiKLud14znmPz6vLIoJ5Lh23rGJSRbTIjmH9YojL+dddyPohP+QLdIXuVw8DD7o9r5FZQxvO4m4k9wADFCcR+Aj5IOCoA/EDgF8GcM2wYgu0YWMVqyXR0uvo7ZFOw7wubIeZ7EkWfnHa8V4g/xh/hbiDm+LgX3RletFnRXpwT8gbO94ytB/Plw6wmNvjsAHBsAQ+GNjf7c1J5/aF2XE/64+rwyG6/F2uK1rsiigsDCymhCtK0T1yVY/G2w4y9wtz364yqzVxvHX+AeZlr8CQ5AOvGbeW0rvPeriFJv3gD8gVOhVwvij3LNoICe3bpttOq+FlR6OeIVFL51txHR3WpxboX4Q/wh/hZ4iUNiaMhR/wIENDZkntLf4LBosOHPdB0+2WsPnRbVNhJq5JGJNmaHEtLGCBpP0RlAO/749XllSVku1F39+bnIOhOdpHM5dD1mKvwxLhHKiDpTeB6Z+3Pn5v64yjElZFZx9aELUSaugus3lbIp8Sc4gKmXOIa0ktu3s4CAiqqbd2r+afPbyKUPBZCJU/xbKmNelPiRJyEP6NZEW3/wgfKmlFBuQlARBsgjvylkga8cWyH+EH+IvwXGX1zTvYkv+YPTRgSTb8SGcVhoCwjRTpSSrc6q0+yaQw1Zqy0DHu1g2cSv79A24ny6iy0WWrU7V9xCW/lPir8tXGXXtAJJu3NlUd4Re6659tCFGHab4Vob/uzjYUFJByYwgKnx97+muIkHGYDTRqXofJELJR1Z2z3rvcl28W27/PosUacg7V3nQUPCvrMe3HqIYyvEH+IP8bfAG1ykeiOfffCjExQkja3S2WwT4dd3aNvSGl1nimlsn24TtZLNr7ukJqg8xR5DerXZ7OCbbgBOc3wP70r57IMfnV/hMEpeje/pkwxpokeM07/R4dAK8Yf4Q/wt7PZmEgMaSCLMi/sWTOxmmg1zf1VjXrY3Q7gHCS+IF/e9ayH+EH+Iv5WCP6m22CftdGQj4g/xh/hD/L1l/J22VNzVKUE56l8Ek9+VIyf8afW5qTdvgG7fzhJMfhF/iD/U0l76oBtfQFCYsPSxcvEH4R7d+AKCwoSlD8Qf4g+1NPFHtz0D/vYa6yn7Ws0KkPPGl5WHP7rtGfA3oNtL2SevvANy2vjyqjI0S+aWkR70AvGH+EMtrW3PP94tAAImqdVXDJco/sj7vxPf+lhhStQZrt46CwTsqkosrPme4i+R/a47pznBkWte7C4XryHEH+IPtbReeqPI40v4pbcVpj2WFoo8voRferNEGX7YqSkRz4Rfr4zSV4g/xB/ib5Go1+rAvkmPPFiB+a9Gx2ffZEcevKgIlG1fLZYHjFDA1YRliVZz255FXoYabpFk8Jq/7SCsVafyIxB/iCESqGQAAAU2SURBVD/E35I58GrFxoDTHng1co283MZ4+pDkVx8mpojzXCv25F5FMesBkf70BECZzJ2+IX1qtqdDI/4Qf4i/BTvtGTUZ/oo4/EH0Z80lZ61uEvnRU14K2dfaxMpwAzkIiwlJIcsj3ex7b/QgLMQf4g/xh/hbJvgzp68lZxpkc7lti5L8GKIIo37Hs7C8hxB/iD/EH+Jv2eCPnve3aVcAu74hLZWtIvhLDzco2OhPsXPEIukrCsrP9lddi3iF+EP8If4Qf8sGf69rgjZxix7uR0Wr2OJqjcZ+2vOqLKVvMjsnOKuDXhB/iD/EH+JvMeLvRQn51g5muw9d+R0qsq/wQobrqirjjvNr+dbLnviG7PIbxJVfxB/iD/G3pPH3Si9WKdmvlvN0mMt7oY8e0kw4x9QSM8ODsBB/iD/EH+JvsePPkMIdnLpJFoTv/CL+UIi/lYS/XO/kXW5HU/y6jXjkAeIPhfhbYXN/eOIL4g+F+Fsp+OtQyRYMf0Y54g/xh/hD/C0Q/vT11tv7AX+PNQtw4ulIg4Lib3R0FPGH+EP8If7e7UFYeuO9mpOAv0H1AkR/T1q/o/gbGxtD/CH+EH+Iv3eqhHpzY315h0rye3X8n03vlH1/Nsc/6DFQ/L18+RLxh/hD/CH+3i3+zM1NTU3Nmgu91Xue6BPfGQGBfU+6Sij7BgcHXy9fQ/wh/hB/ixd/nZ2dZrO5o7m6v+HMA53iSZ18xJDwrP7tyCgfaVBAzmuP+8CeP3+O+EP8If4QfwuAPwBQW1ub1WrtXwgbHh5+vawN8Yf4Q/wtavzRDBTsXYIPulvecR/iD/G3WPAnQ/wJSW5p4e+8e/ny5djY2Ojo6LO3ZnBx6GIZr3Ug/hB/i0v/7k7bf0dNvuncYELk8aVoal32G48Rf6gVjb8HPakn1VoS/enrEXl8fdfWgfhD/KGWM/4e9aWW1xsBf/FqraytE6lHFd/eZej9HfGH+EMtZ/wND6Y2NTVd0On3qLWJRgsSkLKvpKfXvhCB+EP8oZYt/ujutuqW1jOWZkV9g9zUmNDQlGBuXmmSW1oUza2Q89rjvmW/8w7xh1rp+FvY3W2L2Zb9zjvEHwrxtzC72xazrZCdd4g/1Pzgr73p0NLEX/o73t22mG2l7bxD/KHmA38WZXtzOuDv4f1DSwt/TwdO4gQ/GuIPNRf8ZbY2fgv4e9CzxPA3NFCA+END/KHeXKUtmY3mW+1NKb3th/8zumTY95/R1Af9DYg/NMQf6s0Fj52cDWe51N125HF/6pIgILBv6OFN3N2GhvhDzRV/3Nlw7eq+nov/7sl63H/s6UDq8OAiVPrw4EnIee1xH+5uQ0P8oeaEv6W7ew53t6Eh/lBzxd+S2z2Hu9vQlgn+fjV/hfhbKFVYs5bQ7jnc3Ya2rPAH/6DLG08B/n5ry0QYLYA6vsYFBDS0hcEf/DJX37sE+CtrRfwtgOp6ChF/aGgLgz9IZNp7G0rMyhJz5u37SMB3rY7fzYg/NLSFwR/Y4OCgsaMcCFjWqkQCvks191Xi7jk0tIXE3/Pnz+HjBzEgZME3m0/9du/Eb21kJQT1NlRhzars/AZyXnvch7vn0NAWDH9gw8PDeFQR7p5DQ1uJ+KMxIJ7ahrvn0NBWIv6orfBT23D3HBraysUfGhoaGuIPDQ0NDfGHhoaGhvhDQ0NDQ/yhoaGhIf7Q0NDQEH9oaGhoiD80NDQ0xB8aGhoa4g8NDQ0N8YeGhoaG+ENDQ0ND/KGhoaEh/tDQ0NAQf2hoaGiIPzQ0tJVs/x8Xr6boMQqPwwAAAABJRU5ErkJggg==
\ No newline at end of file diff --git a/sca-cpp/trunk/hosting/server/htdocs/home/home.png b/sca-cpp/trunk/hosting/server/htdocs/home/home.png Binary files differdeleted file mode 100644 index 8f5a0b0d86..0000000000 --- a/sca-cpp/trunk/hosting/server/htdocs/home/home.png +++ /dev/null diff --git a/sca-cpp/trunk/hosting/server/htdocs/home/index.html b/sca-cpp/trunk/hosting/server/htdocs/home/index.html index 130c05fda0..d7a098e998 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/home/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/home/index.html @@ -19,7 +19,7 @@ --> <div id="bodydiv" class="body"> -<div class="viewcontent" style="margin-left: auto; margin-right: auto; text-align: center;"> +<div id="viewcontent" class="viewcontent" style="margin-left: auto; margin-right: auto; text-align: center;"> <br/> <div id="hometitle" style="font-size: 28px;"></div> @@ -29,22 +29,27 @@ <div id="homeanimation" style="width: 320px; height: 280px; padding: 0px; margin: 0px auto;"></div> --> -<input type="button" class="graybutton bluebutton" style="font-size: 21px; padding: 10px; height: 50px;" id="getstarted" title="Get Started" value="Get Started"/> +<input type="button" class="bluebutton" style="font-size: 21px; padding: 10px; height: 50px;" id="getstarted" title="Get Started" value="Get Started"/> <br/><br/> -<div class="note">Requires Safari 5+, Chrome 11+, Firefox 4+, IE 9+</div> +<div class="note">Requires Safari 5+, Chrome 11+, Firefox 4+ or IE 9+</div> +<br/> </div> <script type="text/javascript"> -(function() { +(function homebody() { /** - * Set page titles. + * Setup page layout. */ -document.title = config.windowtitle(); -$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; -$('hometitle').innerHTML = config.hometitle(); +(function layout() { + document.title = config.windowtitle(); + $('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; + $('hometitle').innerHTML = config.hometitle(); + if (!ui.isMobile()) + $('viewcontent').className = 'viewcontent flatscrollbars'; +})(); $('getstarted').onclick = function() { return ui.navigate('/#view=store', '_view'); @@ -53,22 +58,24 @@ $('getstarted').onclick = function() { /** * Display animation. */ -var anim = $('homeanimation'); -if (!isNil(anim)) { - anim.style.background = 'url(\'' + ui.b64img(appcache.get('/home/home.b64')) + '\')'; - var bgpos = 0; - setInterval(function() { - bgpos = bgpos -280; - if (bgpos == -2800) - bgpos = 0; - anim.style.backgroundPosition = '0px ' + ui.pixpos(bgpos); - }, 2000); -} +(function homeanimation() { + var anim = $('homeanimation'); + if (!isNil(anim)) { + anim.style.background = 'url(\'' + ui.b64png(appcache.get('/home/home.b64')) + '\')'; + var bgpos = 0; + setInterval(function homeanimation() { + bgpos = bgpos -280; + if (bgpos == -2800) + bgpos = 0; + anim.style.backgroundPosition = '0px ' + ui.pixpos(bgpos); + }, 2000); + } +})(); /** * Show the status. */ -showOnlineStatus(); +onlinestatus(); })(); </script> diff --git a/sca-cpp/trunk/hosting/server/htdocs/index.html b/sca-cpp/trunk/hosting/server/htdocs/index.html index e3e046136d..6ea6f80939 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/index.html @@ -17,50 +17,69 @@ * specific language governing permissions and limitations * under the License. --> -<html manifest="/cache-manifest.cmf"> +<html> <head> +<!-- Firebug inspector --> +<!-- +<script type="text/javascript" src="https://getfirebug.com/releases/lite/1.3/firebug-lite.js"></script> +--> +<!-- Weinre inspector --> +<!-- +<script src="http://www.example.com:9998/target/target-script-min.js#anonymous"></script> +--> <title></title> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"/> +<!-- <meta name="apple-mobile-web-app-capable" content="yes"/> <meta name="apple-mobile-web-app-status-bar-style" content="black"/> -<link rel="apple-touch-icon" href="/public/touchicon.png"/> +--> +<link rel="apple-touch-icon-precomposed" href="/public/touchicon.png"/> <base href="/"/> <script type="text/javascript"> -(function() { +try { + +(function roothead() { window.appcache = {}; /** * Get and cache a resource. */ -appcache.get = function(uri) { +appcache.get = function(uri, mode) { var h = uri.indexOf('#'); var u = h == -1? uri : uri.substring(0, h); // Get resource from local storage first var ls = window.lstorage || localStorage; - var item = null; - try { item = ls.getItem(u); } catch(e) {} - if (item != null && item != '') - return item; + if (mode != 'remote') { + var item = null; + try { item = ls.getItem('ui.r.' + u); } catch(e) {} + if (item != null && item != '') + return item; + if (mode == 'local') + return null; + } // Get resource from network //if (window.debug) debug('appcache.get', u); var http = new XMLHttpRequest(); - http.open("GET", u, false); + http.open("GET", mode == 'remote'? (u + '?t=' + new Date().getTime() + '&r=' + Math.random()) : u, false); http.setRequestHeader("Accept", "*/*"); http.send(null); if (http.status == 200) { - if (http.getResponseHeader("X-Login") != null) { + var xl = http.getResponseHeader("X-Login"); + if (xl != null && xl != '') { if (window.debug) debug('http error', u, 'X-Login'); // Redirect to login page if not signed in document.location = '/login/'; return null; - } else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) { + } + var ct = http.getResponseHeader("Content-Type"); + if (http.responseText == '' || ct == null || ct == '') { if (window.debug) debug('http error', u, 'No-Content'); return null; } - try { ls.setItem(u, http.responseText); } catch(e) {} + try { ls.setItem('ui.r.' + u, http.responseText); } catch(e) {} return http.responseText; } if (window.debug) debug('http error', u, http.status, http.statusText); @@ -83,82 +102,68 @@ appcache.remove = function(uri) { /** * Load Javascript and CSS. */ -(function() { +(function rootboot() { -var bootjs = document.createElement('script'); -bootjs.type = 'text/javascript'; -bootjs.text = appcache.get('/all-min.js'); -document.head.appendChild(bootjs); -document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); +window.eval.call(window, 'try {\n' + appcache.get('/all-min.js') + '\n' + appcache.get('/config-min.js') + '\n} catch(e) { console.log(e.stack); throw e; }\n'); +ui.includeCSS(appcache.get('/ui-min.css')); // Disable cache for testing //lstorage.enabled = false; })(); -/** - * Redirect to login page if not signed in. - */ -(function() { - -if (document.location.protocol == 'https:' && !hasauthcookie()) - document.location = '/login/'; - -})(); +} catch(e) { + if (window.debug) debug(e.stack); + throw e; +} </script> </head> <body class="delayed"> -<div id="mainbodydiv" class="mainbody"> - -<div id="headdiv" class="hsection"> -<script type="text/javascript"> -(function() { -$('headdiv').appendChild(ui.declareScript(appcache.get('/config-min.js'))); - -})(); -</script> +<div id="menucontainer" class="tbarmenu"> +<div id="menu"></div> </div> -<div id="menubackground" class="tbarbackground fixed"></div> -<div id="menu" class="tbarmenu fixed"></div> +<div id="viewheadcontainer" class="viewhead"> +<div id="viewhead"></div> +</div> -<div id="viewheadbackground" class="viewheadbackground fixed"></div> -<div id="viewhead" class="viewhead fixed"></div> +<div id="working" class="working" style="display: none;"></div> <div id="viewcontainer"></div> -<div id="viewfootbackground" class="viewfootbackground fixed"></div> -<div id="viewfoot" class="viewfoot fixed"></div> -<div id="status" class="status fixed" style="visibility: hidden;"></div> +<div id="viewfootcontainer" class="viewfoot"> +<div id="viewfoot"></div> +<div id="status"></div> +</div> + +<div id="installer" class="installer"></div> <script type="text/javascript"> -(function() { +try { -/** - * Init service references. - */ -var editorComp = sca.component("Editor"); -var user= sca.defun(sca.reference(editorComp, "user")); -var accounts = sca.reference(editorComp, "accounts"); +(function rootbody() { /** - * Set page title. + * Setup page layout. */ -document.title = config.windowtitle(); +(function layout() { + + document.title = config.windowtitle(); + $('viewcontainer').className = ui.isMobile()? 'viewcontainer3dm' : 'viewcontainer3d'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; + +})(); /** - * Init div variables. + * Initialize service references. */ -var bdiv = $('mainbodydiv'); -var mdiv = $('menu'); -var hdiv = $('viewhead'); -var vcontainer = $('viewcontainer'); -vcontainer.className = ui.isMobile()? 'viewcontainer3dm' : 'viewcontainer3d'; -var fdiv = $('viewfoot'); +var editorComp = sca.component("Editor"); +var user = sca.defun(sca.reference(editorComp, "user")); +var accounts = sca.reference(editorComp, "accounts"); /** - * The current user name and account entry. + * The current user name. */ window.username = 'anonymous'; @@ -169,134 +174,95 @@ var storecat = 'top'; var storeidx = 0; /** - * Pre-fetch app resources. + * Populate cache with app resources. */ var appresources = [ ['/all-min.js'], ['/ui-min.css'], ['/account/', 9], - ['/clone/', 3], - ['/create/', 2], - ['/delete/', 3], + ['/clone/', 4], + ['/create/', 3], + ['/delete/', 4], ['/graph/', 5], ['/config-min.js'], ['/home/', 0], - ['/home/home.b64'], ['/page/', 4], ['/public/app.b64'], ['/public/config-min.js'], - ['/public/grid72.b64'], - ['/public/iframe-min.html'], ['/public/img.b64'], + ['/public/rate.b64'], + ['/public/ratings.b64'], ['/public/user.b64'], - ['/stats/', 2], + ['/rate/', 4], + ['/search/', 2], + ['/info/', 3], ['/store/', 1] ]; /** - * Show a status message. + * Init status message area. */ -window.showStatus = function(s, c) { - //debug('status', s); - var sdiv = $('status'); - if (isNil(sdiv)) - return s; - sdiv.innerHTML = '<span class="' + (c? c : 'okstatus') + '">' + s + '</span>'; - sdiv.className = 'status fixed'; - sdiv.style.visibility = 'visible'; +(function initstatus() { + if (isNil($('status'))) + return; + $('status').style.display = 'none'; function divtransitionend(e) { - e.target.style.visibility = 'hidden'; - e.target.className = 'status fixed'; + e.target.style.display = 'none'; + e.target.className = ui.isMobile()? 'status3dm' : 'status3d'; + e.target.error = false; } - if (!sdiv.addedTransitionEnd) { - sdiv.addEventListener('webkitTransitionEnd', divtransitionend, false); - sdiv.addEventListener('transitionend', divtransitionend, false); - sdiv.addedTransitionEnd = true; - } - sdiv.className = 'statusout3 fixed'; - return s; -} + $('status').addEventListener('webkitTransitionEnd', divtransitionend, false); + $('status').addEventListener('transitionend', divtransitionend, false); +})(); /** - * Show an error message. + * Show a status message. */ -window.showError = function(s) { - //debug('error', s); - return showStatus(s, 'errorstatus'); -} +window.showstatus = function(s, c) { + //debug('show status', s); + if (isNil($('status')) || $('status').error) + return s; + $('status').innerHTML = '<span class="' + (c? c : 'okstatus') + '">' + s + '</span>'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; + $('status').style.display = 'block'; + $('status').error = c == 'errorstatus'; + if ($('status').delay) + ui.cancelDelay($('status').delay); + $('status').delay = ui.delay(function hidestatus() { + $('status').className = ui.isMobile()? 'statusout3dm' : 'statusout3d'; + $('status').error = false; + }, 3000); + return s; +}; /** - * Show the online/offline status. + * Show an error message. */ -window.showOnlineStatus = function() { - return navigator.onLine? showStatus('Online') : showError('Offline'); -} +window.errorstatus = function(s) { + //debug('error', s); + return showstatus(s, 'errorstatus'); +}; /** - * Handle application cache events. + * Show working status. */ -applicationCache.addEventListener('checking', function(e) { - //debug('appcache checking', e); - showStatus('Checking'); -}, false); -applicationCache.addEventListener('error', function(e) { - //debug('appcache error', e); - showOnlineStatus(); -}, false); -applicationCache.addEventListener('noupdate', function(e) { - //debug('appcache noupdate', e); - showOnlineStatus(); -}, false); -applicationCache.addEventListener('downloading', function(e) { - //debug('appcache downloading', e); - showStatus('Updating'); -}, false); -applicationCache.addEventListener('progress', function(e) { - //debug('appcache progress', e); - showStatus('Updating'); -}, false); -applicationCache.addEventListener('updateready', function(e) { - //debug('appcache updateready', e); - try { - applicationCache.swapCache(); - } catch(e) {} - showOnlineStatus(); - //debug('appcache swapped', e); - - // Update offline resources in local storage and reload the page - map(function(res) { - showStatus('Updating'); - appcache.remove(res[0]); - appcache.get(res[0]); - }, append(appresources, config.appresources())); - window.location.reload(); -}, false); -applicationCache.addEventListener('cached', function(e) { - //debug('appcache cached', e); - showOnlineStatus(); - - // Install offline resources in local storage - map(function(res) { - showStatus('Installing'); - appcache.remove(res[0]); - appcache.get(res[0]); - }, append(appresources, config.appresources())); -}, false); +window.workingstatus = function(w, c) { + //debug('show working', w); + if (isNil($('working'))) + return w; + if (!ui.isMobile()) + $('working').style.top = ui.pixpos(Math.round(window.clientHeight / 2)); + $('working').style.display = w? 'block' : 'none'; + return w; +}; /** - * Handle network offline/online events. + * Show the online/offline status. */ -window.addEventListener('offline', function(e) { - //debug('going offline'); - showStatus('Offline'); -}, false); -window.addEventListener('online', function(e) { - //debug('going online'); - showStatus('Online'); -}, false); - -//debug(navigator.onLine? 'online' : 'offline'); +window.onlinestatus = function() { + return navigator.onLine? (ui.isMobile()? showstatus('Online') : showstatus('Online')) : errorstatus('Offline'); +}; /** * Handle view transitions. @@ -351,32 +317,36 @@ function mkviewdiv(cname) { * Return the last visited location. */ function lastvisited() { - if (username != lstorage.getItem('ui.lastvisit.user')) + if (username != lstorage.getItem('ui.v.user')) return null; - return lstorage.getItem('ui.lastvisit.url'); + return lstorage.getItem('ui.v.url'); } /** * Build and show the menu bar. */ -function showmenu(mdiv, view, appname) { - mdiv.innerHTML = ui.menubar( +function showmenu(view, appname) { + $('menu').innerHTML = ui.menubar( append(mklist(ui.menu('menuhome', 'Home', '/', '_view', view == 'home'), - ui.menu('menustore', 'Store', '/#view=store&category=' + storecat + '&idx=' + storeidx, '_view', view === 'store')), + ui.menu('menustore', 'Store', '/#view=store&category=' + storecat + '&idx=' + storeidx, '_view', view == 'store'), + ui.menu('menusearch', 'Search', '/#view=search', '_view', view == 'search')), (isNil(appname) || appname == 'undefined')? mklist() : mklist( - ui.menu('menustats', 'Stats', '/#view=stats&app=' + appname, '_view', view == 'stats'), - ui.menu('menupage', 'Page', '/#view=page&app=' + appname, '_view', view == 'page'), + ui.menu('menuinfo', 'Info', '/#view=info&app=' + appname, '_view', view == 'info'), + ui.menu('menupage', 'Edit', '/#view=page&app=' + appname, '_view', view == 'page') + /* TODO disabled for now + , ui.menu('menulogic', config.logic(), '/#view=graph&app=' + appname, '_view', view == 'graph'), - ui.menu('menurun', '<span class="greentext" style="font-weight: bold">Run!</span>', '/' + appname + '/', '_blank', false))), + ui.menu('menurun', '<span class="greentext" style="font-weight: bold">Run!</span>', '/' + appname + '/', '_blank', false) + */ + )), (isNil(appname) || appname == 'undefined')? mklist( - hasauthcookie()? ui.menufunc('menusignout', 'Sign out', 'return logout();', false) : ui.menu('menusignin', 'Sign in', '/login/', '_self', false), + ui.menufunc('menusignout', 'Sign out', 'return logout();', false), ui.menu('menuaccount', 'Account', '/#view=account', '_view', view == 'account')) : mklist()); - if (fdiv.innerHTML == '') - fdiv.innerHTML = config.viewfoot(); + $('viewfoot').innerHTML = config.viewfoot(); } /** @@ -390,7 +360,7 @@ function getaccount() { return false; var accountentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); - username = cadr(assoc("'id", cdr(accountentry))); + username = cadr(assoc("'id", accountentry)); return true; } @@ -401,8 +371,8 @@ function showview(url) { //debug('showview', url); // Save last visited location - lstorage.setItem('ui.lastvisit.user', username); - lstorage.setItem('ui.lastvisit.url', url); + lstorage.setItem('ui.v.user', username); + lstorage.setItem('ui.v.url', url); // Determine the view to show var params = ui.fragmentParams(url); @@ -426,10 +396,11 @@ function showview(url) { // Show the menu bar var appname = params['app']; - showmenu(mdiv, view, appname); + showmenu(view, appname); - // Scroll to the top and hide the address bar - window.scrollTo(0, 0); + // Make sure that the document is visible + if (document.body.style.display != 'block') + document.body.style.display = 'block'; // Start to unload the front view and create a new view if (ui.isMobile()) { @@ -444,21 +415,17 @@ function showview(url) { var vdiv = mkviewdiv(vtransition + 'viewloading3dm'); var vdoc = appcache.get(uri); vdiv.innerHTML = vdoc; - vcontainer.appendChild(vdiv); + $('viewcontainer').appendChild(vdiv); map(ui.evalScript, ui.innerScripts(vdiv)); - // Make sure the top document is visible - if (document.body.style.visibility != 'visible') - document.body.style.visibility = 'visible'; - - setTimeout(function() { + ui.async(function mtransitionview() { // Transition the old view out if (!isNil(ovdiv)) ovdiv.className = vtransition + 'viewunloaded3dm'; // Transition the new view in vdiv.className = 'viewloaded3dm'; - }, 100); + }); } else { // Prepare current view for transition out @@ -470,21 +437,17 @@ function showview(url) { var vdiv = mkviewdiv('viewloading3d'); var vdoc = appcache.get(uri); vdiv.innerHTML = vdoc; - vcontainer.appendChild(vdiv); + $('viewcontainer').appendChild(vdiv); map(ui.evalScript, ui.innerScripts(vdiv)); - // Make sure the top document is visible - if (document.body.style.visibility != 'visible') - document.body.style.visibility = 'visible'; - - setTimeout(function() { + ui.async(function transitionview() { // Transition the new view in vdiv.className = 'viewloaded3d'; // Transition the old view out if (!isNil(ovdiv)) ovdiv.parentNode.removeChild(ovdiv); - }, 100); + }); } // Track the current visible view @@ -501,12 +464,14 @@ function updatelocation(url) { // Add url to the history if necessary if (url != ui.pathandparams(location)) { - history.pushState(null, null, url); - //debug('pushstate', history.length); + if (history.pushState) { + history.pushState(null, null, url); + //debug('pushstate', history.length); + } // Update the location hash if necessary var f = ui.fragment(url); - if (f != '' && f != location.hash) { + if (f != location.hash) { location.hash = f; //debug('hash', f); } @@ -520,6 +485,10 @@ function updatelocation(url) { window.onnavigate = function(url) { //debug('onnavigate', url); + // Cleanup installer + if ($('installer').innerHTML != '') + $('installer').innerHTML = ''; + // Update the browser window location updatelocation(url); @@ -541,12 +510,11 @@ window.onloginredirect = function(e) { */ window.logout = function() { // Clear session cookie and user-specific local storage entries - clearauthcookie(); lstorage.removeItem('/r/Editor/accounts'); lstorage.removeItem('/r/Editor/dashboards'); - document.location = '/login/'; + document.location = '/logout/dologout/'; return false; -} +}; /** * Handle history. @@ -555,10 +523,13 @@ window.addEventListener('popstate', function(e) { //debug('onpopstate', history.length); var furl = ui.fragment(location); var url = location.pathname + (furl == ''? '' : '#' + furl); - - // Show the current view if (url == viewurl) return true; + + // Cleanup element lookups memoized in current document + ui.unmemo$(); + + // Show the current view return showview(url); }, false); @@ -567,10 +538,13 @@ window.addEventListener('hashchange', function(e) { //debug('onhashchange'); var furl = ui.fragment(location); var url = location.pathname + (furl == ''? '' : '#' + furl); - - // Show the current view if (url == viewurl) return true; + + // Cleanup element lookups memoized in current document + ui.unmemo$(); + + // Show the current view return showview(url); }, false); @@ -580,18 +554,128 @@ window.addEventListener('hashchange', function(e) { */ document.body.onorientationchange = function(e) { //debug('onorientationchange'); - ui.onorientationchange(e); - - // Resize menu and view header - mdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - hdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - return true; + return ui.onorientationchange(e); }; /** + * Install the application cache. + */ +(function installappcache() { + if (ui.isMobile()) { + // On mobile devices, trigger usage of an application cache manifest + window.onappcachechecking = function(e) { + //debug('appcache checking', e); + workingstatus(true); + showstatus('Checking'); + }; + window.onappcacheerror = function(e) { + //debug('appcache error', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachenoupdate = function(e) { + //debug('appcache noupdate', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachedownloading = function(e) { + //debug('appcache downloading', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheprogress = function(e) { + //debug('appcache progress', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheupdateready = function(e) { + //debug('appcache updateready', e); + try { + applicationCache.swapCache(); + } catch(e) {} + onlinestatus(); + workingstatus(false); + //debug('appcache swapped', e); + + // Update offline resources in local storage and reload the page + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + window.location.reload(); + }; + window.onappcachecached = function(e) { + //debug('appcache cached', e); + onlinestatus(); + workingstatus(false); + + // Install offline resources in local storage + map(function(res) { + showstatus('Installing'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + }; + + window.onloadappcache = function() { + //debug('appcache iframe loaded'); + }; + + ui.delay(function() { + $('installer').innerHTML = '<iframe src="/cache/" class="installer"></iframe>'; + }); + + } else { + // On non-mobile devices, check for cache-manifest changes ourselves. + workingstatus(true); + showstatus('Checking'); + var lcmf = appcache.get('/cache/cache-manifest.cmf', 'local'); + var rcmf = appcache.get('/cache/cache-manifest.cmf', 'remote'); + if (lcmf == rcmf) { + onlinestatus(); + workingstatus(false); + return true; + } + + //debug('cache-manifest changed, reloading'); + ui.delay(function() { + workingstatus(true); + showstatus('Updating'); + ui.delay(function() { + workingstatus(true); + showstatus('Updating'); + map(function(res) { + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + if (!isNil(lcmf)) { + //debug('reloading'); + window.location.reload(); + } + onlinestatus(); + workingstatus(false); + }); + }); + } +})(); + +/** + * Handle network offline/online events. + */ +window.addEventListener('offline', function(e) { + //debug('going offline'); + showstatus('Offline'); +}, false); +window.addEventListener('online', function(e) { + //debug('going online'); + showstatus('Online'); +}, false); + +/** * Initialize the document. */ -function onload() { +window.onload = function() { //debug('onload', history.length); ui.onload(); @@ -625,16 +709,15 @@ function onload() { if (url == viewurl) return true; return showview(url); -} - -onload(); +}; })(); -</script> -<div id="footdiv" class="fsection"> -</div> +} catch(e) { + debug(e.stack); + throw e; +} +</script> -</div> </body> </html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/info/index.html b/sca-cpp/trunk/hosting/server/htdocs/info/index.html new file mode 100644 index 0000000000..0d72062719 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/info/index.html @@ -0,0 +1,494 @@ +<!DOCTYPE html> +<!-- + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. +--> +<div id="bodydiv" class="body"> + +<div id="viewform" class="viewform"> + +<form id="appForm"> +<table style="width: 100%;"> +<tr><td class="label">URL:</td></tr> +<tr><td><input type="text" id="appURL" class="readentry" size="30" readonly="readonly" placeholder="App URL" style="width: 300px;"/></td></tr> +<tr><td class="label">Icon:</td></tr> +<tr><td><img id="appIcon" style="width: 50px; height: 50px; vertical-align: top;"/><input id="uploadIcon" type="button" class="lightbutton" value="Upload" style="display:none;"/><input id="uploadFile" type="file" accept="image/*" style="display: none;"/><span id="refreshingIcon" class="refreshing" style="display:none;"/></td></tr> +<tr><td class="label">Author:</td></tr> +<tr><td><img id="authorPicture" style="width: 50px; height: 50px; vertical-align: middle;"/><input type="text" id="appAuthor" class="readentry" size="30" readonly="readonly" placeholder="Author of the app" style="width: 248px;"/></td></tr> +<tr><td class="label">Rating:</td></tr> +<tr><td><span id="appRating" class="ratings"> </span><input id="rateApp" type="button" class="lightbutton" value="Rate this app"/></td></tr> +<tr><td><input type="text" id="appRatings" class="readentry" size="20" readonly="readonly" placeholder="Number of ratings" style="font-size: 12px;"/></td></tr> +<tr><td class="label">Updated:</td></tr> +<tr><td><input type="text" id="appUpdated" class="readentry" size="30" readonly="readonly" placeholder="App update date" style="width: 300px;"/></td></tr> +<tr><td class="label">Description:</td></tr> +<tr><td><textarea id="appDescription" class="readentry" cols="40" rows="3" readonly="readonly" placeholder="Short description of the app" style="width: 300px;"></textarea></td></tr> +</table> +</form> +<br/> + +</div> + +<script type="text/javascript"> +(function infobody() { + +/** + * Get the app name. + */ +var appname = ui.fragmentParams(location)['app']; + +/** + * Setup page layout. + */ +(function layout() { + document.title = config.windowtitle() + ' - Info - ' + appname; + $('viewhead').innerHTML = '<span id="appname" class="cmenu">' + appname + '</span>' + + '<input type="button" class="redbutton plusminus" style="position: absolute; top: 4px; left: 5px;" id="deleteApp" value="-" title="Delete this app" disabled="true"/>' + + '<span style="position: absolute; top: 0px; right: 5px;">' + + '<input type="button" class="greenbutton" id="runApp" value="Run" title="Run this app"/>' + + '<input type="button" class="bluebutton" id="cloneApp" value="'+ config.clone() +'" title="' + config.clone() + ' this app"/>' + + '</span>'; + if (!ui.isMobile()) + $('viewform').className = 'viewform flatscrollbars'; + $('appURL').value = window.location.hostname + '/' + appname + '/'; + + $('viewform').appendChild(ui.declareCSS( + '.ratings { ' + + 'background: url(\'' + ui.b64png(appcache.get('/public/ratings.b64')) + '\'); ' + + 'vertical-align: middle; width: 50px; height: 14px; display: inline-block; ' + + ' }')); +})(); + +/** + * Set images. + */ +(function drawImages() { + $('appIcon').src = ui.b64png(appcache.get('/public/app.b64')); + $('authorPicture').src = ui.b64png(appcache.get('/public/user.b64')); +})(); + +/** + * Initialize service references. + */ +var editorComp = sca.component("Editor"); +var apps = sca.reference(editorComp, "apps"); +var icons = sca.reference(editorComp, "icons"); +var pictures = sca.reference(editorComp, "pictures"); +var ratings = sca.reference(editorComp, "ratings"); + +/** + * The current app entry, author and saved XML content. + */ +var savedappxml = ''; +var author; +var savediconxml; + +/** + * Get and display the requested app. + */ +(function getapp() { + if (isNil(appname)) + return false; + workingstatus(true); + showstatus('Loading'); + + return apps.get(appname, function(doc) { + + // Stop now if we didn't get the app + if (doc == null) { + errorstatus('Couldn\'t get the app info'); + workingstatus(false); + return false; + } + + var appentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); + author = cadr(assoc("'author", appentry)); + $('appAuthor').value = author.split('@')[0]; + var updated = assoc("'updated", appentry); + $('appUpdated').value = isNil(updated)? '' : xmldatetime(cadr(updated)).toLocaleDateString(); + var content = cadr(assoc("'content", appentry)); + var description = assoc("'description", content); + $('appDescription').value = isNil(description) || isNil(cadr(description))? '' : cadr(description); + //var ratingy = -20 * (4 - Math.floor(Math.random() * 4)); + //$('appRating').style.backgroundPosition = '0px ' + ratingy + 'px'; + //$('appRatings').value = ''; + savedappxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry)))); + + // Enable author to edit and delete the app + if (username == author) { + $('appDescription').readOnly = false; + $('appDescription').className = 'flatentry'; + $('uploadIcon').style.display = 'inline'; + $('deleteApp').disabled = false; + $('deleteApp').onclick = function() { + return ui.navigate('/#view=delete&app=' + appname, '_view'); + } + onlinestatus(); + } else { + showstatus('Read only'); + } + workingstatus(false); + return true; + }); +})(); + +/** + * Get and display the author's picture. + */ +(function getpic(author) { + workingstatus(true); + showstatus('Loading'); + + return pictures.get(author, function(doc) { + + // Stop now if we didn't get a picture + if (doc == null) { + errorstatus('Author picture not available'); + workingstatus(false); + return false; + } + + var picentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); + var content = assoc("'content", picentry); + var picture = assoc("'picture", content); + var img = assoc("'image", picture); + if (!isNil(img)) + $('authorPicture').src = cadr(img); + + onlinestatus(); + workingstatus(false); + return true; + }); + return true; +})(); + +/** + * Get and display the app icon. + */ +(function geticon() { + if (isNil(appname)) + return false; + workingstatus(true); + showstatus('Loading'); + + return icons.get(appname, function(doc) { + // Stop now if we didn't get an icon + if (doc == null) { + errorstatus('Icon not available'); + workingstatus(false); + return false; + } + + var iconentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); + savediconxml = car(atom.writeATOMEntry(valuesToElements(mklist(iconentry)))); + var content = assoc("'content", iconentry); + var icon = assoc("'icon", content); + var img = assoc("'image", icon); + if (!isNil(img)) + $('appIcon').src = cadr(img); + + onlinestatus(); + workingstatus(false); + return true; + }); + return true; +})(); + +/** + * Refresh icon. + */ +var refreshingicon = false; +function refreshicon() { + if (isNil(appname)) + return false; + if (!refreshingicon) + return false; + $('refreshingIcon').style.display = 'inline-block'; + return icons.get(appname, function(doc) { + if (doc == null) { + errorstatus('Icon not available'); + $('refreshingIcon').style.display = 'none'; + refreshingicon = false; + return false; + } + + var iconentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); + var content = assoc("'content", iconentry); + var icon = assoc("'icon", content); + var token = assoc("'token", icon); + + // Update icon + if (isNil(token)) { + var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(iconentry)))); + savediconxml = entryxml; + var img = assoc("'image", icon); + if (!isNil(img)) + $('appIcon').src = cadr(img); + $('refreshingIcon').style.display = 'none'; + refreshingicon = false; + return true; + } + + // Refresh in 2 secs + return ui.delay(refreshicon, 2000); + }, 'remote'); + return true; +} + +/** + * Get and display the app ratings. + */ +(function getratings() { + if (isNil(appname)) + return false; + workingstatus(true); + showstatus('Loading'); + + return ratings.get(appname, function(doc) { + // Stop now if we didn't get an icon + if (doc == null) { + errorstatus('Ratings not available'); + workingstatus(false); + return false; + } + + var ratingsentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); + var aratings = assoc("'ratings", assoc("'content", ratingsentry)); + var ar = assoc("'rating", aratings); + var ar1 = assoc("'rating1", aratings); + var ar2 = assoc("'rating2", aratings); + var ar3 = assoc("'rating3", aratings); + var ar4 = assoc("'rating4", aratings); + var rating = isNil(ar)? 0 : Number(cadr(ar)); + var reviews = (isNil(ar1)? 0 : Number(cadr(ar1))) + (isNil(ar2)? 0 : Number(cadr(ar2))) + (isNil(ar3)? 0 : Number(cadr(ar3))) + (isNil(ar4)? 0 : Number(cadr(ar4))); + + var ratingy = -20 * (4 - Math.floor(rating)); + $('appRating').style.backgroundPosition = '0px ' + ratingy + 'px'; + $('appRatings').value = reviews + (reviews > 1? ' ratings' : ' rating'); + + onlinestatus(); + workingstatus(false); + return true; + }); + return true; +})(); + +/** + * Save the current app. + */ +function saveapp(entryxml) { + workingstatus(true); + showstatus('Saving'); + savedappxml = entryxml; + apps.put(appname, savedappxml, function(e) { + if (e) { + showstatus('Local copy'); + workingstatus(false); + return false; + } + + showstatus('Saved'); + workingstatus(false); + return false; + }); + return true; +} + +/** + * Save the app icon. + */ +function saveicon(entryxml) { + workingstatus(true); + showstatus('Uploading'); + savedappxml = entryxml; + icons.put(appname, savedappxml, function(e) { + if (e) { + showstatus('Local copy'); + workingstatus(false); + return false; + } + + showstatus('Uploaded'); + workingstatus(false); + return true; + }); + return true; +} + +/** + * Handle a change event + */ +function onappchange() { + if (username != author) + return false; + + // Validate user input + var description = $('appDescription').value; + if (description.length > 120) { + errorstatus('Description cannot be longer than 120 characters'); + return false; + } + + // Save the changes + var appentry = mklist("'entry", mklist("'title", appname), mklist("'id", appname), mklist("'content", mklist("'info", mklist("'description", description)))); + var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry)))); + if (savedappxml == entryxml) + return false; + showstatus('Modified'); + return saveapp(entryxml); +} + +$('appDescription').onchange = onappchange; + +/** + * Handle a key event. + */ +var lastkeyup = null; +$('appDescription').onkeyup = function() { + var t = new Date().getTime(); + lastkeyup = t; + ui.delay(function() { + return t == lastkeyup? onappchange() : true; + }, 2000); +}; + +/** + * Handle a form submit event. + */ +$('appForm').onsubmit = function() { + onappchange(); + return false; +}; + +/** + * Handle Clone button event. + */ +$('cloneApp').onclick = function() { + return ui.navigate('/#view=clone&app=' + appname, '_view'); +}; + +/** + * Handle Run button event. + */ +$('runApp').onclick = function() { + return ui.navigate('/' + appname + '/', '_blank'); +}; + +/** + * Read and upload icon file. + */ +function uploadicon(files) { + if (username != author) + return false; + if (!files || files.length == 0) + return false; + if (!files[0].type.match('image.*')) { + errorstatus('Please select an image'); + return false; + } + workingstatus(true); + showstatus('Loading'); + + // Read the selected file into a 50x50 image + return ui.readimage(files[0], + function(e) { + errorstatus('Couldn\'t read the file'); + workingstatus(false); + }, + function(p) { + showstatus('Loading ' + p + '%'); + }, + function(url) { + // Update the app icon + $('appIcon').src = url; + showstatus('Loaded'); + + // Now upload it + ui.delay(function() { + var iconentry = mklist("'entry", mklist("'title", appname), mklist("'id", appname), mklist("'author", username), mklist("'content", mklist("'icon", mklist("'image", url)))); + var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(iconentry)))); + if (savediconxml == entryxml) { + onlinestatus(); + workingstatus(false); + return false; + } + return saveicon(entryxml); + }); + }, 50, 50); +} + +/** + * Upload an icon in an email. + */ +function emailicon() { + + // Generate and put an icon email upload token + workingstatus(true); + showstatus('Uploading'); + var token = uuid4(); + var iconentry = mklist("'entry", mklist("'title", appname), mklist("'id", appname), mklist("'author", username), mklist("'content", mklist("'icon", mklist("'token", token)))); + var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(iconentry)))); + icons.put(appname, entryxml, function(e) { + if (e) { + showstatus('Local copy'); + workingstatus(false); + return false; + } + workingstatus(false); + + // Open the email app + var mailto = safeb64encode('i/' + appname + '/' + token); + ui.navigate('mailto:' + mailto + '@' + topdomainname(window.location.hostname) + '?subject=Email to upload&body=Paste icon here', '_self'); + + // Refresh app icon + refreshingicon = true; + return ui.delay(refreshicon, 500); + }, 'remote'); +} + +/** + * Handle icon upload events. + */ +$('uploadIcon').onclick = function() { + if (ui.isMobile()) + return emailicon(); + return $('uploadFile').click(); +}; +$('uploadFile').onchange = function(e) { + return uploadicon(e.target.files); +}; +$('appIcon').ondrag = function(e) { + e.stopPropagation(); + e.preventDefault(); + e.dataTransfer.dropEffect = 'copy'; +}; +$('appIcon').ondrop = function(e) { + e.stopPropagation(); + e.preventDefault(); + return uploadicon(e.dataTransfer.files); +}; + +/** + * Handle rate button event. + */ +$('rateApp').onclick = function() { + return ui.navigate('/#view=rate&app=' + appname, '_view'); +}; + +})(); +</script> + +</div> diff --git a/sca-cpp/trunk/hosting/server/htdocs/login/index.html b/sca-cpp/trunk/hosting/server/htdocs/login/index.html index bf09339927..efc3feaeaa 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/login/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/login/index.html @@ -19,87 +19,113 @@ --> <html> <head> +<!-- Firebug inspector --> +<!-- +<script type="text/javascript" src="https://getfirebug.com/releases/lite/1.3/firebug-lite.js"></script> +--> +<!-- Weinre inspector --> +<!-- +<script src="http://www.example.com:9998/target/target-script-min.js#anonymous"></script> +--> <title>Sign in</title> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"/> +<!-- <meta name="apple-mobile-web-app-capable" content="yes"/> <meta name="apple-mobile-web-app-status-bar-style" content="black"/> +--> +<link rel="apple-touch-icon-precomposed" href="/public/touchicon.png"/> <base href="/login/"/> <script type="text/javascript"> -(function() { +try { + +(function loginhead() { window.appcache = {}; /** * Get and cache a resource. */ -appcache.get = function(uri) { +appcache.get = function(uri, mode) { var h = uri.indexOf('#'); var u = h == -1? uri : uri.substring(0, h); // Get resource from local storage first var ls = window.lstorage || localStorage; - var item = null; - try { item = ls.getItem(u); } catch(e) {} - if (item != null && item != '') - return item; + if (mode != 'remote') { + var item = null; + try { item = ls.getItem('ui.r.' + u); } catch(e) {} + if (item != null && item != '') + return item; + if (mode == 'local') + return null; + } // Get resource from network + //if (window.debug) debug('appcache.get', u); var http = new XMLHttpRequest(); - http.open("GET", u, false); + http.open("GET", mode == 'remote'? (u + '?t=' + new Date().getTime() + '&r=' + Math.random()) : u, false); http.setRequestHeader("Accept", "*/*"); http.send(null); if (http.status == 200) { - if (http.getResponseHeader("X-Login") != null) { + var xl = http.getResponseHeader("X-Login"); + if (xl != null && xl != '') { if (window.debug) debug('http error', u, 'X-Login'); return null; - } else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) { + } + var ct = http.getResponseHeader("Content-Type"); + if (http.responseText == '' || ct == null || ct == '') { if (window.debug) debug('http error', u, 'No-Content'); return null; } - try { ls.setItem(u, http.responseText); } catch(e) {} + try { ls.setItem('ui.r.' + u, http.responseText); } catch(e) {} return http.responseText; } if (window.debug) debug('http error', u, http.status, http.statusText); return null; }; +appcache.remove = function(uri) { + var h = uri.indexOf('#'); + var u = h == -1? uri : uri.substring(0, h); + var ls = window.lstorage || localStorage; + try { ls.removeItem(u); } catch(e) {} + return true; +}; + })(); /** * Load Javascript and CSS. */ -(function() { +(function loginboot() { + +window.eval.call(window, 'try {\n' + appcache.get('/all-min.js') + '\n' + appcache.get('/public/config-min.js') + '\n} catch(e) { console.log(e.stack); throw e; }\n'); +ui.includeCSS(appcache.get('/ui-min.css')); -var bootjs = document.createElement('script'); -bootjs.type = 'text/javascript'; -bootjs.text = appcache.get('/all-min.js'); -document.head.appendChild(bootjs); -document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); +// Disable cache for testing +//lstorage.enabled = false; })(); +} catch(e) { + if (window.debug) debug(e.stack); + throw e; +} </script> </head> -<body class="delayed""> -<div id="mainbodydiv" class="bodydiv"> - -<div id="headdiv" class="hsection"> -<script type="text/javascript"> -(function() { +<body class="delayed"> -$('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js'))); - -})(); -</script> +<div id="menucontainer" class="tbarmenu"> +<div id="menu"></div> </div> -<div id="menubackground" class="tbarbackground fixed"></div> -<div id="menu" class="tbarmenu fixed"></div> - -<div id="viewheadbackground" class="viewheadbackground fixed"></div> -<div id="viewhead" class="viewhead fixed"> +<div id="viewheadcontainer" class="viewhead"> +<div id="viewhead"> <span class="cmenu">Sign in</span> </div> +</div> + +<div id="working" class="working" style="display: none;"></div> <div id="viewcontainer"> <div id="view"> @@ -109,9 +135,9 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js')) <form id="formSignin" name="formSignin" method="POST" action="/login/dologin" style="width: 100%;"> <table style="width: 100%;"> <tr><td><span id="loginprompt" style="font-size: 16px;"></span></tr></td> -<tr><td><input type="text" class="flatentry" name="httpd_username" value="" placeholder="User id"/></td></tr> +<tr><td><input type="text" class="flatentry" name="httpd_username" value="" placeholder="Username or email"/></td></tr> <tr><td><input type="password" class="flatentry" name="httpd_password" value="" placeholder="Password"/></td></tr> -<tr><td><input type="submit" class="graybutton bluebutton" style="font-size: 16px; line-height: 16px; padding: 6px; height: 32px" value="Sign in"/></td></tr> +<tr><td><input type="submit" class="bluebutton" style="font-size: 16px; line-height: 16px; padding: 6px; height: 32px" value="Sign in"/></td></tr> </table> <input type="hidden" name="httpd_location" value="/"/> </form> @@ -120,7 +146,7 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js')) <form name="facebookOAuth2Form" style="width: 100%;"> <table style="width: 100%;"> <tr><td><span style="font-size: 16px;">Sign in with your <span style="font-weight: bold;">Facebook</span> account</span></td></tr> -<tr><td><input type="button" id="facebookOAuth2Signin" value="Sign in" class="graybutton bluebutton" style="font-size: 16px; line-height: 16px; padding: 6px; height: 32px"/></td></tr> +<tr><td><input type="button" id="facebookOAuth2Signin" value="Sign in" class="bluebutton" style="font-size: 16px; line-height: 16px; padding: 6px; height: 32px"/></td></tr> </table> </form> <br/> @@ -128,7 +154,7 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js')) <form name="googleOAuth2Form" style="width: 100%;"> <table style="width: 100%;"> <tr><td><span style="font-size: 16px;">Sign in with your <span style="font-weight: bold;" >Google</span> account</span></td></tr> -<tr><td><input type="button" id="googleOAuth2Signin" value="Sign in" class="graybutton bluebutton" style="font-size: 16px; line-height: 16px; padding: 6px; height: 32px"/></td></tr> +<tr><td><input type="button" id="googleOAuth2Signin" value="Sign in" class="bluebutton" style="font-size: 16px; line-height: 16px; padding: 6px; height: 32px"/></td></tr> </table> </form> <br/> @@ -147,78 +173,110 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js')) </div> </div> -<div id="viewfootbackground" class="viewfootbackground fixed"></div> -<div id="viewfoot" class="viewfoot fixed"></div> -<div id="status" class="status fixed" style="visibility: hidden;"></div> +<div id="viewfootcontainer" class="viewfoot"> +<div id="viewfoot"></div> +<div id="status"></div> +</div> + +<div id="installer" class="installer"></div> <script type="text/javascript"> -(function() { +try { + +(function loginbody() { /** - * Init div variables. + * Setup page layout. */ -var mbdiv = $('menubackground'); -var mdiv = $('menu'); -var hdiv = $('viewhead'); -var hbdiv = $('viewheadbackground'); -$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm'; -$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm'; -$('loginprompt').innerHTML = config.loginprompt(); -var fdiv = $('viewfoot'); +(function layout() { + $('viewcontainer').className = ui.isMobile()? 'viewcontainer3dm' : 'viewcontainer3d'; + $('view').className = ui.isMobile()? 'viewloaded3dm' : 'viewloaded3d'; + $('loginprompt').innerHTML = config.loginprompt(); + document.title = config.windowtitle() + ' - Sign in'; + $('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>' + + '<span class="rmenu"><input type="button" id="signUp" class="redbutton" style="font-size: 16px; line-height: 16px; padding: 6px; height: 32px" title="' + config.signuptitle() + '" value="Sign up"/></span>'; + if (!ui.isMobile()) + $('viewcontent').className = 'viewcontent flatscrollbars'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; +})(); /** - * Set page titles. + * Setup menu bar. */ -document.title = config.windowtitle() + ' - Sign in'; -$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; +(function showmenu() { + $('menu').innerHTML = ui.menubar(mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), mklist()); + $('viewfoot').innerHTML = config.viewfoot(); +})(); /** - * Build and show the menu bar. + * Initialize status message area. */ -function showmenu(mdiv) { - mdiv.innerHTML = ui.menubar(mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), mklist()); - fdiv.innerHTML = config.viewfoot(); -} - -showmenu(mdiv); +(function initstatus() { + if (isNil($('status'))) + return; + $('status').style.display = 'none'; + + function divtransitionend(e) { + e.target.style.display = 'none'; + e.target.className = ui.isMobile()? 'status3dm' : 'status3d'; + e.target.error = false; + } + $('status').addEventListener('webkitTransitionEnd', divtransitionend, false); + $('status').addEventListener('transitionend', divtransitionend, false); +})(); /** * Show a status message. */ -window.showStatus = function(s, c) { - //debug('status', s); - var sdiv = $('status'); - if (isNil(sdiv)) +window.showstatus = function(s, c) { + //debug('show status', s); + if (isNil($('status')) || $('status').error) return s; - sdiv.innerHTML = '<span class="' + (c? c : 'okstatus') + '">' + s + '</span>'; - sdiv.className = 'status fixed'; - sdiv.style.visibility = 'visible'; - - function divtransitionend(e) { - e.target.style.visibility = 'hidden'; - e.target.className = 'status fixed'; - } - if (!sdiv.addedTransitionEnd) { - sdiv.addEventListener('webkitTransitionEnd', divtransitionend, false); - sdiv.addEventListener('transitionend', divtransitionend, false); - sdiv.addedTransitionEnd = true; - } - sdiv.className = 'statusout3 fixed'; + $('status').innerHTML = '<span class="' + (c? c : 'okstatus') + '">' + s + '</span>'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; + $('status').style.display = 'block'; + $('status').error = c == 'errorstatus'; + if ($('status').delay) + ui.cancelDelay($('status').delay); + $('status').delay = ui.delay(function hidestatus() { + $('status').className = ui.isMobile()? 'statusout3dm' : 'statusout3d'; + $('status').error = false; + }, 3000); return s; -} +}; /** * Show an error message. */ -window.showError = function(s) { +window.errorstatus = function(s) { //debug('error', s); - return showStatus(s, 'errorstatus'); -} + return showstatus(s, 'errorstatus'); +}; + +/** + * Show working status. + */ +window.workingstatus = function(w, c) { + //debug('show working', w); + if (isNil($('working'))) + return w; + if (!ui.isMobile()) + $('working').style.top = ui.pixpos(Math.round(window.clientHeight / 2)); + $('working').style.display = w? 'block' : 'none'; + return w; +}; + +/** + * Show the online/offline status. + */ +window.onlinestatus = function() { + return navigator.onLine? (ui.isMobile()? showstatus('Online') : showstatus('Online')) : errorstatus('Offline'); +}; /** - * Parse the query parameeters. + * Parse the query parameters. */ -function queryParams() { +function qparams() { var qp = new Array(); var qs = window.location.search.substring(1).split('&'); for (var i = 0; i < qs.length; i++) { @@ -232,20 +290,17 @@ function queryParams() { /** * Show login status. */ -function showLoginStatus() { - var a = queryParams()['openauth_attempt']; - debug('a', a); +function loginstatus() { + var a = qparams()['openauth_attempt']; if (typeof(a) != 'undefined' && a == '1') - showError('Incorrect email or password, please try again'); + errorstatus('Incorrect email or password, please try again'); } -showLoginStatus(); - /** * Return the referrer URL. */ -function openauthReferrer() { - var r = queryParams()['openauth_referrer']; +function openauthreferrer() { + var r = qparams()['openauth_referrer']; if (typeof(r) == 'undefined' || domainname(r) != domainname(window.location.hostname)) return '/'; var q = r.indexOf('?'); @@ -257,9 +312,8 @@ function openauthReferrer() { /** * Signin with OAuth 2.0. */ -function submitOAuth2Signin(w) { +function submitoauth2signin(w) { parms = w(); - clearauthcookie(); lstorage.removeItem('/r/Editor/accounts'); lstorage.removeItem('/r/Editor/dashboards'); document.oauth2Signin.oauth2_authorize.value = parms[0]; @@ -268,70 +322,195 @@ function submitOAuth2Signin(w) { document.oauth2Signin.oauth2_info.value = parms[3]; document.oauth2Signin.oauth2_scope.value = parms[4]; document.oauth2Signin.oauth2_display.value = parms[5]; - document.oauth2Signin.openauth_referrer.value = openauthReferrer(); + document.oauth2Signin.openauth_referrer.value = openauthreferrer(); document.oauth2Signin.action = '/oauth2/authorize/'; document.oauth2Signin.submit(); } -function withFacebook() { +function withfacebook() { var parms = ['https://graph.facebook.com/oauth/authorize', 'https://graph.facebook.com/oauth/access_token', 'facebook.com', 'https://graph.facebook.com/me', 'email', ui.isMobile()? 'touch' : 'page']; return parms; } -function withGoogle() { +function withgoogle() { var parms = ['https://accounts.google.com/o/oauth2/auth', 'https://accounts.google.com/o/oauth2/token', 'google.com', 'https://www.googleapis.com/oauth2/v1/userinfo', 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile', '']; return parms; } $('facebookOAuth2Signin').onclick = function() { - return submitOAuth2Signin(withFacebook); + return submitoauth2signin(withfacebook); }; $('googleOAuth2Signin').onclick = function() { - return submitOAuth2Signin(withGoogle); + return submitoauth2signin(withgoogle); }; /** - * Signin with a userid and password. + * Signin with a username and password. */ -function submitFormSignin() { - clearauthcookie(); +$('formSignin').onsubmit = function submitformsignin() { document.formSignin.httpd_location.value = '/'; document.formSignin.submit(); -} +}; -$('formSignin').onsubmit = submitFormSignin; +/** + * Signup. + */ +$('signUp').onclick = function submitsignup() { + ui.navigate('/public/notyet/', '_self'); +}; /** * Handle orientation change. */ document.body.onorientationchange = function(e) { //debug('onorientationchange'); - ui.onorientationchange(e); - - // Resize menu and view header - mdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - hdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - return true; + return ui.onorientationchange(e); }; /** + * Populate cache with app resources. + */ +var appresources = [ + ['/all-min.js'], + ['/ui-min.css'], + ['/public/config-min.js'] +]; + +/** + * Install the application cache. + */ +(function installappcache() { + if (ui.isMobile()) { + // On mobile devices, trigger usage of an application cache manifest + window.onappcachechecking = function(e) { + //debug('appcache checking', e); + workingstatus(true); + showstatus('Checking'); + }; + window.onappcacheerror = function(e) { + //debug('appcache error', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachenoupdate = function(e) { + //debug('appcache noupdate', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachedownloading = function(e) { + //debug('appcache downloading', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheprogress = function(e) { + //debug('appcache progress', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheupdateready = function(e) { + //debug('appcache updateready', e); + try { + applicationCache.swapCache(); + } catch(e) {} + onlinestatus(); + workingstatus(false); + //debug('appcache swapped', e); + + // Update offline resources in local storage and reload the page + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + window.location.reload(); + }; + window.onappcachecached = function(e) { + //debug('appcache cached', e); + onlinestatus(); + workingstatus(false); + + // Install offline resources in local storage + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + }; + + window.onloadappcache = function() { + //debug('appcache iframe loaded'); + }; + + ui.delay(function() { + $('installer').innerHTML = '<iframe src="/public/cache/" class="installer"></iframe>'; + }); + + } else { + // On non-mobile devices, check for cache-manifest changes ourselves. + workingstatus(true); + showstatus('Checking'); + var lcmf = appcache.get('/public/cache/cache-manifest.cmf', 'local'); + var rcmf = appcache.get('/public/cache/cache-manifest.cmf', 'remote'); + if (lcmf == rcmf) { + onlinestatus(); + workingstatus(false); + return true; + } + + //debug('cache-manifest changed, reloading'); + ui.delay(function() { + workingstatus(true); + showstatus('Updating'); + ui.delay(function() { + workingstatus(true); + showstatus('Updating'); + map(function(res) { + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + if (!isNil(lcmf)) { + //debug('reloading'); + window.location.reload(); + } + onlinestatus(); + workingstatus(false); + }); + }); + } +})(); + +/** + * Handle network offline/online events. + */ +window.addEventListener('offline', function(e) { + //debug('going offline'); + showstatus('Offline'); +}, false); +window.addEventListener('online', function(e) { + //debug('going online'); + showstatus('Online'); +}, false); + +/** * Initialize the document. */ -function onload() { +window.onload = function() { //debug('onload'); ui.onload(); - // Show the page - document.body.style.visibility = 'visible'; + // Show the login status + loginstatus(); return true; -} - -onload(); +}; })(); + +} catch(e) { + debug(e.stack); + throw e; +} </script> -</div> </body> </html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/page/index.html b/sca-cpp/trunk/hosting/server/htdocs/page/index.html index 6a6e042c74..c6e1108ce5 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/page/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/page/index.html @@ -19,59 +19,69 @@ --> <div id="bodydiv" class="body"> -<div id="contentdiv" class="viewcontent" style="width: 2500px;"> -<div id="pagediv" class="pagediv" style="top: 0px; left: -2500px; width: 5000px; height: 5000px;"> +<div id="viewcontent" class="viewcontent"> +<div id="pagecontainer"> +<div id="pagediv" class="pagediv"> +</div> <!-- -<div class="guide" style="position: absolute; left: 2500px; top: 0px; width: 320px; height: 460px;"></div> -<div class="guide" style="position: absolute; left: 2500px; top: 0px; width: 480px; height: 300px;"></div> -<div class="guide" style="position: absolute; left: 2500px; top: 0px; width: 768px; height: 911px;"></div> -<div class="guide" style="position: absolute; left: 2500px; top: 0px; width: 1024px; height: 655px;"></div> +<div class="guide" style="position: absolute; left: 0px; top: 0px; width: 320px; height: 460px;"></div> +<div class="guide" style="position: absolute; left: 0px; top: 0px; width: 480px; height: 300px;"></div> +<div class="guide" style="position: absolute; left: 0px; top: 0px; width: 768px; height: 911px;"></div> +<div class="guide" style="position: absolute; left: 0px; top: 0px; width: 1024px; height: 655px;"></div> --> -<span class="h1" id="palette:h1" style="position: absolute; left: 0px; top: 0px;"><h1>Header Level 1</h1></span> -<span class="h2" id="palette:h2" style="position: absolute; left: 0px; top: 30px;"><h2>Header Level 2</h2></span> -<span class="section" id="palette:section" style="position: absolute; left: 0px; top: 60px; width: 220px;"><span class="Section">Section</span></span> -<span class="text" id="palette:text" style="position: absolute; left: 0px; top: 90px;"><span>Some text</span></span> -<span class="link" id="palette:link" style="position: absolute; left: 80px; top: 90px;"><a href="/"><span>Link</span></a></span> -<span class="button" id="palette:graybutton" style="position: absolute; left: 0px; top: 120px;"><input type="button" value="Button" class="graybutton"/></span> -<span class="button" id="palette:bluebutton" style="position: absolute; left: 80px; top: 120px;"><input type="button" value="Button" class="graybutton bluebutton"/></span> -<span class="button" id="palette:redbutton" style="position: absolute; left: 160px; top: 120px;"><input type="button" value="Button" class="graybutton redbutton"/></span> -<span class="entry" id="palette:entry" style="position: absolute; left: 0px; top: 160px;"><input type="text" value="Entry Field" class="flatentry" size="20" autocapitalize="off"/></span> -<span class="password" id="palette:password" style="position: absolute; left: 0px; top: 190px;"><input type="password" value="Password" class="flatentry" size="20"/></span> -<span class="checkbox" id="palette:checkbox" style="position: absolute; left: 0px; top: 220px;"><input type="checkbox" value="Checkbox" class="flatcheckbox"/><span>Checkbox</span></span> +</div> + +<div id="playdiv" class="playdiv" style="display: none;"></div> + +</div> + +<div id="palettecontainer"> +<div id="paletteview" style="display: none;"> + +<div id="palettecontent" class="palettecontent"> +<table class="palettetable"> +<tr><td class="palettetd"><span class="hd1" id="palette:h1"><span>Header 1</span></span></td></tr> +<tr><td class="palettetd"><span class="hd2" id="palette:h2"><span>Header 2</span></span></td></tr> +<tr><td class="palettetd"><span class="section" id="palette:section"><span>Section</span></span></td></tr> +<tr><td class="palettetd"><span class="text" id="palette:text"><span>Some text</span></span></td></tr> +<tr><td class="palettetd"><span class="link" id="palette:link"><a href="/"><span>Link</span></a></span></td></tr> +<tr><td class="palettetd"><span class="button" id="palette:graybutton"><input type="button" value="Button" class="graybutton"/></span></td></tr> +<tr><td class="palettetd"><span class="button" id="palette:redbutton"><input type="button" value="Button" class="redbutton"/></span></td></tr> +<tr><td class="palettetd"><span class="button" id="palette:greenbutton"><input type="button" value="Button" class="greenbutton"/></span></td></tr> +<tr><td class="palettetd"><span class="button" id="palette:bluebutton"><input type="button" value="Button" class="bluebutton"/></span></td></tr> +<tr><td class="palettetd"><span class="entry" id="palette:entry"><input type="text" value="Entry Field" class="flatentry" size="10" autocapitalize="off" readonly="true" style="cursor: default;"/></span></td></tr> +<tr><td class="palettetd"><span class="password" id="palette:password"><input type="password" value="Password" class="flatentry" size="10" readonly="true" style="cursor: default;"/></span></td></tr> +<tr><td class="palettetd"><span class="checkbox" id="palette:checkbox"><input type="checkbox" value="Checkbox" class="flatcheckbox"/><span>Checkbox</span></span></td></tr> <!-- -<span class="select" id="palette:select" style="position: absolute; left: 80px; top: 220px;"><select><option value="select">Selection</option></select></span> +<tr><td class="palettetd"><span class="select" id="palette:select"><select disabled="true"><option value="select">Selection</option></select></span></td></tr> --> -<span class="list" id="palette:list" style="position: absolute; left: 0px; top: 250px; width: 220px;"> -<table class="datatable" style="width: 220px;"> -<tr><td class="datatd">List</td></tr> -<tr><td class="datatd">List</td></tr> -<tr><td class="datatd">List</td></tr> +<tr><td class="palettetd"><span class="list" id="palette:list"> +<table class="datatable"> +<tr><td class="datatd"><span>List</span></td></tr> +<tr><td class="datatd"><span>List</span></td></tr> +<tr><td class="datatd"><span>List</span></td></tr> </table> -</span> -<span class="table" id="palette:table" style="position: absolute; left: 0px; top: 320px; width: 220px;"> -<table class="datatable" style="width: 220px;"> -<tr><td class="datatdl">Table</td><td class="datatdr">Table</td></tr> -<tr><td class="datatdl">Table</td><td class="datatdr">Table</td></tr> -<tr><td class="datatdl">Table</td><td class="datatdr">Table</td></tr> +</span></td></tr> +<tr><td class="palettetd"><span class="table" id="palette:table"> +<table class="datatable"> +<tr><td class="datatdl"><span>Table</span></td><td class="datatdr"><span>Table</span></td></tr> +<tr><td class="datatdl"><span>Table</span></td><td class="datatdr"><span>Table</span></td></tr> +<tr><td class="datatdl"><span>Table</span></td><td class="datatdr"><span>Table</span></td></tr> +</table> +</span></td></tr> +<tr><td class="palettetd"><span class="img" id="palette:img"><img id="imgimg"/></span></td></tr> </table> -</span> -<!-- -<span class="iframe fakeframe" id="palette:iframe" style="position: absolute; left: 0px; top: 380px; width: 200px;"><a href="/public/iframe-min.html"><span class="fakeframe"><span>Frame ...</span></span></a></span> ---> -<span class="img" id="palette:img" style="position: absolute; left: 0px; top: 410px;"><img id="imgimg"/></span> </div> -<div id="playdiv" style="visibility: hidden; position: absolute; top: 0px; left: 0px; width: 2500px; height: 5000px;"> </div> - </div> -<div id="buffer" style="visibility: hidden; width: 0px; height: 0px"></div> +<div id="xhtmlbuffer" style="display: none;"></div> <script type="text/javascript"> -(function() { +(function pagebody() { /** * Get the current app name. @@ -79,402 +89,247 @@ var appname = ui.fragmentParams(location)['app']; /** - * Return the link to an app. + * Setup page layout. */ -function applink(appname) { - var protocol = location.protocol; - var host = location.hostname; - var port = ':' + location.port; - if (port == ':80' || port == ':443' || port == ':') - port = ''; - var link = protocol + '//' + host + port + '/' + appname + '/'; - return link; -} - -/** - * Set page titles. - */ -document.title = config.windowtitle() + ' - Page - ' + appname; +(function layout() { + document.title = config.windowtitle() + ' - Page - ' + appname; + + $('viewhead').innerHTML = '<span id="appTitle" class="cmenu">' + appname + '</span>' + + '<input type="button" id="deleteWidgetButton" title="Delete a Widget" class="redbutton plusminus" style="position: absolute; top: 4px; left: 5px;" disabled="true" value="-"/>' + + '<span style="position: absolute; top: 0px; left: 45px; right: 115px; padding: 0px; background: transparent;"><input id="widgetValue" type="text" value="" class="flatentry" title="Widget value" autocapitalize="off" placeholder="Value" style="position: absolute; left: 0px; top: 4px; width: 100%; display: none;" readonly="readonly"/></span>' + + '<input type="button" id="playPageButton" title="View page" class="greenbutton plusminus" style="position: absolute; top: 4px; right: 75px;" value=">"/>' + + '<input type="button" id="copyWidgetButton" title="Copy a Widget" class="bluebutton" style="position: absolute; top: 4px; right: 40px; font-size: 16px;" disabled="true" value="C"/>' + + '<input type="button" id="addWidgetButton" title="Add a Widget" class="bluebutton plusminus" style="position: absolute; top: 4px; right: 5px;" disabled="true" value="+"/>'; + + if (ui.isMobile()) { + $('palettecontainer').className = 'palettecontainer3dm'; + $('paletteview').className = 'paletteloaded3dm'; + } else { + $('viewcontent').className = 'viewcontent flatscrollbars'; + $('palettecontainer').className = 'palettecontainer3d'; + $('paletteview').className = 'paletteloaded3d'; + $('palettecontent').className = 'palettecontent flatscrollbars'; + } -/** - * Set header div. - */ -$('viewhead').innerHTML = '<span id="appTitle" class="cmenu">' + appname + '</span>' + -'<input type="button" id="deleteWidgetButton" title="Delete a Widget" class="graybutton redbutton plusminus" style="position: absolute; top: 4px; left: 5px;" disabled="true" value="-"/>' + -'<span style="position: absolute; top: 0px; left: 45px; right: 115px; padding: 0px; background: transparent;"><input id="widgetValue" type="text" value="" class="flatentry" title="Widget value" autocapitalize="off" placeholder="Value" style="position: absolute; left: 0px; top: 4px; width: 100%; visibility: hidden;" readonly="readonly"/></span>' + -'<input type="button" id="playPageButton" title="View page" class="graybutton plusminus" style="position: absolute; top: 4px; right: 75px;" value=">"/>' + -'<input type="button" id="copyWidgetButton" title="Copy a Widget" class="graybutton bluebutton" style="position: absolute; top: 4px; right: 40px; font-size: 16px;" disabled="true" value="C"/>' + -'<input type="button" id="addWidgetButton" title="Add a Widget" class="graybutton bluebutton plusminus" style="position: absolute; top: 4px; right: 5px;" disabled="true" value="+"/>'; + $('imgimg').src = ui.b64png(appcache.get('/public/img.b64')); +})(); /** * Track the current page, author, and saved XHTML content. */ var author = ''; var editable = false; -var savedpagexhtml = ''; +var savedxhtml = ''; /** - * Page editor area, widget value field, add, delete and play page buttons. + * Initialize component references. */ -var cdiv = $('contentdiv'); -var pagediv = $('pagediv'); -var evisible = true; -var pdiv = $('playdiv'); -var wadd = $('addWidgetButton'); -var wdelete = $('deleteWidgetButton'); -var wcopy = $('copyWidgetButton'); -var wvalue = $('widgetValue'); -var atitle = $('appTitle'); -var pplay = $('playPageButton'); +var editorComp = sca.component('Editor'); +var pages = sca.reference(editorComp, 'pages'); /** - * Set images. + * Return the transform property of a widget. */ -$('imgimg').src = ui.b64img(appcache.get('/public/img.b64')); +var msiefixupbounds = ui.isMSIE(); +function widgettransform(e) { + if (!isNil(e.xtranslate)) + return [e.xtranslate, e.ytranslate]; + var t = e.style.getPropertyValue('-webkit-transform') || e.style.getPropertyValue('-moz-transform') || + e.style.getPropertyValue('-ms-transform') || e.style.getPropertyValue('-o-transform') || + e.style.getPropertyValue('transform'); + if (t) { + var xy = t.split('(')[1].split(')')[0].split(','); + return [ui.numpos(xy[0]), ui.numpos(xy[1])]; + } + if (e.id.substring(0, 8) == 'palette:') { + // On Internet Explorer get the view bounding rect as the palette + // doesn't return a correct bounding rect + var pbr = msiefixupbounds? $('viewcontent').getBoundingClientRect() : $('palettecontent').getBoundingClientRect(); + var br = e.getBoundingClientRect(); + return [br.left - pbr.left, br.top - pbr.top]; + } + return [0, 0]; +} /** - * Init component references. + * Return the x position of a widget. */ -var editorComp = sca.component('Editor'); -var pages = sca.reference(editorComp, 'pages'); +function widgetxpos(e) { + var t = widgettransform(e)[0]; + return ui.numpos(e.style.left) + (isNil(t)? 0 : t); +} /** - * Page editing functions. + * Return the y position of a widget. */ -var page = {}; +function widgetypos(e) { + var t = widgettransform(e)[1]; + return ui.numpos(e.style.top) + (isNil(t)? 0 : t); +} /** - * Default positions and sizes. + * Return the class of a widget. */ -page.palcx = 2500; +function widgetclass(e) { + return e.className.split(' ')[0]; +} /** - * Init a page editor. + * Initialize a widget. */ -page.mkedit = function(pagediv, atitle, wvalue, wadd, wcopy, wdelete, onchange, onselect) { - - // Track element dragging and selection - page.dragging = null; - page.selected = null; - wvalue.readOnly = true; - wvalue.style.visibility = 'hidden'; - atitle.style.visibility = 'visible'; - page.mousemoved = false; - wcopy.disabled = true; - wdelete.disabled = true; - wadd.disabled = !editable; - - // Trigger widget select and page change events - page.onpagechange = onchange; - page.onselectwidget = onselect; - - /** - * Handle a mouse down event. - */ - function onmousedown(e) { - // On mouse controlled devices, run component selection logic right away - var selected = page.selected; - if (typeof e.touches == 'undefined') { - //debug('onmousedown-click'); - onclick(e); - } - - // Find a draggable element - var dragging = page.draggable(e.target, pagediv); - if (dragging == null || dragging != page.selected) - return true; - page.dragging = dragging; - - // Remember mouse position - var pos = typeof e.touches != "undefined"? e.touches[0] : e; - page.mousemoved = false; - page.dragX = pos.screenX; - page.dragY = pos.screenY; - page.moveX = pos.screenX; - page.moveY = pos.screenY; - - // Prevent default behavior on first click on a widget - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - return true; +function fixupwidget(e) { + + // Add draggable class + var wc = e.className; + e.className = ui.isMobile()? (wc + ' draggable3dm') : (wc + ' draggable3d'); + + // Convert widget position to a CSS transform + var x = ui.numpos(e.style.left); + var y = ui.numpos(e.style.top); + var t = 'translate(' + x + 'px,' + y + 'px)'; + e.style.setProperty('-webkit-transform', t, null); + e.style.setProperty('-moz-transform', t, null); + e.style.setProperty('-o-transform', t, null); + e.style.setProperty('-ms-transform', t, null); + e.style.setProperty('transform', t, null); + e.xtranslate = x; + e.ytranslate = y; + e.style.left = ui.pixpos(0); + e.style.top = ui.pixpos(0); + + if (wc == 'entry' || wc == 'password') { + var i = car(childElements(e)); + i.readOnly = true; + i.style.cursor = 'default'; + return e; } - - if (!ui.isMobile()) { - pagediv.onmousedown = function(e) { - //debug('onmousedown'); - return onmousedown(e); - }; - } else { - pagediv.ontouchstart = function(e) { - //debug('ontouchstart'); - return onmousedown(e); - }; + if (wc == 'link') { + var l = car(childElements(e)); + l.onclick = function(e) { return false; }; + return e; } + return e; +} - /** - * Handle a mouse up event. - */ - function onmouseup(e) { - if (page.dragging == null) - return true; - - // Snap to grid - var newX = page.gridsnap(ui.numpos(page.dragging.style.left)); - var newY = page.gridsnap(ui.numpos(page.dragging.style.top)); - page.dragging.style.left = ui.pixpos(newX); - page.dragging.style.top = ui.pixpos(newY); - - // Fixup widget style - page.initwidget(page.dragging); +/** + * Cleanup a widget before saving it. + */ +function cleanupwidget(e) { + //debug('cleanupwidget', e); - // Forget dragged element - page.dragging = null; + // Adjust widget class + var wc = widgetclass(e); + e.className = wc; - // Trigger page change event - page.onpagechange(false); + // Convert CSS transform to an absolute position + e.style.left = ui.pixpos(widgetxpos(e)); + e.style.top = ui.pixpos(widgetypos(e)); + e.style.removeProperty('-webkit-transform'); + e.style.removeProperty('-moz-transform'); + e.style.removeProperty('-o-transform'); + e.style.removeProperty('-ms-transform'); + e.style.removeProperty('transform'); + e.xtranslate = null; + e.ytranslate = null; - // Simulate onclick event - onclick(e); + // Clear outline + e.style.removeProperty('outline'); - return true; + if (wc == 'entry' || wc == 'password') { + var i = car(childElements(e)); + i.readOnly = false; + i.style.cursor = null; + return e; } - - if (!ui.isMobile()) { - pagediv.onmouseup = function(e) { - //debug('onmouseup'); - return onmouseup(e); - }; - } else { - pagediv.ontouchend = function(e) { - //debug('ontouchend'); - return onmouseup(e); - } + if (wc == 'link') { + var l = car(childElements(e)); + l.onclick = null; + return e; } + return e; +} + +/** + * Clone a widget. + */ +function clonewidget(e) { /** - * Handle a mouse move event. + * Clone an element's HTML. */ - function onmousemove(e) { - - // Track mouse moves - page.mousemoved = true; - - if (page.dragging == null) - return true; - - // Ignore duplicate mouse move events - if (page.moveX == page.dragX && page.moveY == page.dragY) - return true; - - // Compute position of dragged element - var curX = ui.numpos(page.dragging.style.left); - var curY = ui.numpos(page.dragging.style.top); - var newX = curX + (page.moveX - page.dragX); - var newY = curY + (page.moveY - page.dragY); - if (newX >= page.palcx) - page.dragX = page.moveX; - else - newX = page.palcx; - if (newY >= 0) - page.dragY = page.moveY; - else - newY = 0; - - // Move the dragged element - page.dragging.style.left = ui.pixpos(newX); - page.dragging.style.top = ui.pixpos(newY); - page.constrainwidget(page.dragging); - - return true; - } - - if (!ui.isMobile()) { - window.onmousemove = function(e) { + function mkclone(e) { + var ne = document.createElement('span'); - // Remember mouse position - page.moveX = e.screenX; - page.moveY = e.screenY; + // Skip the palette: prefix + ne.id = 'page:' + e.id.substr(8); - return onmousemove(e); - }; - } else { - pagediv.ontouchmove = function(e) { + // Copy the class and HTML content + ne.className = widgetclass(e); + ne.innerHTML = e.innerHTML; - // Remember touch position - var pos = e.touches[0]; - if (page.moveX == pos.screenX && page.moveY == pos.screenY) - return true; - page.moveX = pos.screenX; - page.moveY = pos.screenY; - if (page.moveX == page.dragX && page.moveY == page.dragY) - return true; + // Fixup the widget + fixupwidget(ne); - onmousemove(e); - }; + return ne; } /** - * Handle a mouse click event. + * Clone an element's position. */ - function onclick(e) { - - // Find selected element - var selected = page.draggable(e.target, pagediv); - if (selected == null) { - if (page.selected != null) { - - // Reset current selection - page.selectwidget(page.selected, false, atitle, wvalue, wcopy, wdelete); - page.selected = null; - - // Trigger widget select event - page.onselectwidget(null); - } - - // Dismiss the palette - if (ui.numpos(pagediv.style.left) != (page.palcx * -1)) - pagediv.style.left = ui.pixpos(page.palcx * -1); - - return true; - } - - // Deselect the previously selected element - page.selectwidget(page.selected, false, atitle, wvalue, wcopy, wdelete); - - // Clone element dragged from palette - if (selected.id.substring(0, 8) == 'palette:') { - page.selected = page.clone(selected); - - // Move into the editing area and hide the palette - page.selected.style.left = ui.pixpos(ui.numpos(page.selected.style.left) + page.palcx); - page.initwidget(page.selected); - pagediv.style.left = ui.pixpos(page.palcx * -1); - page.constrainwidget(page.selected); - - // Bring it to the top - page.bringtotop(page.selected); - - // Trigger page change event - page.onpagechange(true); - - // Select the element - page.selectwidget(page.selected, true, atitle, wvalue, wcopy, wdelete); - - // Trigger widget select event - page.onselectwidget(page.selected); - - return true; + function posclone(ne, e) { + ne.style.position = 'absolute'; + movewidget(ne, widgetxpos(e), widgetypos(e)); + return ne; + } - } + return posclone(mkclone(e), e); +} - // Bring selected element to the top - page.selected = selected; - page.bringtotop(page.selected); +/** + * Select a widget. + */ +function selectwidget(n, s) { + //debug('selectwidget', n, s); + if (isNil(n) || !s) { + // Clear the widget value field + $('widgetValue').value = ''; + $('widgetValue').readOnly = true; + $('widgetValue').style.display = 'none'; - // Select the element - page.selectwidget(page.selected, true, atitle, wvalue, wcopy, wdelete); + // Show the app title + $('appTitle').style.display = 'block'; - // Trigger widget select event - page.onselectwidget(page.selected); + // Update the copy and delete buttons + $('copyWidgetButton').disabled = true; + $('deleteWidgetButton').disabled = true; + // Clear the widget outline + if (!isNil(n)) + n.style.removeProperty('outline'); return true; } - if (!ui.isMobile()) { - pagediv.onclick = function(e) { - //debug('onclick'); - return onclick(e); - }; - } else { - pagediv.onclick = function(e) { - //debug('onclick'); - return onclick(e); - }; - } - - /** - * Handle field on change events. - */ - wvalue.onchange = wvalue.onblur = function() { - if (page.selected == null) - return false; - page.settext(page.selected, wvalue.value); - - // Trigger page change event - page.onpagechange(true); - return false; - }; - - // Handle add widget event. - wadd.onclick = function() { - - // Show the palette - pagediv.style.left = ui.pixpos(0); - return false; - }; - - // Handle delete event. - wdelete.onclick = function() { - if (page.selected == null) - return false; - - // Reset current selection - page.selectwidget(page.selected, false, atitle, wvalue, wcopy, wdelete); - - // Remove selected widget - page.selected.parentNode.removeChild(page.selected); - page.selected = null; - - // Trigger widget select event - page.onselectwidget(null); - - // Trigger page change event - page.onpagechange(true); - return false; - }; - - // Handle copy event. - wcopy.onclick = function() { - if (page.selected == null) - return false; - if (page.selected.id.substring(0, 8) == 'palette:') - return false; - - // Reset current selection - page.selectwidget(page.selected, false, atitle, wvalue, wcopy, wdelete); - - // Clone selected widget - page.selected = page.clone(page.selected); - - // Move 10 pixels down right - page.selected.style.left = ui.pixpos(ui.numpos(page.selected.style.left) + 10); - page.selected.style.top = ui.pixpos(ui.numpos(page.selected.style.top) + 10); - page.constrainwidget(page.selected); - - // Bring it to the top - page.bringtotop(page.selected); - - // Select the element - page.selectwidget(page.selected, true, atitle, wvalue, wcopy, wdelete); + // Outline the widget + n.style.outline = '2px solid #598edd'; - // Trigger widget select event - page.onselectwidget(page.selected); + // Update the widget value field + $('widgetValue').value = widgettext(n); + $('widgetValue').readOnly = false || !editable; + $('widgetValue').style.display = 'block'; - // Trigger page change event - page.onpagechange(true); - return false; - }; + // Hide the app title + $('appTitle').style.display = 'none'; - return pagediv; -}; + // Update the copy and delete buttons + $('copyWidgetButton').disabled = false || !editable; + $('deleteWidgetButton').disabled = false || !editable; + return true; +} /** * Return the text of a widget. */ -page.text = function(e) { +function widgettext(e) { function formula(e) { var f = e.id; if (f.substring(0, 5) != 'page:') @@ -483,43 +338,36 @@ page.text = function(e) { } function constant(e, f) { - if (e.className == 'h1' || e.className == 'h2' || e.className == 'text' || e.className == 'section') { + var wc = widgetclass(e); + if (wc == 'hd1' || wc == 'hd2' || wc == 'text' || wc == 'section') { var t = car(childElements(e)).innerHTML; return t == f? '' : t; } - if (e.className == 'button' || e.className == 'checkbox') { + if (wc == 'button' || wc == 'checkbox') { var t = car(childElements(e)).value; return t == f? '' : t; } - if (e.className == 'entry' || e.className == 'password') { + if (wc == 'entry' || wc == 'password') { var t = car(childElements(e)).defaultValue; return t == f? '' : t; } - /* - if (e.className == 'select') { + if (wc == 'select') { var t = car(childElements(car(childElements(e)))).value; return t == f? '' : t; } - */ - if (e.className == 'link') { + if (wc == 'link') { var lhr = car(childElements(e)).href; var hr = lhr.substring(0, 5) == 'link:'? lhr.substring(5) : ''; var t = car(childElements(car(childElements(e)))).innerHTML; return t == f? hr : (t == hr? hr : (t == ''? hr : hr + ',' + t)); } - if (e.className == 'img') { + if (wc == 'img') { var src = car(childElements(e)).src; return src == location.href? '' : src; } - /* - if (e.className == 'iframe') { - var hr = car(childElements(e)).href; - return hr == location.href? '' : hr; - } - */ - if (e.className == 'list') + if (wc == 'list') return ''; - if (e.className == 'table') + if (wc == 'table') return ''; return ''; } @@ -527,41 +375,36 @@ page.text = function(e) { var f = formula(e); var c = constant(e, f); return f == ''? c : (c == ''? f : f + ',' + c); -}; +} /** * Return true if a widget has editable text. */ -page.hastext = function(e) { - if (e.className == 'h1' || e.className == 'h2' || e.className == 'text' || e.className == 'section') +function widgethastext(e) { + var wc = widgetclass(e); + if (wc == 'hd1' || wc == 'hd2' || wc == 'text' || wc == 'section') return true; - if (e.className == 'button' || e.className == 'checkbox') + if (wc == 'button' || wc == 'checkbox') return true; - if (e.className == 'entry' || e.className == 'password') + if (wc == 'entry' || wc == 'password') return true; - /* - if (e.className == 'select') + if (wc == 'select') return false; - */ - if (e.className == 'link') - return true; - if (e.className == 'img') + if (wc == 'link') return true; - /* - if (e.className == 'iframe') + if (wc == 'img') return true; - */ - if (e.className == 'list') + if (wc == 'list') return false; - if (e.className == 'table') + if (wc == 'table') return false; return false; -}; +} /** * Set the text of a widget. */ -page.settext = function(e, t) { +function setwidgettext(e, t) { function formula(t) { if (t.length > 1 && t.substring(0, 1) == '=') return car(t.split(',')); @@ -575,268 +418,690 @@ page.settext = function(e, t) { var f = formula(t); var c = constant(t); - e.id = f != ''? f.substring(1) : ('page:' + e.className); + var wc = widgetclass(e); + e.id = f != ''? f.substring(1) : ('page:' + wc); - if (e.className == 'h1' || e.className == 'h2' || e.className == 'text' || e.className == 'section') { + if (wc == 'hd1' || wc == 'hd2' || wc == 'text' || wc == 'section') { car(childElements(e)).innerHTML = isNil(c)? f : car(c); return t; } - if (e.className == 'button') { + if (wc == 'button') { car(childElements(e)).value = isNil(c)? f : car(c); return t; } - if (e.className == 'entry' || e.className == 'password') { + if (wc == 'entry' || wc == 'password') { car(childElements(e)).defaultValue = isNil(c)? f : car(c); return t; } - if (e.className == 'checkbox') { + if (wc == 'checkbox') { car(childElements(e)).value = isNil(c)? f : car(c); map(function(n) { if (n.nodeName == "SPAN") n.innerHTML = isNil(c)? f : car(c); return n; }, nodeList(e.childNodes)); return t; } - /* - if (e.className == 'select') { + if (wc == 'select') { var ce = car(childElements(car(childElements(e)))); ce.value = isNil(c)? f : car(c); ce.innerHTML = isNil(c)? f : car(c); return t; } - */ - if (e.className == 'list') { + if (wc == 'list') { e.innerHTML = '<table class="datatable" style="width: 100%;;"><tr><td class="datatd">' + (isNil(c)? f : car(c)) + '</td></tr><tr><td class="datatd">...</td></tr></table>'; return t; } - if (e.className == 'table') { + if (wc == 'table') { e.innerHTML = '<table class="datatable" style="width: 100%;"><tr><td class="datatdl">' + (isNil(c)? f : car(c)) + '</td><td class="datatdr">...</td></tr><tr><td class="datatdl">...</td><td class="datatdr">...</td></tr></table>'; return t; } - if (e.className == 'link') { + if (wc == 'link') { var ce = car(childElements(e)); ce.href = isNil(c)? 'link:/index.html' : ('link:' + car(c)); car(childElements(ce)).innerHTML = isNil(c)? (f != ''? f : '/index.html') : isNil(cdr(c))? (f != ''? f : car(c)) : cadr(c); return t; } - if (e.className == 'img') { + if (wc == 'img') { car(childElements(e)).src = isNil(c)? '/public/img.png' : car(c); return t; } - /* - if (e.className == 'iframe') { - car(childElements(e)).href = isNil(c)? '/public/iframe-min.html' : car(c); - return t; - } - */ return ''; -}; +} /** - * Initialize a widget. + * Align a pos along a 9pixel grid. */ -page.initwidget = function(e) { - - // Add a Webkit transform to leverage hardware acceleration - e.style.setProperty('-webkit-transform', 'translate(0px, 0px)', null); - - /* - if (e.className == 'iframe') { - var f = car(childElements(e)); - //e.innerHTML = '<iframe src="' + f.href + '" frameborder="no" scrolling="no"></iframe>'; - return e; - } - */ +function snaptogrid(x) { + return Math.round(x / 10) * 10; +} - if (e.className == 'section') { - e.style.width = '100%'; - return e; - } - if (e.className == 'text' || e.className == 'h1' || e.className == 'h2') { - return e; - } - if (e.className == 'button') { - return e; - } - if (e.className == 'checkbox') { - return e; - } - if (e.className == 'list' || e.className == 'table') { - e.style.width = '100%'; - var t = car(childElements(e)); - t.style.width = '100%'; - return e; - } - if (e.className == 'img') { - var i = car(childElements(e)); - if (i.src != '' && i.src.substring(0, 5) == 'data:') - i.src = '/public/img.png'; - return e; - } - if (e.className == 'entry' || e.className == 'password') { - var i = car(childElements(e)); - i.readOnly = true; - i.style.cursor = 'default'; - return e; - } - if (e.className == 'link') { - var l = car(childElements(e)); - l.onclick = function(e) { return false; }; - return e; - } - return e; +/** + * Bring a node to the top. + */ +function bringtotop(n) { + n.parentNode.appendChild(n); } /** - * Enforce widget position and style constraints. + * Move a widget. */ -page.constrainwidget = function(e) { - if (e.className == 'section' || e.className == 'list' || e.className == 'table') { - e.style.left = ui.pixpos(page.palcx); - return e; - } +var iefixuptransform = ui.isMSIE(); +var fffixupoutline = ui.isFirefox() && (ui.firefoxVersion() > 4); +function movewidget(e, x, y) { + var t = 'translate(' + x + 'px,' + y + 'px)'; + e.style.setProperty('-webkit-transform', t, null); + e.style.setProperty('-moz-transform', t, null); + e.style.setProperty('-o-transform', t, null); + // On Internet Explorer set the property directly as setProperty + // doesn't seem to apply + if (iefixuptransform) + e.style.msTransform = t; + e.style.setProperty('transform', t, null); + e.xtranslate = x; + e.ytranslate = y; return e; -}; +} /** - * Cleanup of a widget before saving it. + * Return a widget bounding rect. */ -page.cleanupwidget = function(e) { - //debug('cleanupwidget', e); +var fffixupbounds = ui.isFirefox() && (ui.firefoxVersion() < 12); +function widgetbounds(e) { + var br = e.getBoundingClientRect(); + if (!fffixupbounds) + return br; + + // On Firefox < 12, apply CSS transform translation to bounding rect manually + //debug('fixup br', e, br.left, br.top, br.right, br.bottom, t[0], t[1]); + function fixuptransform(e) { + var t = widgettransform(e); + if (!isNil(e.xtranslate)) + return [e.xtranslate, e.ytranslate]; + var t = e.style.getPropertyValue('-webkit-transform') || e.style.getPropertyValue('-moz-transform') || + e.style.getPropertyValue('-ms-transform') || e.style.getPropertyValue('-o-transform') || + e.style.getPropertyValue('transform'); + if (t) { + var xy = t.split('(')[1].split(')')[0].split(','); + return [ui.numpos(xy[0]), ui.numpos(xy[1])]; + } + return [0, 0]; + } - // Clear outline - e.style.outline = null; + var t = fixuptransform(e); + var fbr = new Object(); + fbr.left = br.left + t[0]; + fbr.top = br.top + t[1]; + fbr.right = fbr.left + e.offsetWidth; + fbr.bottom = fbr.top + e.offsetHeight; + return fbr; +} - // Clear the Webkit transform - e.style.removeProperty('-webkit-transform'); +/** + * Find a draggable element in a list. + */ +function draggable(x, y, l) { + //debug('draggable?', x, y, l); + if (isNil(l)) + return null; + var n = car(l); + if (isNil(n.id) || n.id == '') { + var d = draggable(x, y, reverse(nodeList(n.childNodes))); + if (!isNil(d)) + return d; + return draggable(x, y, cdr(l)); + } + var br = widgetbounds(n); + //debug('element br', n, br.left, br.top, br.right, br.bottom); + if (x >= br.left && x <= br.right && y >= br.top && y <= br.bottom) + return n; + return draggable(x, y, cdr(l)); +} - if (e.className == 'entry' || e.className == 'password') { - var i = car(childElements(e)); - i.readOnly = false; - i.style.cursor = null; - return e; +/** + * Play page in a frame. + */ +function showplaying() { + $('playPageButton').value = '<'; + $('playdiv').style.display = 'block'; + $('playdiv').visible = true; + $('playdiv').innerHTML = ''; + $('playdiv').innerHTML = '<iframe id="playappframe" style="position: relative; border: 0px;" scrolling="no" frameborder="0" src="/' + appname + '"></iframe>'; + if ($('pagediv').visible) { + $('pagediv').style.display = 'none' + $('pagediv').visible = false; } - if (e.className == 'link') { - var l = car(childElements(e)); - l.onclick = null; - return e; + hidepalette(); + return true; +} + +/** + * Show the page editor. + */ +function showeditor() { + $('playPageButton').value = '>'; + $('pagediv').style.display = 'block' + $('pagediv').visible = true; + if ($('playdiv').visible) { + $('playdiv').style.display = 'none'; + $('playdiv').innerHTML = ''; + $('playdiv').visible = false; } - return e; + hidepalette(); + return true; } /** - * Find a draggable element in a hierarchy of elements. + * Palette animation. */ -page.draggable = function(n, e) { - if (n == e) - return null; - if (!isNil(n.id) && n.id != '') - return n; - return page.draggable(n.parentNode, e); +function palettetransitionend(e) { + if ($('paletteview').className == 'paletteunloaded3dm') + $('paletteview').style.display = 'none'; } +$('paletteview').addEventListener('webkitTransitionEnd', palettetransitionend, false); +$('paletteview').addEventListener('transitionend', palettetransitionend, false); + /** - * Align a pos along a 9pixel grid. + * Show the palette. */ -page.gridsnap = function(x) { - return Math.round(x / 9) * 9; +function showpalette() { + if (ui.isMobile()) { + $('paletteview').className = 'paletteloading3dm'; + $('paletteview').style.display = 'block'; + $('paletteview').visible = true; + ui.async(function transitionview() { + $('paletteview').className = 'paletteloaded3dm'; + }); + } else { + $('paletteview').className = 'paletteloaded3d'; + $('paletteview').style.display = 'block'; + $('paletteview').visible = true; + } + return true; } /** - * Bring an element and its parent to the top. + * Hide the palette. */ -page.bringtotop = function(n) { - n.parentNode.appendChild(n); +function hidepalette() { + if (ui.isMobile()) { + $('paletteview').className = 'paletteunloading3dm'; + $('paletteview').visible = false; + ui.async(function transitionview() { + $('paletteview').className = 'paletteunloaded3dm'; + }); + } else { + $('paletteview').className = 'paletteunloaded3d'; + $('paletteview').style.display = 'none'; + $('paletteview').visible = false; + } + return true; } /** - * Select a widget. + * Create page editor. */ -page.selectwidget = function(n, s, atitle, wvalue, wcopy, wdelete) { - //debug('selectwidget', n, s); - if (isNil(n) || !s) { - // Clear the widget value field - wvalue.value = ''; - wvalue.readOnly = true; - wvalue.style.visibility = 'hidden'; - atitle.style.visibility = 'visible'; - wcopy.disabled = true; - wdelete.disabled = true; +function mkeditor() { + + // Initialize header elements + $('widgetValue').readOnly = true; + $('widgetValue').style.display = 'none'; + $('appTitle').style.display = 'block'; + $('copyWidgetButton').disabled = true; + $('deleteWidgetButton').disabled = true; + $('addWidgetButton').disabled = !editable; + + // Track widget dragging and selection + var dragging = null; + var selected = null; + var moved = false; + var mdown = false; + var moveX = 0; + var moveY = 0; + var dragX = 0; + var dragY = 0; - // Clear the widget outline - if (!isNil(n)) - n.style.outline = null; + /** + * Handle a page change event + */ + function onpagechange(prop) { + if (!editable) + return false; + + var newxml = pagexhtml(); + if (savedxhtml == newxml) + return false; + showstatus('Modified'); + + // Save property changes right away + if (prop) + return save(newxml); + // Autosave other changes after 1 second + ui.delay(function autosave() { + if (savedxhtml == newxml) { + showstatus('Saved'); + return false; + } + return save(newxml); + }, 1000); return true; } - // Outline the widget - n.style.outline = '2px solid #598edd'; + /** + * Handle a widget select event. + */ + function onselectwidget(w) { + if (w == selected) + return true; + selected = w; + return true; + } - // Update the widget value field - wvalue.value = page.text(n); - wvalue.readOnly = false || !editable; - wvalue.style.visibility = 'visible'; - atitle.style.visibility = 'hidden'; - wcopy.disabled = false || !editable; - wdelete.disabled = false || !editable; + /** + * Render widget move animation. + */ + function onmoveanimation() { + //debug('onmoveanimation'); - return true; -}; + // Stop animation if we're not dragging an element anymore + if (dragging == null) + return true; -/** - * Clone a palette element. - */ -page.clone = function(e) { + // Request the next animation frame + ui.animation(onmoveanimation); + + // Nothing to do if the selected widget has not moved + if (moveX == dragX && moveY == dragY) + return true; + + // Compute position of dragged element + var curX = widgetxpos(dragging); + var curY = widgetypos(dragging); + var newX = curX + (moveX - dragX); + var newY = curY + (moveY - dragY); + + var okx = true; + if (newX + dragging.clientWidth > 1024) { + newX = 1024 - dragging.clientWidth; + okx = false; + } + if (newX < 0) { + newX = 0; + okx = false; + } + if (okx) + dragX = moveX; + var oky = true; + if (newY + dragging.clientHeight > 1024) { + newY = 1024 - dragging.clientHeight; + oky= false; + } + if (newY < 0) { + newY = 0; + oky = false; + } + if (oky) + dragY = moveY; + + // On Firefox > 4, remove outline before moving widget as it's not + // correctly painted + if (fffixupoutline) + dragging.style.removeProperty('outline'); + + // Move the dragged element + movewidget(dragging, newX, newY); + + return true; + } /** - * Clone an element's HTML. + * Handle a mouse down event. */ - function mkclone(e) { - var ne = document.createElement('span'); + function onmousedown(e) { + // On mouse controlled devices, run component selection logic right away + if (!ui.isMobile()) { + //debug('onmousedown-click'); + onclick(e); + } - // Skip the palette: prefix - ne.id = 'page:' + e.id.substr(8); + // Find a draggable widget + var d = draggable(moveX, moveY, reverse(nodeList($('pagediv').childNodes))); + //debug('dragging', d, 'selected', selected); + if (d == null || d != selected) + return true; + dragging = d; - // Copy the class and HTML content - ne.className = e.className; - ne.innerHTML = e.innerHTML; + // Remember mouse position + dragX = moveX; + dragY = moveY; - // Fixup the widget style - page.initwidget(ne); + // Start move animation + ui.animation(onmoveanimation); - return ne; + e.preventDefault(); + return true; + } + + if (!ui.isMobile()) { + $('pagediv').onmousedown = function(e) { + //debug('onmousedown', e.target); + mdown = true; + moveX = e.clientX; + moveY = e.clientY; + moved = false; + return onmousedown(e); + }; + $('palettecontent').onmousedown = function(e) { + //debug('onmousedown', e.target); + mdown = true; + moveX = e.clientX; + moveY = e.clientY; + moved = false; + return onmousedown(e); + }; + } else { + $('pagediv').ontouchstart = function(e) { + //debug('ontouchstart'); + mdown = true; + moveX = e.touches[0].clientX; + moveY = e.touches[0].clientY; + moved = false; + return onmousedown(e); + }; + $('pagediv').addEventListener('touchstart', function(e) { + //debug('ontouchstart'); + mdown = true; + moveX = e.touches[0].clientX; + moveY = e.touches[0].clientY; + moved = false; + return onmousedown(e); + }, false); + $('palettecontent').ontouchstart = function(e) { + //debug('ontouchstart'); + mdown = true; + moveX = e.touches[0].clientX; + moveY = e.touches[0].clientY; + moved = false; + return onmousedown(e); + }; } /** - * Clone an element's position. + * Handle a mouse up event. */ - function posclone(ne, e) { - ne.style.position = 'absolute'; - ne.style.left = ui.pixpos(ui.numpos(e.style.left)); - ne.style.top = ui.pixpos(ui.numpos(e.style.top)); - e.parentNode.appendChild(ne); - return ne; + function onmouseup(e) { + // Simulate onclick event + if (ui.isMobile() && !moved) { + //debug('ontouchend-click'); + return onclick(e); + } + + if (dragging == null) + return true; + + // Snap dragged widget to grid + var newX = snaptogrid(widgetxpos(dragging)); + var newY = snaptogrid(widgetypos(dragging)); + movewidget(dragging, newX, newY); + + // Forget dragged element + dragging = null; + + // Trigger page change event + onpagechange(false); + + // On Firefox > 4, re-apply the outline after the widget has been repositioned + if (fffixupoutline && !isNil(selected)) { + ui.delay(function() { + if (!isNil(selected)) + selected.style.outline = '2px solid #598edd'; + }, 32); + } + return true; } - return posclone(mkclone(e), e); -}; + if (!ui.isMobile()) { + window.onmouseup = function(e) { + //debug('onmouseup'); + if (!mdown) + return true; + return onmouseup(e); + }; + } else { + window.ontouchend = function(e) { + //debug('ontouchend'); + if (!mdown) + return true; + return onmouseup(e); + } + } -/** - * Track the current widget. - */ -var widget = null; + if (!ui.isMobile()) { + window.onmousemove = function(e) { + //debug('onmousemove'); + + // Record mouse position + if (e.clientX != moveX) { + moved = true; + moveX = e.clientX; + } + if (e.clientY != moveY) { + moved = true; + moveY = e.clientY; + } + if (dragging == null) + return true; + return false; + }; + } else { + window.ontouchmove = function(e) { + //debug('ontouchmove'); + + // Record touch position + var t = e.touches[0]; + if (t.clientX != moveX) { + moved = true; + moveX = t.clientX; + } + if (t.clientY != moveY) { + moved = true; + moveY = t.clientY; + } + if (dragging == null) + return true; + return false; + }; + } + + /** + * Handle a mouse click event. + */ + function onclick(e) { + + // Find selected element + var palvis = $('paletteview').visible? true : false; + var s = draggable(moveX, moveY, reverse(nodeList((palvis? $('palettecontent') : $('pagediv')).childNodes))); + //debug('selected', s); + if (s == null) { + if (selected != null) { + + // Reset current selection + selectwidget(selected, false); + selected = null; + + // Trigger widget select event + onselectwidget(null); + } + + // Dismiss the palette + if (palvis && isNil(draggable(moveX, moveY, mklist($('palettecontent'))))) + hidepalette(); + + return true; + } + + // Deselect the previously selected element + selectwidget(selected, false); + + // Clone widget dragged from palette + if (s.id.substring(0, 8) == 'palette:') { + selected = clonewidget(s); + + // Add it to the page + $('pagediv').appendChild(selected); + movewidget(selected, widgetxpos(selected) + $('viewcontent').scrollLeft, widgetypos(selected) + $('viewcontent').scrollTop); + + // Hide the palette + hidepalette(); + + // Trigger page change event + onpagechange(true); + + // Select the element + selectwidget(selected, true); + + // Trigger widget select event + onselectwidget(selected); + + return true; + } + + // Bring selected widget to the top + selected = s; + bringtotop(selected); + + // Select the widget + selectwidget(selected, true); + + // Trigger widget select event + onselectwidget(selected); + + return true; + } + + /* + if (!ui.isMobile()) { + $('pagediv').onclick = function(e) { + //debug('onclick'); + moveX = e.clientX; + moveY = e.clientY; + return onclick(e); + }; + } else { + window.onclick = function(e) { + //debug('onclick'); + moveX = e.touches[0].clientX; + moveY = e.touches[0].clientY; + return onclick(e); + }; + } + */ + + /** + * Handle field on change events. + */ + $('widgetValue').onchange = $('widgetValue').onblur = function() { + if (selected == null) + return false; + setwidgettext(selected, $('widgetValue').value); + + // Trigger page change event + onpagechange(true); + return false; + }; + + // Handle add widget event. + $('addWidgetButton').onclick = function() { + + // Show / hide the palette + if ($('paletteview').visible) + return hidepalette(); + return showpalette(); + }; + + // Handle delete event. + $('deleteWidgetButton').onclick = function() { + if (selected == null) + return false; + + // Reset current selection + selectwidget(selected, false); + + // Remove selected widget + selected.parentNode.removeChild(selected); + selected = null; + + // Trigger widget select event + onselectwidget(null); + + // Trigger page change event + onpagechange(true); + return false; + }; + + // Handle copy event. + $('copyWidgetButton').onclick = function() { + if (selected == null) + return false; + if (selected.id.substring(0, 8) == 'palette:') + return false; + + // Reset current selection + selectwidget(selected, false); + + // Clone selected widget + selected = clonewidget(selected); + + // Add it to the page + $('pagediv').appendChild(selected); + + // Move 10 pixels down right + movewidget(selected, widgetxpos(selected) + 10, widgetypos(selected) + 10); + + // Bring it to the top + bringtotop(selected); + + // Select the element + selectwidget(selected, true); + + // Trigger widget select event + onselectwidget(selected); + + // Trigger page change event + onpagechange(true); + return false; + }; + + /** + * Handle play page button event. + */ + $('playPageButton').onclick = function() { + + // Show / hide the page play frame + if ($('playdiv').visible) + return showeditor(); + return showplaying(); + } + + // Show the editor + showeditor(); + + return true; +}; /** - * Get and display an app page. + * Get and display the requested app page. */ -function getpage(name, pagediv) { - if (isNil(name)) +(function getpage() { + if (isNil(appname)) return false; - showStatus('Loading'); + workingstatus(true); + showstatus('Loading'); - return pages.get(name, function(doc) { + return pages.get(appname, function(doc) { // Stop now if we didn't get a page if (doc == null) { - showError('App not available'); + errorstatus('Couldn\'t get the app info'); + workingstatus(false); return false; } @@ -844,67 +1109,56 @@ function getpage(name, pagediv) { var pageentry = car(atom.readATOMEntry(mklist(doc))); var content = namedElementChild("'content", pageentry); var el = isNil(content)? mklist() : elementChildren(content); - var buffer = $('buffer'); if (isNil(el)) - buffer.innerHTML = '<div id="page"></div>'; + $('xhtmlbuffer').innerHTML = '<div id="page"></div>'; else - buffer.innerHTML = writeStrings(writeXML(el, false)); + $('xhtmlbuffer').innerHTML = writeStrings(writeXML(el, false)); // Remove any existing page nodes from the editor div var fnodes = filter(function(e) { - if (isNil(e.id) || e.id == '' || e.id.substr(0, 8) == 'palette:') - return false; - var x = ui.numpos(e.style.left) - 2500; - if (x < 0 || ui.numpos(e.style.top) < 0) + if (isNil(e.id) || e.id == '') return false; return true; - }, nodeList(pagediv.childNodes)); + }, nodeList($('pagediv').childNodes)); map(function(e) { - pagediv.removeChild(e); + $('pagediv').removeChild(e); }, fnodes); - // Append new page nodes to editor + // Fixup widgets and append them to the editor map(function(e) { - pagediv.appendChild(e); - if (!isNil(e.style)) - e.style.left = ui.pixpos(ui.numpos(e.style.left) + 2500); - page.initwidget(e); + $('pagediv').appendChild(e); + fixupwidget(e); return e; - }, nodeList(buffer.childNodes[0].childNodes)); + }, nodeList($('xhtmlbuffer').childNodes[0].childNodes)); - savedpagexhtml = pagexhtml(pagediv); + savedxhtml = pagexhtml($('pagediv')); // Enable author to edit the page author = elementValue(namedElementChild("'author", pageentry)); editable = author == username; - wadd.disabled = !editable; - showStatus(editable? onlineStatus() : 'Read only'); + $('addWidgetButton').disabled = !editable; + if (editable) + onlinestatus(); + else + showstatus('Read only'); + workingstatus(false); return true; }); -} - -/** - * Handle add widget button click event. - */ -wadd.onclick = function(e) { - // Show the widget palette - pagediv.style.left = ui.pixpos(0); -}; +})(); /** * Return the current page XHTML content. */ -function pagexhtml(pagediv) { +function pagexhtml() { // Copy page DOM to hidden buffer - var buffer = $('buffer'); - buffer.innerHTML = '<div id="page"></div>' - var div = buffer.childNodes[0]; + $('xhtmlbuffer').innerHTML = '<div id="page"></div>' + var div = $('xhtmlbuffer').childNodes[0]; // Capture the nodes inside the page div - div.innerHTML = pagediv.innerHTML; + div.innerHTML = $('pagediv').innerHTML; var nodes = nodeList(div.childNodes); map(function(e) { div.removeChild(e); @@ -915,30 +1169,25 @@ function pagexhtml(pagediv) { // part of the page, as well as nodes positioned out the // editing area var fnodes = filter(function(e) { - if (isNil(e.id) || e.id == '' || e.id.substr(0, 8) == 'palette:') - return false; - var x = ui.numpos(e.style.left) - 2500; - if (x < 0 || ui.numpos(e.style.top) < 0) + if (isNil(e.id) || e.id == '') return false; return true; }, nodes); // Reposition and cleanup nodes map(function(e) { - var x = ui.numpos(e.style.left) - 2500; - e.style.left = ui.pixpos(x); - page.cleanupwidget(e); + cleanupwidget(e); return e; }, fnodes); // Sort them by position var snodes = fnodes.sort(function(a, b) { - var ay = ui.numpos(a.style.top); - var by = ui.numpos(b.style.top); + var ay = widgetypos(a); + var by = widgetypos(b); if (ay < by) return -1; if (ay > by) return 1; - var ax = ui.numpos(a.style.left); - var bx = ui.numpos(b.style.left); + var ax = widgetxpos(a); + var bx = widgetxpos(b); if (ax < bx) return -1; if (ax > bx) return 1; return 0; @@ -960,10 +1209,11 @@ function pagexhtml(pagediv) { * Save the current page. */ function save(newxml) { - showStatus('Saving'); + workingstatus(true); + showstatus('Saving'); // Get the current page XHTML content - savedpagexhtml = newxml; + savedxhtml = newxml; // Update the page ATOM entry var entry = '<?xml version="1.0" encoding="UTF-8"?>\n' + '<entry xmlns="http://www.w3.org/2005/Atom">' + @@ -972,108 +1222,21 @@ function save(newxml) { pages.put(appname, entry, function(e) { if (e) { - showStatus('Local copy'); + showstatus('Local copy'); + workingstatus(false); return false; } - showStatus('Saved'); + showstatus('Saved'); + workingstatus(false); return false; }); return true; }; /** - * Handle a page change event - */ -function onpagechange(prop) { - if (!editable) - return false; - - var newxml = pagexhtml(pagediv); - if (savedpagexhtml == newxml) - return false; - showStatus('Modified'); - - // Save property changes right away - if (prop) - return save(newxml); - - // Autosave other changes after 1 second - setTimeout(function() { - if (savedpagexhtml == newxml) { - showStatus('Saved'); - return false; - } - return save(newxml); - }, 1000); - return true; -} - -/** - * Handle a widget select event. - */ -function onselectwidget(w) { - if (w == widget) - return true; - widget = w; - return true; -} - -/** - * Play page in a frame. - */ -function playpage() { - if (!evisible) - return true; - page.selectwidget(widget, false, atitle, wvalue, wcopy, wdelete); - page.selected = null; - pplay.value = '<'; - evisible = false; - pdiv.style.visibility = 'visible'; - pdiv.innerHTML = ''; - pdiv.innerHTML = '<iframe id="playappframe" style="position: relative; height: 5000px; width: 2500px; border: 0px;" scrolling="no" frameborder="0" src="/' + - appname + '"></iframe>'; - setTimeout(function() { - pagediv.style.visibility = 'hidden' - }, 0); - return true; -} - -/** - * Show the page editor. - */ -function showedit() { - if (evisible) - return true; - pplay.value = '>'; - pagediv.style.visibility = 'visible' - evisible = true; - page.selectwidget(widget, true, atitle, wvalue, wcopy, wdelete); - page.selected = widget; - setTimeout(function() { - pdiv.style.visibility = 'hidden'; - pdiv.innerHTML = ''; - }, 0); - return true; -} - -/** - * Handle play page button event. - */ -pplay.onclick = function() { - if (!evisible) - return showedit(); - return playpage(); -} - -/** * Initialize the page editor. */ -page.mkedit(pagediv, atitle, wvalue, wadd, wcopy, wdelete, onpagechange, onselectwidget); - -/** - * Get and display the current app page. - */ -getpage(appname, pagediv); +mkeditor(); })(); </script> diff --git a/sca-cpp/trunk/hosting/server/htdocs/proxy/public/cache/cache-template.cmf b/sca-cpp/trunk/hosting/server/htdocs/proxy/public/cache/cache-template.cmf new file mode 100644 index 0000000000..232b0f9a0e --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/proxy/public/cache/cache-template.cmf @@ -0,0 +1,11 @@ +CACHE MANIFEST + +# Version SHA1 + +# App resources +/favicon.ico +/proxy/public/oops/ + +NETWORK: +* + diff --git a/sca-cpp/trunk/hosting/server/htdocs/proxy/public/cache/index.html b/sca-cpp/trunk/hosting/server/htdocs/proxy/public/cache/index.html new file mode 100644 index 0000000000..4006001435 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/proxy/public/cache/index.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<!-- + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. +--> +<html manifest="/proxy/public/cache/cache-manifest.cmf"> +<head> +<script type="text/javascript"> +applicationCache.addEventListener('checking', function(e) { + return window.parent.onappcachechecking(e); +}, false); +applicationCache.addEventListener('error', function(e) { + return window.parent.onappcacheerror(e); +}, false); +applicationCache.addEventListener('noupdate', function(e) { + return window.parent.onappcachenoupdate(e); +}, false); +applicationCache.addEventListener('downloading', function(e) { + return window.parent.onappcachedownloading(e); +}, false); +applicationCache.addEventListener('progress', function(e) { + return window.parent.onappcacheprogress(e); +}, false); +applicationCache.addEventListener('updateready', function(e) { + return window.parent.onappcacheupdateready(e); +}, false); +applicationCache.addEventListener('cached', function(e) { + return window.parent.onappcachecached(e); +}, false); +window.onload = function() { + window.parent.onloadappcache(); +}; +</script> +</head> +<body> +</body> +</html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/proxy/public/oops/index.html b/sca-cpp/trunk/hosting/server/htdocs/proxy/public/oops/index.html index b1d118d59a..5ea9d7619d 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/proxy/public/oops/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/proxy/public/oops/index.html @@ -19,33 +19,50 @@ --> <html> <head> +<!-- Firebug inspector --> +<!-- +<script type="text/javascript" src="https://getfirebug.com/releases/lite/1.3/firebug-lite.js"></script> +--> +<!-- Weinre inspector --> +<!-- +<script src="http://www.example.com:9998/target/target-script-min.js#anonymous"></script> +--> <title>Oops</title> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"/> +<!-- <meta name="apple-mobile-web-app-capable" content="yes"/> <meta name="apple-mobile-web-app-status-bar-style" content="black"/> +--> +<link rel="apple-touch-icon-precomposed" href="/proxy/public/touchicon.png"/> <base href="/proxy/public/oops/"/> <script type="text/javascript"> -(function() { +try { + +(function oopshead() { window.appcache = {}; /** * Get and cache a resource. */ -appcache.get = function(uri) { +appcache.get = function(uri, mode) { var h = uri.indexOf('#'); var u = h == -1? uri : uri.substring(0, h); // Get resource from local storage first var ls = window.lstorage || localStorage; - var item = null; - try { item = ls.getItem(u); } catch(e) {} - if (item != null && item != '') - return item; + if (mode != 'remote') { + var item = null; + try { item = ls.getItem('ui.r.' + u); } catch(e) {} + if (item != null && item != '') + return item; + if (mode == 'local') + return null; + } // Get resource from network var http = new XMLHttpRequest(); - http.open("GET", u, false); + http.open("GET", mode == 'remote'? (u + '?t=' + new Date().getTime() + '&r=' + Math.random()) : u, false); http.setRequestHeader("Accept", "*/*"); http.send(null); if (http.status == 200) { @@ -56,7 +73,7 @@ appcache.get = function(uri) { if (window.debug) debug('http error', u, 'No-Content'); return null; } - try { ls.setItem(u, http.responseText); } catch(e) {} + try { ls.setItem('ui.r.' + u, http.responseText); } catch(e) {} return http.responseText; } if (window.debug) debug('http error', u, http.status, http.statusText); @@ -68,36 +85,32 @@ appcache.get = function(uri) { /** * Load Javascript and CSS. */ -(function() { +(function oopsboot() { var bootjs = document.createElement('script'); bootjs.type = 'text/javascript'; -bootjs.text = appcache.get('/proxy/all-min.js'); -document.head.appendChild(bootjs); -document.head.appendChild(ui.declareCSS(appcache.get('/proxy/ui-min.css'))); +bootjs.text = 'try {\n' + appcache.get('/proxy/all-min.js') + '\n' + appcache.get('/proxy/public/config-min.js') + '\n} catch(e) { console.log(e.stack); throw e; }\n'; +var head = document.getElementsByTagName('head')[0]; +head.appendChild(bootjs); +head.appendChild(ui.declareCSS(appcache.get('/proxy/ui-min.css'))); })(); +} catch(e) { + if (window.debug) debug(e.stack); + throw e; +} </script> </head> <body class="delayed"> -<div id="mainbodydiv" class="mainbodydiv"> - -<div id="headdiv" class="hsection"> -<script type="text/javascript"> -(function() { -$('headdiv').appendChild(ui.declareScript(appcache.get('/proxy/public/config-min.js'))); - -})(); -</script> +<div id="menucontainer" class="tbarmenu"> +<div id="menu"></div> </div> -<div id="menubackground" class="tbarbackground fixed"></div> -<div id="menu" class="tbarmenu fixed"></div> - -<div id="viewheadbackground" class="viewheadbackground fixed"></div> -<div id="viewhead" class="viewhead fixed"></div> +<div id="viewheadcontainer" class="viewhead"> +<div id="viewhead"></div> +</div> <div id="viewcontainer"> <div id="view"> @@ -110,84 +123,250 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/proxy/public/config-min </div> </div> -<div id="viewfootbackground" class="viewfootbackground fixed"></div> -<div id="viewfoot" class="viewfoot fixed"></div> +<div id="viewfootcontainer" class="viewfoot"> +<div id="viewfoot"></div> +<div id="status"></div> +</div> + +<div id="installer" class="installer"></div> <script type="text/javascript"> -(function() { +try { + +(function oopsbody() { /** - * Init div variables. + * Setup page layout. */ -var mdiv = $('menu'); -var hdiv = $('viewhead'); -$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm'; -$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm'; -var fdiv = $('viewfoot'); +(function layout() { + $('viewcontainer').className = ui.isMobile()? 'viewcontainer3dm' : 'viewcontainer3d'; + $('view').className = ui.isMobile()? 'viewloaded3dm' : 'viewloaded3d'; + document.title = config.windowtitle() + ' - Oops'; + $('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; + if (!ui.isMobile()) + $('viewcontent').className = 'viewcontent flatscrollbars'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; +})(); /** - * Set page title. + * Setup menu bar. */ -document.title = config.windowtitle() + ' - Oops'; -$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; +(function showmenu() { + $('menu').innerHTML = ui.menubar( + mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), mklist()); + $('viewfoot').innerHTML = config.viewfoot(); +})(); /** - * Build and show the menu bar. + * Initialize status message area. */ -function showmenu(mdiv) { - mdiv.innerHTML = ui.menubar( - mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), - mklist(hasauthcookie()? ui.menufunc('menusignout', 'Sign out', 'return logout();', false) : ui.menu('menusignin', 'Sign in', '/login/', '_self', false))); - fdiv.innerHTML = config.viewfoot(); -} +(function initstatus() { + if (isNil($('status'))) + return; + $('status').style.display = 'none'; + + function divtransitionend(e) { + e.target.style.display = 'none'; + e.target.className = ui.isMobile()? 'status3dm' : 'status3d'; + e.target.error = false; + } + $('status').addEventListener('webkitTransitionEnd', divtransitionend, false); + $('status').addEventListener('transitionend', divtransitionend, false); +})(); -showmenu(mdiv); +/** + * Show a status message. + */ +window.showstatus = function(s, c) { + //debug('show status', s); + if (isNil($('status')) || $('status').error) + return s; + $('status').innerHTML = '<span class="' + (c? c : 'okstatus') + '">' + s + '</span>'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; + $('status').style.display = 'block'; + $('status').error = c == 'errorstatus'; + if ($('status').delay) + ui.cancelDelay($('status').delay); + $('status').delay = ui.delay(function hidestatus() { + $('status').className = ui.isMobile()? 'statusout3dm' : 'statusout3d'; + $('status').error = false; + }, 3000); + return s; +}; /** - * Log the current user out. + * Show an error message. */ -window.logout = function() { - // Clear session cookie and user-specific local storage entries - clearauthcookie(); - lstorage.removeItem('/r/Editor/accounts'); - lstorage.removeItem('/r/Editor/dashboards'); - document.location = '/login/'; - return false; -} +window.errorstatus = function(s) { + //debug('error', s); + return showstatus(s, 'errorstatus'); +}; + +/** + * Show working status. + */ +window.workingstatus = function(w, c) { + //debug('show working', w); + if (isNil($('working'))) + return w; + if (!ui.isMobile()) + $('working').style.top = ui.pixpos(Math.round(window.clientHeight / 2)); + $('working').style.display = w? 'block' : 'none'; + return w; +}; + +/** + * Show the online/offline status. + */ +window.onlinestatus = function() { + return navigator.onLine? (ui.isMobile()? showstatus('Online') : showstatus('Online')) : errorstatus('Offline'); + +}; /** * Handle orientation change. */ document.body.onorientationchange = function(e) { //debug('onorientationchange'); - ui.onorientationchange(e); - - // Resize menu and view header - mdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - hdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - return true; + return ui.onorientationchange(e); }; /** - * Initialize the document. + * Populate cache with app resources. */ -function onload() { - //debug('onload'); - ui.onload(); +var appresources = [ + ['/proxy/all-min.js'], + ['/proxy/ui-min.css'], + ['/proxy/public/config-min.js'] +]; - // Show the page - document.body.style.visibility = 'visible'; - return true; -} +/** + * Install the application cache. + */ +(function installappcache() { + if (ui.isMobile()) { + // On mobile devices, trigger usage of an application cache manifest + window.onappcachechecking = function(e) { + //debug('appcache checking', e); + workingstatus(true); + showstatus('Checking'); + }; + window.onappcacheerror = function(e) { + //debug('appcache error', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachenoupdate = function(e) { + //debug('appcache noupdate', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachedownloading = function(e) { + //debug('appcache downloading', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheprogress = function(e) { + //debug('appcache progress', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheupdateready = function(e) { + //debug('appcache updateready', e); + try { + applicationCache.swapCache(); + } catch(e) {} + onlinestatus(); + workingstatus(false); + //debug('appcache swapped', e); + + // Update offline resources in local storage and reload the page + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + window.location.reload(); + }; + window.onappcachecached = function(e) { + //debug('appcache cached', e); + onlinestatus(); + workingstatus(false); + + // Install offline resources in local storage + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + }; + + window.onloadappcache = function() { + //debug('appcache iframe loaded'); + }; + + ui.delay(function() { + $('installer').innerHTML = '<iframe src="/proxy/public/cache/" class="installer"></iframe>'; + }); + + } else { + // On non-mobile devices, check for cache-manifest changes ourselves. + workingstatus(true); + showstatus('Checking'); + var lcmf = appcache.get('/proxy/public/cache/cache-manifest.cmf', 'local'); + var rcmf = appcache.get('/proxy/public/cache/cache-manifest.cmf', 'remote'); + if (lcmf == rcmf) { + onlinestatus(); + workingstatus(false); + return true; + } -onload(); + //debug('cache-manifest changed, reloading'); + ui.delay(function() { + showstatus('Updating'); + ui.delay(function() { + map(function(res) { + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + if (!isNil(lcmf)) { + //debug('reloading'); + window.location.reload(); + } + onlinestatus(); + workingstatus(false); + }); + }); + } +})(); + +/** + * Handle network offline/online events. + */ +window.addEventListener('offline', function(e) { + //debug('going offline'); + showstatus('Offline'); +}, false); +window.addEventListener('online', function(e) { + //debug('going online'); + showstatus('Online'); +}, false); + +/** + * Initialize the document. + */ +window.onload = function() { + //debug('onload'); + return ui.onload(); +}; })(); -</script> -<div id="footdiv" class="fsection"> -</div> +} catch(e) { + debug(e.stack); + throw e; +} +</script> -</div> </body> </html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/app.b64 b/sca-cpp/trunk/hosting/server/htdocs/public/app.b64 index 7ed235aa14..4690a5c79c 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/app.b64 +++ b/sca-cpp/trunk/hosting/server/htdocs/public/app.b64 @@ -1 +1 @@ -iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAABGdBTUEAALGPC/xhBQAAAAxQTFRFyN+N+dR1/PCI////6HjE5gAAADJJREFUKM9j+I8EPjBQifeBAQSY6coLBYN6inhaq0Bg6SDn/f//akB466ExTS6P2ukMAKumzarJO/66AAAAAElFTkSuQmCC
\ No newline at end of file +iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAfklEQVRo3u3YoQ2EQBBA0VlauEJwODQUcw1QAQ1QDGgcjkKo4U4Q0JhNCLyvVs3kZd1E6F6l49H02y/Hgqn7pIiIeogs8+fvbiie8iMgICAgICAgICAgICAgICDSlc67Vu6709pWWeaX4+KuBQICAgICAgICAgICAgICovf1B5ehDldEK+NOAAAAAElFTkSuQmCC diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/app.png b/sca-cpp/trunk/hosting/server/htdocs/public/app.png Binary files differindex 1f73274b76..7293fe7ee5 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/app.png +++ b/sca-cpp/trunk/hosting/server/htdocs/public/app.png diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/app.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/app.xcf Binary files differindex 741b7ff43f..b144d46743 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/app.xcf +++ b/sca-cpp/trunk/hosting/server/htdocs/public/app.xcf diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/cache/cache-template.cmf b/sca-cpp/trunk/hosting/server/htdocs/public/cache/cache-template.cmf new file mode 100644 index 0000000000..40da327179 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/cache/cache-template.cmf @@ -0,0 +1,17 @@ +CACHE MANIFEST + +# Version SHA1 + +# App resources +/favicon.ico +/login/ +/public/img.png +/public/notauth/ +/public/notfound/ +/public/notyet/ +/public/oops/ +/public/touchicon.png + +NETWORK: +* + diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/cache/index.html b/sca-cpp/trunk/hosting/server/htdocs/public/cache/index.html new file mode 100644 index 0000000000..c810cc16b8 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/cache/index.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<!-- + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. +--> +<html manifest="/public/cache/cache-manifest.cmf"> +<head> +<script type="text/javascript"> +applicationCache.addEventListener('checking', function(e) { + return window.parent.onappcachechecking(e); +}, false); +applicationCache.addEventListener('error', function(e) { + return window.parent.onappcacheerror(e); +}, false); +applicationCache.addEventListener('noupdate', function(e) { + return window.parent.onappcachenoupdate(e); +}, false); +applicationCache.addEventListener('downloading', function(e) { + return window.parent.onappcachedownloading(e); +}, false); +applicationCache.addEventListener('progress', function(e) { + return window.parent.onappcacheprogress(e); +}, false); +applicationCache.addEventListener('updateready', function(e) { + return window.parent.onappcacheupdateready(e); +}, false); +applicationCache.addEventListener('cached', function(e) { + return window.parent.onappcachecached(e); +}, false); +window.onload = function() { + window.parent.onloadappcache(); +}; +</script> +</head> +<body> +</body> +</html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/config.js b/sca-cpp/trunk/hosting/server/htdocs/public/config.js index 54818f4810..be23c7c01e 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/config.js +++ b/sca-cpp/trunk/hosting/server/htdocs/public/config.js @@ -35,6 +35,10 @@ config.loginprompt = function() { return '<span>Sign in with your userid and password</span>'; }; +config.signuptitle = function() { + return 'Sign up for an account'; +}; + config.viewfoot = function() { return ui.menubar(mklist(ui.menu('menuabout', 'About', '/', '_view', 'note')), mklist()); }; diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/delete.b64 b/sca-cpp/trunk/hosting/server/htdocs/public/delete.b64 deleted file mode 100644 index c8137d7ab4..0000000000 --- a/sca-cpp/trunk/hosting/server/htdocs/public/delete.b64 +++ /dev/null @@ -1 +0,0 @@ -iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAAXNSR0IArs4c6QAAAAZiS0dEANAAPwBBXloXjQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sEFhQaKzNh4PgAAAMKSURBVEjHxZZPTBNBFMa/maVbWjcUi0YiIHIoNBADTUgsqCWgUUFjwkk5CXLUBKIc9KIXjx64oMSDoiggGC8koImCGDWYkADRIiQQgikWCq0WoXW33R0PpYjSLeWP8btN3sv85s17894QrKNeIBng8gFmJSDZgGIAqJeBjQCkH5AHioGZaHsQNUMP+ByKYB0ByVjvIAxsUkHcrRJI9pggXYBWB1pLQUqxQSlg3X4o9WWAqArpAhL04JoIYMQmxQCPD3JlGbCwBtIFaPXgWrcC+AtUEY6Ihg060NrtACyf3KgDrQ2v6e8kbzwH0URBSnvA56xAKIJ1kRzNbS2ZNhYssjodVj41VbPaxqemaqxOh9XGgkXmtpbMyKDQvqQXSKbg2iKGzfPE0v8uV7BYDIuDg95B66FhJkmM8DyxfHifK+TlGRaHhryDBwuHmSSxyBUnn6Ohh6aSQElin86U26XZWVGwWAxZD5tMAGBufmAS8vIMkssl2s+Uj6gBQuLySS/oTQpyONr9GmxHhAMvnltovJZ+73vjTiyyJSmipHw8WTrkfd33Y52385arAr1EAF00R3HqixRwu38mnT61O35/uh4AJq7Ujc0/affEUGsCDfWi9TXX3uEOeDwBABCnp/3OO42uGPuAgQLUG4urueVRlsZo1ACANiVFZ7rTkBFjMXtpqJtGV9q1q3uNJ47vlpd88kTt5VEWCLLk6gtpeyrP74qheY5wlaB6AhSqOSUUFOzIun8vh8RxZKKmZvRrw20X0WjkxCKbceexo0Z3Z+d8wDUXVIeQdgrIA6rFl5DAmVsfZ1MtT+faO5zOxrtzADB1/Ybj28tX85wgxOU8e5pN9XqqHos8QIuBGQY2GTEPD5tM8en79P7x8aWxqurx1bbPZytGRYfDrzOZBHPzA5PanCkGZki4d3GQG7DNksFdLIFkpwBQAsmugHVvJ0AB6w5PypW79EOpZ4BnOwAM8Pih1P/R6gGgDBB9kCu3Clo1GcU1kGXQgg9yxWavTgHrXp6IC///t/Iv/l2/AGa0Qa2X0eC0AAAAAElFTkSuQmCC
\ No newline at end of file diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/delete.png b/sca-cpp/trunk/hosting/server/htdocs/public/delete.png Binary files differdeleted file mode 100644 index fb56bae030..0000000000 --- a/sca-cpp/trunk/hosting/server/htdocs/public/delete.png +++ /dev/null diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/delete.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/delete.xcf Binary files differdeleted file mode 100644 index 7691f50cc5..0000000000 --- a/sca-cpp/trunk/hosting/server/htdocs/public/delete.xcf +++ /dev/null diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/grid72.b64 b/sca-cpp/trunk/hosting/server/htdocs/public/grid72.b64 deleted file mode 100644 index 34be13e5ca..0000000000 --- a/sca-cpp/trunk/hosting/server/htdocs/public/grid72.b64 +++ /dev/null @@ -1 +0,0 @@ -iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAgMAAAAog1vUAAAABGdBTUEAALGPC/xhBQAAAAlQTFRFwuD84/T+////fj9v9QAAACxJREFUOMtjWLUqa9WsVctWrYQxVjAMCqFQdBDCMOrUUaeOOnXUqYPPqZgAABmg/C7pJC7lAAAAAElFTkSuQmCC
\ No newline at end of file diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/grid72.png b/sca-cpp/trunk/hosting/server/htdocs/public/grid72.png Binary files differdeleted file mode 100644 index cf6008171a..0000000000 --- a/sca-cpp/trunk/hosting/server/htdocs/public/grid72.png +++ /dev/null diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/iframe.html b/sca-cpp/trunk/hosting/server/htdocs/public/iframe.html deleted file mode 100644 index e2b862dbaa..0000000000 --- a/sca-cpp/trunk/hosting/server/htdocs/public/iframe.html +++ /dev/null @@ -1,28 +0,0 @@ -<!DOCTYPE html> -<!-- - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. ---> -<html> -<head> -</head> -<body style="margin:3px; padding: 0px; background-color: #dcdcdc;"> - -<div>frame ...</div> - -</body> -</html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/img.b64 b/sca-cpp/trunk/hosting/server/htdocs/public/img.b64 index 97dae687a0..1025ce0d3a 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/img.b64 +++ b/sca-cpp/trunk/hosting/server/htdocs/public/img.b64 @@ -1 +1 @@ -iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAAIRQTFRFwdt/w9yEw9+MxN2GxN6NxN+Oxd2Mxd6Nxt6Lx96Lx96Nx9+NyN6MyN+MyN+N8u2I8+2I+NBq+NFr+NFt+NFu+NJz+NN0+dR1+dR3+dZw+dh4+9Fy+9Nz++5++++B+++F/NNz/PCH/PCI/PGW/PKc/fKd/vzp/vzq/v7+/v/z/9Jx////nQZfHwAAAIxJREFUOMtj0CYAGKiiQANdUAPdBAZmFMCIYQUzHwrgpKECblYwYEJ2LYoCHi0FMBCEAmF0E3hkxFGABJICXnYWFhY2aVE4EENTwCWgCARKCCCFoUAJFQw9BYycnBz8eBSA04cqPhNAQIX+CiSFhIRE8CiQ10ROMNgUqKNnHGU5FCCrhqZAg7Z5Ey8AALiBh6brcmloAAAAAElFTkSuQmCC
\ No newline at end of file +iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAX0lEQVRYw2NgGGDAyMDAwODe/vY/OZp3VgozMjAwMNhOYSBL/+EcBkamgQ6BUQeMOmDUAaMOGHXAqANGAcX1OQMDA8NFDzOy9OvvODXaHhh1wKgDRh0w6oBRB4y2BxgA0K4ON379R2QAAAAASUVORK5CYII= diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/img.png b/sca-cpp/trunk/hosting/server/htdocs/public/img.png Binary files differindex 2363b25e8e..e05e74fa2d 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/img.png +++ b/sca-cpp/trunk/hosting/server/htdocs/public/img.png diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/img.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/img.xcf Binary files differindex ffcc124584..c1736338ec 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/img.xcf +++ b/sca-cpp/trunk/hosting/server/htdocs/public/img.xcf diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/notauth/index.html b/sca-cpp/trunk/hosting/server/htdocs/public/notauth/index.html index 89852393bf..f453e0491c 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/notauth/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/public/notauth/index.html @@ -19,33 +19,50 @@ --> <html> <head> +<!-- Firebug inspector --> +<!-- +<script type="text/javascript" src="https://getfirebug.com/releases/lite/1.3/firebug-lite.js"></script> +--> +<!-- Weinre inspector --> +<!-- +<script src="http://www.example.com:9998/target/target-script-min.js#anonymous"></script> +--> <title>Sorry</title> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"/> +<!-- <meta name="apple-mobile-web-app-capable" content="yes"/> <meta name="apple-mobile-web-app-status-bar-style" content="black"/> +--> +<link rel="apple-touch-icon-precomposed" href="/public/touchicon.png"/> <base href="/public/notauth/"/> <script type="text/javascript"> -(function() { +try { + +(function notauthhead() { window.appcache = {}; /** * Get and cache a resource. */ -appcache.get = function(uri) { +appcache.get = function(uri, mode) { var h = uri.indexOf('#'); var u = h == -1? uri : uri.substring(0, h); // Get resource from local storage first var ls = window.lstorage || localStorage; - var item = null; - try { item = ls.getItem(u); } catch(e) {} - if (item != null && item != '') - return item; + if (mode != 'remote') { + var item = null; + try { item = ls.getItem('ui.r.' + u); } catch(e) {} + if (item != null && item != '') + return item; + if (mode == 'local') + return null; + } // Get resource from network var http = new XMLHttpRequest(); - http.open("GET", u, false); + http.open("GET", mode == 'remote'? (u + '?t=' + new Date().getTime() + '&r=' + Math.random()) : u, false); http.setRequestHeader("Accept", "*/*"); http.send(null); if (http.status == 200) { @@ -56,7 +73,7 @@ appcache.get = function(uri) { if (window.debug) debug('http error', u, 'No-Content'); return null; } - try { ls.setItem(u, http.responseText); } catch(e) {} + try { ls.setItem('ui.r.' + u, http.responseText); } catch(e) {} return http.responseText; } if (window.debug) debug('http error', u, http.status, http.statusText); @@ -68,128 +85,287 @@ appcache.get = function(uri) { /** * Load Javascript and CSS. */ -(function() { +(function notauthboot() { var bootjs = document.createElement('script'); bootjs.type = 'text/javascript'; -bootjs.text = appcache.get('/all-min.js'); -document.head.appendChild(bootjs); -document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); +bootjs.text = 'try {\n' + appcache.get('/all-min.js') + '\n' + appcache.get('/public/config-min.js') + '\n} catch(e) { console.log(e.stack); throw e; }\n'; +var head = document.getElementsByTagName('head')[0]; +head.appendChild(bootjs); +head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); })(); +} catch(e) { + if (window.debug) debug(e.stack); + throw e; +} </script> </head> <body class="delayed"> -<div id="mainbodydiv" class="mainbodydiv"> - -<div id="headdiv" class="hsection"> -<script type="text/javascript"> -(function() { -$('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js'))); - -})(); -</script> +<div id="menucontainer" class="tbarmenu"> +<div id="menu"></div> </div> -<div id="menubackground" class="tbarbackground fixed"></div> -<div id="menu" class="tbarmenu fixed"></div> - -<div id="viewheadbackground" class="viewheadbackground fixed"></div> -<div id="viewhead" class="viewhead fixed"></div> +<div id="viewheadcontainer" class="viewhead"> +<div id="viewhead"></div> +</div> <div id="viewcontainer"> <div id="view"> <div id="viewcontent" class="viewcontent" style="margin-left: auto; margin-right: auto; text-align: center;"> <br/> -<div class="hd2">Sorry, you're not authorized to view this page.</div> +<div class="hd2">Sorry, you're not authorized<br/>to view this page.</div> </div> </div> </div> -<div id="viewfootbackground" class="viewfootbackground fixed"></div> -<div id="viewfoot" class="viewfoot fixed"></div> +<div id="viewfootcontainer" class="viewfoot"> +<div id="viewfoot"></div> +<div id="status"></div> +</div> + +<div id="installer" class="installer"></div> <script type="text/javascript"> -(function() { +try { + +(function notauthbody() { /** - * Init div variables. + * Setup page layout. */ -var mdiv = $('menu'); -var hdiv = $('viewhead'); -$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; -$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm'; -$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm'; -var fdiv = $('viewfoot'); +(function layout() { + $('viewcontainer').className = ui.isMobile()? 'viewcontainer3dm' : 'viewcontainer3d'; + $('view').className = ui.isMobile()? 'viewloaded3dm' : 'viewloaded3d'; + document.title = config.windowtitle() + ' - Sorry'; + $('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; + if (!ui.isMobile()) + $('viewcontent').className = 'viewcontent flatscrollbars'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; +})(); /** - * Set page title. + * Setup menu bar. */ -document.title = config.windowtitle() + ' - Sorry'; -$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; +(function showmenu() { + $('menu').innerHTML = ui.menubar( + mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), mklist()); + $('viewfoot').innerHTML = config.viewfoot(); +})(); /** - * Build and show the menu bar. + * Initialize status message area. */ -function showmenu(mdiv) { - mdiv.innerHTML = ui.menubar( - mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), - mklist(hasauthcookie()? ui.menufunc('menusignout', 'Sign out', 'return logout();', false) : ui.menu('menusignin', 'Sign in', '/login/', '_self', false))); - fdiv.innerHTML = config.viewfoot(); -} +(function initstatus() { + if (isNil($('status'))) + return; + $('status').style.display = 'none'; + + function divtransitionend(e) { + e.target.style.display = 'none'; + e.target.className = ui.isMobile()? 'status3dm' : 'status3d'; + e.target.error = false; + } + $('status').addEventListener('webkitTransitionEnd', divtransitionend, false); + $('status').addEventListener('transitionend', divtransitionend, false); +})(); -showmenu(mdiv); +/** + * Show a status message. + */ +window.showstatus = function(s, c) { + //debug('show status', s); + if (isNil($('status')) || $('status').error) + return s; + $('status').innerHTML = '<span class="' + (c? c : 'okstatus') + '">' + s + '</span>'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; + $('status').style.display = 'block'; + $('status').error = c == 'errorstatus'; + if ($('status').delay) + ui.cancelDelay($('status').delay); + $('status').delay = ui.delay(function hidestatus() { + $('status').className = ui.isMobile()? 'statusout3dm' : 'statusout3d'; + $('status').error = false; + }, 3000); + return s; +}; /** - * Log the current user out. + * Show an error message. */ -window.logout = function() { - // Clear session cookie and user-specific local storage entries - clearauthcookie(); - lstorage.removeItem('/r/Editor/accounts'); - lstorage.removeItem('/r/Editor/dashboards'); - document.location = '/login/'; - return false; -} +window.errorstatus = function(s) { + //debug('error', s); + return showstatus(s, 'errorstatus'); +}; + +/** + * Show working status. + */ +window.workingstatus = function(w, c) { + //debug('show working', w); + if (isNil($('working'))) + return w; + if (!ui.isMobile()) + $('working').style.top = ui.pixpos(Math.round(window.clientHeight / 2)); + $('working').style.display = w? 'block' : 'none'; + return w; +}; + +/** + * Show the online/offline status. + */ +window.onlinestatus = function() { + return navigator.onLine? (ui.isMobile()? showstatus('Online') : showstatus('Online')) : errorstatus('Offline'); +}; /** * Handle orientation change. */ document.body.onorientationchange = function(e) { //debug('onorientationchange'); - ui.onorientationchange(e); + return ui.onorientationchange(e); +}; - // Resize menu and view header - mdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - hdiv.style.width = ui.pixpos(document.documentElement.clientWidth); +/** + * Populate cache with app resources. + */ +var appresources = [ + ['/all-min.js'], + ['/ui-min.css'], + ['/public/config-min.js'] +]; - return true; -}; +/** + * Install the application cache. + */ +(function installappcache() { + if (ui.isMobile()) { + // On mobile devices, trigger usage of an application cache manifest + window.onappcachechecking = function(e) { + //debug('appcache checking', e); + workingstatus(true); + showstatus('Checking'); + }; + window.onappcacheerror = function(e) { + //debug('appcache error', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachenoupdate = function(e) { + //debug('appcache noupdate', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachedownloading = function(e) { + //debug('appcache downloading', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheprogress = function(e) { + //debug('appcache progress', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheupdateready = function(e) { + //debug('appcache updateready', e); + try { + applicationCache.swapCache(); + } catch(e) {} + onlinestatus(); + workingstatus(false); + //debug('appcache swapped', e); + + // Update offline resources in local storage and reload the page + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + window.location.reload(); + }; + window.onappcachecached = function(e) { + //debug('appcache cached', e); + onlinestatus(); + workingstatus(false); + + // Install offline resources in local storage + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + }; + + window.onloadappcache = function() { + //debug('appcache iframe loaded'); + }; + + ui.delay(function() { + $('installer').innerHTML = '<iframe src="/public/cache/" class="installer"></iframe>'; + }); + + } else { + // On non-mobile devices, check for cache-manifest changes ourselves. + workingstatus(true); + showstatus('Checking'); + var lcmf = appcache.get('/public/cache/cache-manifest.cmf', 'local'); + var rcmf = appcache.get('/public/cache/cache-manifest.cmf', 'remote'); + if (lcmf == rcmf) { + onlinestatus(); + workingstatus(false); + return true; + } + + //debug('cache-manifest changed, reloading'); + ui.delay(function() { + showstatus('Updating'); + ui.delay(function() { + map(function(res) { + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + if (!isNil(lcmf)) { + //debug('reloading'); + window.location.reload(); + } + onlinestatus(); + workingstatus(false); + }); + }); + } +})(); + +/** + * Handle network offline/online events. + */ +window.addEventListener('offline', function(e) { + //debug('going offline'); + showstatus('Offline'); +}, false); +window.addEventListener('online', function(e) { + //debug('going online'); + showstatus('Online'); +}, false); /** * Initialize the document. */ -function onload() { +window.onload = function() { //debug('onload'); - ui.onload(); - - // Show the page - document.body.style.visibility = 'visible'; - return true; -} - -onload(); + return ui.onload(); +}; })(); -</script> -<div id="footdiv" class="fsection"> -</div> +} catch(e) { + debug(e.stack); + throw e; +} +</script> -</div> </body> </html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/notfound/index.html b/sca-cpp/trunk/hosting/server/htdocs/public/notfound/index.html index 8f0d486854..c8475147d7 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/notfound/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/public/notfound/index.html @@ -19,33 +19,50 @@ --> <html> <head> +<!-- Firebug inspector --> +<!-- +<script type="text/javascript" src="https://getfirebug.com/releases/lite/1.3/firebug-lite.js"></script> +--> +<!-- Weinre inspector --> +<!-- +<script src="http://www.example.com:9998/target/target-script-min.js#anonymous"></script> +--> <title>Page not found</title> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"/> +<!-- <meta name="apple-mobile-web-app-capable" content="yes"/> <meta name="apple-mobile-web-app-status-bar-style" content="black"/> +--> +<link rel="apple-touch-icon-precomposed" href="/public/touchicon.png"/> <base href="/public/notfound/"/> <script type="text/javascript"> -(function() { +try { + +(function notfoundhead() { window.appcache = {}; /** * Get and cache a resource. */ -appcache.get = function(uri) { +appcache.get = function(uri, mode) { var h = uri.indexOf('#'); var u = h == -1? uri : uri.substring(0, h); // Get resource from local storage first var ls = window.lstorage || localStorage; - var item = null; - try { item = ls.getItem(u); } catch(e) {} - if (item != null && item != '') - return item; + if (mode != 'remote') { + var item = null; + try { item = ls.getItem('ui.r.' + u); } catch(e) {} + if (item != null && item != '') + return item; + if (mode == 'local') + return null; + } // Get resource from network var http = new XMLHttpRequest(); - http.open("GET", u, false); + http.open("GET", mode == 'remote'? (u + '?t=' + new Date().getTime() + '&r=' + Math.random()) : u, false); http.setRequestHeader("Accept", "*/*"); http.send(null); if (http.status == 200) { @@ -56,7 +73,7 @@ appcache.get = function(uri) { if (window.debug) debug('http error', u, 'No-Content'); return null; } - try { ls.setItem(u, http.responseText); } catch(e) {} + try { ls.setItem('ui.r.' + u, http.responseText); } catch(e) {} return http.responseText; } if (window.debug) debug('http error', u, http.status, http.statusText); @@ -68,36 +85,32 @@ appcache.get = function(uri) { /** * Load Javascript and CSS. */ -(function() { +(function notfoundboot() { var bootjs = document.createElement('script'); bootjs.type = 'text/javascript'; -bootjs.text = appcache.get('/all-min.js'); -document.head.appendChild(bootjs); -document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); +bootjs.text = 'try {\n' + appcache.get('/all-min.js') + '\n' + appcache.get('/public/config-min.js') + '\n} catch(e) { console.log(e.stack); throw e; }\n'; +var head = document.getElementsByTagName('head')[0]; +head.appendChild(bootjs); +head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); })(); +} catch(e) { + if (window.debug) debug(e.stack); + throw e; +} </script> </head> <body class="delayed"> -<div id="mainbodydiv" class="mainbodydiv"> - -<div id="headdiv" class="hsection"> -<script type="text/javascript"> -(function() { -$('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js'))); - -})(); -</script> +<div id="menucontainer" class="tbarmenu"> +<div id="menu"></div> </div> -<div id="menubackground" class="tbarbackground fixed"></div> -<div id="menu" class="tbarmenu fixed"></div> - -<div id="viewheadbackground" class="viewheadbackground fixed"></div> -<div id="viewhead" class="viewhead fixed"></div> +<div id="viewheadcontainer" class="viewhead"> +<div id="viewhead"></div> +</div> <div id="viewcontainer"> <div id="view"> @@ -105,90 +118,255 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js')) <br/> <div class="hd2">Sorry, that page was not found.</div> -<div>You may have clicked an expired link or mistyped the address.</div> +<div>You may have clicked an expired link<br/>or mistyped the address.</div> </div> </div> </div> -<div id="viewfootbackground" class="viewfootbackground fixed"></div> -<div id="viewfoot" class="viewfoot fixed"></div> +<div id="viewfootcontainer" class="viewfoot"> +<div id="viewfoot"></div> +<div id="status"></div> +</div> + +<div id="installer" class="installer"></div> <script type="text/javascript"> -(function() { +try { + +(function notfoundbody() { /** - * Init div variables. + * Setup page layout. */ -var mdiv = $('menu'); -var hdiv = $('viewhead'); -$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm'; -$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm'; -var fdiv = $('viewfoot'); +(function layout() { + $('viewcontainer').className = ui.isMobile()? 'viewcontainer3dm' : 'viewcontainer3d'; + $('view').className = ui.isMobile()? 'viewloaded3dm' : 'viewloaded3d'; + document.title = config.windowtitle() + ' - Page not found'; + $('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; + if (!ui.isMobile()) + $('viewcontent').className = 'viewcontent flatscrollbars'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; +})(); /** - * Set page title. + * Setup menu bar. */ -document.title = config.windowtitle() + ' - Page not found'; -$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; +(function showmenu() { + $('menu').innerHTML = ui.menubar( + mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), mklist()); + $('viewfoot').innerHTML = config.viewfoot(); +})(); /** - * Build and show the menu bar. + * Initialize status message area. */ -function showmenu(mdiv) { - mdiv.innerHTML = ui.menubar( - mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), - mklist(hasauthcookie()? ui.menufunc('menusignout', 'Sign out', 'return logout();', false) : ui.menu('menusignin', 'Sign in', '/login/', '_self', false))); - fdiv.innerHTML = config.viewfoot(); -} +(function initstatus() { + if (isNil($('status'))) + return; + $('status').style.display = 'none'; + + function divtransitionend(e) { + e.target.style.display = 'none'; + e.target.className = ui.isMobile()? 'status3dm' : 'status3d'; + e.target.error = false; + } + $('status').addEventListener('webkitTransitionEnd', divtransitionend, false); + $('status').addEventListener('transitionend', divtransitionend, false); +})(); -showmenu(mdiv); +/** + * Show a status message. + */ +window.showstatus = function(s, c) { + //debug('show status', s); + if (isNil($('status')) || $('status').error) + return s; + $('status').innerHTML = '<span class="' + (c? c : 'okstatus') + '">' + s + '</span>'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; + $('status').style.display = 'block'; + $('status').error = c == 'errorstatus'; + if ($('status').delay) + ui.cancelDelay($('status').delay); + $('status').delay = ui.delay(function hidestatus() { + $('status').className = ui.isMobile()? 'statusout3dm' : 'statusout3d'; + $('status').error = false; + }, 3000); + return s; +}; /** - * Log the current user out. + * Show an error message. */ -window.logout = function() { - // Clear session cookie and user-specific local storage entries - clearauthcookie(); - lstorage.removeItem('/r/Editor/accounts'); - lstorage.removeItem('/r/Editor/dashboards'); - document.location = '/login/'; - return false; -} +window.errorstatus = function(s) { + //debug('error', s); + return showstatus(s, 'errorstatus'); +}; + +/** + * Show working status. + */ +window.workingstatus = function(w, c) { + //debug('show working', w); + if (isNil($('working'))) + return w; + if (!ui.isMobile()) + $('working').style.top = ui.pixpos(Math.round(window.clientHeight / 2)); + $('working').style.display = w? 'block' : 'none'; + return w; +}; + +/** + * Show the online/offline status. + */ +window.onlinestatus = function() { + return navigator.onLine? (ui.isMobile()? showstatus('Online') : showstatus('Online')) : errorstatus('Offline'); +}; /** * Handle orientation change. */ document.body.onorientationchange = function(e) { //debug('onorientationchange'); - ui.onorientationchange(e); - - // Resize menu and view header - mdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - hdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - return true; + return ui.onorientationchange(e); }; /** - * Initialize the document. + * Populate cache with app resources. */ -function onload() { - //debug('onload'); - ui.onload(); +var appresources = [ + ['/all-min.js'], + ['/ui-min.css'], + ['/public/config-min.js'] +]; - // Show the page - document.body.style.visibility = 'visible'; - return true; -} +/** + * Install the application cache. + */ +(function installappcache() { + if (ui.isMobile()) { + // On mobile devices, trigger usage of an application cache manifest + window.onappcachechecking = function(e) { + //debug('appcache checking', e); + workingstatus(true); + showstatus('Checking'); + }; + window.onappcacheerror = function(e) { + //debug('appcache error', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachenoupdate = function(e) { + //debug('appcache noupdate', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachedownloading = function(e) { + //debug('appcache downloading', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheprogress = function(e) { + //debug('appcache progress', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheupdateready = function(e) { + //debug('appcache updateready', e); + try { + applicationCache.swapCache(); + } catch(e) {} + onlinestatus(); + workingstatus(false); + //debug('appcache swapped', e); + + // Update offline resources in local storage and reload the page + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + window.location.reload(); + }; + window.onappcachecached = function(e) { + //debug('appcache cached', e); + onlinestatus(); + workingstatus(false); + + // Install offline resources in local storage + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + }; + + window.onloadappcache = function() { + //debug('appcache iframe loaded'); + }; + + ui.delay(function() { + $('installer').innerHTML = '<iframe src="/public/cache/" class="installer"></iframe>'; + }); + + } else { + // On non-mobile devices, check for cache-manifest changes ourselves. + workingstatus(true); + showstatus('Checking'); + var lcmf = appcache.get('/public/cache/cache-manifest.cmf', 'local'); + var rcmf = appcache.get('/public/cache/cache-manifest.cmf', 'remote'); + if (lcmf == rcmf) { + onlinestatus(); + workingstatus(false); + return true; + } + + //debug('cache-manifest changed, reloading'); + ui.delay(function() { + showstatus('Updating'); + ui.delay(function() { + map(function(res) { + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + if (!isNil(lcmf)) { + //debug('reloading'); + window.location.reload(); + } + onlinestatus(); + workingstatus(false); + }); + }); + } +})(); -onload(); +/** + * Handle network offline/online events. + */ +window.addEventListener('offline', function(e) { + //debug('going offline'); + showstatus('Offline'); +}, false); +window.addEventListener('online', function(e) { + //debug('going online'); + showstatus('Online'); +}, false); + +/** + * Initialize the document. + */ +window.onload = function() { + //debug('onload'); + return ui.onload(); +}; })(); -</script> -<div id="footdiv" class="fsection"> -</div> +} catch(e) { + debug(e.stack); + throw e; +} +</script> -</div> </body> </html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/notyet/index.html b/sca-cpp/trunk/hosting/server/htdocs/public/notyet/index.html index e43a992f38..4bcb3728ae 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/notyet/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/public/notyet/index.html @@ -19,33 +19,50 @@ --> <html> <head> +<!-- Firebug inspector --> +<!-- +<script type="text/javascript" src="https://getfirebug.com/releases/lite/1.3/firebug-lite.js"></script> +--> +<!-- Weinre inspector --> +<!-- +<script src="http://www.example.com:9998/target/target-script-min.js#anonymous"></script> +--> <title>Page not found</title> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"/> +<!-- <meta name="apple-mobile-web-app-capable" content="yes"/> <meta name="apple-mobile-web-app-status-bar-style" content="black"/> +--> +<link rel="apple-touch-icon-precomposed" href="/public/touchicon.png"/> <base href="/public/notyet/"/> <script type="text/javascript"> -(function() { +try { + +(function notyethead() { window.appcache = {}; /** * Get and cache a resource. */ -appcache.get = function(uri) { +appcache.get = function(uri, mode) { var h = uri.indexOf('#'); var u = h == -1? uri : uri.substring(0, h); // Get resource from local storage first var ls = window.lstorage || localStorage; - var item = null; - try { item = ls.getItem(u); } catch(e) {} - if (item != null && item != '') - return item; + if (mode != 'remote') { + var item = null; + try { item = ls.getItem('ui.r.' + u); } catch(e) {} + if (item != null && item != '') + return item; + if (mode == 'local') + return null; + } // Get resource from network var http = new XMLHttpRequest(); - http.open("GET", u, false); + http.open("GET", mode == 'remote'? (u + '?t=' + new Date().getTime() + '&r=' + Math.random()) : u, false); http.setRequestHeader("Accept", "*/*"); http.send(null); if (http.status == 200) { @@ -56,7 +73,7 @@ appcache.get = function(uri) { if (window.debug) debug('http error', u, 'No-Content'); return null; } - try { ls.setItem(u, http.responseText); } catch(e) {} + try { ls.setItem('ui.r.' + u, http.responseText); } catch(e) {} return http.responseText; } if (window.debug) debug('http error', u, http.status, http.statusText); @@ -68,127 +85,288 @@ appcache.get = function(uri) { /** * Load Javascript and CSS. */ -(function() { +(function notyetboot() { var bootjs = document.createElement('script'); bootjs.type = 'text/javascript'; -bootjs.text = appcache.get('/all-min.js'); -document.head.appendChild(bootjs); -document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); +bootjs.text = 'try {\n' + appcache.get('/all-min.js') + '\n' + appcache.get('/public/config-min.js') + '\n} catch(e) { console.log(e.stack); throw e; }\n'; +var head = document.getElementsByTagName('head')[0]; +head.appendChild(bootjs); +head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); })(); +} catch(e) { + if (window.debug) debug(e.stack); + throw e; +} </script> </head> <body class="delayed"> -<div id="mainbodydiv" class="mainbodydiv"> - -<div id="headdiv" class="hsection"> -<script type="text/javascript"> -(function() { -$('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js'))); - -})(); -</script> +<div id="menucontainer" class="tbarmenu"> +<div id="menu"></div> </div> -<div id="menubackground" class="tbarbackground fixed"></div> -<div id="menu" class="tbarmenu fixed"></div> - -<div id="viewheadbackground" class="viewheadbackground fixed"></div> -<div id="viewhead" class="viewhead fixed"></div> +<div id="viewheadcontainer" class="viewhead"> +<div id="viewhead"></div> +</div> <div id="viewcontainer"> <div id="view"> <div id="viewcontent" class="viewcontent" style="margin-left: auto; margin-right: auto; text-align: center;"> <br/> -<div class="hd2">Sorry, that page is still under construction.</div> +<div class="hd2">Sorry, this page is still<br/>under construction.</div> <div>Please check back later.</div> </div> </div> </div> -<div id="viewfootbackground" class="viewfootbackground fixed"></div> -<div id="viewfoot" class="viewfoot fixed"></div> +<div id="viewfootcontainer" class="viewfoot"> +<div id="viewfoot"></div> +<div id="status"></div> +</div> + +<div id="installer" class="installer"></div> <script type="text/javascript"> -(function() { +try { + +(function notyetbody() { /** - * Init div variables. + * Setup page layout. */ -var mdiv = $('menu'); -var hdiv = $('viewhead'); -$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm'; -$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm'; -var fdiv = $('viewfoot'); +(function layout() { + $('viewcontainer').className = ui.isMobile()? 'viewcontainer3dm' : 'viewcontainer3d'; + $('view').className = ui.isMobile()? 'viewloaded3dm' : 'viewloaded3d'; + document.title = config.windowtitle() + ' - Page not found'; + $('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; + if (!ui.isMobile()) + $('viewcontent').className = 'viewcontent flatscrollbars'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; +})(); /** - * Set page title. + * Setup menu bar. */ -document.title = config.windowtitle() + ' - Page not found'; -$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; +(function showmenu() { + $('menu').innerHTML = ui.menubar( + mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), mklist()); + $('viewfoot').innerHTML = config.viewfoot(); +})(); /** - * Build and show the menu bar. + * Initialize status message area. */ -function showmenu(mdiv) { - mdiv.innerHTML = ui.menubar( - mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), - mklist(hasauthcookie()? ui.menufunc('menusignout', 'Sign out', 'return logout();', false) : ui.menu('menusignin', 'Sign in', '/login/', '_self', false))); - fdiv.innerHTML = config.viewfoot(); -} +(function initstatus() { + if (isNil($('status'))) + return; + $('status').style.display = 'none'; + + function divtransitionend(e) { + e.target.style.display = 'none'; + e.target.className = ui.isMobile()? 'status3dm' : 'status3d'; + e.target.error = false; + } + $('status').addEventListener('webkitTransitionEnd', divtransitionend, false); + $('status').addEventListener('transitionend', divtransitionend, false); +})(); -showmenu(mdiv); +/** + * Show a status message. + */ +window.showstatus = function(s, c) { + //debug('show status', s); + if (isNil($('status')) || $('status').error) + return s; + $('status').innerHTML = '<span class="' + (c? c : 'okstatus') + '">' + s + '</span>'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; + $('status').style.display = 'block'; + $('status').error = c == 'errorstatus'; + if ($('status').delay) + ui.cancelDelay($('status').delay); + $('status').delay = ui.delay(function hidestatus() { + $('status').className = ui.isMobile()? 'statusout3dm' : 'statusout3d'; + $('status').error = false; + }, 3000); + return s; +}; /** - * Log the current user out. + * Show an error message. */ -window.logout = function() { - // Clear session cookie and user-specific local storage entries - clearauthcookie(); - lstorage.removeItem('/r/Editor/accounts'); - lstorage.removeItem('/r/Editor/dashboards'); - document.location = '/login/'; - return false; -} +window.errorstatus = function(s) { + //debug('error', s); + return showstatus(s, 'errorstatus'); +}; + +/** + * Show working status. + */ +window.workingstatus = function(w, c) { + //debug('show working', w); + if (isNil($('working'))) + return w; + if (!ui.isMobile()) + $('working').style.top = ui.pixpos(Math.round(window.clientHeight / 2)); + $('working').style.display = w? 'block' : 'none'; + return w; +}; + +/** + * Show the online/offline status. + */ +window.onlinestatus = function() { + return navigator.onLine? (ui.isMobile()? showstatus('Online') : showstatus('Online')) : errorstatus('Offline'); +}; /** * Handle orientation change. */ document.body.onorientationchange = function(e) { //debug('onorientationchange'); - ui.onorientationchange(e); - - // Resize menu and view header - mdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - hdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - return true; + return ui.onorientationchange(e); }; /** - * Initialize the document. + * Populate cache with app resources. */ -function onload() { - //debug('onload'); - ui.onload(); +var appresources = [ + ['/all-min.js'], + ['/ui-min.css'], + ['/public/config-min.js'] +]; - // Show the page - document.body.style.visibility = 'visible'; - return true; -} +/** + * Install the application cache. + */ +(function installappcache() { + if (ui.isMobile()) { + // On mobile devices, trigger usage of an application cache manifest + window.onappcachechecking = function(e) { + //debug('appcache checking', e); + workingstatus(true); + showstatus('Checking'); + }; + window.onappcacheerror = function(e) { + //debug('appcache error', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachenoupdate = function(e) { + //debug('appcache noupdate', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachedownloading = function(e) { + //debug('appcache downloading', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheprogress = function(e) { + //debug('appcache progress', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheupdateready = function(e) { + //debug('appcache updateready', e); + try { + applicationCache.swapCache(); + } catch(e) {} + onlinestatus(); + workingstatus(false); + //debug('appcache swapped', e); + + // Update offline resources in local storage and reload the page + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + window.location.reload(); + }; + window.onappcachecached = function(e) { + //debug('appcache cached', e); + onlinestatus(); + workingstatus(false); + + // Install offline resources in local storage + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + }; + + window.onloadappcache = function() { + //debug('appcache iframe loaded'); + }; + + ui.delay(function() { + $('installer').innerHTML = '<iframe src="/public/cache/" class="installer"></iframe>'; + }); + + } else { + // On non-mobile devices, check for cache-manifest changes ourselves. + workingstatus(true); + showstatus('Checking'); + var lcmf = appcache.get('/public/cache/cache-manifest.cmf', 'local'); + var rcmf = appcache.get('/public/cache/cache-manifest.cmf', 'remote'); + if (lcmf == rcmf) { + onlinestatus(); + workingstatus(false); + return true; + } + + //debug('cache-manifest changed, reloading'); + ui.delay(function() { + showstatus('Updating'); + ui.delay(function() { + map(function(res) { + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + if (!isNil(lcmf)) { + //debug('reloading'); + window.location.reload(); + } + onlinestatus(); + workingstatus(false); + }); + }); + } +})(); -onload(); +/** + * Handle network offline/online events. + */ +window.addEventListener('offline', function(e) { + //debug('going offline'); + showstatus('Offline'); +}, false); +window.addEventListener('online', function(e) { + //debug('going online'); + showstatus('Online'); +}, false); + +/** + * Initialize the document. + */ +window.onload = function() { + //debug('onload'); + return ui.onload(); +}; })(); -</script> -<div id="footdiv" class="fsection"> -</div> +} catch(e) { + debug(e.stack); + throw e; +} +</script> -</div> </body> </html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/oops/index.html b/sca-cpp/trunk/hosting/server/htdocs/public/oops/index.html index cc97c5362e..68554efdb0 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/oops/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/public/oops/index.html @@ -19,33 +19,50 @@ --> <html> <head> +<!-- Firebug inspector --> +<!-- +<script type="text/javascript" src="https://getfirebug.com/releases/lite/1.3/firebug-lite.js"></script> +--> +<!-- Weinre inspector --> +<!-- +<script src="http://www.example.com:9998/target/target-script-min.js#anonymous"></script> +--> <title>Oops</title> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"/> +<!-- <meta name="apple-mobile-web-app-capable" content="yes"/> <meta name="apple-mobile-web-app-status-bar-style" content="black"/> +--> +<link rel="apple-touch-icon-precomposed" href="/public/touchicon.png"/> <base href="/public/oops/"/> <script type="text/javascript"> -(function() { +try { + +(function oopshead() { window.appcache = {}; /** * Get and cache a resource. */ -appcache.get = function(uri) { +appcache.get = function(uri, mode) { var h = uri.indexOf('#'); var u = h == -1? uri : uri.substring(0, h); // Get resource from local storage first var ls = window.lstorage || localStorage; - var item = null; - try { item = ls.getItem(u); } catch(e) {} - if (item != null && item != '') - return item; + if (mode != 'remote') { + var item = null; + try { item = ls.getItem('ui.r.' + u); } catch(e) {} + if (item != null && item != '') + return item; + if (mode == 'local') + return null; + } // Get resource from network var http = new XMLHttpRequest(); - http.open("GET", u, false); + http.open("GET", mode == 'remote'? (u + '?t=' + new Date().getTime() + '&r=' + Math.random()) : u, false); http.setRequestHeader("Accept", "*/*"); http.send(null); if (http.status == 200) { @@ -56,7 +73,7 @@ appcache.get = function(uri) { if (window.debug) debug('http error', u, 'No-Content'); return null; } - try { ls.setItem(u, http.responseText); } catch(e) {} + try { ls.setItem('ui.r.' + u, http.responseText); } catch(e) {} return http.responseText; } if (window.debug) debug('http error', u, http.status, http.statusText); @@ -68,36 +85,32 @@ appcache.get = function(uri) { /** * Load Javascript and CSS. */ -(function() { +(function oopsboot() { var bootjs = document.createElement('script'); bootjs.type = 'text/javascript'; -bootjs.text = appcache.get('/all-min.js'); -document.head.appendChild(bootjs); -document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); +bootjs.text = 'try {\n' + appcache.get('/all-min.js') + '\n' + appcache.get('/public/config-min.js') + '\n} catch(e) { console.log(e.stack); throw e; }\n'; +var head = document.getElementsByTagName('head')[0]; +head.appendChild(bootjs); +head.appendChild(ui.declareCSS(appcache.get('/ui-min.css'))); })(); +} catch(e) { + if (window.debug) debug(e.stack); + throw e; +} </script> </head> <body class="delayed"> -<div id="mainbodydiv" class="mainbodydiv"> - -<div id="headdiv" class="hsection"> -<script type="text/javascript"> -(function() { -$('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js'))); - -})(); -</script> +<div id="menucontainer" class="tbarmenu"> +<div id="menu"></div> </div> -<div id="menubackground" class="tbarbackground fixed"></div> -<div id="menu" class="tbarmenu fixed"></div> - -<div id="viewheadbackground" class="viewheadbackground fixed"></div> -<div id="viewhead" class="viewhead fixed"></div> +<div id="viewheadcontainer" class="viewhead"> +<div id="viewhead"></div> +</div> <div id="viewcontainer"> <div id="view"> @@ -110,84 +123,249 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js')) </div> </div> -<div id="viewfootbackground" class="viewfootbackground fixed"></div> -<div id="viewfoot" class="viewfoot fixed"></div> +<div id="viewfootcontainer" class="viewfoot"> +<div id="viewfoot"></div> +<div id="status"></div> +</div> + +<div id="installer" class="installer"></div> <script type="text/javascript"> -(function() { +try { + +(function oopsbody() { /** - * Init div variables. + * Setup page layout. */ -var mdiv = $('menu'); -var hdiv = $('viewhead'); -$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm'; -$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm'; -var fdiv = $('viewfoot'); +(function layout() { + $('viewcontainer').className = ui.isMobile()? 'viewcontainer3dm' : 'viewcontainer3d'; + $('view').className = ui.isMobile()? 'viewloaded3dm' : 'viewloaded3d'; + document.title = config.windowtitle() + ' - Oops'; + $('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; + if (!ui.isMobile()) + $('viewcontent').className = 'viewcontent flatscrollbars'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; +})(); /** - * Set page title. + * Setup menu bar. */ -document.title = config.windowtitle() + ' - Oops'; -$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle() + '</span>'; +(function showmenu() { + $('menu').innerHTML = ui.menubar( + mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), mklist()); + $('viewfoot').innerHTML = config.viewfoot(); +})(); /** - * Build and show the menu bar. + * Initialize status message area. */ -function showmenu(mdiv) { - mdiv.innerHTML = ui.menubar( - mklist(ui.menu('menuhome', 'Home', '/', '_self', false)), - mklist(hasauthcookie()? ui.menufunc('menusignout', 'Sign out', 'return logout();', false) : ui.menu('menusignin', 'Sign in', '/login/', '_self', false))); - fdiv.innerHTML = config.viewfoot(); -} +(function initstatus() { + if (isNil($('status'))) + return; + $('status').style.display = 'none'; + + function divtransitionend(e) { + e.target.style.display = 'none'; + e.target.className = ui.isMobile()? 'status3dm' : 'status3d'; + e.target.error = false; + } + $('status').addEventListener('webkitTransitionEnd', divtransitionend, false); + $('status').addEventListener('transitionend', divtransitionend, false); +})(); -showmenu(mdiv); +/** + * Show a status message. + */ +window.showstatus = function(s, c) { + //debug('show status', s); + if (isNil($('status')) || $('status').error) + return s; + $('status').innerHTML = '<span class="' + (c? c : 'okstatus') + '">' + s + '</span>'; + $('status').className = ui.isMobile()? 'status3dm' : 'status3d'; + $('status').style.display = 'block'; + $('status').error = c == 'errorstatus'; + if ($('status').delay) + ui.cancelDelay($('status').delay); + $('status').delay = ui.delay(function hidestatus() { + $('status').className = ui.isMobile()? 'statusout3dm' : 'statusout3d'; + $('status').error = false; + }, 3000); + return s; +}; /** - * Log the current user out. + * Show an error message. */ -window.logout = function() { - // Clear session cookie and user-specific local storage entries - clearauthcookie(); - lstorage.removeItem('/r/Editor/accounts'); - lstorage.removeItem('/r/Editor/dashboards'); - document.location = '/login/'; - return false; -} +window.errorstatus = function(s) { + //debug('error', s); + return showstatus(s, 'errorstatus'); +}; + +/** + * Show working status. + */ +window.workingstatus = function(w, c) { + //debug('show working', w); + if (isNil($('working'))) + return w; + if (!ui.isMobile()) + $('working').style.top = ui.pixpos(Math.round(window.clientHeight / 2)); + $('working').style.display = w? 'block' : 'none'; + return w; +}; + +/** + * Show the online/offline status. + */ +window.onlinestatus = function() { + return navigator.onLine? (ui.isMobile()? showstatus('Online') : showstatus()) : errorstatus('Offline'); +}; /** * Handle orientation change. */ document.body.onorientationchange = function(e) { //debug('onorientationchange'); - ui.onorientationchange(e); - - // Resize menu and view header - mdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - hdiv.style.width = ui.pixpos(document.documentElement.clientWidth); - return true; + return ui.onorientationchange(e); }; /** - * Initialize the document. + * Populate cache with app resources. */ -function onload() { - //debug('onload'); - ui.onload(); +var appresources = [ + ['/all-min.js'], + ['/ui-min.css'], + ['/public/config-min.js'] +]; - // Show the page - document.body.style.visibility = 'visible'; - return true; -} +/** + * Install the application cache. + */ +(function installappcache() { + if (ui.isMobile()) { + // On mobile devices, trigger usage of an application cache manifest + window.onappcachechecking = function(e) { + //debug('appcache checking', e); + workingstatus(true); + showstatus('Checking'); + }; + window.onappcacheerror = function(e) { + //debug('appcache error', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachenoupdate = function(e) { + //debug('appcache noupdate', e); + onlinestatus(); + workingstatus(false); + }; + window.onappcachedownloading = function(e) { + //debug('appcache downloading', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheprogress = function(e) { + //debug('appcache progress', e); + workingstatus(true); + showstatus('Updating'); + }; + window.onappcacheupdateready = function(e) { + //debug('appcache updateready', e); + try { + applicationCache.swapCache(); + } catch(e) {} + onlinestatus(); + workingstatus(false); + //debug('appcache swapped', e); + + // Update offline resources in local storage and reload the page + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + window.location.reload(); + }; + window.onappcachecached = function(e) { + //debug('appcache cached', e); + onlinestatus(); + workingstatus(false); + + // Install offline resources in local storage + map(function(res) { + showstatus('Updating'); + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + }; + + window.onloadappcache = function() { + //debug('appcache iframe loaded'); + }; + + ui.delay(function() { + $('installer').innerHTML = '<iframe src="/public/cache/" class="installer"></iframe>'; + }); + + } else { + // On non-mobile devices, check for cache-manifest changes ourselves. + workingstatus(true); + showstatus('Checking'); + var lcmf = appcache.get('/public/cache/cache-manifest.cmf', 'local'); + var rcmf = appcache.get('/public/cache/cache-manifest.cmf', 'remote'); + if (lcmf == rcmf) { + onlinestatus(); + workingstatus(false); + return true; + } + + //debug('cache-manifest changed, reloading'); + ui.delay(function() { + showstatus('Updating'); + ui.delay(function() { + map(function(res) { + appcache.remove(res[0]); + appcache.get(res[0], 'remote'); + }, append(appresources, config.appresources())); + if (!isNil(lcmf)) { + //debug('reloading'); + window.location.reload(); + } + onlinestatus(); + workingstatus(false); + }); + }); + } +})(); -onload(); +/** + * Handle network offline/online events. + */ +window.addEventListener('offline', function(e) { + //debug('going offline'); + showstatus('Offline'); +}, false); +window.addEventListener('online', function(e) { + //debug('going online'); + showstatus('Online'); +}, false); + +/** + * Initialize the document. + */ +window.onload = function() { + //debug('onload'); + return ui.onload(); +}; })(); -</script> -<div id="footdiv" class="fsection"> -</div> +} catch(e) { + debug(e.stack); + throw e; +} +</script> -</div> </body> </html> diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/rate.png b/sca-cpp/trunk/hosting/server/htdocs/public/rate.png Binary files differnew file mode 100644 index 0000000000..27c744c5a6 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/rate.png diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/rate.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/rate.xcf Binary files differnew file mode 100644 index 0000000000..eb807f6005 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/rate.xcf diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/ratings.png b/sca-cpp/trunk/hosting/server/htdocs/public/ratings.png Binary files differnew file mode 100644 index 0000000000..9c10be52dd --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/ratings.png diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/ratings.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/ratings.xcf Binary files differnew file mode 100644 index 0000000000..cad8d31b2e --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/ratings.xcf diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/search.png b/sca-cpp/trunk/hosting/server/htdocs/public/search.png Binary files differnew file mode 100644 index 0000000000..d5178fea3c --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/search.png diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/search.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/search.xcf Binary files differnew file mode 100644 index 0000000000..30d03df40a --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/search.xcf diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-50.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-50.xcf Binary files differnew file mode 100644 index 0000000000..8dcc8e4f1b --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-50.xcf diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-53.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-53.xcf Binary files differnew file mode 100644 index 0000000000..b2dcd6f12c --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-53.xcf diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-57.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-57.xcf Binary files differnew file mode 100644 index 0000000000..5ab284973d --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-57.xcf diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.b64 b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.b64 deleted file mode 100644 index 2239f6ae0f..0000000000 --- a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.b64 +++ /dev/null @@ -1 +0,0 @@ -iVBORw0KGgoAAAANSUhEUgAAADkAAAA5CAIAAAADehTSAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sDGxMkCJXGmL8AAAHwSURBVGje7ZpNbhNBEIXf625sCzA/QUhkg8SGiGxZcpDcgRux5hLkEjkE7BAS9gIyk+muxyZIsSeOG09bsXHX0p4pfVNdP8/loSTsiTnsj1XWyrrMutVSG+ic/ftNqe1mIMtSjsPUu9EQJ6H/UdvNLr59cgwFWaM1p8dnLx6dFGYF6RhIXzLVGIChB3VX8Fg0DWrPqqyHxTq4MUnKKEEBoNvIN4uxiqTkofUXpgZKsqtMx3Djpb45lNWAxxbfXf6wtdH9+vkKBLLGrFLz4M1HTk+K5gAIgBCVcaTI1gOK/acazqqbw2PdYzE7tdyh9AFJTL0zNDIJMInAZpKPzBzmIZuUnjoa9QQkOBHAyWbigYDaTslybg/59f7Q4+003pqwhqcbqjLH9H2OXw0Ksl6XsWB/a39lhf1rz8vOnKoHKmtlrayVtbLuuc6SFK1Z2hEZkBwAv1us4zA9PT7rDX3v9dPiOeBxT/uY0A+qd6Pbl2Sax/kXDN9LlcrXO3Rk9k/QWluVtbIe2O5toBGwFum3bLH/pEso7RarrPNHH/D8JbBIpsjJqx2Lq3Xu2Xv61yvXJzf6/b3nK2Htyu8WB9P/XltF/wfVllgFxet9azGL+bjMD5IUYbPSMktwT8hRSdalkizcufKcs77vUlkr61bsD5lbwtgOKPT2AAAAAElFTkSuQmCC
\ No newline at end of file diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.png b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.png Binary files differindex f22c33d2a0..1975eb16bf 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.png +++ b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.png diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.xcf Binary files differdeleted file mode 100644 index fc713b478b..0000000000 --- a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.xcf +++ /dev/null diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/user.b64 b/sca-cpp/trunk/hosting/server/htdocs/public/user.b64 index 7ed235aa14..70e650a50b 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/user.b64 +++ b/sca-cpp/trunk/hosting/server/htdocs/public/user.b64 @@ -1 +1 @@ -iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAABGdBTUEAALGPC/xhBQAAAAxQTFRFyN+N+dR1/PCI////6HjE5gAAADJJREFUKM9j+I8EPjBQifeBAQSY6coLBYN6inhaq0Bg6SDn/f//akB466ExTS6P2ukMAKumzarJO/66AAAAAElFTkSuQmCC
\ No newline at end of file +iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAADG0lEQVRo3u3a326iQBQG8G8EhAIRMFXHJrXEJo0XNfER+jz7PrvP04fojb0gJq2BaCOmBUuRMntls82a/hkYdDeeW+OnPweGMycC/0kRALi+vmb/MuLq6orU/pcVOUAOEEEliwhVVRWUUjiOA13XIcsysizDarVCGIYIggAvLy/7C5EkCf1+H91uF4SQd68pigLLsmBZFs7OzuD7PjzPw+vr635BDMPA5eUlNE37fM8nBCcnJ2g2m7i5uUEcx/txj5imidFo9CXEn6VpGkajEUzT3D1EVVUMh0PIMt/iyrKM4XAIVVV3CxkMBqjX64Uy6vU6BoPB7iCUUti2Xco9Zts2KKXVQwghcF231C3Udd2/djvhkE6nU/i63na/dTqdaiFFLgMRuTXem9OyLCGQRqPBtXlwQRzHEXdAIoQrv8b7q4ksnnwuiGEYQiE8+VyQsnerMvK5IIqiCIXw5HNBajWx5zGefK5vxJjYoQtPPhckyzKhEJ58LkiapkIhPMdgLshqtRIKeX5+rgby9PQkFMKTzwVZLpdCITz5XJAoipAkiRBEkiSIoqi6Nn42mwmB8OZyQ6bTKfI8LxWR5zmm02m1kDRN4ft+qRDf97m39kK9xmQyKe2ZkqYpJpPJbqYoWZZhPB4XblkYYxiPx4U6hsLd32KxgOd5hTI8z8NisSiUUcrs9/7+HgDQ7/e/Nc5hjMHzvLf37xyywcRxjIuLiy/NgJMkwe3tLcIwLOXzC0EURYFhGDAMA5qmQdM0rNdrSJIEWZa3rk6e50jTFFEUodlsQtd1xHGMOI6xXq+rgWiaBsdxYNs2Go3Gt6fvm0PTBr1tlR4fH7FcLhGG4be6h08hpmmi1Wrh+PgYuq4L7bE2wHa7/dZlPzw8YD6ff9q2bIXIsgxKKSilwicmH5Wu6+j1euj1eojjGEEQIAiCrdv0O8jR0RFOT0/RbrchSRL2qQzDwPn5OVzXxWw2w93d3btzi7wBuK6LVqvFPQ2vqiRJQrfbBaUU8/n8rRsgjLGfjLEf+w746FlECPlFmOiRSEV1+OfDAXKAfFy/AU/NFLNNzbKxAAAAAElFTkSuQmCC diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/user.png b/sca-cpp/trunk/hosting/server/htdocs/public/user.png Binary files differindex 1f73274b76..e99b30815d 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/public/user.png +++ b/sca-cpp/trunk/hosting/server/htdocs/public/user.png diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/user.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/user.xcf Binary files differnew file mode 100644 index 0000000000..2f304c45a7 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/public/user.xcf diff --git a/sca-cpp/trunk/hosting/server/htdocs/rate/index.html b/sca-cpp/trunk/hosting/server/htdocs/rate/index.html new file mode 100644 index 0000000000..22f11f631a --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/rate/index.html @@ -0,0 +1,190 @@ +<!DOCTYPE html> +<!-- + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. +--> +<div id="bodydiv" class="body"> + +<div id="viewform" class="viewform"> + +<form id="rateAppForm"> +<table style="width: 100%;"> +<tr><td class="label">Rating:</td></tr> +<tr><td class="lightlabel" id="taptorate"></td></tr> +<tr><td class="label"> +<span style="display: inline-block; width: 2px;"></span> +<span id="rateApp1" class="graystar"> </span><span style="display: inline-block; width: 20px;"></span> +<span id="rateApp2" class="graystar"> </span><span style="display: inline-block; width: 20px;"></span> +<span id="rateApp3" class="graystar"> </span><span style="display: inline-block; width: 20px;"></span> +<span id="rateApp4" class="graystar"> </span> +</td></tr> +<tr><td class="lightlabel" id="ratedescription"> </span></td></tr> +<tr><td style="padding-top: 20px;"> +<input id="rateAppDoneButton" type="button" class="graybutton" value="Done"/> +</td></tr> +</table> +</form> +<br/> + +</div> + +<script type="text/javascript"> +(function ratebody() { + +/** + * Get the app name. + */ +var appname = ui.fragmentParams(location)['app']; + +/** + * Setup page layout. + */ +(function layout() { + document.title = config.windowtitle() + ' - ' + 'Rate' + ' - ' + appname; + $('viewhead').innerHTML = '<span class="smenu">' + 'Rate' + ' ' + appname + '</span>'; + if (!ui.isMobile()) + $('viewform').className = 'viewform flatscrollbars'; + + $('viewform').appendChild(ui.declareCSS( + '.redstar { ' + + 'background: url(\'' + ui.b64png(appcache.get('/public/rate.b64')) + '\'); background-repeat: no-repeat; ' + + 'vertical-align: middle; width: 40px; height: 40px; display: inline-block; background-position: 0px 1px;' + + ' } ' + + '.graystar { ' + + 'background: url(\'' + ui.b64png(appcache.get('/public/rate.b64')) + '\'); background-repeat: no-repeat; ' + + 'vertical-align: middle; width: 40px; height: 40px; display: inline-block; background-position: -50px 1px;' + + ' }')); + + $('taptorate').innerHTML = ui.isMobile()? 'Tap a star to select a rating' : ' Click a star to select a rating'; +})(); + +/** + * Initialize service references. + */ +var editorComp = sca.component("Editor"); +var reviews = sca.reference(editorComp, "reviews"); + +/** + * Initialize the rate buttons. + */ +var rateAppButtons = [ + [$('rateApp1'), 1, function() { return onclickrating(1); }, 'Don\'t like it'], + [$('rateApp2'), 2, function() { return onclickrating(2); }, 'It\'s ok'], + [$('rateApp3'), 3, function() { return onclickrating(3); }, 'It\'s good'], + [$('rateApp4'), 4, function() { return onclickrating(4); }, 'It\'s great'] +]; +(function initRateAppButtons() { + map(function(b) { + b[0].onclick = b[2]; + }, rateAppButtons); +})(); + +/** + * Select a rating. + */ +var selectedrating = 0; +function selectrating(r) { + selectedrating = r; + map(function(b) { + b[0].className = b[1] <= r? 'redstar' : 'graystar'; + }, rateAppButtons); + $('ratedescription').innerHTML = rateAppButtons[r - 1][3]; + return true; +} + +/** + * The current app entry and corresponding saved XML content. + */ +var appentry; +var savedxml = ''; + +/** + * Get and display the requested app rating. + */ +(function getrating() { + if (isNil(appname)) + return false; + workingstatus(true); + showstatus('Loading'); + + return reviews.get(appname, function(doc) { + + // Stop now if we didn't get the rating + if (doc == null) { + onlinestatus(); + workingstatus(false); + return false; + } + + appentry = doc != null? car(elementsToValues(atom.readATOMEntry(mklist(doc)))) : mklist("'entry", mklist("'title", ''), mklist("'id", appname)); + savedxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry)))); + var content = cadr(assoc("'content", appentry)); + if (!isNil(content)) + selectrating(parseInt(cadr(content))); + + onlinestatus(); + workingstatus(false); + return true; + }); +})(); + +/** + * Save an app rating. + */ +function save(name, entryxml) { + workingstatus(true); + showstatus('Saving'); + + savedxml = entryxml; + reviews.put(name, savedxml, function(e) { + if (e) { + showstatus('Local copy'); + workingstatus(false); + return false; + } + showstatus('Saved'); + workingstatus(false); + return false; + }); + return false; +} + +/** + * Handle rating click event. + */ +function onclickrating(r) { + // Select the rating + selectrating(r); + + // Save + showstatus('Saving'); + appentry = mklist("'entry", mklist("'title", appname), mklist("'id", appname), mklist("'content", mklist("'rating", selectedrating.toString()))); + var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry)))); + return save(appname, entryxml); +} + +/** + * Navigate back. + */ +$('rateAppDoneButton').onclick = function() { + history.back(); +}; + +})(); +</script> + +</div> diff --git a/sca-cpp/trunk/hosting/server/htdocs/search/index.html b/sca-cpp/trunk/hosting/server/htdocs/search/index.html new file mode 100644 index 0000000000..47d5a757f3 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/htdocs/search/index.html @@ -0,0 +1,196 @@ +<!DOCTYPE html> +<!-- + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. +--> +<div id="bodydiv" class="body"> + +<div id="viewcontent" class="viewcontent"> + +<div id="apps"></div> +<br/> + +</div> + +<script type="text/javascript"> +(function searchbody() { + +/** + * Setup page layout. + */ +(function layout() { + document.title = config.windowtitle() + ' - Apps'; + if (!ui.isMobile()) + $('viewcontent').className = 'viewcontent flatscrollbars'; + + $('viewhead').innerHTML = '<form id="searchForm">' + + '<span style="position: absolute; top: 0px; left: 5px; right: 70px; padding: 0px; background: transparent;"><input type="text" id="searchQuery" value="" class="flatentry" title="Search" autocapitalize="off" placeholder="Search for apps" style="position: absolute; left: 0px; top: 4px; width: 100%;"></span>' + + '<input type="submit" id="searchButton" title="Search" class="bluebutton search" style="position: absolute; top: 4px; right: 5px; width: 60px; background-position: center center; background-repeat: no-repeat; background-image: url(\'' + ui.b64png(appcache.get('/public/search.b64')) + '\');" value=" "/>' + + '</form>'; + + $('viewcontent').appendChild(ui.declareCSS( + '.ratings { ' + + 'background: url(\'' + ui.b64png(appcache.get('/public/ratings.b64')) + '\'); ' + + 'margin-top: 6px; width: 50px; height: 14px; display: inline-block; ' + + ' }')); +})(); + +/** + * Initialize service references. + */ +var editorComp = sca.component("Editor"); +var search = sca.reference(editorComp, "search"); +var icons = sca.reference(editorComp, "icons"); + +/** + * Edit an app. + */ +function editapp(appname) { + return ui.navigate('/#view=page&app=' + appname, '_view'); +} + +/** + * View an app. + */ +function viewapp(appname) { + return ui.navigate('/#view=info&app=' + appname, '_view'); +} + +/** + * Get and display an app icon. + */ +function geticon(appname) { + if (isNil(appname)) + return false; + + return icons.get(appname, function(doc) { + // Stop now if we didn't get an icon + if (doc == null) + return false; + + var iconentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); + var content = assoc("'content", iconentry); + var icon = assoc("'icon", content); + var img = assoc("'image", icon); + if (!isNil(img)) { + var appimg = $('search_app_img_' + appname); + if (!isNil(appimg)) + appimg.src = cadr(img); + } + return true; + }); + return true; +} + +/** + * Get and display list of apps. + */ +function getapps(query) { + workingstatus(true); + showstatus('Searching'); + + function display(doc) { + + // Stop now if we didn't get the apps + if (doc == null) { + errorstatus('Not available'); + workingstatus(false); + return false; + } + + var feed = car(elementsToValues(atom.readATOMFeed(mklist(doc)))); + var aentries = assoc("'entry", feed); + var entries = isNil(aentries)? mklist() : isList(car(cadr(aentries)))? cadr(aentries) : mklist(cdr(aentries)); + + var defappimg = ui.b64png(appcache.get('/public/app.b64')); + + var apps = '<div>'; + var icons = mklist(); + + (function displayentries(entries) { + if (isNil(entries)) + return apps; + var entry = car(entries); + var title = cadr(assoc("'title", entry)) + var name = cadr(assoc("'id", entry)); + var author = cadr(assoc("'author", entry)); + var updated = xmldatetime(cadr(assoc("'updated", entry))).toLocaleDateString(); + + var aratings = assoc("'info", assoc("'content", entry)); + var ar = assoc("'rating", aratings); + var ar1 = assoc("'rating1", aratings); + var ar2 = assoc("'rating2", aratings); + var ar3 = assoc("'rating3", aratings); + var ar4 = assoc("'rating4", aratings); + var rating = isNil(ar)? 0 : Number(cadr(ar)); + var reviews = (isNil(ar1)? 0 : Number(cadr(ar1))) + (isNil(ar2)? 0 : Number(cadr(ar2))) + (isNil(ar3)? 0 : Number(cadr(ar3))) + (isNil(ar4)? 0 : Number(cadr(ar4))); + + apps += '<div class="box">' + apps += '<div class="appicon">' + apps += ui.href('appicon_' + name, '/#view=info&app=' + name, '_view', '<img id="search_app_img_' + name + '" src="' + defappimg + '" width="50" height="50"></img>'); + //apps += '<br/><input type="button" class="lightbutton" value="Run" onclick="ui.navigate(\'/' + name + '/\', \'_blank\');"/>'; + apps += '</div>' + apps += '<div class="appdetails">'; + apps += '<span class="apptitle">' + ui.href('search_app_title_' + name, '/#view=info&app=' + name, '_view', name) + '</span>'; + apps += '<br/><span>' + 'by ' + author.split('@')[0] + '</span>'; + var ratingy = -20 * (4 - Math.floor(rating)); + apps += '<br/><span class="ratings" style="background-position: 0px ' + ratingy + 'px;"> </span>'; + apps += '<br/><span style="font-size: 10px;">' + reviews + (reviews > 1? ' ratings' : ' rating') + '</span>'; + /*apps += '<br/><span>' + updated + '</span>';*/ + apps += '</div>'; + apps += '</div>'; + + icons = cons(name, icons); + + return displayentries(cdr(entries)); + })(entries); + + apps += '</div>'; + $('apps').innerHTML = apps; + + ui.unmemo$('search_app_'); + + (function displayicons(icons) { + if (isNil(icons)) + return true; + geticon(car(icons)); + return displayicons(cdr(icons)); + })(reverse(icons)); + + onlinestatus(); + workingstatus(false); + } + + return search.get('?q=' + query, display); +} + +/** + * Handle search form submit. + */ +$('searchForm').onsubmit = function() { + if ($('searchQuery').value.trim() == '') + return false; + if (ui.isMobile()) + $('searchQuery').blur(); + getapps($('searchQuery').value.trim()); + return false; +}; + +})(); +</script> + +</div> diff --git a/sca-cpp/trunk/hosting/server/htdocs/stats/index.html b/sca-cpp/trunk/hosting/server/htdocs/stats/index.html deleted file mode 100644 index 7c3d9a6434..0000000000 --- a/sca-cpp/trunk/hosting/server/htdocs/stats/index.html +++ /dev/null @@ -1,179 +0,0 @@ -<!DOCTYPE html> -<!-- - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. ---> -<div id="bodydiv" class="body"> - -<div class="viewform"> - -<form id="appForm"> -<table style="width: 100%;"> -<tr><tr><td><b>Icon:</b></td></tr> -<tr><td><img id="appimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Title:</b></td></tr> -<tr><td><input type="text" class="flatentry" id="appTitle" size="30" readonly="readonly" placeholder="Enter the title of your app" style="width: 300px;"/></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Author:</b></td></tr> -<tr><td><span id="appAuthor"></span></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Updated:</b></td></tr> -<tr><td><span id="appUpdated"></span></td></tr> -<tr><tr><td style="padding-top: 6px;"><b>Description:</b></td></tr> -<tr><td><textarea id="appDescription" class="flatentry" cols="40" rows="3" readonly="readonly" placeholder="Enter a short description of your app" style="width: 300px;"></textarea></td></tr> -</table> -</form> - -</div> - -<script type="text/javascript"> -(function() { - -/** - * Get the app name. - */ -var appname = ui.fragmentParams(location)['app']; - -/** - * Set page titles. - */ -document.title = config.windowtitle() + ' - Stats - ' + appname; -$('viewhead').innerHTML = '<span id="appname" class="cmenu">' + appname + '</span>' + -'<input type="button" class="graybutton redbutton plusminus" style="position: absolute; top: 4px; left: 5px;" id="deleteApp" value="-" title="Delete the app" disabled="true"/>' + -'<input type="button" class="graybutton bluebutton" style="position: absolute; top: 4px; right: 5px;" id="cloneApp" value="'+ config.clone() +'" title="' + config.clone() + ' this app"/>'; - -/** - * Set images. - */ -$('appimg').src = ui.b64img(appcache.get('/public/app.b64')); - -/** - * Init service references. - */ -var editorComp = sca.component("Editor"); -var apps = sca.reference(editorComp, "apps"); - -/** - * The current app entry, author and saved XML content. - */ -var savedappentryxml = ''; -var author; -var appentry; - -/** - * Get and display an app. - */ -function getapp(name) { - if (isNil(name)) - return false; - showStatus('Loading'); - - return apps.get(name, function(doc) { - - // Stop now if we didn't get the app - if (doc == null) { - showError('App not available'); - return false; - } - - appentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); - $('appTitle').value = cadr(assoc("'title", cdr(appentry))); - author = cadr(assoc("'author", cdr(appentry))); - $('appAuthor').innerHTML = author; - $('appUpdated').innerHTML = cadr(assoc("'updated", cdr(appentry))); - var content = cadr(assoc("'content", cdr(appentry))); - var description = assoc("'description", content); - $('appDescription').value = isNil(description) || isNil(cadr(description))? '' : cadr(description); - savedappentryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry)))); - - // Enable author to edit and delete the app - if (username == author) { - $('appTitle').readOnly = false; - $('appDescription').readOnly = false; - $('deleteApp').disabled = false; - $('deleteApp').onclick = function() { - return ui.navigate('/#view=delete&app=' + appname, '_view'); - } - showOnlineStatus(); - } else { - $('appTitle').placeholder = ''; - $('appDescription').placeholder = ''; - showStatus('Read only'); - } - return true; - }); -} - -/** - * Save the current app. - */ -function save(entryxml) { - showStatus('Saving'); - savedappentryxml = entryxml; - apps.put(appname, savedappentryxml, function(e) { - if (e) { - showStatus('Local copy'); - return false; - } - - showStatus('Saved'); - return false; - }); - return true; -} - -/** - * Handle a change event - */ -function onappchange() { - if (username != author) - return false; - var title = $('appTitle').value; - var description = $('appDescription').value; - appentry = mklist("'entry", mklist("'title", title != ''? title : appname), mklist("'id", appname), mklist("'content", mklist("'stats", mklist("'description", description)))); - var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry)))); - if (savedappentryxml == entryxml) - return false; - showStatus('Modified'); - return save(entryxml); -} - -$('appTitle').onchange = onappchange; -$('appDescription').onchange = onappchange; - -/** - * Handle a form submit event. - */ -$('appForm').onsubmit = function() { - onappchange(); - return false; -}; - -/** - * Handle Clone button event. - */ -$('cloneApp').onclick = function() { - return ui.navigate('/#view=clone&app=' + appname, '_view'); -} - -/** - * Get the current app. - */ -getapp(appname); - -})(); -</script> - -</div> diff --git a/sca-cpp/trunk/hosting/server/htdocs/store/index.html b/sca-cpp/trunk/hosting/server/htdocs/store/index.html index 1264007fe3..15e7aeed0c 100644 --- a/sca-cpp/trunk/hosting/server/htdocs/store/index.html +++ b/sca-cpp/trunk/hosting/server/htdocs/store/index.html @@ -19,24 +19,39 @@ --> <div id="bodydiv" class="body"> -<div id="apps" class="viewcontent"></div> +<div id="viewcontent" class="viewcontent"> + +<div id="apps"></div> +<br/> + +</div> <script type="text/javascript"> -(function() { +(function storebody() { /** - * Set page titles. + * Setup page layout. */ -document.title = config.windowtitle() + ' - Store'; +(function layout() { + document.title = config.windowtitle() + ' - Apps'; + if (!ui.isMobile()) + $('viewcontent').className = 'viewcontent flatscrollbars'; + + $('viewcontent').appendChild(ui.declareCSS( + '.ratings { ' + + 'background: url(\'' + ui.b64png(appcache.get('/public/ratings.b64')) + '\'); ' + + 'margin-top: 6px; width: 50px; height: 14px; display: inline-block; ' + + ' }')); +})(); /** * The store categories */ var categories = [ - //['Featured', 'featured', 1], + ['Featured', 'featured', 1], ['Top', 'top', 2], - ['New', 'new', 3], - ['Search', 'all', 4], + //['New', 'new', 3], + //['Search', 'all', 4], ['My Apps', 'myapps', 5] ]; @@ -45,22 +60,22 @@ var categories = [ */ function findcategory(name) { if (isNil(name)) - return findcategory('top'); + return findcategory('featured'); var f = filter(function(c) { return cadr(c) == name }, categories); if (isNil(f)) - return findcategory('top'); + return findcategory('featured'); return car(f); } /** - * Get the current store category. + * Get the requested store category. */ var catname = cadr(findcategory(ui.fragmentParams(location)['category'])); /** * Build the store menu bar */ -function catmenu() { +$('viewhead').innerHTML = (function catmenu() { function catmenuitem(name, cat, idx) { var c = cat == catname? 'smenu' : 'amenu'; return '<span>' + ui.href('storecat_' + cat, '/#view=store&category=' + cat + '&idx=' + idx, '_view', '<span class="' + c + '">' + name + '</span>') + '</span>'; @@ -68,34 +83,31 @@ function catmenu() { var m = ''; map(function(c) { m += catmenuitem(car(c), cadr(c), caddr(c)); }, categories); - m += '<span class="rmenu"><input type="button" class="graybutton bluebutton" id="createApp" title="Create a new app" Value="Create"/></span>'; + m += '<span class="rmenu"><input type="button" class="bluebutton" id="createApp" title="Create a new app" Value="Create"/></span>'; return m; -} +})(); -/** - * Build the store menu bar. - */ -$('viewhead').innerHTML = catmenu(); /** - * Init service references. + * Initialize service references. */ var editorComp = sca.component("Editor"); var store = sca.reference(editorComp, "store"); var dashboards = sca.reference(editorComp, "dashboards"); +var icons = sca.reference(editorComp, "icons"); /** * Edit an app. */ -function editApp(appname) { +function editapp(appname) { return ui.navigate('/#view=page&app=' + appname, '_view'); } /** * View an app. */ -function viewApp(appname) { - return ui.navigate('/#view=stats&app=' + appname, '_view'); +function viewapp(appname) { + return ui.navigate('/#view=info&app=' + appname, '_view'); } /** @@ -103,66 +115,118 @@ function viewApp(appname) { */ $('createApp').onclick = function() { return ui.navigate('/#view=create', '_view'); +}; + +/** + * Get and display an app icon. + */ +function geticon(appname) { + if (isNil(appname)) + return false; + + return icons.get(appname, function(doc) { + // Stop now if we didn't get an icon + if (doc == null) + return false; + + var iconentry = car(elementsToValues(atom.readATOMEntry(mklist(doc)))); + var content = assoc("'content", iconentry); + var icon = assoc("'icon", content); + var img = assoc("'image", icon); + if (!isNil(img)) { + var appimg = $('store_app_img_' + appname); + if (!isNil(appimg)) + appimg.src = cadr(img); + } + return true; + }); + return true; } /** * Get and display list of apps. */ -function getapps(catname) { - //debug('catname', catname); - showStatus('Loading'); +(function getapps() { + workingstatus(true); + showstatus('Loading'); function display(doc) { // Stop now if we didn't get the apps if (doc == null) { - showError('App not available'); + errorstatus('Couldn\'t get the list of apps'); + workingstatus(false); return false; } - showOnlineStatus(); - var apps = '<div>'; var feed = car(elementsToValues(atom.readATOMFeed(mklist(doc)))); - var aentries = assoc("'entry", cdr(feed)); + var aentries = assoc("'entry", feed); var entries = isNil(aentries)? mklist() : isList(car(cadr(aentries)))? cadr(aentries) : mklist(cdr(aentries)); - var appimg = ui.b64img(appcache.get('/public/app.b64')); + var defappimg = ui.b64png(appcache.get('/public/app.b64')); - function displayentries(entries) { + var apps = '<div>'; + var icons = mklist(); + + (function displayentries(entries) { if (isNil(entries)) return apps; var entry = car(entries); var title = cadr(assoc("'title", entry)) var name = cadr(assoc("'id", entry)); var author = cadr(assoc("'author", entry)); - var updated = cadr(assoc("'updated", entry)); + var updated = xmldatetime(cadr(assoc("'updated", entry))).toLocaleDateString(); + + var aratings = assoc("'info", assoc("'content", entry)); + var ar = assoc("'rating", aratings); + var ar1 = assoc("'rating1", aratings); + var ar2 = assoc("'rating2", aratings); + var ar3 = assoc("'rating3", aratings); + var ar4 = assoc("'rating4", aratings); + var rating = isNil(ar)? 0 : Number(cadr(ar)); + var reviews = (isNil(ar1)? 0 : Number(cadr(ar1))) + (isNil(ar2)? 0 : Number(cadr(ar2))) + (isNil(ar3)? 0 : Number(cadr(ar3))) + (isNil(ar4)? 0 : Number(cadr(ar4))); apps += '<div class="box">' - apps += '<span class="appicon">' + ui.href('appicon_' + name, '/#view=stats&app=' + name, '_view', '<img src="' + appimg + '" width="50" height="50"></img>') + '</span>'; - apps += '<span>' - apps += '<span class="apptitle">' + ui.href('apptitle_' + name, '/#view=stats&app=' + name, '_view', name) + '</span>'; + apps += '<div class="appicon">' + apps += ui.href('appicon_' + name, '/#view=info&app=' + name, '_view', '<img id="store_app_img_' + name + '" src="' + defappimg + '" width="50" height="50"></img>'); + //apps += '<br/><input type="button" class="lightbutton" value="Run" onclick="ui.navigate(\'/' + name + '/\', \'_blank\');"/>'; + apps += '</div>' + apps += '<div class="appdetails">'; + apps += '<span class="apptitle">' + ui.href('store_app_title_' + name, '/#view=info&app=' + name, '_view', name) + '</span>'; if (catname != 'myapps') apps += '<br/><span>' + 'by ' + author.split('@')[0] + '</span>'; - apps += '</span>'; + var ratingy = -20 * (4 - Math.floor(rating)); + apps += '<br/><span class="ratings" style="background-position: 0px ' + ratingy + 'px;"> </span>'; + apps += '<br/><span style="font-size: 10px;">' + reviews + (reviews > 1? ' ratings' : ' rating') + '</span>'; + /*apps += '<br/><span>' + updated + '</span>';*/ + apps += '</div>'; apps += '</div>'; - return displayentries(cdr(entries)); - } - displayentries(entries); + icons = cons(name, icons); + + return displayentries(cdr(entries)); + })(entries); apps += '</div>'; $('apps').innerHTML = apps; + + ui.unmemo$('store_app_'); + + (function displayicons(icons) { + if (isNil(icons)) + return true; + geticon(car(icons)); + return displayicons(cdr(icons)); + })(reverse(icons)); + + onlinestatus(); + workingstatus(false); } if (catname == 'myapps') return dashboards.get('', display); return store.get(catname, display); -} - -/** - * Get and display the list of apps. - */ -getapps(catname); +})(); })(); </script> diff --git a/sca-cpp/trunk/hosting/server/icons.py b/sca-cpp/trunk/hosting/server/icons.py new file mode 100644 index 0000000000..7ee9ae9bfb --- /dev/null +++ b/sca-cpp/trunk/hosting/server/icons.py @@ -0,0 +1,175 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# App icons collection implementation +from StringIO import StringIO +try: + from PIL import Image +except: + Image = None +from base64 import b64encode, b64decode +from urllib import urlopen +from util import * +from atomutil import * +from sys import debug + +# Convert an id to an icon id +def iconid(id): + return ("apps", car(id), "app.icon") + +# Convert image to a 50x50 PNG image +def to50x50png(url): + debug('icons.py::to50x50png::url', url) + if Image is None: + return url + img = Image.open(StringIO(b64decode(url.split(',')[1])) if url.startswith('data:') else StringIO(urlopen(url).read())) + t = img.resize((50, 50)) + obuf = StringIO() + t.save(obuf, 'PNG') + return 'data:image/png;base64,' + b64encode(obuf.getvalue()).replace('\n', '') + +# Put an icon +def put(id, icon, user, cache, apps): + debug('icons.py::put::id', id) + debug('icons.py::put::icon', icon) + + # Get the requested app + app = apps.get(id) + if isNil(app): + debug('icons.py::put', 'app not found', id) + return False + + # Check app author + if author(app) != user.get(()) and user.get(()) != 'admin': + debug('icons.py::put', 'different author', author(app)) + return False + + # Get image and token from input icon + def image(c): + img = assoc("'image", c) + return None if isNil(img) else to50x50png(cadr(img)) + def token(c): + tok = assoc("'token", c) + return None if isNil(tok) else cadr(tok) + img = image(content(icon)) + tok = token(content(icon)) + + # Update the icon + # Put with an upload token + if not isNil(tok): + debug('icons.py::put::token', tok) + + # Token alone, store token with existing image, if any + if isNil(img): + eicon = cache.get(iconid(id)) + eimg = None if isNil(eicon) else image(content(eicon)) + if isNil(eimg): + iconentry = mkentry(title(app), car(id), author(app), now(), ("'icon", ("'token", tok))) + debug('icons.py::put::iconentry', iconentry) + return cache.put(iconid(id), iconentry) + + debug('icons.py::put::eimg', eimg) + iconentry = mkentry(title(app), car(id), author(app), now(), ("'icon", ("'image", eimg), ("'token", tok))) + debug('icons.py::put::iconentry', iconentry) + return cache.put(iconid(id), iconentry) + + # Token plus image, put image if token is valid, removing the token + debug('icons.py::put::img', img) + eicon = cache.get(iconid(id)) + etok = None if isNil(eicon) else token(content(eicon)) + debug('icons.py::put::etok', etok) + if isNil(etok) or tok != etok: + debug('icons.py::put', 'invalid token', tok) + return False + + iconentry = mkentry(title(app), car(id), author(app), now(), ("'icon", ("'image", img))) + debug('icons.py::put::iconentry', iconentry) + return cache.put(iconid(id), iconentry) + + # Update icon image + if not isNil(img): + debug('icons.py::put::img', img) + iconentry = mkentry(title(app), car(id), author(app), now(), ("'icon", ("'image", img))) + debug('icons.py::put::iconentry', iconentry) + return cache.put(iconid(id), iconentry) + + # Put default empty icon + iconentry = mkentry(title(app), car(id), author(app), now(), ()) + debug('icons.py::put::iconentry', iconentry) + return cache.put(iconid(id), iconentry) + +# Get an icon +def get(id, user, cache, apps): + debug('icons.py::get::id', id) + if isNil(id): + return (("'feed", ("'title", "Icons"), ("'id", "icons")),) + + # Get the requested app + app = apps.get(id) + if isNil(app): + debug('icons.py::get', 'app not found', id) + + # Return a default new icon + return mkentry(car(id), car(id), user.get(()), now(), ()) + + # Get the requested icon + icon = cache.get(iconid(id)) + if isNil(icon): + debug('icons.py::get', 'icon not found', id) + + # Return a default new icon + return mkentry(title(app), car(id), author(app), now(), ()) + + # Get image, token, and updated date from icon + def image(c): + img = assoc("'image", c) + return None if isNil(img) else cadr(img) + def token(c): + tok = assoc("'token", c) + return None if isNil(tok) else cadr(tok) + img = image(content(icon)) + tok = token(content(icon)) + + # Return the icon + iconc = (() if isNil(img) else (("'image", img),)) + (() if isNil(tok) or (user.get(()) != author(app) and user.get(()) != 'admin') else (("'token", tok),)) + if isNil(iconc): + iconentry = mkentry(title(app), car(id), author(app), updated(icon), ()) + debug('icons.py::get::iconentry', iconentry) + return iconentry + + iconentry = mkentry(title(app), car(id), author(app), updated(icon), ("'icon",) + iconc) + debug('icons.py::get::iconentry', iconentry) + return iconentry + +# Delete an icon +def delete(id, user, cache, apps): + debug('icons.py::delete::id', id) + + # Get the requested app + app = apps.get(id) + if isNil(app): + debug('icons.py::delete', 'app not found', id) + return False + + # Check app author + if author(app) != user.get(()): + debug('icons.py::delete', 'different author', author(app)) + return False + + # Delete the icon + return cache.delete(iconid(id)) + diff --git a/sca-cpp/trunk/hosting/server/imapd-start b/sca-cpp/trunk/hosting/server/imapd-start new file mode 100755 index 0000000000..5e02b6a79d --- /dev/null +++ b/sca-cpp/trunk/hosting/server/imapd-start @@ -0,0 +1,71 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Load media received from an IMAP inbox +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` +mkdir -p $1 +root=`echo "import os; print os.path.realpath('$1')" | python` +imaphost=$2 +imapport=$3 +imapuser=$4 +imappass=$5 +admin=$6 +if [ "$admin" = "" ]; then + admin=admin +fi +apass=$7 +if [ "$apass" = "" ]; then + apass=admin +fi +log=$8 + +python_prefix=`cat $here/../../modules/python/python.prefix` + +# Get HTTP server conf +conf=`cat $root/conf/httpd.conf | grep "# Generated by: httpd-conf"` +sslconf=`cat $root/conf/httpd.conf | grep "# Generated by: httpd-ssl-conf"` +if [ "$sslconf" = "" ]; then + scheme="http" + addr=`echo $conf | awk '{ print $7 }'` + host=`$here/../../modules/http/httpd-addr ip $addr` + if [ "$host" = "" ]; then + host="localhost" + fi + port=`$here/../../modules/http/httpd-addr port $addr` +else + scheme="https" + ssladdr=`echo $sslconf | awk '{ print $6 }'` + host=`$here/../../modules/http/httpd-addr ip $ssladdr` + if [ "$host" = "" ]; then + host="localhost" + fi + port=`$here/../../modules/http/httpd-addr port $ssladdr` +fi + +# Configure logging +if [ "$log" = "" ]; then + mkdir -p $root/logs + log="cat >>$root/logs/imapd" +fi +mkdir -p $root/imapd +echo $log > $root/imapd/logger + +# Run imapd script +nohup /bin/sh -c "($python_prefix/bin/python $here/imapd.py imaps://$imaphost:$imapport/ $imapuser $imappass $scheme://$host:$port/r/Editor/ $admin $apass $root 2>&1 | sh $root/imapd/logger)" 1>/dev/null 2>/dev/null & + diff --git a/sca-cpp/trunk/hosting/server/imapd-stop b/sca-cpp/trunk/hosting/server/imapd-stop new file mode 100755 index 0000000000..ef4b50f69e --- /dev/null +++ b/sca-cpp/trunk/hosting/server/imapd-stop @@ -0,0 +1,56 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Stop imapd +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` +root=`echo "import os; print os.path.realpath('$1')" | python` +imaphost=$2 +imapport=$3 + +python_prefix=`cat $here/../../modules/python/python.prefix` + +# Get HTTP server conf +conf=`cat $root/conf/httpd.conf | grep "# Generated by: httpd-conf"` +sslconf=`cat $root/conf/httpd.conf | grep "# Generated by: httpd-ssl-conf"` +if [ "$sslconf" = "" ]; then + scheme="http" + addr=`echo $conf | awk '{ print $7 }'` + host=`$here/../../modules/http/httpd-addr ip $addr` + if [ "$host" = "" ]; then + host="localhost" + fi + port=`$here/../../modules/http/httpd-addr port $addr` +else + scheme="https" + ssladdr=`echo $sslconf | awk '{ print $6 }'` + host=`$here/../../modules/http/httpd-addr ip $ssladdr` + if [ "$host" = "" ]; then + host="localhost" + fi + port=`$here/../../modules/http/httpd-addr port $ssladdr` +fi + +# Kill imapd processes +imapc="$python_prefix/bin/python $here/imapd.py imaps://$imaphost:$imapport/" +editc="$scheme://$host:$port/r/Editor/" +k=`ps -ef | grep -v grep | grep "${imapc} " | grep " $editc" | awk '{ print $2 }'` +if [ "$k" != "" ]; then + kill $k +fi + diff --git a/sca-cpp/trunk/hosting/server/imapd.py b/sca-cpp/trunk/hosting/server/imapd.py new file mode 100644 index 0000000000..a60e4e11c6 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/imapd.py @@ -0,0 +1,233 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Load media received from an IMAP inbox +from imaplib2 import IMAP4_SSL +from threading import Thread, Event +from email import message_from_string +import re +from StringIO import StringIO +from PIL import Image +from base64 import b64encode, b64decode +from httplib import HTTPConnection, HTTPSConnection +from urlparse import urlparse +from util import * +from sys import stderr, argv, exit +from traceback import print_exc + +# Debug print +def debug(*args): + if True: + print >> stderr, args + +# Fetch an email and return the image it contains +def fetchmail(id, imap): + + # Extract address from the To header + htyp, hdata = imap.fetch(id, '(BODY.PEEK[HEADER])') + header = message_from_string(hdata[0][1]) + if header['To'] is None: + return (None, 'Couldn\'t retrieve email address') + b64aparts = re.findall('[\w=-_]+@[\w.]+', header['To']) + if len(b64aparts) == 0: + return (None, 'Couldn\'t parse email address') + b64address = (b64aparts[len(b64aparts) - 1]).split('@')[0] + try: + address = b64decode((b64address.replace('-', '+').replace('_', '/') + '===')[0: len(b64address) + (len(b64address) % 4)]) + except: + return (None, 'Couldn\'t decode email address') + + # Check if the address targets a picture (p/) or an icon (i/) + if address[0:2] != 'p/' and address[0:2] != 'i/': + return (None, 'Email address must start with p/ or i/') + debug('putimages.py::readimage::address', address) + + # Extract image mime body part + btyp, bdata = imap.fetch(id, '(BODY.PEEK[TEXT])') + msg = message_from_string(hdata[0][1] + bdata[0][1]) + debug('putimages.py::readimage::msg', msg) + parts = map(lambda p: p.get_payload(decode=True), filter(lambda p: p.get_content_type().startswith('image/'), msg.walk())) + if len(parts) == 0: + return (None, 'Email doesn\'t contain an image') + + # Convert image to a 50x50 PNG image + img = Image.open(StringIO(parts[0])) + t = img.resize((50, 50)) + obuf = StringIO() + t.save(obuf, 'PNG') + dataurl = 'data:image/png;base64,' + b64encode(obuf.getvalue()).replace('\n', '') + + # Return address, image url pair + return (address, dataurl) + +def putimage(address, dataurl, httpurl, httpuser, httppass): + + # Put image into the image database + id = address.split('/')[1] + token = address.split('/')[2] + entry = '<?xml version="1.0" encoding="UTF-8"?>\n' + \ + '<entry xmlns="http://www.w3.org/2005/Atom">\n' + \ + '<title type="text">' + id + '</title>\n' + \ + '<id>' + id + '</id>\n' + \ + '<content type="application/xml">\n' + \ + ('<picture>\n' if address[0:2] == 'p/' else '<icon>\n') + \ + '<image>' + dataurl + '</image>\n' + \ + '<token>' + token + '</token>\n' + \ + ('</picture>\n' if address[0:2] == 'p/' else '</icon>\n') + \ + '</content>\n' + \ + '</entry>' + + url = urlparse(httpurl) + conn = HTTPSConnection(url.hostname, 443 if url.port is None else url.port) if url.scheme == 'https' else \ + HTTPConnection(url.hostname, 80 if url.port is None else url.port) + #conn.set_debuglevel(9) + path = url.path + ('pictures/' if address[0:2] == 'p/' else 'icons/') + id + puturl = url.scheme + '//' + url.netloc + path + debug('imapd.py::putimage::url', puturl) + auth = b64encode("%s:%s" % (httpuser, httppass)).replace('\n', '') + headers = { 'Authorization' : 'Basic ' + auth, 'X-Forwarded-Server' : url.hostname, 'Content-type': 'application/atom+xml', 'Accept': '*/*' } + conn.request('PUT', path, entry, headers) + response = conn.getresponse() + if response.status != 200: + debug('imapd.py::putimage::error', response.status, response.reason) + return (None, 'Put error: ' + repr(response.status) + ' : ' + response.reason) + conn.close() + + return (puturl, entry) + +# Read and process an email +def processmail(id, imap, httpurl, httpuser, httppass): + if id == '': + return None + + # Read email and any image in it + address, dataurl = fetchmail(id, imap) + if address is None: + # Mark email as seen if it doesn't contain an image + debug('imapd.py::processmail::seen', id) + imap.store(id, '+FLAGS', '\SEEN') + return None + + # Put image into the database + put = putimage(address, dataurl, httpurl, httpuser, httppass) + if put[0] is None: + return None + + # Mark email as seen if processed successfully + debug('imapd.py::processmail::seen', id) + imap.store(id, '+FLAGS', '\SEEN') + return put[0] + +# IMAP idle thread +def idle(imap, httpurl, httpuser, httppass, stop, stopped): + try: + sync = Event() + while True: + # Stop the thread + if stop.isSet(): + debug('imapd.py::idle::stopped') + stopped.set() + return + + # Wait for changes + def callback(args): + debug('imapd.py::idle::callback') + if not stop.isSet(): + sync.set() + stop.set() + + debug('imapd.py::idle::waiting') + imap.idle(callback = callback) + stop.wait() + + # Handle email change event + if sync.isSet(): + stop.clear() + sync.clear() + debug('imapd.py::idle::sync') + + # List unseen emails + typ, data = imap.search(None, 'UNSEEN') + debug('imapd.py::idle::search', typ, data) + + # Process unseen email + map(lambda id: processmail(id, imap, httpurl, httpuser, httppass), data[0].split(' ')) + + except Exception as e: + debug('imapd.py::idle::except', e) + print_exc() + stopped.set() + return + +# Main processing loop +def main(imapurl, imapuser, imappass, httpurl, httpuser, httppass): + try: + # Connect and login + url = urlparse(imapurl) + imap = IMAP4_SSL(url.hostname, 993 if url.port is None else url.port) + imap.login(imapuser, imappass) + imap.select('Inbox') + debug('imapd.py::main::connected') + + try: + # Start imap idle thread + stop = Event() + stopped = Event() + idling = Thread(target=idle, args=(imap, httpurl, httpuser, httppass, stop, stopped)) + idling.start() + + # List unseen emails + typ, data = imap.search(None, 'UNSEEN') + debug('imapd.py::main::search', typ, data) + + # Process unseen emails + map(lambda id: processmail(id, imap, httpurl, httpuser, httppass), data[0].split(' ')) + + # Wait 60 seconds + debug('imapd.py::main::waiting') + try: + stopped.wait() + except KeyboardInterrupt: + pass + + # Stop the thread + debug('imapd.py::main::stopping') + stop.set() + idling.join() + + # Close and logout + debug('imapd.py::main::disconnecting') + imap.close() + imap.logout() + return 0 + + except Exception as e: + debug('imapd.py::except', e) + print_exc() + # Close and logout + imap.close() + imap.logout() + return 1 + + except Exception as e: + debug('imapd.py::except', e) + print_exc() + return 1 + +if __name__ == '__main__': + exit(main(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6])) + diff --git a/sca-cpp/trunk/hosting/server/load-authn b/sca-cpp/trunk/hosting/server/load-authn new file mode 100755 index 0000000000..fab6dc18fe --- /dev/null +++ b/sca-cpp/trunk/hosting/server/load-authn @@ -0,0 +1,58 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` +cd $here +var=$HOME/var +httpd_prefix=`cat $here/../../modules/http/httpd.prefix` + +user=$1 +pass=$2 + +tmp=$3 +if [ "$tmp" = "" ]; then + tmp="$here/tmp" +fi +host=$4 +if [ "$host" = "" ]; then + host="localhost" +fi + +# Get password hash +rm -f $tmp/sqldb/load-authn.passwd +touch $tmp/sqldb/load-authn.passwd +$httpd_prefix/bin/htpasswd -b $tmp/sqldb/load-authn.passwd "$user" "$pass" 2>/dev/null +hash=`cat $tmp/sqldb/load-authn.passwd | awk -F ":" '{ print $2 }'` +rm -f $tmp/sqldb/load-authn.passwd + +# Compute user id +slash=`echo $user | grep "/"` +if [ "$slash" = "" ]; then + id="\"$user\"" +else + id=`echo $user | awk -F "/" '{ printf "\"%s\" \"%s\"", $2, $3 }'` +fi + +# Load into database +cat >$tmp/sqldb/load-authn.sql <<EOF +insert into data values('("authn" $id "user.authn")', '((entry (title "$user") (id "$user") (content (hash "$hash"))))'); +EOF + +$here/../../components/sqldb/pgsql <$tmp/sqldb/load-authn.sql + diff --git a/sca-cpp/trunk/hosting/server/load-tables b/sca-cpp/trunk/hosting/server/load-tables new file mode 100755 index 0000000000..a070ae1d7d --- /dev/null +++ b/sca-cpp/trunk/hosting/server/load-tables @@ -0,0 +1,73 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` + +tmp=$1 +if [ "$tmp" = "" ]; then + tmp="$here/tmp" +fi +host=$2 +if [ "$host" = "" ]; then + host="localhost" +fi + +# Populate tables +rm -f $tmp/sqldb/load-tables.sql +for key in `ls $here/data/accounts`; do + val=`cat $here/data/accounts/$key/user.account | sed "s/'/''/g"` + cat >>$tmp/sqldb/load-tables.sql <<EOF +insert into data values('("accounts" "$key" "user.account")', '$val'); +EOF +done + +for key in `ls $here/data/store`; do + val=`cat $here/data/store/$key/store.apps | sed "s/'/''/g"` + cat >>$tmp/sqldb/load-tables.sql <<EOF +insert into data values('("store" "$key" "store.apps")', '$val'); +EOF +done + +for key in `ls $here/data/dashboards`; do + val=`cat $here/data/dashboards/$key/user.apps | sed "s/'/''/g"` + cat >>$tmp/sqldb/load-tables.sql <<EOF +insert into data values('("dashboards" "$key" "user.apps")', '$val'); +EOF +done + +for key in `ls $here/data/apps`; do + sval=`cat $here/data/apps/$key/app.info | sed "s/'/''/g"` + cval=`cat $here/data/apps/$key/app.composite | $here/../../modules/scheme/xml-element | $here/../../modules/scheme/element-value | sed "s/'/''/g"` + hval=`cat $here/data/apps/$key/htdocs/app.html | $here/../../modules/scheme/xml-element | $here/../../modules/scheme/element-value | sed "s/'/''/g"` + cat >>$tmp/sqldb/load-tables.sql <<EOF +insert into data values('("apps" "$key" "app.info")', '$sval'); +insert into data values('("apps" "$key" "app.composite")', '$cval'); +insert into data values('("apps" "$key" "htdocs" "app.html")', '$hval'); +EOF +done + +for key in `ls $here/data/palettes`; do + val=`cat $here/data//palettes/$key/palette.composite | $here/../../modules/scheme/xml-element | $here/../../modules/scheme/element-value | sed "s/'/''/g"` + cat >>$tmp/sqldb/load-tables.sql <<EOF +insert into data values('("palettes" "$key" "palette.composite")', '$val'); +EOF +done + +$here/../../components/sqldb/pgsql $host 6432 <$tmp/sqldb/load-tables.sql + diff --git a/sca-cpp/trunk/hosting/server/pages.py b/sca-cpp/trunk/hosting/server/pages.py index a4f6d056f9..d8c774c14d 100644 --- a/sca-cpp/trunk/hosting/server/pages.py +++ b/sca-cpp/trunk/hosting/server/pages.py @@ -16,8 +16,8 @@ # under the License. # App pages collection implementation -from time import strftime from util import * +from atomutil import * from sys import debug # Convert an id to a page id @@ -30,19 +30,18 @@ def put(id, page, user, cache, apps): debug('pages.py::put::page', page) # Get the requested app - app = apps.get(id); - if isNil(app) or app is None: + app = apps.get(id) + if isNil(app): debug('pages.py::put', 'app not found', id) return False # Check app author - author = cadr(assoc("'author", car(app))) - if author != user.get(()): - debug('pages.py::put', 'different author', author) + if author(app) != user.get(()): + debug('pages.py::put', 'different author', author(app)) return False # Update the page in the page db - pageentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y')), assoc("'content", car(page))),) + pageentry = mkentry(title(app), car(id), user.get(()), now(), content(page)) debug('pages.py::put::pageentry', pageentry) return cache.put(pageid(id), pageentry) @@ -54,24 +53,24 @@ def get(id, user, cache, apps): # Get the requested app app = apps.get(id) - if isNil(app) or app is None: + if isNil(app): debug('pages.py::get', 'app not found', id) # Return a default new page - return (("'entry", ("'title", car(id)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y'))),) + return mkentry(car(id), car(id), user.get(()), now(), ()) # Get the requested page page = cache.get(pageid(id)) - if isNil(page) or page is None: + if isNil(page): debug('pages.py::get', 'page not found', id) # Return a default new page - return (("'entry", ("'title", car(id)), ("'id", car(id)), assoc("'author", car(app)), assoc("'updated", car(app))),) + return mkentry(title(app), car(id), author(app), now(), ()) # Return the page - def updated(u): - return assoc("'updated", car(app)) if isNil(u) or u is None else u - pageentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), assoc("'author", car(app)), updated(assoc("'updated", car(page))), assoc("'content", car(page))),) + debug('pages.py::get::page', page) + debug('pages.py::get::page::content', content(page)) + pageentry = mkentry(title(app), car(id), author(app), updated(page), content(page)) debug('pages.py::get::pageentry', pageentry) return pageentry @@ -80,15 +79,14 @@ def delete(id, user, cache, apps): debug('pages.py::delete::id', id) # Get the requested app - app = apps.get(id); - if isNil(app) or app is None: + app = apps.get(id) + if isNil(app): debug('pages.py::delete', 'app not found', id) return False # Check app author - author = cadr(assoc("'author", car(app))) - if author != user.get(()): - debug('pages.py::delete', 'different author', author) + if author(app) != user.get(()): + debug('pages.py::delete', 'different author', author(app)) return False # Delete the page diff --git a/sca-cpp/trunk/hosting/server/palettes.py b/sca-cpp/trunk/hosting/server/palettes.py index 321db3cf46..38e012835a 100644 --- a/sca-cpp/trunk/hosting/server/palettes.py +++ b/sca-cpp/trunk/hosting/server/palettes.py @@ -32,5 +32,5 @@ def put(id, palette, cache): def get(id, cache): if isNil(id): return (("'feed", ("'title", "Palettes"), ("'id", "palettes")),) - return (("'entry", ("'title", car(id)), ("'id", car(id)), ("'content", car(cache.get(paletteid(id))))),) + return mkentry(car(id), car(id), None, now(), car(cache.get(paletteid(id)))) diff --git a/sca-cpp/trunk/hosting/server/pgsql b/sca-cpp/trunk/hosting/server/pgsql new file mode 100755 index 0000000000..69ab04a55b --- /dev/null +++ b/sca-cpp/trunk/hosting/server/pgsql @@ -0,0 +1,32 @@ +#!/bin/sh + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` + +q="$2" +if [ "$q" = "" ]; then + host="localhost" + q="$1" +else + host="$1" +fi + +# Start pgsql command +$here/../../components/sqldb/pgsql $host 6432 "$q" + diff --git a/sca-cpp/trunk/hosting/server/pictures.py b/sca-cpp/trunk/hosting/server/pictures.py new file mode 100644 index 0000000000..fab7df47ce --- /dev/null +++ b/sca-cpp/trunk/hosting/server/pictures.py @@ -0,0 +1,141 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Pictures collection implementation +from StringIO import StringIO +try: + from PIL import Image +except: + Image = None +from base64 import b64encode, b64decode +from urllib import urlopen +from util import * +from atomutil import * +from sys import debug + +# Convert a particular user id to a picture id +def pictureid(id): + return ('accounts', id, 'user.picture') + +# Convert image to a 50x50 PNG image +def to50x50png(url): + debug('pictures.py::to50x50png::url', url) + if Image is None: + return url + img = Image.open(StringIO(b64decode(url.split(',')[1])) if url.startswith('data:') else StringIO(urlopen(url).read())) + t = img.resize((50, 50)) + obuf = StringIO() + t.save(obuf, 'PNG') + return 'data:image/png;base64,' + b64encode(obuf.getvalue()).replace('\n', '') + +# Update the user's picture +def put(id, picture, user, cache): + debug('pictures.py::put::id', id) + debug('pictures.py::put::picture', picture) + + picid = user.get(()) if isNil(id) else car(id) + + # Only the admin can update other user's pictures + if picid != user.get(()) and user.get(()) != 'admin': + debug('pictures.py::put', 'not owner or admin', user.get(())) + return False + + # Get image and token from input picture + def image(c): + img = assoc("'image", c) + return None if isNil(img) else to50x50png(cadr(img)) + def token(c): + tok = assoc("'token", c) + return None if isNil(tok) else cadr(tok) + img = image(content(picture)) + tok = token(content(picture)) + + # Update the picture + # Put with an upload token + if not isNil(tok): + debug('pictures.py::put::token', tok) + + # Token alone, store token with existing image, if any + if isNil(img): + epicture = cache.get(pictureid(picid)) + eimg = None if isNil(epicture) else image(content(epicture)) + if isNil(eimg): + picentry = mkentry(title(picture), picid, picid, now(), ("'picture", ("'token", tok))) + debug('pictures.py::put::picentry', picentry) + return cache.put(pictureid(picid), picentry) + + debug('pictures.py::put::eimg', eimg) + picentry = mkentry(title(picture), picid, picid, now(), ("'picture", ("'image", eimg), ("'token", tok))) + debug('pictures.py::put::picentry', picentry) + return cache.put(pictureid(picid), picentry) + + # Token plus image, put image if token is valid, removing the token + debug('pictures.py::put::img', img) + epicture = cache.get(pictureid(picid)) + etok = None if isNil(epicture) else token(content(epicture)) + debug('pictures.py::put::etok', etok) + if isNil(etok) or tok != etok: + debug('pictures.py::put', 'invalid token', tok) + return False + + picentry = mkentry(title(picture), picid, picid, now(), ("'picture", ("'image", img))) + debug('pictures.py::put::picentry', picentry) + return cache.put(pictureid(picid), picentry) + + # Update picture image + if not isNil(img): + debug('pictures.py::put::img', img) + picentry = mkentry(title(picture), picid, picid, now(), ("'picture", ("'image", img))) + debug('pictures.py::put::picentry', picentry) + return cache.put(pictureid(picid), picentry) + + # Put default empty picture + picentry = mkentry(title(picture), picid, picid, now(), ()) + debug('pictures.py::put::picentry', picentry) + return cache.put(pictureid(picid), picentry) + +# Get a user's picture +def get(id, user, cache): + debug('pictures.py::get::id', id) + + # Get the requested picture + picid = user.get(()) if isNil(id) else car(id) + picture = cache.get(pictureid(picid)) + if isNil(picture): + return mkentry(picid, picid, picid, now(), ()) + + # Get image and token from picture + def image(c): + img = assoc("'image", c) + return None if isNil(img) else cadr(img) + def token(c): + tok = assoc("'token", c) + return None if isNil(tok) else cadr(tok) + img = image(content(picture)) + tok = token(content(picture)) + + # Return the picture + picc = (() if isNil(img) else (("'image", img),)) + (() if isNil(tok) or (user.get(()) != author(picture) and user.get(()) != 'admin') else (("'token", tok),)) + if isNil(picc): + picentry = mkentry(title(picture), picid, author(picture), updated(picture), ()) + debug('pictures.py::get::picentry', picentry) + return picentry + + picentry = mkentry(title(picture), picid, author(picture), updated(picture), ("'picture",) + picc) + debug('pictures.py::get::picentry', picentry) + return picentry + diff --git a/sca-cpp/trunk/hosting/server/proxy-start b/sca-cpp/trunk/hosting/server/proxy-start index f06de9fc01..6ec55767c1 100755 --- a/sca-cpp/trunk/hosting/server/proxy-start +++ b/sca-cpp/trunk/hosting/server/proxy-start @@ -35,6 +35,17 @@ fi ../../components/cache/memcached-start tmp 11211 ../../components/cache/memcached-start tmp 11212 +# Configure database +../../components/sqldb/pgsql-conf tmp + +# Start database +../../components/sqldb/pgsql-start tmp + +# Load database tables +./drop-tables 1>/dev/null 2>/dev/null +./create-tables >/dev/null +./load-tables >/dev/null + # Configure server ../../modules/http/httpd-conf tmp www.example.com 9090 htdocs ../../modules/http/httpd-event-conf tmp diff --git a/sca-cpp/trunk/hosting/server/ratings.py b/sca-cpp/trunk/hosting/server/ratings.py new file mode 100644 index 0000000000..5638d8609f --- /dev/null +++ b/sca-cpp/trunk/hosting/server/ratings.py @@ -0,0 +1,158 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# App ratings collection implementation +from util import * +from atomutil import * +from sys import debug + +# Convert an app id to an app ratings id +def ratingsid(id): + return ("ratings", car(id), "app.ratings") + +# Put an app ratings into the ratings db +def put(id, ratings, user, cache, db, apps): + debug('ratings.py::put::id', id) + debug('ratings.py::put::ratings', ratings) + + # Get the requested app + app = apps.get(id) + if isNil(app): + debug('ratings.py::put', 'app not found', id) + return False + + # Check user + if user.get(()) != 'admin': + debug('ratings.py::put', 'not admin', user.get(())) + return False + + # Update app ratings in the ratings db + ratingsentry = mkentry(title(app), car(id), author(app), now(), content(ratings)) + debug('ratings.py::put::ratingsentry', ratingsentry) + return cache.put(ratingsid(id), ratingsentry) + +# Patch an app ratings in the ratings db +def patch(id, ratings, user, cache, db, apps): + debug('ratings.py::patch::id', id) + debug('ratings.py::patch::ratings', ratings) + + # Get the requested app + app = apps.get(id) + if isNil(app): + debug('ratings.py::patch', 'app not found', id) + return False + + # Get old and new ratings + orating = cadr(assoc("'old", content(ratings))) + nrating = cadr(assoc("'new", content(ratings))) + + # Configure patch script + script = """ + (define author "{0}") + (define updated "{1}") + (define orating {2}) + (define nrating {3}) + + (define (patch id e) + (define (rating x e) + (define a (tree-select-assoc (list x) e)) + (if (null? a) (list x "0") (car a)) + ) + + (define cratings (list (rating 'rating1 e) (rating 'rating2 e) (rating 'rating3 e) (rating 'rating4 e))) + + (define (calcrating v i) + (define nv (+ v (if (= i orating) (- 1) (if (= i nrating) 1 0)))) + (if (< nv 0) 0 nv) + ) + + (define (calcratings r i) + (if (null? r) + r + (cons (list (car (car r)) (calcrating (cadr (car r)) i)) (calcratings (cdr r) (+ 1 i))) + ) + ) + + (define nratings (calcratings cratings 1)) + + (define neg (+ 1 (+ (* (cadr (assoc 'rating1 nratings)) 2) (cadr (assoc 'rating2 nratings))))) + (define pos (+ 1 (+ (* (cadr (assoc 'rating4 nratings)) 2) (cadr (assoc 'rating3 nratings))))) + (define arating (* 4 (/ (- (/ (+ pos 1.9208) (+ pos neg)) (/ (* 1.96 (sqrt (+ (/ (* pos neg) (+ pos neg)) 0.9604))) (+ pos neg))) (+ 1 (/ 3.8416 (+ pos neg)))))) + + (list (list 'entry (list 'title (cadr id)) (list 'id (cadr id)) (list 'author author) (list 'updated updated) (list 'content (cons 'ratings (cons (list 'rating arating) nratings))))) + ) + """.format(author(app), now(), orating, nrating) + + # Update app ratings in the ratings db + ratingsentry = mkentry(title(app), car(id), author(app), now(), ("'patch", script)) + debug('ratings.py::put::ratingsentry', ratingsentry) + return cache.patch(ratingsid(id), ratingsentry) + +# Get app ratings from the ratings db +def get(id, user, cache, db, apps): + debug('ratings.py::get::id', id) + + # Return the top ratings + if isNil(id): + topentries = db.get((("'regex", '("ratings" .* "app.ratings")'), ("'rank", "(regexp_matches(value, '(.*\(rating )([^\)]+)(\).*)'))[2]::float"), ("'limit", 25))) + flatentries = tuple(map(lambda v: car(v), () if isNil(topentries) else topentries)) + def rating(e): + return cadr(assoc("'rating", assoc("'ratings", assoc("'content", e)))) + sortedentries = tuple(sorted(flatentries, key = rating, reverse = True)) + topratings = ((("'feed", ("'title", "Ratings"), ("'id", 'ratings')) + sortedentries),) + debug('ratings.py::get::topratings', topratings) + return topratings + + # Get the requested app + app = apps.get(id) + if isNil(app): + debug('ratings.py::get', 'app not found', id) + + # Return default ratings + return mkentry(car(id), car(id), user.get(()), now(), ()) + + # Get the requested ratings + ratings = cache.get(ratingsid(id)) + if isNil(ratings): + debug('ratings.py::get', 'ratings not found', id) + + # Return default ratings + return mkentry(title(app), car(id), author(app), now(), ()) + + # Return the ratings + ratingsentry = mkentry(title(app), car(id), author(app), updated(ratings), content(ratings)) + debug('ratings.py::get::ratings', ratingsentry) + return ratingsentry + +# Delete an app ratings from the ratings db +def delete(id, user, cache, db, apps): + debug('ratings.py::delete::id', id) + + # Get the requested app + app = apps.get(id) + if isNil(app): + debug('ratings.py::delete', 'app not found', id) + return False + + # Check user + if user.get(()) != 'admin': + debug('ratings.py::delete', 'not admin', user.get(())) + return False + + # Delete the composite + return cache.delete(ratingsid(id)) + diff --git a/sca-cpp/trunk/hosting/server/reviews.py b/sca-cpp/trunk/hosting/server/reviews.py new file mode 100644 index 0000000000..3c175d8a0f --- /dev/null +++ b/sca-cpp/trunk/hosting/server/reviews.py @@ -0,0 +1,156 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# App reviews collection implementation +from util import * +from atomutil import * +from sys import debug + +# Convert a particular user id to a reviews id +def reviewsid(user): + return ("reviews", user.get(()), "user.reviews") + +# Get user reviews from the cache +def getreviews(id, cache): + debug('reviews.py::getreviews::id', id) + val = cache.get(id) + if isNil(val): + return () + reviews = cdddr(car(val)) + if not isNil(reviews) and isList(car(cadr(car(reviews)))): + # Expand list of entries + ereviews = tuple(map(lambda e: cons("'entry", e), cadr(car(reviews)))) + debug('reviews.py::getreviews::ereviews', ereviews) + return ereviews + + debug('reviews.py::getreviews::reviews', reviews) + return reviews + +# Get a review from a user's reviews +def getreview(id, reviews): + if isNil(reviews): + return None + if car(id) == entryid(reviews): + return (car(reviews),) + return getreview(id, cdr(reviews)) + +# Get reviews from the user's reviews +def get(id, user, cache, apps, ratings): + debug('reviews.py::get::id', id) + if isNil(id): + reviews = ((("'feed", ("'title", "Your Reviews"), ("'id", user.get(()))) + getreviews(reviewsid(user), cache)),) + debug('reviews.py::get::reviews', reviews) + return reviews + + # Get the requested app + app = apps.get(id) + if isNil(app): + debug('reviews.py::get', 'app not found', id) + return False + + # Get the review + review = getreview(id, getreviews(reviewsid(user), cache)) + if isNil(review): + debug('reviews.py::get', 'review not found', id) + + # Return a default empty review + return mkentry(car(id), car(id), user.get(()), now(), ()) + + debug('reviews.py::get::review', review) + return review + +# Patch an app ratings +def patchratings(id, user, ratings, oreview, nreview): + patch = ("'patch", ("'old", "0" if isNil(oreview) else cadr(content(oreview))), ("'new", "0" if isNil(nreview) else cadr(content(nreview)))) + patchentry = mkentry(car(id), car(id), user.get(()), now(), patch); + debug('reviews.py::patchratings::patchentry', patchentry) + return ratings.patch(id, patchentry) + +# Put reviews into the cache +def putreviews(id, reviews, cache): + debug('reviews.py::putreviews::id', id) + debug('reviews.py::putreviews::reviews', reviews) + val = ((("'feed", ("'title", "Your Reviews"), ("'id", cadr(id))) + reviews),) + return cache.put(id, val) + +# Put a review into a user's reviews +def putreview(id, review, reviews): + if isNil(reviews): + return review + if car(id) == entryid(reviews): + return cons(car(review), cdr(reviews)) + return cons(car(reviews), putreview(id, review, cdr(reviews))) + +# Put a review into the user's reviews +def put(id, review, user, cache, apps, ratings): + debug('reviews.py::put::id', id) + debug('reviews.py::put::review', review) + + # Get the requested app + app = apps.get(id) + if isNil(app): + debug('reviews.py::put', 'app not found', id) + return False + + reviewentry = mkentry(title(review), car(id), user.get(()), now(), content(review)) + debug('reviews.py::put::reviewentry', reviewentry) + + # Get old review + reviews = getreviews(reviewsid(user), cache) + oreview = getreview(id, reviews) + + # Update the user's reviews record + nreviews = putreview(id, reviewentry, reviews) + putreviews(reviewsid(user), nreviews, cache) + + # Update the app's ratings + return patchratings(id, user, ratings, oreview, review) + +# Delete a review from a reviews record +def deletereview(id, reviews): + if isNil(reviews): + return () + if car(id) == entryid(reviews): + return cdr(reviews) + return cons(car(reviews), deletereview(id, cdr(reviews))) + +# Delete reviews from the user's reviews record +def delete(id, user, cache, apps, ratings): + debug('reviews.py::delete::id', id) + if isNil(id): + return cache.delete(reviewsid(user)) + + # Get the requested app + app = apps.get(id) + if isNil(app): + debug('reviews.py::delete', 'app not found', id) + return False + + # Get the review + reviews = getreviews(reviewsid(user), cache) + review = getreview(id, reviews) + if isNil(review): + debug('reviews.py::delete', 'review not found', id) + return False + + # Update the user's reviews record + nreviews = deletereview(id, reviews) + putreviews(reviewsid(user), nreviews, cache) + + # Update the app's ratings + return patchratings(id, user, ratings, review, None) + diff --git a/sca-cpp/trunk/hosting/server/search.py b/sca-cpp/trunk/hosting/server/search.py new file mode 100644 index 0000000000..b1a7d36733 --- /dev/null +++ b/sca-cpp/trunk/hosting/server/search.py @@ -0,0 +1,57 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Search implementation +from util import * +from atomutil import * +from sys import debug + +# Merge ratings into a list of apps +def mergeratings(entries, ratings): + debug('search.py::mergeratings::entries', entries) + + def mergerating(app): + debug('search.py::mergerating::app', app) + id = (entryid(app),) + info = content(app) + rating = ratings.get(id) + rates = content(rating) + mergedentry = mkentry(title(app), car(id), author(app), updated(app), ("'info",) + (() if isNil(info) else cdr(info)) + (() if isNil(rates) else cdr(rates))) + return mergedentry + + mergedentries = tuple(filter(lambda e: not isNil(e), map(lambda e: car(mergerating((e,))), entries))) + debug('search.py::mergeratings::mergedentries', mergedentries) + return mergedentries + +# Search apps +def get(id, user, cache, db, apps, ratings): + debug('search.py::get::id', id) + q = assoc("'q", id) + if isNil(q): + return None + + # Run the search + foundentries = db.get((("'regex", '("apps" .* "app.info")'), ("'textsearch", cadr(q)), ("'limit", 25))) + debug('search.py::get::foundentries', foundentries) + + # Merge app ratings + appentries = mergeratings(tuple(map(lambda v: car(v), () if isNil(foundentries) else foundentries)), ratings) + + results = ((("'feed", ("'title", "Search Results"), ("'id", 'search')) + appentries),) + debug('search.py::get::results', results) + return results + diff --git a/sca-cpp/trunk/hosting/server/selector.py b/sca-cpp/trunk/hosting/server/selector.py index 7fcdd65a0f..33cd9512cc 100644 --- a/sca-cpp/trunk/hosting/server/selector.py +++ b/sca-cpp/trunk/hosting/server/selector.py @@ -21,8 +21,10 @@ from util import * # Get the database to use for a particular key def get(id, db): if isNil(id): - return db[0] + return db + if not isNil(filter(lambda i: isList(i) and not isNil(i) and car(i) == "'limit", id)): + return db if cadr(id)[0:1].lower() < 'm': - return db[0] - return db[1] + return (db[0],) + return (db[1],) diff --git a/sca-cpp/trunk/hosting/server/server.composite b/sca-cpp/trunk/hosting/server/server.composite index 7e4d7196d0..b3b95fb6b2 100644 --- a/sca-cpp/trunk/hosting/server/server.composite +++ b/sca-cpp/trunk/hosting/server/server.composite @@ -37,12 +37,17 @@ <implementation.widget location="/index.html"/> <reference name="user" target="User"/> <reference name="accounts" target="Accounts"/> + <reference name="pictures" target="Pictures"/> <reference name="dashboards" target="Dashboards"/> <reference name="apps" target="Apps"/> <reference name="store" target="AppStore"/> + <reference name="search" target="Search"/> <reference name="palettes" target="Palettes"/> + <reference name="icons" target="Icons"/> <reference name="composites" target="Composites"/> <reference name="pages" target="Pages"/> + <reference name="reviews" target="Reviews"/> + <reference name="ratings" target="Ratings"/> <reference name="log" target="Log"/> </component> @@ -60,6 +65,12 @@ <reference name="cache" target="Cache"/> </component> + <component name="Pictures"> + <implementation.python script="pictures.py"/> + <reference name="user" target="User"/> + <reference name="cache" target="Cache"/> + </component> + <component name="Authenticator"> <implementation.python script="authn.py"/> <reference name="cache" target="Cache"/> @@ -70,6 +81,7 @@ <reference name="user" target="User"/> <reference name="cache" target="Cache"/> <reference name="apps" target="Apps"/> + <reference name="ratings" target="Ratings"/> </component> <component name="AppStore"> @@ -77,6 +89,16 @@ <reference name="user" target="User"/> <reference name="cache" target="Cache"/> <reference name="apps" target="Apps"/> + <reference name="ratings" target="Ratings"/> + </component> + + <component name="Search"> + <implementation.python script="search.py"/> + <reference name="user" target="User"/> + <reference name="cache" target="Cache"/> + <reference name="db" target="Database"/> + <reference name="apps" target="Apps"/> + <reference name="ratings" target="Ratings"/> </component> <component name="Apps"> @@ -87,25 +109,49 @@ <reference name="store" target="AppStore"/> <reference name="composites" target="Composites"/> <reference name="pages" target="Pages"/> + <reference name="icons" target="Icons"/> </component> <component name="Composites"> <implementation.python script="composites.py"/> <reference name="user" target="User"/> - <reference name="cache" target="Doccache"/> + <reference name="cache" target="Cache"/> <reference name="apps" target="Apps"/> </component> <component name="Pages"> <implementation.python script="pages.py"/> <reference name="user" target="User"/> - <reference name="cache" target="Doccache"/> + <reference name="cache" target="Cache"/> + <reference name="apps" target="Apps"/> + </component> + + <component name="Icons"> + <implementation.python script="icons.py"/> + <reference name="user" target="User"/> + <reference name="cache" target="Cache"/> + <reference name="apps" target="Apps"/> + </component> + + <component name="Reviews"> + <implementation.python script="reviews.py"/> + <reference name="user" target="User"/> + <reference name="cache" target="Cache"/> + <reference name="apps" target="Apps"/> + <reference name="ratings" target="Ratings"/> + </component> + + <component name="Ratings"> + <implementation.python script="ratings.py"/> + <reference name="user" target="User"/> + <reference name="cache" target="Cache"/> + <reference name="db" target="Database"/> <reference name="apps" target="Apps"/> </component> <component name="Palettes"> <implementation.python script="palettes.py"/> - <reference name="cache" target="Doccache"/> + <reference name="cache" target="Cache"/> </component> <component name="Cache"> @@ -116,14 +162,6 @@ <reference name="l2writer" target="Database"/> </component> - <component name="Doccache"> - <implementation.cpp path="../../components/cache" library="libdatacache"/> - <reference name="l1reader" target="Memcache"/> - <reference name="l1writer" target="Memcache"/> - <reference name="l2reader" target="Documents"/> - <reference name="l2writer" target="Documents"/> - </component> - <component name="Memcache"> <implementation.cpp path="../../components/cache" library="libmemcache"/> <property name="server">localhost:11211</property> @@ -131,16 +169,11 @@ </component> <component name="Database"> - <implementation.cpp path="../../components/filedb" library="libfiledb"/> - <property name="dbname">data</property> - <property name="format">scheme</property> + <implementation.cpp path="../../components/sqldb" library="libsqldb"/> + <property name="conninfo">host=localhost port=6432 dbname=db</property> + <property name="table">data</property> </component> - <component name="Documents"> - <implementation.cpp path="../../components/filedb" library="libfiledb"/> - <property name="dbname">data</property> - <property name="format">xml</property> - </component> <component name="Log"> <implementation.python script="log.py"/> diff --git a/sca-cpp/trunk/hosting/server/ssl-proxy-start b/sca-cpp/trunk/hosting/server/ssl-proxy-start index e38f54055b..9678ed9b87 100755 --- a/sca-cpp/trunk/hosting/server/ssl-proxy-start +++ b/sca-cpp/trunk/hosting/server/ssl-proxy-start @@ -35,6 +35,17 @@ fi ../../components/cache/memcached-start tmp 11211 ../../components/cache/memcached-start tmp 11212 +# Configure database +../../components/sqldb/pgsql-conf tmp + +# Start database +../../components/sqldb/pgsql-start tmp + +# Load database tables +./drop-tables 1>/dev/null 2>/dev/null +./create-tables >/dev/null +./load-tables >/dev/null + # Configure server ../../modules/http/httpd-conf tmp www.example.com 9090 htdocs ../../modules/http/httpd-event-conf tmp @@ -217,7 +228,7 @@ EOF # cat >tmp/proxy/conf/mod-security-audit-log.conf <<EOF ## Generated by: start $* -#SecAuditLog "|$here/../../components/log/scribe-cat $host secaudit secaudit" +#SecAuditLog "|$here/../../components/log/scribe-cat localhost secaudit secaudit" # #EOF diff --git a/sca-cpp/trunk/hosting/server/ssl-start b/sca-cpp/trunk/hosting/server/ssl-start index d699089847..a3bde7f28d 100755 --- a/sca-cpp/trunk/hosting/server/ssl-start +++ b/sca-cpp/trunk/hosting/server/ssl-start @@ -22,7 +22,6 @@ # 127.0.0.1 www.example.com here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` -host=`hostname` # Create SSL certificates ../../modules/http/ssl-ca-conf tmp www.example.com @@ -40,6 +39,17 @@ fi ../../components/cache/memcached-start tmp 11211 ../../components/cache/memcached-start tmp 11212 +# Configure database +../../components/sqldb/pgsql-conf tmp + +# Start database +../../components/sqldb/pgsql-start tmp + +# Load database tables +./drop-tables 1>/dev/null 2>/dev/null +./create-tables >/dev/null +./load-tables >/dev/null + # Clear document cache rm -rf tmp/cache @@ -79,20 +89,20 @@ rm -rf tmp/cache if [ -x ../../components/log/scribe-cat ]; then cat >tmp/conf/log.conf <<EOF # Generated by: ssl-start $* -ErrorLog "|$here/../../components/log/scribe-cat $host server" -CustomLog "|$here/../../components/log/scribe-cat $host server" combined +ErrorLog "|$here/../../components/log/scribe-cat localhost server" +CustomLog "|$here/../../components/log/scribe-cat localhost server" combined EOF cat >tmp/conf/log-ssl.conf <<EOF # Generated by: ssl-start $* -CustomLog "|$here/../../components/log/scribe-cat $host server" sslcombined +CustomLog "|$here/../../components/log/scribe-cat localhost server" sslcombined EOF cat >tmp/conf/mod-security-log.conf <<EOF # Generated by: ssl-start $* -SecAuditLog "|$here/../../components/log/scribe-cat $host secaudit" +SecAuditLog "|$here/../../components/log/scribe-cat localhost secaudit" EOF diff --git a/sca-cpp/trunk/hosting/server/start b/sca-cpp/trunk/hosting/server/start index d4443cb250..959863472b 100755 --- a/sca-cpp/trunk/hosting/server/start +++ b/sca-cpp/trunk/hosting/server/start @@ -22,7 +22,6 @@ # 127.0.0.1 www.example.com here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here` -host=`hostname` # Configure and start logging if [ -x ../../components/log/scribe-cat ]; then @@ -36,6 +35,17 @@ fi ../../components/cache/memcached-start tmp 11211 ../../components/cache/memcached-start tmp 11212 +# Configure database +../../components/sqldb/pgsql-conf tmp + +# Start database +../../components/sqldb/pgsql-start tmp + +# Load database tables +./drop-tables 1>/dev/null 2>/dev/null +./create-tables >/dev/null +./load-tables >/dev/null + # Clear document cache rm -rf tmp/cache @@ -55,8 +65,8 @@ if [ -x ../../components/log/scribe-cat ]; then cat >tmp/conf/log.conf <<EOF # Generated by: start $* LogLevel notice -ErrorLog "|$here/../../components/log/scribe-cat $host server" -CustomLog "|$here/../../components/log/scribe-cat $host server" combined +ErrorLog "|$here/../../components/log/scribe-cat localhost server" +CustomLog "|$here/../../components/log/scribe-cat localhost server" combined EOF diff --git a/sca-cpp/trunk/hosting/server/stop b/sca-cpp/trunk/hosting/server/stop index 82ecd101ef..eca57cd2f6 100755 --- a/sca-cpp/trunk/hosting/server/stop +++ b/sca-cpp/trunk/hosting/server/stop @@ -23,6 +23,8 @@ ../../components/cache/memcached-stop tmp 11211 ../../components/cache/memcached-stop tmp 11212 +../../components/sqldb/pgsql-stop tmp + if [ -x ../../components/log/scribe-cat ]; then ../../components/log/scribed-client-stop tmp ../../components/log/scribed-central-stop tmp diff --git a/sca-cpp/trunk/hosting/server/store.py b/sca-cpp/trunk/hosting/server/store.py index 054f546c2d..058505b4ee 100644 --- a/sca-cpp/trunk/hosting/server/store.py +++ b/sca-cpp/trunk/hosting/server/store.py @@ -17,6 +17,7 @@ # Stores collection implementation from util import * +from atomutil import * from sys import debug # Convert a particular store tag to a store id @@ -26,8 +27,10 @@ def storeid(tag): # Get a store from the cache def getstore(id, cache): debug('store.py::getstore::id', id) + + # Lookup the requested store val = cache.get(id) - if isNil(val) or val is None: + if isNil(val): return () store = cdddr(car(val)) if not isNil(store) and isList(car(cadr(car(store)))): @@ -47,7 +50,7 @@ def putstore(id, store, cache): return cache.put(id, val) # Put an app into a store -def put(id, app, user, cache, apps): +def put(id, app, user, cache, apps, ratings): debug('store.py::put::id', id) debug('store.py::put::app', app) tag = car(id) @@ -56,31 +59,61 @@ def put(id, app, user, cache, apps): def putapp(appid, app, store): if isNil(store): return app - if car(appid) == cadr(assoc("'id", car(store))): + if car(appid) == entryid(store): return cons(car(app), cdr(store)) return cons(car(store), putapp(appid, app, cdr(store))) - appentry = (("'entry", assoc("'title", car(app)), ("'id", car(appid)), ("'author", user.get(())), assoc("'updated", car(app)), assoc("'content", car(app))),) + appentry = mkentry(title(app), car(appid), author(app), updated(app), content(app)) debug('store.py::put::appentry', appentry) store = putapp(appid, appentry, getstore(storeid(tag), cache)) return putstore(storeid(tag), store, cache) +# Merge app info and ratings into a list of apps +def mergeapps(entries, apps, ratings): + debug('store.py::mergeapps::entries', entries) + + def mergeapp(entry): + debug('store.py::mergeapp::entry', entry) + id = (entryid(entry),) + app = apps.get(id) + if isNil(app): + return ((),) + info = content(app) + rating = ratings.get(id) + rates = content(rating) + mergedentry = mkentry(title(app), car(id), author(app), updated(app), ("'info",) + (() if isNil(info) else cdr(info)) + (() if isNil(rates) else cdr(rates))) + return mergedentry + + mergedentries = tuple(filter(lambda e: not isNil(e), map(lambda e: car(mergeapp((e,))), entries))) + debug('store.py::mergeapps::mergedentries', mergedentries) + return mergedentries + # Get apps from a store -def get(id, user, cache, apps): +def get(id, user, cache, apps, ratings): debug('store.py::get::id', id) tag = car(id) - appid = cdr(id) + # Collect the top rated apps + if tag == 'top': + topratings = ratings.get(()) + topapps = mergeapps(cdddr(car(topratings)), apps, ratings) + topstore = ((("'feed", ("'title", 'App Store'), ("'id", tag)) + topapps),) + debug('store.py::get::store', topstore) + return topstore + + # Collect the featured apps + appid = cdr(id) def findapp(appid, store): if isNil(store): return None - if car(appid) == cadr(assoc("'id", car(store))): + if car(appid) == entryid(store): return (car(store),) return findapp(appid, cdr(store)) if isNil(appid): - store = ((("'feed", ("'title", "App Store"), ("'id", tag)) + getstore(storeid(tag), cache)),) + storeapps = mergeapps(getstore(storeid(tag), cache), apps, ratings) + store = ((("'feed", ("'title", "App Store"), ("'id", tag)) + storeapps),) debug('store.py::get::store', store) return store @@ -89,7 +122,7 @@ def get(id, user, cache, apps): return app # Delete apps from a store -def delete(id, user, cache, apps): +def delete(id, user, cache, apps, ratings): debug('store.py::delete::id', id) tag = car(id) appid = cdr(id) @@ -100,7 +133,7 @@ def delete(id, user, cache, apps): def deleteapp(appid, store): if isNil(store): return () - if car(appid) == cadr(assoc("'id", car(store))): + if car(appid) == entryid(store): return cdr(store) return cons(car(store), deleteapp(appid, cdr(store))) diff --git a/sca-cpp/trunk/hosting/server/test.py b/sca-cpp/trunk/hosting/server/test.py index 2575fb7b92..5670ec2ea9 100755 --- a/sca-cpp/trunk/hosting/server/test.py +++ b/sca-cpp/trunk/hosting/server/test.py @@ -20,21 +20,34 @@ import sys sys.debug = lambda *l: sys.stderr.write('python::debug ' + repr(l) + '\n') +from sys import debug import time -time.strftime = lambda f: 'Jan 01, 2012' +time.strftime = lambda f, t: '2012-01-01T00:00:00+00:00' +try: + import PIL +except: + PIL = None import unittest from test.property import * from test.reference import * from test.cache import * +from util import * +from atomutil import * import user import accounts +import pictures import pages +import icons import composites import apps import store import dashboards +import reviews +import ratings +import selector +import search def testUser(): # Return current user @@ -43,11 +56,11 @@ def testUser(): def testAccounts(): # Get default account - defaccount = (("'entry", ("'title", 'jdoe@example.com'), ("'id", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012')),) + defaccount = (("'entry", ("'title", 'jdoe@example.com'), ("'id", 'jdoe@example.com'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) assert accounts.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {})) == defaccount # Get user's account - jdoe = (("'entry", ("'title", 'John Doe'), ("'id", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'key", 'value'))),) + jdoe = (("'entry", ("'title", 'John Doe'), ("'id", 'jdoe@example.com'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'key", 'value'))),) assert accounts.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('accounts', 'jdoe@example.com', 'user.account') : jdoe})) == jdoe # Put and get account @@ -56,27 +69,41 @@ def testAccounts(): assert accounts.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1) == jdoe return True +def testPictures(): + if PIL is None: + return True + img16 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAOUlEQVQ4y2N0b3/7nwEH2FkpzGg7hQGn/OEcBkYmBgrBqAFUMIBiQDCeL3qY4ZTX33FqNB0Mj3QAAFC7Dhs7i7zzAAAAAElFTkSuQmCC' + img50 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAg0lEQVR4nO3YsRVFUBAG4Xu1oBCZTEwxGlDBa0AxxDKZQtRARAfrzWG+aLP/TLq5/e1HCjAPZb7uZkwhG0uf7o0iYuAfDKExhMYQGkNoDKExhMYQGkNoDKExRPqI/MTzbOvqkI1qWn3QYRlCYwiNITSG0BhCYwiNITSG0BhC85oQKcgJzukOV+8REuQAAAAASUVORK5CYII=' + + # Put and get picture + pic16 = (("'entry", ("'title", 'jdoe@example.com'), ("'id", 'jdoe@example.com'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'picture", ("'image", img16)))),) + pic50 = (("'entry", ("'title", 'jdoe@example.com'), ("'id", 'jdoe@example.com'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'picture", ("'image", img50)))),) + cache1 = mkcache('cache', {}) + assert pictures.put((), pic16, mkref('user', lambda id: 'jdoe@example.com'), cache1) == True + assert pictures.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1) == pic50 + return True + def testPages(): # Get default page - defpage = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012')),) - app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 03, 2012'), ("'content", ())),) + defpage = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) + app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-03T00:00:00+00:00'), ("'content",)),) assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: None)) == defpage - defpagefromapp = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 03, 2012')),) + defpagefromapp = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: app1)) == defpagefromapp # Get a page - page1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ())),) + page1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content",)),) assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('apps', 'app1', 'htdocs', 'app.html') : page1}), mkref('apps', lambda id: app1)) == page1 # Put and get a page cache1 = mkcache('cache', {}) - page1updated = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012'), ("'content", ())),) + page1updated = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) assert pages.put(('app1',), page1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == True assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == page1updated # Reject put from user other than the author - app1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", 'Jan 03, 2012'), ("'content", ())),) - page1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", 'Jan 02, 2012')),) + app1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", '2012-01-03T00:00:00+00:00'), ("'content",)),) + page1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", '2012-01-02T00:00:00+00:00')),) assert pages.put(('app1',), page1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False assert pages.put(('app1',), page1otherauthor, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == page1updated @@ -90,27 +117,82 @@ def testPages(): assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == defpagefromapp return True +def testIcons(): + if PIL is None: + return True + img16 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAOUlEQVQ4y2N0b3/7nwEH2FkpzGg7hQGn/OEcBkYmBgrBqAFUMIBiQDCeL3qY4ZTX33FqNB0Mj3QAAFC7Dhs7i7zzAAAAAElFTkSuQmCC' + img50 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAg0lEQVR4nO3YsRVFUBAG4Xu1oBCZTEwxGlDBa0AxxDKZQtRARAfrzWG+aLP/TLq5/e1HCjAPZb7uZkwhG0uf7o0iYuAfDKExhMYQGkNoDKExhMYQGkNoDKExRPqI/MTzbOvqkI1qWn3QYRlCYwiNITSG0BhCYwiNITSG0BhC85oQKcgJzukOV+8REuQAAAAASUVORK5CYII=' + + # Get default icon + deficon = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) + app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-03T00:00:00+00:00'), ()),) + assert icons.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: None)) == deficon + deficonfromapp = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) + assert icons.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: app1)) == deficonfromapp + + # Get a icon + icon1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'icon", ("'image", img16)))),) + assert icons.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('apps', 'app1', 'app.icon') : icon1}), mkref('apps', lambda id: app1)) == icon1 + + # Put and get a icon + cache1 = mkcache('cache', {}) + icon1updated = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'icon", ("'image", img50)))),) + assert icons.put(('app1',), icon1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == True + assert icons.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == icon1updated + + # Reject put from user other than the author + app1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", '2012-01-03T00:00:00+00:00'), ("'content",)),) + icon1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", '2012-01-02T00:00:00+00:00')),) + assert icons.put(('app1',), icon1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False + assert icons.put(('app1',), icon1otherauthor, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False + assert icons.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == icon1updated + + # Reject delete from user other than the author + assert icons.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False + assert icons.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == icon1updated + + # Put an upload token in an icon + icon1token = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'icon", ("'token", '1234')))),) + assert icons.put(('app1',), icon1token, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == True + assert icons.get(('app1',), mkref('user', lambda id: 'another@example.com'), cache1, mkref('apps', lambda id: app1)) == icon1updated + icon1updatedwithtoken = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'icon", ("'image", img50), ("'token", '1234')))),) + assert icons.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == icon1updatedwithtoken + + # Reject upload with invalid token + icon1badtoken = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'icon", ("'image", img50), ("'token", '4567')))),) + assert icons.put(('app1',), icon1badtoken, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == False + + # Upload with valid token + icon1oktoken = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'icon", ("'image", img50), ("'token", '1234')))),) + assert icons.put(('app1',), icon1oktoken, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == True + assert icons.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == icon1updated + + # Delete a icon + assert icons.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == True + assert icons.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == deficonfromapp + return True + def testComposites(): # Get default composite - defcomposite = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012')),) - app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 03, 2012'), ("'content", ())),) + defcomposite = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) + app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-03T00:00:00+00:00'), ("'content",)),) assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: None)) == defcomposite - defcompositefromapp = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 03, 2012')),) + defcompositefromapp = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: app1)) == defcompositefromapp # Get a composite - composite1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ())),) + composite1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content",)),) assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('apps', 'app1', 'app.composite') : composite1}), mkref('apps', lambda id: app1)) == composite1 # Put and get a composite cache1 = mkcache('cache', {}) - composite1updated = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012'), ("'content", ())),) + composite1updated = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) assert composites.put(('app1',), composite1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == True assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == composite1updated # Reject put from user other than the author - app1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", 'Jan 03, 2012'), ("'content", ())),) - composite1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", 'Jan 02, 2012')),) + app1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", '2012-01-03T00:00:00+00:00'), ("'content",)),) + composite1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", '2012-01-02T00:00:00+00:00')),) assert composites.put(('app1',), composite1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False assert composites.put(('app1',), composite1otherauthor, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == composite1updated @@ -126,171 +208,298 @@ def testComposites(): def testApps(): # Get default app - defapp = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012'), ("'content", ("'stats", ("'description", '')))),) - assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == defapp + assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None), mkref('icons', lambda id: None)) is None # Get an app - app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012'), ("'content", ("'stats", ("'description", '')))),) - assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('apps', 'app1', 'app.stats') : app1}), mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == app1 + app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", '')))),) + assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('apps', 'app1', 'app.info') : app1}), mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None), mkref('icons', lambda id: None)) == app1 # Put and get an app cache1 = mkcache('cache', {}) - assert apps.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('dashboard', lambda id, app: True), mkref('store', lambda id, app: True), mkref('composites', lambda id, app: True), mkref('pages', lambda id, app: True)) == True - assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == app1 + assert apps.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('dashboard', lambda id, app: True), mkref('store', lambda id, app: True), mkref('composites', lambda id, app: True), mkref('pages', lambda id, app: True), mkref('icons', lambda id, app: True)) == True + assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None), mkref('icons', lambda id: None)) == app1 return True # Reject put from user other than the author - app1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", 'Jan 03, 2012'), ("'content", ())),) - assert apps.put(('app1',), app1, mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id, app: True), mkref('store', lambda id, app: True), mkref('composites', lambda id, app: True), mkref('pages', lambda id, app: True)) == false - assert apps.get(('app1',), mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == app1 + app1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", '2012-01-03T00:00:00+00:00'), ("'content",)),) + assert apps.put(('app1',), app1, mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id, app: True), mkref('store', lambda id, app: True), mkref('composites', lambda id, app: True), mkref('pages', lambda id, app: True), mkref('icons', lambda id, app: True)) == false + assert apps.get(('app1',), mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None), mkref('icons', lambda id: None)) == app1 # Reject delete from user other than the author - assert apps.delete(('app1',), mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == False - assert apps.get(('app1',), mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == app1 + assert apps.delete(('app1',), mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None), mkref('icons', lambda id: None)) == False + assert apps.get(('app1',), mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None), mkref('icons', lambda id: None)) == app1 # Delete an app - assert apps.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == True - assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == defapp + assert apps.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None), mkref('icons', lambda id: None)) == True + assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None), mkref('icons', lambda id: None)) is None return True def testStore(): + getapp = lambda id: (("'entry", ("'title", car(id)), ("'id", car(id)), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", car(id))))),) + # Get default store - defstore = (("'feed", ("'title", 'App Store'), ("'id", 'top')),) - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: None)) == defstore + defstore = (("'feed", ("'title", 'App Store'), ("'id", 'featured')),) + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', getapp), mkref('ratings', lambda id: None)) == defstore # Get a store - store1= (("'feed", ("'title", 'App Store'), ("'id", 'top'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))),) - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('store', 'top', 'store.apps') : store1}), mkref('apps', lambda id: None)) == store1 + store1= (("'feed", ("'title", 'App Store'), ("'id", 'featured'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2'))))),) + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('store', 'featured', 'store.apps') : store1}), mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1 - store1compact = (("'feed", ("'title", 'App Store'), ("'id", 'top'), ("'entry", ((("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), (("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))))),) - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('store', 'top', 'store.apps') : store1compact}), mkref('apps', lambda id: None)) == store1 + store1compact = (("'feed", ("'title", 'App Store'), ("'id", 'featured'), ("'entry", ((("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))), (("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2'))))))),) + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('store', 'featured', 'store.apps') : store1compact}), mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1 # Put an app in an empty store cache1 = mkcache('cache', {}) - app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))),) - store1withapp1 = (("'feed", ("'title", 'App Store'), ("'id", 'top'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1'))))),) - assert store.put(('top', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp1 - assert store.put(('top', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp1 + app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))),) + store1withapp1 = (("'feed", ("'title", 'App Store'), ("'id", 'featured'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1'))))),) + assert store.put(('featured', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1withapp1 + assert store.put(('featured', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1withapp1 # Put a second app in the store - app2 = (("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2')))),) - store1withapp2 = (("'feed", ("'title", 'App Store'), ("'id", 'top'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))),) - assert store.put(('top', 'app2'), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp2 - assert store.put(('top', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp2 - assert store.put(('top', 'app2'), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp2 + app2 = (("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2')))),) + store1withapp2 = (("'feed", ("'title", 'App Store'), ("'id", 'featured'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2'))))),) + assert store.put(('featured', 'app2'), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1withapp2 + assert store.put(('featured', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1withapp2 + assert store.put(('featured', 'app2'), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1withapp2 # Put a third app in the store - app3 = (("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app3')))),) - store1withapp3 = (("'feed", ("'title", 'App Store'), ("'id", 'top'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2')))), ("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app3'))))),) - assert store.put(('top', 'app3'), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp3 - assert store.put(('top', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp3 - assert store.put(('top', 'app2'), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp3 - assert store.put(('top', 'app3'), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp3 + app3 = (("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app3')))),) + store1withapp3 = (("'feed", ("'title", 'App Store'), ("'id", 'featured'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2')))), ("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app3'))))),) + assert store.put(('featured', 'app3'), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1withapp3 + assert store.put(('featured', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1withapp3 + assert store.put(('featured', 'app2'), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1withapp3 + assert store.put(('featured', 'app3'), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1withapp3 # Get an app from the store - assert store.get(('top','app1'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app1 - assert store.get(('top','app2'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app2 - assert store.get(('top','app3'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app3 + assert store.get(('featured','app1'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == app1 + assert store.get(('featured','app2'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == app2 + assert store.get(('featured','app3'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == app3 # Put a third app in the store, starting from a compacted list - cache2 = mkcache('cache', {('store', 'top', 'store.apps') : store1compact}) - assert store.put(('top', 'app3'), app3, mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == store1withapp3 + cache2 = mkcache('cache', {('store', 'featured', 'store.apps') : store1compact}) + assert store.put(('featured', 'app3'), app3, mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', getapp), mkref('ratings', lambda id: None)) == store1withapp3 # Delete the apps - assert store.delete(('top', 'app2'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.delete(('top', 'app4'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == False - assert store.delete(('top', 'app1'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.delete(('top', 'app3'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == defstore + assert store.delete(('featured', 'app2'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.delete(('featured', 'app4'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == False + assert store.delete(('featured', 'app1'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.delete(('featured', 'app3'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == defstore # Delete a store - assert store.delete(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == True - assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == defstore + assert store.delete(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert store.get(('featured',), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', getapp), mkref('ratings', lambda id: None)) == defstore return True def testDashboards(): + getapp = lambda id: (("'entry", ("'title", car(id)), ("'id", car(id)), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", car(id))))),) + # Get default dashboard defdashboard = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com')),) - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: None)) == defdashboard + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', getapp), mkref('ratings', lambda id: None)) == defdashboard # Get the user's dashboard - dash1= (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))),) - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('dashboards', 'jdoe@example.com', 'user.apps') : dash1}), mkref('apps', lambda id: None)) == dash1 + dash1= (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2'))))),) + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('dashboards', 'jdoe@example.com', 'user.apps') : dash1}), mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1 - dash1compact = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ((("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), (("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))))),) - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('dashboards', 'jdoe@example.com', 'user.apps') : dash1compact}), mkref('apps', lambda id: None)) == dash1 + dash1compact = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ((("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))), (("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2'))))))),) + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('dashboards', 'jdoe@example.com', 'user.apps') : dash1compact}), mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1 # Put an app in an empty dashboard cache1 = mkcache('cache', {}) - app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))),) - dash1withapp1 = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1'))))),) - assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp1 - assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp1 + app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))),) + dash1withapp1 = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1'))))),) + assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1withapp1 + assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1withapp1 # Put a second app in the dashboard - app2 = (("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2')))),) - dash1withapp2 = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))),) - assert dashboards.put(('app2',), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp2 - assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp2 - assert dashboards.put(('app2',), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp2 + app2 = (("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2')))),) + dash1withapp2 = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2'))))),) + assert dashboards.put(('app2',), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1withapp2 + assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1withapp2 + assert dashboards.put(('app2',), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1withapp2 # Put a third app in the dashboard - app3 = (("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app3')))),) - dash1withapp3 = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2')))), ("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app3'))))),) - assert dashboards.put(('app3',), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp3 - assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp3 - assert dashboards.put(('app2',), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp3 - assert dashboards.put(('app3',), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp3 + app3 = (("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app3')))),) + dash1withapp3 = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2')))), ("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app3'))))),) + assert dashboards.put(('app3',), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1withapp3 + assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1withapp3 + assert dashboards.put(('app2',), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1withapp3 + assert dashboards.put(('app3',), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1withapp3 # Get an app from the user's dashboard - assert dashboards.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app1 - assert dashboards.get(('app2',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app2 - assert dashboards.get(('app3',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app3 + assert dashboards.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == app1 + assert dashboards.get(('app2',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == app2 + assert dashboards.get(('app3',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == app3 # Put a third app in the dashboard, starting from a compacted list cache2 = mkcache('cache', {('dashboards', 'jdoe@example.com', 'user.apps') : dash1compact}) - assert dashboards.put(('app3',), app3, mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == dash1withapp3 + assert dashboards.put(('app3',), app3, mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', getapp), mkref('ratings', lambda id: None)) == dash1withapp3 # Delete the apps - assert dashboards.delete(('app2',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.delete(('app4',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == False - assert dashboards.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.delete(('app3',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == defdashboard + assert dashboards.delete(('app2',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.delete(('app4',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == False + assert dashboards.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.delete(('app3',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', getapp), mkref('ratings', lambda id: None)) == defdashboard # Delete the dashboard - assert dashboards.delete((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == True - assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == defdashboard + assert dashboards.delete((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', getapp), mkref('ratings', lambda id: None)) == True + assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', getapp), mkref('ratings', lambda id: None)) == defdashboard + return True + +def testReviews(): + app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'info", ("'description", '')))),) + + # Get default reviews + defreviews = (("'feed", ("'title", 'Your Reviews'), ("'id", 'jdoe@example.com')),) + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == defreviews + + # Get the user's reviews + reviews1= (("'feed", ("'title", 'Your Reviews'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '1'))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '2')))),) + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('reviews', 'jdoe@example.com', 'user.reviews') : reviews1}), mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1 + + reviews1compact = (("'feed", ("'title", 'Your Reviews'), ("'id", 'jdoe@example.com'), ("'entry", ((("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '1'))), (("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '2')))))),) + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('reviews', 'jdoe@example.com', 'user.reviews') : reviews1compact}), mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1 + + # Put a review in an empty reviews record + cache1 = mkcache('cache', {}) + app1review = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '1'))),) + reviews1withapp1 = (("'feed", ("'title", 'Your Reviews'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '1')))),) + assert reviews.put(('app1',), app1review, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1withapp1 + assert reviews.put(('app1',), app1review, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1withapp1 + + # Put a second review in the reviews record + app2review = (("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '2'))),) + reviews1withapp2 = (("'feed", ("'title", 'Your Reviews'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '1'))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '2')))),) + assert reviews.put(('app2',), app2review, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1withapp2 + assert reviews.put(('app1',), app1review, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1withapp2 + assert reviews.put(('app2',), app2review, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1withapp2 + + # Put a third review in the reviews record + app3review = (("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '3'))),) + reviews1withapp3 = (("'feed", ("'title", 'Your Reviews'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '1'))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '2'))), ("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'rating", '3')))),) + assert reviews.put(('app3',), app3review, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1withapp3 + assert reviews.put(('app1',), app1review, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1withapp3 + assert reviews.put(('app2',), app2review, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1withapp3 + assert reviews.put(('app3',), app3review, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1withapp3 + + # Get a review from the user's reviews + assert reviews.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == app1review + assert reviews.get(('app2',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == app2review + assert reviews.get(('app3',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == app3review + + # Put a third review in the user's reviews record, starting from a compacted list + cache2 = mkcache('cache', {('reviews', 'jdoe@example.com', 'user.reviews') : reviews1compact}) + assert reviews.put(('app3',), app3review, mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == reviews1withapp3 + + # Delete the reviews + assert reviews.delete(('app2',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.delete(('app4',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == False + assert reviews.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.delete(('app3',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == defreviews + + # Delete the reviews record + assert reviews.delete((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == True + assert reviews.get((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: app1), mkref('ratings', lambda id, patch: True)) == defreviews + return True + +def testRatings(): + # Get default ratings + defratings = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) + app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-03T00:00:00+00:00'), ("'content",)),) + assert ratings.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('db', lambda id: None), mkref('apps', lambda id: None)) == defratings + defratingsfromapp = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) + assert ratings.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('db', lambda id: None), mkref('apps', lambda id: app1)) == defratingsfromapp + + # Get an app ratings + ratings1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content",)),) + assert ratings.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('ratings', 'app1', 'app.ratings') : ratings1}), mkref('db', lambda id: None), mkref('apps', lambda id: app1)) == ratings1 + + # Put and get an app ratings + cache1 = mkcache('cache', {}) + ratings1updated = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content",)),) + assert ratings.put(('app1',), ratings1, mkref('user', lambda id: 'admin'), cache1, mkref('db', lambda id: None), mkref('apps', lambda id: app1)) == True + assert ratings.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('db', lambda id: None), mkref('apps', lambda id: app1)) == ratings1updated + + # Reject put from user other than admin + ratings1otheruser = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-04T00:00:00+00:00'), ("'content",)),) + assert ratings.put(('app1',), ratings1otheruser, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('db', lambda id: None), mkref('apps', lambda id: app1)) == False + assert ratings.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('db', lambda id: None), mkref('apps', lambda id: app1)) == ratings1updated + + # Patch an app ratings + app2 = (("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-03T00:00:00+00:00'), ("'content",)),) + ratings2patch = (("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-01T00:00:00+00:00'), ("'content", ("'patch", ("'old", '3'), ("'new", '4')))),) + assert ratings.patch(('app2',), ratings2patch, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('db', lambda id: None), mkref('apps', lambda id: app2)) == True + ratings2patched = cache1.get(("ratings", 'app2', "app.ratings")) + assert(cadr(content(ratings2patched)).find('(define orating 3)') != -1) + assert(cadr(content(ratings2patched)).find('(define nrating 4)') != -1) + + # Reject delete from user other than admin + assert ratings.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('db', lambda id: None), mkref('apps', lambda id: app1)) == False + assert ratings.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('db', lambda id: None), mkref('apps', lambda id: app1)) == ratings1updated + + # Delete an app ratings + assert ratings.delete(('app1',), mkref('user', lambda id: 'admin'), cache1, mkref('db', lambda id: None), mkref('apps', lambda id: app1)) == True + assert ratings.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('db', lambda id: None), mkref('apps', lambda id: app1)) == defratingsfromapp + return True + +def testSelector(): + assert selector.get(('apps', 'abc', 'app.info'), (1, 2)) == (1,) + assert selector.get(('apps', 'nbc', 'app.info'), (1, 2)) == (2,) + assert selector.get(('apps', 'nbc', 'app.info', ("'xyz", 'utv')), (1, 2)) == (2,) + assert selector.get(('apps', 'nbc', 'app.info', ("'limit", '10')), (1, 2)) == (1, 2) + return True + +def testSearch(): + assert search.get((("'q", 'abc def'),), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('db', lambda id: ((("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))),), (("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2')))),))), mkref('apps', lambda id: None), mkref('ratings', lambda id: None)) == (("'feed", ("'title", 'Search Results'), ("'id", 'search'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", '2012-01-02T00:00:00+00:00'), ("'content", ("'info", ("'description", 'app2'))))),) return True if __name__ == '__main__': print 'Testing...' testUser() testAccounts() + testPictures() testPages() testComposites() + testIcons() testApps() testStore() testDashboards() + testReviews() + testRatings() + testSelector() + testSearch() print 'OK' diff --git a/sca-cpp/trunk/hosting/server/test/cache.py b/sca-cpp/trunk/hosting/server/test/cache.py index 98fa174c00..54462ab6ff 100644 --- a/sca-cpp/trunk/hosting/server/test/cache.py +++ b/sca-cpp/trunk/hosting/server/test/cache.py @@ -31,6 +31,10 @@ class cache: self.values[id] = value return True + def patch(self, id, value): + self.values[id] = value + return True + def post(self, id): return self.put(id) diff --git a/sca-cpp/trunk/hosting/server/test/reference.py b/sca-cpp/trunk/hosting/server/test/reference.py index fe4a66a087..df422b292c 100644 --- a/sca-cpp/trunk/hosting/server/test/reference.py +++ b/sca-cpp/trunk/hosting/server/test/reference.py @@ -26,7 +26,7 @@ class reference: return self.l(*args) def __getattr__(self, name): - if name == "get" or name == "put": + if name == 'get' or name == 'put' or name == 'patch' or name == 'post' or name == 'delete': return self raise AttributeError() diff --git a/sca-cpp/trunk/hosting/server/util.py b/sca-cpp/trunk/hosting/server/util.py index 24467fd2cb..791951a71c 100644 --- a/sca-cpp/trunk/hosting/server/util.py +++ b/sca-cpp/trunk/hosting/server/util.py @@ -60,7 +60,7 @@ def reverse(l): def isNil(l): if isinstance(l, streampair): return l.isNil() - return l == () + return l is None or l == () def isSymbol(v): return isinstance(v, basestring) and v[0:1] == "'" @@ -131,12 +131,25 @@ def cons_stream(car, cdr): # Scheme-like associations def assoc(k, l): if l == (): - return None - + return () if k == car(car(l)): return car(l) return assoc(k, cdr(l)) +def delAssoc(k, l): + if l == (): + return () + if k == car(car(l)): + return delAssoc(k, cdr(l)) + return cons(car(l), delAssoc(k, cdr(l))) + +def substAssoc(k, n, l, a = False): + if l == (): + return (n,) if a else () + if k == car(car(l)): + return cons(n, substAssoc(k, n, cdr(l), False)) + return cons(car(l), substAssoc(k, n, cdr(l), a)) + # Currying / partial function application def curry(f, *args): return lambda *a: f(*(args + a)) |