summaryrefslogtreecommitdiffstats
path: root/sca-cpp/trunk/hosting/server
diff options
context:
space:
mode:
authorjsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68>2013-01-03 07:41:53 +0000
committerjsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68>2013-01-03 07:41:53 +0000
commitd7069b5a2e7859ab14c5a909d5e5fc6bc84b80cb (patch)
treed8027520fb22c176f54e860c0d2ebd000b1c457f /sca-cpp/trunk/hosting/server
parent9e1b9e73145e00ea591bd1e0e9777625bad66dc9 (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 'sca-cpp/trunk/hosting/server')
-rw-r--r--sca-cpp/trunk/hosting/server/Makefile.am40
-rw-r--r--sca-cpp/trunk/hosting/server/accounts.py14
-rw-r--r--sca-cpp/trunk/hosting/server/apps.py67
-rw-r--r--sca-cpp/trunk/hosting/server/atomutil.py68
-rw-r--r--sca-cpp/trunk/hosting/server/authn.py3
-rwxr-xr-xsca-cpp/trunk/hosting/server/clean-tables37
-rw-r--r--sca-cpp/trunk/hosting/server/composites.py37
-rwxr-xr-xsca-cpp/trunk/hosting/server/config-backup2
-rwxr-xr-xsca-cpp/trunk/hosting/server/create-tables38
-rw-r--r--sca-cpp/trunk/hosting/server/dashboards.py40
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/me360/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/me360/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/nearme/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/nearme/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/nearme2/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/nearme2/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/new/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/new/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/ourphotos/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/ourphotos/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/shoppingcart/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/shoppingcart/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/slice/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/slice/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/test/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/test/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testanimation/app.info (renamed from sca-cpp/trunk/hosting/server/data/apps/testanimation/app.stats)2
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testdb/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testdb/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testevents/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testevents/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testhttp/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testhttp/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testlogic/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testlogic/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testsearch/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testsearch/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testsms/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testsms/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testsocial/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testsocial/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testtext/app.info (renamed from sca-cpp/trunk/hosting/server/data/apps/testtext/app.stats)2
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testurl/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testurl/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testvalues/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testvalues/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testwidgets/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testwidgets/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testwidgets2/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testwidgets2/app.stats1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/testwidgets3/app.info (renamed from sca-cpp/trunk/hosting/server/data/apps/testwidgets3/app.stats)2
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/twsms/app.info1
-rw-r--r--sca-cpp/trunk/hosting/server/data/apps/twsms/app.stats1
-rwxr-xr-xsca-cpp/trunk/hosting/server/drop-tables38
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/account/index.html335
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/app/cache/cache-template.cmf (renamed from sca-cpp/trunk/hosting/server/htdocs/app/cache-template.cmf)2
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/app/index.html102
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/cache/cache-template.cmf (renamed from sca-cpp/trunk/hosting/server/htdocs/cache-template.cmf)2
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/cache/index.html51
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/clone/index.html135
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/config.js2
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/create/index.html97
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/delete/index.html91
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/favicon-16.xcfbin0 -> 1134 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/favicon-32.xcfbin0 -> 1456 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/favicon.icobin2238 -> 153 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/graph/index.html195
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/home/home.b641
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/home/home.pngbin9595 -> 0 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/home/index.html47
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/index.html503
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/info/index.html494
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/login/index.html409
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/page/index.html1581
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/proxy/public/cache/cache-template.cmf11
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/proxy/public/cache/index.html51
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/proxy/public/oops/index.html327
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/app.b642
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/app.pngbin147 -> 222 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/app.xcfbin1294 -> 1906 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/cache/cache-template.cmf17
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/cache/index.html51
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/config.js4
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/delete.b641
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/delete.pngbin906 -> 0 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/delete.xcfbin2008 -> 0 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/grid72.b641
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/grid72.pngbin138 -> 0 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/iframe.html28
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/img.b642
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/img.pngbin357 -> 191 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/img.xcfbin1639 -> 1456 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/notauth/index.html328
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/notfound/index.html328
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/notyet/index.html328
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/oops/index.html326
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/rate.pngbin0 -> 1690 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/rate.xcfbin0 -> 3754 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/ratings.pngbin0 -> 726 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/ratings.xcfbin0 -> 6344 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/search.pngbin0 -> 351 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/search.xcfbin0 -> 1263 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/touchicon-50.xcfbin0 -> 1742 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/touchicon-53.xcfbin0 -> 1748 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/touchicon-57.xcfbin0 -> 1988 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/touchicon.b641
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/touchicon.pngbin606 -> 243 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/touchicon.xcfbin3400 -> 0 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/user.b642
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/user.pngbin147 -> 891 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/user.xcfbin0 -> 2706 bytes
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/rate/index.html190
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/search/index.html196
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/stats/index.html179
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/store/index.html152
-rw-r--r--sca-cpp/trunk/hosting/server/icons.py175
-rwxr-xr-xsca-cpp/trunk/hosting/server/imapd-start71
-rwxr-xr-xsca-cpp/trunk/hosting/server/imapd-stop56
-rw-r--r--sca-cpp/trunk/hosting/server/imapd.py233
-rwxr-xr-xsca-cpp/trunk/hosting/server/load-authn58
-rwxr-xr-xsca-cpp/trunk/hosting/server/load-tables73
-rw-r--r--sca-cpp/trunk/hosting/server/pages.py36
-rw-r--r--sca-cpp/trunk/hosting/server/palettes.py2
-rwxr-xr-xsca-cpp/trunk/hosting/server/pgsql32
-rw-r--r--sca-cpp/trunk/hosting/server/pictures.py141
-rwxr-xr-xsca-cpp/trunk/hosting/server/proxy-start11
-rw-r--r--sca-cpp/trunk/hosting/server/ratings.py158
-rw-r--r--sca-cpp/trunk/hosting/server/reviews.py156
-rw-r--r--sca-cpp/trunk/hosting/server/search.py57
-rw-r--r--sca-cpp/trunk/hosting/server/selector.py8
-rw-r--r--sca-cpp/trunk/hosting/server/server.composite71
-rwxr-xr-xsca-cpp/trunk/hosting/server/ssl-proxy-start13
-rwxr-xr-xsca-cpp/trunk/hosting/server/ssl-start20
-rwxr-xr-xsca-cpp/trunk/hosting/server/start16
-rwxr-xr-xsca-cpp/trunk/hosting/server/stop2
-rw-r--r--sca-cpp/trunk/hosting/server/store.py53
-rwxr-xr-xsca-cpp/trunk/hosting/server/test.py437
-rw-r--r--sca-cpp/trunk/hosting/server/test/cache.py4
-rw-r--r--sca-cpp/trunk/hosting/server/test/reference.py2
-rw-r--r--sca-cpp/trunk/hosting/server/util.py19
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
new file mode 100644
index 0000000000..e43dcba033
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/favicon-16.xcf
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/favicon-32.xcf b/sca-cpp/trunk/hosting/server/htdocs/favicon-32.xcf
new file mode 100644
index 0000000000..c1736338ec
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/favicon-32.xcf
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/favicon.ico b/sca-cpp/trunk/hosting/server/htdocs/favicon.ico
index a7b502b9e1..bd60462963 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/favicon.ico
+++ b/sca-cpp/trunk/hosting/server/htdocs/favicon.ico
Binary files differ
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="&gt;"/>' +
-'<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
deleted file mode 100644
index 8f5a0b0d86..0000000000
--- a/sca-cpp/trunk/hosting/server/htdocs/home/home.png
+++ /dev/null
Binary files differ
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">&nbsp;</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="&gt;"/>' +
+ '<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="&gt;"/>' +
-'<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
index 1f73274b76..7293fe7ee5 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/public/app.png
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/app.png
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/app.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/app.xcf
index 741b7ff43f..b144d46743 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/public/app.xcf
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/app.xcf
Binary files differ
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
deleted file mode 100644
index fb56bae030..0000000000
--- a/sca-cpp/trunk/hosting/server/htdocs/public/delete.png
+++ /dev/null
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/delete.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/delete.xcf
deleted file mode 100644
index 7691f50cc5..0000000000
--- a/sca-cpp/trunk/hosting/server/htdocs/public/delete.xcf
+++ /dev/null
Binary files differ
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
deleted file mode 100644
index cf6008171a..0000000000
--- a/sca-cpp/trunk/hosting/server/htdocs/public/grid72.png
+++ /dev/null
Binary files differ
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
index 2363b25e8e..e05e74fa2d 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/public/img.png
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/img.png
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/img.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/img.xcf
index ffcc124584..c1736338ec 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/public/img.xcf
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/img.xcf
Binary files differ
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
new file mode 100644
index 0000000000..27c744c5a6
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/rate.png
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/rate.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/rate.xcf
new file mode 100644
index 0000000000..eb807f6005
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/rate.xcf
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/ratings.png b/sca-cpp/trunk/hosting/server/htdocs/public/ratings.png
new file mode 100644
index 0000000000..9c10be52dd
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/ratings.png
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/ratings.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/ratings.xcf
new file mode 100644
index 0000000000..cad8d31b2e
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/ratings.xcf
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/search.png b/sca-cpp/trunk/hosting/server/htdocs/public/search.png
new file mode 100644
index 0000000000..d5178fea3c
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/search.png
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/search.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/search.xcf
new file mode 100644
index 0000000000..30d03df40a
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/search.xcf
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-50.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-50.xcf
new file mode 100644
index 0000000000..8dcc8e4f1b
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-50.xcf
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-53.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-53.xcf
new file mode 100644
index 0000000000..b2dcd6f12c
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-53.xcf
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-57.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-57.xcf
new file mode 100644
index 0000000000..5ab284973d
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon-57.xcf
Binary files differ
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
index f22c33d2a0..1975eb16bf 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.png
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.png
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.xcf
deleted file mode 100644
index fc713b478b..0000000000
--- a/sca-cpp/trunk/hosting/server/htdocs/public/touchicon.xcf
+++ /dev/null
Binary files differ
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
index 1f73274b76..e99b30815d 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/public/user.png
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/user.png
Binary files differ
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/user.xcf b/sca-cpp/trunk/hosting/server/htdocs/public/user.xcf
new file mode 100644
index 0000000000..2f304c45a7
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/user.xcf
Binary files differ
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">&nbsp;</span><span style="display: inline-block; width: 20px;"></span>
+<span id="rateApp2" class="graystar">&nbsp;</span><span style="display: inline-block; width: 20px;"></span>
+<span id="rateApp3" class="graystar">&nbsp;</span><span style="display: inline-block; width: 20px;"></span>
+<span id="rateApp4" class="graystar">&nbsp;</span>
+</td></tr>
+<tr><td class="lightlabel" id="ratedescription">&nbsp;</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&nbsp;' + author.split('@')[0] + '</span>';
+ var ratingy = -20 * (4 - Math.floor(rating));
+ apps += '<br/><span class="ratings" style="background-position: 0px ' + ratingy + 'px;">&nbsp;</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&nbsp;' + author.split('@')[0] + '</span>';
- apps += '</span>';
+ var ratingy = -20 * (4 - Math.floor(rating));
+ apps += '<br/><span class="ratings" style="background-position: 0px ' + ratingy + 'px;">&nbsp;</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))