From 74b5bd2bfaa1d15c8e2f379b33ae601c9da2de13 Mon Sep 17 00:00:00 2001 From: Noah Slater Date: Wed, 7 Jul 2010 18:49:29 +0000 Subject: update trunk with version info assuming 1.0.0 passes the vote git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@961477 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 5 ++++- NEWS | 5 ++++- acinclude.m4.in | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 2224718f..1c609a20 100644 --- a/CHANGES +++ b/CHANGES @@ -1,11 +1,14 @@ Apache CouchDB CHANGES ====================== -Version 1.0.0 +Version 1.1.0 -------------- This version has not been released yet. +Version 1.0.0 +-------------- + HTTP Interface: * Mask passwords in active tasks and logging. diff --git a/NEWS b/NEWS index b4fcdf32..cbda7fda 100644 --- a/NEWS +++ b/NEWS @@ -7,11 +7,14 @@ For details about backwards incompatible changes, see: Each release section notes when backwards incompatible changes have been made. -Version 1.0.0 +Version 1.1.0 -------------- This version has not been released yet. +Version 1.0.0 +-------------- + * Mask passwords in active tasks and logging. * Update mochijson2 to allow output of BigNums not in float form. * Added support for X-HTTP-METHOD-OVERRIDE. diff --git a/acinclude.m4.in b/acinclude.m4.in index ccfe9da7..253831be 100644 --- a/acinclude.m4.in +++ b/acinclude.m4.in @@ -16,8 +16,8 @@ m4_define([LOCAL_PACKAGE_IDENTIFIER], [couchdb]) m4_define([LOCAL_PACKAGE_TARNAME], [apache-couchdb]) m4_define([LOCAL_PACKAGE_NAME], [Apache CouchDB]) m4_define([LOCAL_BUG_URI], [https://issues.apache.org/jira/browse/COUCHDB]) -m4_define([LOCAL_VERSION_MAJOR], [0]) -m4_define([LOCAL_VERSION_MINOR], [12]) +m4_define([LOCAL_VERSION_MAJOR], [1]) +m4_define([LOCAL_VERSION_MINOR], [1]) m4_define([LOCAL_VERSION_REVISION], [0]) m4_define([LOCAL_VERSION_STAGE], [a]) m4_define([LOCAL_VERSION_RELEASE], [%release%]) -- cgit v1.2.3 From b458999a36785410d7a45b5d4cb5bea2d5638994 Mon Sep 17 00:00:00 2001 From: Noah Slater Date: Wed, 7 Jul 2010 20:10:20 +0000 Subject: fixed up version numbers, etc git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@961493 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 32 ++++++++++++++++++++++---------- NEWS | 18 ++++++++++++------ 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 1c609a20..8cf39aab 100644 --- a/CHANGES +++ b/CHANGES @@ -2,11 +2,32 @@ Apache CouchDB CHANGES ====================== Version 1.1.0 --------------- +------------- This version has not been released yet. Version 1.0.0 +------------- + +Security: + +* Added authentication caching, to avoid repeated opening and closing of the + users database for each request requiring authentication. + +Storage System: + +* Small optimization for reordering result lists. +* More efficient header commits. +* Use O_APPEND to save lseeks. +* Faster implementation of pread_iolist(). Further improves performance on + concurrent reads. + +View Server: + +* Faster default view collation. +* Added option to include update_seq in view responses. + +Version 0.11.1 -------------- HTTP Interface: @@ -43,8 +64,6 @@ Build and System Integration: Security: * Added authentication redirect URL to log in clients. - * Added authentication caching, to avoid repeated opening and closing of the - users database for each request requiring authentication. * Fixed query parameter encoding issue in oauth.js. * Made authentication timeout configurable. * Temporary views are now admin-only resources. @@ -56,14 +75,9 @@ Storage System: it's correct. * Make file deletions async to avoid pauses during compaction and db deletion. - * Small optimization for reordering result lists. * Fixed for wrong offset when writing headers and converting them to blocks, only triggered when header is larger than 4k. - * More efficient header commits. * Preserve _revs_limit and instance_start_time after compaction. - * Use O_APPEND to save lseeks. - * Faster implementation of pread_iolist(). Further improves performance on - concurrent reads. Configuration System: @@ -84,9 +98,7 @@ View Server: * Provide a UUID to update functions (and all other functions) that they can use to create new docs. - * Faster default view collation. * Upgrade CommonJS modules support to 1.1.1. - * Added option to include update_seq in view responses. * Fixed erlang filter funs and normalize filter fun API. * Fixed hang in view shutdown. diff --git a/NEWS b/NEWS index cbda7fda..d0ad8cfd 100644 --- a/NEWS +++ b/NEWS @@ -8,11 +8,22 @@ For details about backwards incompatible changes, see: Each release section notes when backwards incompatible changes have been made. Version 1.1.0 --------------- +------------- This version has not been released yet. Version 1.0.0 +------------- + +* More efficient header commits. +* Use O_APPEND to save lseeks. +* Faster implementation of pread_iolist(). Further improves performance on + concurrent reads. +* Added authentication caching +* Faster default view collation. +* Added option to include update_seq in view responses. + +Version 0.11.1 -------------- * Mask passwords in active tasks and logging. @@ -41,11 +52,7 @@ Version 1.0.0 deletion. * Fixed for wrong offset when writing headers and converting them to blocks, only triggered when header is larger than 4k. - * More efficient header commits. * Preserve _revs_limit and instance_start_time after compaction. - * Use O_APPEND to save lseeks. - * Faster implementation of pread_iolist(). Further improves performance on - concurrent reads. * Fixed timeout with large .ini files. * Added tests for couch.js and jquery.couch.js * Added various API features to jquery.couch.js @@ -61,7 +68,6 @@ Version 1.0.0 * Allow isolation of databases with vhosts. * Made the test suite overall more reliable. - Version 0.11.0 -------------- -- cgit v1.2.3 From ddd9811edb767d6ca9ba77ce6ae1eedffac6a8ca Mon Sep 17 00:00:00 2001 From: "Damien F. Katz" Date: Thu, 8 Jul 2010 14:54:29 +0000 Subject: Fix for occasional failed tests caused by responding with 202 success to compaction requests before the compaction actually begins. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@961791 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_db.erl | 2 +- src/couchdb/couch_db_updater.erl | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl index cf25dc62..80f0d7bf 100644 --- a/src/couchdb/couch_db.erl +++ b/src/couchdb/couch_db.erl @@ -103,7 +103,7 @@ monitor(#db{main_pid=MainPid}) -> erlang:monitor(process, MainPid). start_compact(#db{update_pid=Pid}) -> - gen_server:cast(Pid, start_compact). + gen_server:call(Pid, start_compact). delete_doc(Db, Id, Revisions) -> DeletedDocs = [#doc{id=Id, revs=[Rev], deleted=true} || Rev <- Revisions], diff --git a/src/couchdb/couch_db_updater.erl b/src/couchdb/couch_db_updater.erl index 30275012..f0879700 100644 --- a/src/couchdb/couch_db_updater.erl +++ b/src/couchdb/couch_db_updater.erl @@ -143,21 +143,22 @@ handle_call({purge_docs, IdRevs}, _From, Db) -> ok = gen_server:call(Db2#db.main_pid, {db_updated, Db2}), couch_db_update_notifier:notify({updated, Db#db.name}), - {reply, {ok, (Db2#db.header)#db_header.purge_seq, IdRevsPurged}, Db2}. - - -handle_cast(start_compact, Db) -> + {reply, {ok, (Db2#db.header)#db_header.purge_seq, IdRevsPurged}, Db2}; +handle_call(start_compact, _From, Db) -> case Db#db.compactor_pid of nil -> ?LOG_INFO("Starting compaction for db \"~s\"", [Db#db.name]), Pid = spawn_link(fun() -> start_copy_compact(Db) end), Db2 = Db#db{compactor_pid=Pid}, ok = gen_server:call(Db#db.main_pid, {db_updated, Db2}), - {noreply, Db2}; + {reply, ok, Db2}; _ -> % compact currently running, this is a no-op - {noreply, Db} - end; + {reply, ok, Db} + end. + + + handle_cast({compact_done, CompactFilepath}, #db{filepath=Filepath}=Db) -> {ok, NewFd} = couch_file:open(CompactFilepath), {ok, NewHeader} = couch_file:read_header(NewFd), -- cgit v1.2.3 From 5c55239750edc5540825ef0bbf83fccb1bed459f Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Thu, 8 Jul 2010 18:09:20 +0000 Subject: use json as the default content-type for requests from jquery.couch.js git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@961854 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/jquery.couch.js | 1 + 1 file changed, 1 insertion(+) diff --git a/share/www/script/jquery.couch.js b/share/www/script/jquery.couch.js index f9647ecd..879c2444 100644 --- a/share/www/script/jquery.couch.js +++ b/share/www/script/jquery.couch.js @@ -595,6 +595,7 @@ function ajax(obj, options, errorMessage, ajaxOptions) { options = $.extend({successStatus: 200}, options); + ajaxOptions = $.extend({contentType: "application/json"}, ajaxOptions); errorMessage = errorMessage || "Unknown error"; $.ajax($.extend($.extend({ type: "GET", dataType: "json", cache : !$.browser.msie, -- cgit v1.2.3 From f3442c25ae26369a516cf12b94f86cdec406c5b6 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Thu, 8 Jul 2010 19:23:33 +0000 Subject: Fix deletion of view files after compaction. Also added test for view compaction. Thanks Adam for reporting the issue. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@961893 13f79535-47bb-0310-9956-ffa450edef68 --- share/Makefile.am | 1 + share/www/script/couch_tests.js | 1 + share/www/script/test/view_compaction.js | 102 +++++++++++++++++++++++++++++++ src/couchdb/couch_view_group.erl | 4 +- 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 share/www/script/test/view_compaction.js diff --git a/share/Makefile.am b/share/Makefile.am index fc052de0..c85f683f 100644 --- a/share/Makefile.am +++ b/share/Makefile.am @@ -162,6 +162,7 @@ nobase_dist_localdata_DATA = \ www/script/test/view_collation.js \ www/script/test/view_collation_raw.js \ www/script/test/view_conflicts.js \ + www/script/test/view_compaction.js \ www/script/test/view_errors.js \ www/script/test/view_include_docs.js \ www/script/test/view_multi_key_all_docs.js \ diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js index dc283bba..c5257ea6 100644 --- a/share/www/script/couch_tests.js +++ b/share/www/script/couch_tests.js @@ -85,6 +85,7 @@ loadTest("uuids.js"); loadTest("view_collation.js"); loadTest("view_collation_raw.js"); loadTest("view_conflicts.js"); +loadTest("view_compaction.js"); loadTest("view_errors.js"); loadTest("view_include_docs.js"); loadTest("view_multi_key_all_docs.js"); diff --git a/share/www/script/test/view_compaction.js b/share/www/script/test/view_compaction.js new file mode 100644 index 00000000..318dfdb5 --- /dev/null +++ b/share/www/script/test/view_compaction.js @@ -0,0 +1,102 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +couchTests.view_compaction = function(debug) { + + if (debug) debugger; + + var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit": "true"}); + + db.deleteDb(); + db.createDb(); + + var ddoc = { + _id: "_design/foo", + language: "javascript", + views: { + view1: { + map: "function(doc) { emit(doc._id, doc.value) }" + }, + view2: { + map: "function(doc) { emit(doc._id, doc.value); }", + reduce: "function(keys, values, rereduce) { return sum(values); }" + } + } + }; + T(db.save(ddoc).ok); + + var docs = makeDocs(0, 1000); + db.bulkSave(docs); + + var resp = db.view('foo/view1', {}); + T(resp.rows.length === 1000); + + resp = db.view('foo/view2', {}); + T(resp.rows.length === 1); + + resp = db.designInfo("_design/foo"); + T(resp.view_index.update_seq === 1001); + + + // update docs + for (var i = 0; i < docs.length; i++) { + docs[i].integer = docs[i].integer + 1; + } + db.bulkSave(docs); + + + resp = db.view('foo/view1', {}); + T(resp.rows.length === 1000); + + resp = db.view('foo/view2', {}); + T(resp.rows.length === 1); + + resp = db.designInfo("_design/foo"); + T(resp.view_index.update_seq === 2001); + + + // update docs again... + for (var i = 0; i < docs.length; i++) { + docs[i].integer = docs[i].integer + 2; + } + db.bulkSave(docs); + + + resp = db.view('foo/view1', {}); + T(resp.rows.length === 1000); + + resp = db.view('foo/view2', {}); + T(resp.rows.length === 1); + + resp = db.designInfo("_design/foo"); + T(resp.view_index.update_seq === 3001); + + + // compact view group + var xhr = CouchDB.request("POST", "/" + db.name + "/_compact" + "/foo"); + T(JSON.parse(xhr.responseText).ok === true); + + resp = db.designInfo("_design/foo"); + while (resp.view_index.compact_running === true) { + resp = db.designInfo("_design/foo"); + } + + + resp = db.view('foo/view1', {}); + T(resp.rows.length === 1000); + + resp = db.view('foo/view2', {}); + T(resp.rows.length === 1); + + resp = db.designInfo("_design/foo"); + T(resp.view_index.update_seq === 3001); +}; \ No newline at end of file diff --git a/src/couchdb/couch_view_group.erl b/src/couchdb/couch_view_group.erl index 8d3a3d86..f01befdf 100644 --- a/src/couchdb/couch_view_group.erl +++ b/src/couchdb/couch_view_group.erl @@ -186,7 +186,7 @@ handle_cast({compact_done, #group{current_seq=NewSeq} = NewGroup}, ?LOG_INFO("View index compaction complete for ~s ~s", [DbName, GroupId]), FileName = index_file_name(RootDir, DbName, GroupSig), CompactName = index_file_name(compact, RootDir, DbName, GroupSig), - couch_file:delete(FileName), + ok = couch_file:delete(RootDir, FileName), ok = file:rename(CompactName, FileName), %% if an updater is running, kill it and start a new one @@ -545,7 +545,7 @@ reset_file(Db, Fd, DbName, #group{sig=Sig,name=Name} = Group) -> init_group(Db, Fd, reset_group(Group), nil). delete_index_file(RootDir, DbName, GroupSig) -> - couch_file:delete(index_file_name(RootDir, DbName, GroupSig)). + couch_file:delete(RootDir, index_file_name(RootDir, DbName, GroupSig)). init_group(Db, Fd, #group{views=Views}=Group, nil) -> init_group(Db, Fd, Group, -- cgit v1.2.3 From fe73841cf1d2efe08d918d979db003e176aa258f Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Thu, 8 Jul 2010 19:47:08 +0000 Subject: validate admin and content type for compact requests git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@961904 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_httpd_db.erl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index f1514785..783ed9f9 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -111,12 +111,15 @@ handle_changes_req(#httpd{method='GET'}=Req, Db) -> handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) -> send_method_not_allowed(Req, "GET,HEAD"). -handle_compact_req(#httpd{method='POST',path_parts=[DbName,_,Id|_]}=Req, _Db) -> +handle_compact_req(#httpd{method='POST',path_parts=[DbName,_,Id|_]}=Req, Db) -> + ok = couch_db:check_is_admin(Db), couch_httpd:validate_ctype(Req, "application/json"), ok = couch_view_compactor:start_compact(DbName, Id), send_json(Req, 202, {[{ok, true}]}); handle_compact_req(#httpd{method='POST'}=Req, Db) -> + ok = couch_db:check_is_admin(Db), + couch_httpd:validate_ctype(Req, "application/json"), ok = couch_db:start_compact(Db), send_json(Req, 202, {[{ok, true}]}); @@ -125,6 +128,8 @@ handle_compact_req(Req, _Db) -> handle_view_cleanup_req(#httpd{method='POST'}=Req, Db) -> % delete unreferenced index files + ok = couch_db:check_is_admin(Db), + couch_httpd:validate_ctype(Req, "application/json"), ok = couch_view:cleanup_index_files(Db), send_json(Req, 202, {[{ok, true}]}); -- cgit v1.2.3 From 44fe80dfdcf8540402e80580bcfa4f184cbc3b21 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Fri, 9 Jul 2010 09:25:44 +0000 Subject: Adding one more assertion to the view_compaction.js test to verify that the view group disk size is smaller after compaction. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@962460 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/test/view_compaction.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/share/www/script/test/view_compaction.js b/share/www/script/test/view_compaction.js index 318dfdb5..a11fb7bd 100644 --- a/share/www/script/test/view_compaction.js +++ b/share/www/script/test/view_compaction.js @@ -80,6 +80,7 @@ couchTests.view_compaction = function(debug) { resp = db.designInfo("_design/foo"); T(resp.view_index.update_seq === 3001); + var disk_size_before_compact = resp.view_index.disk_size; // compact view group var xhr = CouchDB.request("POST", "/" + db.name + "/_compact" + "/foo"); @@ -99,4 +100,5 @@ couchTests.view_compaction = function(debug) { resp = db.designInfo("_design/foo"); T(resp.view_index.update_seq === 3001); + T(resp.view_index.disk_size < disk_size_before_compact); }; \ No newline at end of file -- cgit v1.2.3 From ba34253d2bb6fa561a247fb0ed2f5acd7cd66e7f Mon Sep 17 00:00:00 2001 From: Noah Slater Date: Fri, 9 Jul 2010 15:51:32 +0000 Subject: added some list padding to NEWS and CHANGES git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@962577 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 18 +++++++++--------- NEWS | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 8cf39aab..d6ab8cf6 100644 --- a/CHANGES +++ b/CHANGES @@ -11,21 +11,21 @@ Version 1.0.0 Security: -* Added authentication caching, to avoid repeated opening and closing of the - users database for each request requiring authentication. + * Added authentication caching, to avoid repeated opening and closing of the + users database for each request requiring authentication. Storage System: -* Small optimization for reordering result lists. -* More efficient header commits. -* Use O_APPEND to save lseeks. -* Faster implementation of pread_iolist(). Further improves performance on - concurrent reads. + * Small optimization for reordering result lists. + * More efficient header commits. + * Use O_APPEND to save lseeks. + * Faster implementation of pread_iolist(). Further improves performance on + concurrent reads. View Server: -* Faster default view collation. -* Added option to include update_seq in view responses. + * Faster default view collation. + * Added option to include update_seq in view responses. Version 0.11.1 -------------- diff --git a/NEWS b/NEWS index d0ad8cfd..8e4f4e8b 100644 --- a/NEWS +++ b/NEWS @@ -15,13 +15,13 @@ This version has not been released yet. Version 1.0.0 ------------- -* More efficient header commits. -* Use O_APPEND to save lseeks. -* Faster implementation of pread_iolist(). Further improves performance on - concurrent reads. -* Added authentication caching -* Faster default view collation. -* Added option to include update_seq in view responses. + * More efficient header commits. + * Use O_APPEND to save lseeks. + * Faster implementation of pread_iolist(). Further improves performance on + concurrent reads. + * Added authentication caching + * Faster default view collation. + * Added option to include update_seq in view responses. Version 0.11.1 -------------- -- cgit v1.2.3 From 10ba6ce1529f04e567e3a71a9898b2c9a623dd51 Mon Sep 17 00:00:00 2001 From: Adam Kocoloski Date: Sun, 11 Jul 2010 01:00:50 +0000 Subject: deleting a DB while it was being opened would crash couch_server git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@962964 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_server.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/couchdb/couch_server.erl b/src/couchdb/couch_server.erl index 43fd9044..88bd6107 100644 --- a/src/couchdb/couch_server.erl +++ b/src/couchdb/couch_server.erl @@ -326,7 +326,7 @@ handle_call({delete, DbName, _Options}, _From, Server) -> couch_util:shutdown_sync(Pid), true = ets:delete(couch_dbs_by_name, DbName), true = ets:delete(couch_dbs_by_pid, Pid), - [gen_server:send_result(F, not_found) || F <- Froms], + [gen_server:reply(F, not_found) || F <- Froms], true; [{_, {opened, Pid, LruTime}}] -> couch_util:shutdown_sync(Pid), -- cgit v1.2.3 From 828d8eba2628fed701cbb59e0edd6da8fb482293 Mon Sep 17 00:00:00 2001 From: Adam Kocoloski Date: Sun, 11 Jul 2010 01:02:14 +0000 Subject: thank you dialyzer git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@962965 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_httpd_db.erl | 4 +--- src/couchdb/couch_query_servers.erl | 20 +++++--------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 783ed9f9..60bf38cb 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -970,9 +970,7 @@ db_attachment_req(#httpd{method=Method,mochi_req=MochiReq}=Req, Db, DocId, FileN end, - fun() -> couch_httpd:recv(Req, 0) end; - Length -> - exit({length_not_integer, Length}) + fun() -> couch_httpd:recv(Req, 0) end end, att_len = case couch_httpd:header_value(Req,"Content-Length") of undefined -> diff --git a/src/couchdb/couch_query_servers.erl b/src/couchdb/couch_query_servers.erl index b2aa7a12..3df7beb0 100644 --- a/src/couchdb/couch_query_servers.erl +++ b/src/couchdb/couch_query_servers.erl @@ -130,8 +130,6 @@ os_reduce(Lang, OsRedSrcs, KVs) -> end, {ok, OsResults}. -os_rereduce(_Lang, [], _KVs) -> - {ok, []}; os_rereduce(Lang, OsRedSrcs, KVs) -> Proc = get_os_process(Lang), try proc_prompt(Proc, [<<"rereduce">>, OsRedSrcs, KVs]) of @@ -275,23 +273,15 @@ handle_call({get_proc, #doc{body={Props}}=DDoc, DDocKey}, _From, {Langs, PidProc case ets:lookup(LangProcs, Lang) of [{Lang, [P|Rest]}] -> % find a proc in the set that has the DDoc - case proc_with_ddoc(DDoc, DDocKey, [P|Rest]) of - {ok, Proc} -> - rem_from_list(LangProcs, Lang, Proc), - {reply, {ok, Proc, get_query_server_config()}, Server}; - Error -> - {reply, Error, Server} - end; + {ok, Proc} = proc_with_ddoc(DDoc, DDocKey, [P|Rest]), + rem_from_list(LangProcs, Lang, Proc), + {reply, {ok, Proc, get_query_server_config()}, Server}; _ -> case (catch new_process(Langs, Lang)) of {ok, Proc} -> add_value(PidProcs, Proc#proc.pid, Proc), - case proc_with_ddoc(DDoc, DDocKey, [Proc]) of - {ok, Proc2} -> - {reply, {ok, Proc2, get_query_server_config()}, Server}; - Error -> - {reply, Error, Server} - end; + {ok, Proc2} = proc_with_ddoc(DDoc, DDocKey, [Proc]), + {reply, {ok, Proc2, get_query_server_config()}, Server}; Error -> {reply, Error, Server} end -- cgit v1.2.3 From d4446bfb78a2f5f9049e5aa52f2927769e239286 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Sun, 11 Jul 2010 10:11:55 +0000 Subject: Removing couch_util:read_file_size/1 because same functionality is provided by filelib:file_size/1. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@963038 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_log.erl | 2 +- src/couchdb/couch_util.erl | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/couchdb/couch_log.erl b/src/couchdb/couch_log.erl index 2d62cbb5..d21f7ecc 100644 --- a/src/couchdb/couch_log.erl +++ b/src/couchdb/couch_log.erl @@ -139,7 +139,7 @@ log(Fd, Pid, Level, Format, Args) -> read(Bytes, Offset) -> LogFileName = couch_config:get("log", "file"), - LogFileSize = couch_util:file_read_size(LogFileName), + LogFileSize = filelib:file_size(LogFileName), {ok, Fd} = file:open(LogFileName, [read]), Start = lists:max([LogFileSize - Bytes, 0]) + Offset, diff --git a/src/couchdb/couch_util.erl b/src/couchdb/couch_util.erl index e50ab2fe..ff4cbe36 100644 --- a/src/couchdb/couch_util.erl +++ b/src/couchdb/couch_util.erl @@ -18,7 +18,7 @@ -export([abs_pathname/1,abs_pathname/2, trim/1, ascii_lower/1]). -export([encodeBase64Url/1, decodeBase64Url/1]). -export([to_hex/1, parse_term/1, dict_find/3]). --export([file_read_size/1, get_nested_json_value/2, json_user_ctx/1]). +-export([get_nested_json_value/2, json_user_ctx/1]). -export([proplist_apply_field/2, json_apply_field/2]). -export([to_binary/1, to_integer/1, to_list/1, url_encode/1]). -export([json_encode/1, json_decode/1]). @@ -314,14 +314,6 @@ dict_find(Key, Dict, DefaultValue) -> DefaultValue end. - -file_read_size(FileName) -> - case file:read_file_info(FileName) of - {ok, FileInfo} -> - FileInfo#file_info.size; - Error -> Error - end. - to_binary(V) when is_binary(V) -> V; to_binary(V) when is_list(V) -> -- cgit v1.2.3 From f5f04686b010d6326e88cfafcdc6629560c36d0d Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Sun, 11 Jul 2010 10:31:38 +0000 Subject: Removing unnecessary code. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@963042 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_util.erl | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/couchdb/couch_util.erl b/src/couchdb/couch_util.erl index ff4cbe36..252d4e04 100644 --- a/src/couchdb/couch_util.erl +++ b/src/couchdb/couch_util.erl @@ -144,18 +144,11 @@ get_nested_json_value(Value, []) -> get_nested_json_value(_NotJSONObj, _) -> throw({not_found, json_mismatch}). -proplist_apply_field(H, L) -> - {R} = json_apply_field(H, {L}), - R. - -json_apply_field(H, {L}) -> - json_apply_field(H, L, []). -json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) -> - json_apply_field({Key, NewValue}, Headers, Acc); -json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) -> - json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]); -json_apply_field({Key, NewValue}, [], Acc) -> - {[{Key, NewValue}|Acc]}. +proplist_apply_field({K, _V} = KV, L) -> + lists:keystore(K, 1, L, KV). + +json_apply_field({K, _V} = KV, {L}) -> + {lists:keystore(K, 1, L, KV)}. json_user_ctx(#db{name=DbName, user_ctx=Ctx}) -> {[{<<"db">>, DbName}, -- cgit v1.2.3 From c795df0dd2176e919dac722dd59d41c25df19b98 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Sun, 11 Jul 2010 10:50:34 +0000 Subject: Removing unused function couch_util:ascii_lower/1. If ever needed, the exact same functionality is provided by string:to_lower/1 (ASCII based, not Unicode nor charset based). git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@963045 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_util.erl | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/couchdb/couch_util.erl b/src/couchdb/couch_util.erl index 252d4e04..80c92271 100644 --- a/src/couchdb/couch_util.erl +++ b/src/couchdb/couch_util.erl @@ -15,7 +15,7 @@ -export([priv_dir/0, start_driver/1, normpath/1]). -export([should_flush/0, should_flush/1, to_existing_atom/1]). -export([rand32/0, implode/2, collate/2, collate/3]). --export([abs_pathname/1,abs_pathname/2, trim/1, ascii_lower/1]). +-export([abs_pathname/1,abs_pathname/2, trim/1]). -export([encodeBase64Url/1, decodeBase64Url/1]). -export([to_hex/1, parse_term/1, dict_find/3]). -export([get_nested_json_value/2, json_user_ctx/1]). @@ -196,18 +196,6 @@ separate_cmd_args(" " ++ Rest, CmdAcc) -> separate_cmd_args([Char|Rest], CmdAcc) -> separate_cmd_args(Rest, [Char | CmdAcc]). -% lowercases string bytes that are the ascii characters A-Z. -% All other characters/bytes are ignored. -ascii_lower(String) -> - ascii_lower(String, []). - -ascii_lower([], Acc) -> - lists:reverse(Acc); -ascii_lower([Char | RestString], Acc) when Char >= $A, Char =< $B -> - ascii_lower(RestString, [Char + ($a-$A) | Acc]); -ascii_lower([Char | RestString], Acc) -> - ascii_lower(RestString, [Char | Acc]). - % Is a character whitespace? is_whitespace($\s) -> true; is_whitespace($\t) -> true; -- cgit v1.2.3 From c5e6b0459adae4c9ef9386170993d7dd1b320cd0 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Sun, 11 Jul 2010 21:30:46 +0000 Subject: Simplification - avoid 2 proplists lookups and an unnecessary list to binary conversion. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@963151 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_httpd_db.erl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 60bf38cb..05979e46 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -600,13 +600,12 @@ db_doc_req(#httpd{method='POST'}=Req, Db, DocId) -> couch_doc:validate_docid(DocId), couch_httpd:validate_ctype(Req, "multipart/form-data"), Form = couch_httpd:parse_form(Req), - case proplists:is_defined("_doc", Form) of - true -> - Json = ?JSON_DECODE(couch_util:get_value("_doc", Form)), - Doc = couch_doc_from_req(Req, DocId, Json); - false -> - Rev = couch_doc:parse_rev(list_to_binary(couch_util:get_value("_rev", Form))), - {ok, [{ok, Doc}]} = couch_db:open_doc_revs(Db, DocId, [Rev], []) + case couch_util:get_value("_doc", Form) of + undefined -> + Rev = couch_doc:parse_rev(couch_util:get_value("_rev", Form)), + {ok, [{ok, Doc}]} = couch_db:open_doc_revs(Db, DocId, [Rev], []); + Json -> + Doc = couch_doc_from_req(Req, DocId, ?JSON_DECODE(Json)) end, UpdatedAtts = [ #att{name=validate_attachment_name(Name), -- cgit v1.2.3 From 9599f7cd77ef8a6feacec3c8ead9432af02a57d3 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Mon, 12 Jul 2010 17:24:00 +0000 Subject: Reverting revision 963042 - lists:keystore/4 only replaces the first occurrence in the list. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@963367 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_util.erl | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/couchdb/couch_util.erl b/src/couchdb/couch_util.erl index 80c92271..2aa69af8 100644 --- a/src/couchdb/couch_util.erl +++ b/src/couchdb/couch_util.erl @@ -144,11 +144,18 @@ get_nested_json_value(Value, []) -> get_nested_json_value(_NotJSONObj, _) -> throw({not_found, json_mismatch}). -proplist_apply_field({K, _V} = KV, L) -> - lists:keystore(K, 1, L, KV). - -json_apply_field({K, _V} = KV, {L}) -> - {lists:keystore(K, 1, L, KV)}. +proplist_apply_field(H, L) -> + {R} = json_apply_field(H, {L}), + R. + +json_apply_field(H, {L}) -> + json_apply_field(H, L, []). +json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) -> + json_apply_field({Key, NewValue}, Headers, Acc); +json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) -> + json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]); +json_apply_field({Key, NewValue}, [], Acc) -> + {[{Key, NewValue}|Acc]}. json_user_ctx(#db{name=DbName, user_ctx=Ctx}) -> {[{<<"db">>, DbName}, -- cgit v1.2.3 From 934693735ad6c6358a240a466d4ce121f150e1aa Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Tue, 13 Jul 2010 14:25:03 +0000 Subject: test and fix for deleting users documents git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@963723 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/test/users_db.js | 7 ++++++- src/couchdb/couch_js_functions.hrl | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/share/www/script/test/users_db.js b/share/www/script/test/users_db.js index b85adf08..667ff3c1 100644 --- a/share/www/script/test/users_db.js +++ b/share/www/script/test/users_db.js @@ -85,8 +85,13 @@ couchTests.users_db = function(debug) { T(/conflict/.test(e.reason)) } + // you can delete a user doc + s = CouchDB.session().userCtx; + T(s.name == null); + T(s.roles.indexOf("_admin") !== -1); + T(usersDb.deleteDoc(jchrisWithConflict).ok); }; - + usersDb.deleteDb(); run_on_modified_server( [{section: "couch_httpd_auth", diff --git a/src/couchdb/couch_js_functions.hrl b/src/couchdb/couch_js_functions.hrl index 3214b1f6..1f314f6e 100644 --- a/src/couchdb/couch_js_functions.hrl +++ b/src/couchdb/couch_js_functions.hrl @@ -12,10 +12,6 @@ -define(AUTH_DB_DOC_VALIDATE_FUNCTION, <<" function(newDoc, oldDoc, userCtx) { - if ((oldDoc && oldDoc.type !== 'user') || newDoc.type !== 'user') { - throw({forbidden : 'doc.type must be user'}); - } // we only allow user docs for now - if (newDoc._deleted === true) { // allow deletes by admins and matching users // without checking the other fields @@ -27,6 +23,10 @@ } } + if ((oldDoc && oldDoc.type !== 'user') || newDoc.type !== 'user') { + throw({forbidden : 'doc.type must be user'}); + } // we only allow user docs for now + if (!newDoc.name) { throw({forbidden: 'doc.name is required'}); } -- cgit v1.2.3 From 32c60cf5c554a1ff3819cc658ebf5846ccb9d6be Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Tue, 13 Jul 2010 14:25:06 +0000 Subject: show the complete document body even when the document is deleted (Couch lets you store data with a delete but did not render it to JSON) git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@963725 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_doc.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/couchdb/couch_doc.erl b/src/couchdb/couch_doc.erl index 50dbd9d0..acae0c55 100644 --- a/src/couchdb/couch_doc.erl +++ b/src/couchdb/couch_doc.erl @@ -27,8 +27,8 @@ to_json_rev(0, []) -> to_json_rev(Start, [FirstRevId|_]) -> [{<<"_rev">>, ?l2b([integer_to_list(Start),"-",revid_to_str(FirstRevId)])}]. -to_json_body(true, _Body) -> - [{<<"_deleted">>, true}]; +to_json_body(true, {Body}) -> + Body ++ [{<<"_deleted">>, true}]; to_json_body(false, {Body}) -> Body. -- cgit v1.2.3 From d44a6346ecd5869c626452df9eb22711c32f119e Mon Sep 17 00:00:00 2001 From: Jan Lehnardt Date: Wed, 14 Jul 2010 09:18:03 +0000 Subject: fix test report sharing git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@963982 13f79535-47bb-0310-9956-ffa450edef68 --- share/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/share/Makefile.am b/share/Makefile.am index c85f683f..b6f97e29 100644 --- a/share/Makefile.am +++ b/share/Makefile.am @@ -52,6 +52,7 @@ nobase_dist_localdata_DATA = \ www/dialog/_delete_database.html \ www/dialog/_delete_document.html \ www/dialog/_database_security.html \ + www/dialog/_share_test_reports.html \ www/dialog/_save_view_as.html \ www/dialog/_upload_attachment.html \ www/document.html \ -- cgit v1.2.3 From 0dfa5afb632d06cfeb98853aa2c41f4aa763546a Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Wed, 14 Jul 2010 17:49:39 +0000 Subject: Bug fix: badmatch error after deleting a user doc (uncovered by revision 963723). git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@964108 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/test/auth_cache.js | 12 ++++++++++++ src/couchdb/couch_auth_cache.erl | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/share/www/script/test/auth_cache.js b/share/www/script/test/auth_cache.js index 4d380e41..8c3fa943 100644 --- a/share/www/script/test/auth_cache.js +++ b/share/www/script/test/auth_cache.js @@ -225,7 +225,19 @@ couchTests.auth_cache = function(debug) { T(misses_after === misses_before); T(hits_after === (hits_before + 2)); + T(authDb.deleteDoc(fdmanana).ok); T(CouchDB.logout().ok); + + hits_before = hits_after; + misses_before = misses_after; + + T(CouchDB.login("fdmanana", "javascript").error === "unauthorized"); + + hits_after = hits(); + misses_after = misses(); + + T(misses_after === (misses_before + 1)); + T(hits_after === hits_before); } diff --git a/src/couchdb/couch_auth_cache.erl b/src/couchdb/couch_auth_cache.erl index bff801fa..078bfcc1 100644 --- a/src/couchdb/couch_auth_cache.erl +++ b/src/couchdb/couch_auth_cache.erl @@ -289,7 +289,7 @@ refresh_entry(Db, #doc_info{high_seq = DocSeq} = DocInfo) -> [] -> ok; [{UserName, {_OldCreds, ATime}}] -> - {ok, Doc} = couch_db:open_doc(Db, DocInfo, [conflicts]), + {ok, Doc} = couch_db:open_doc(Db, DocInfo, [conflicts, deleted]), NewCreds = user_creds(Doc), true = ets:insert(?BY_USER, {UserName, {NewCreds, ATime}}) end; -- cgit v1.2.3 From de53d5ca550ce54bf72273bb05687b87b4817483 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Wed, 14 Jul 2010 17:56:06 +0000 Subject: Fix test assertions introduced in revision 964108. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@964111 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/test/auth_cache.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/www/script/test/auth_cache.js b/share/www/script/test/auth_cache.js index 8c3fa943..75827dbd 100644 --- a/share/www/script/test/auth_cache.js +++ b/share/www/script/test/auth_cache.js @@ -236,8 +236,8 @@ couchTests.auth_cache = function(debug) { hits_after = hits(); misses_after = misses(); - T(misses_after === (misses_before + 1)); - T(hits_after === hits_before); + T(misses_after === misses_before); + T(hits_after === (hits_before + 1)); } -- cgit v1.2.3 From 29f789e66f71f08434985df0c7622407f0b54102 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Wed, 14 Jul 2010 21:12:19 +0000 Subject: Adapt test to change introduced in revision 963725 - deleted documents can now have non-empty bodies. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@964201 13f79535-47bb-0310-9956-ffa450edef68 --- test/etap/031-doc-to-json.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/etap/031-doc-to-json.t b/test/etap/031-doc-to-json.t index 05425d38..605a6d00 100755 --- a/test/etap/031-doc-to-json.t +++ b/test/etap/031-doc-to-json.t @@ -78,8 +78,8 @@ test_to_json_success() -> }, { #doc{deleted=true, body={[{<<"foo">>, <<"bar">>}]}}, - {[{<<"_id">>, <<>>}, {<<"_deleted">>, true}]}, - "Deleted docs drop body members." + {[{<<"_id">>, <<>>}, {<<"foo">>, <<"bar">>}, {<<"_deleted">>, true}]}, + "Deleted docs no longer drop body members." }, { #doc{meta=[ -- cgit v1.2.3 From 501e2bd5b85eb4df71e071e3df284df9bf36d92a Mon Sep 17 00:00:00 2001 From: Robert Newson Date: Thu, 15 Jul 2010 19:39:18 +0000 Subject: Update AUTHORS to use my new apache.org address. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@964549 13f79535-47bb-0310-9956-ffa450edef68 --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index b1a3559e..e0181c1d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,6 +15,6 @@ documentation or developing software. Some of these people are: * Mark Hammond * Benoît Chesneau * Filipe Manana - * Robert Newson + * Robert Newson For a list of other credits see the `THANKS` file. -- cgit v1.2.3 From aa979801f94952acd558f7809417b40104846cfb Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Fri, 16 Jul 2010 21:32:57 +0000 Subject: Fix for a pull replication, targeted to a 1.0 CouchDB server, where the source DB is in a remote CouchDB 0.11.0 server and the target DB is local (1.0 CouchDB DB). Closes ticket COUCHDB-827. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@964956 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_db.erl | 16 +++++++++++++--- src/couchdb/couch_rep_att.erl | 8 ++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl index 80f0d7bf..b445046f 100644 --- a/src/couchdb/couch_db.erl +++ b/src/couchdb/couch_db.erl @@ -901,10 +901,20 @@ with_stream(Fd, #att{md5=InMd5,type=Type,encoding=Enc}=Att, Fun) -> write_streamed_attachment(_Stream, _F, 0) -> ok; +% LenLeft might be different from the total size of what function F returns. +% This happens when doing a pull replication of compressed attachments from a +% 0.11.0 server, where LenLeft will match the uncompressed size but we end up +% receiving the attachment compressed (therefore a size different from LenLeft). +% This is because 0.11.0 doesn't understand the query parameter +% "?att_encoding_info=true" when we do a doc request (GET /somedb/somedoc). write_streamed_attachment(Stream, F, LenLeft) -> - Bin = F(), - ok = couch_stream:write(Stream, Bin), - write_streamed_attachment(Stream, F, LenLeft - size(Bin)). + case F() of + Bin when is_binary(Bin) -> + ok = couch_stream:write(Stream, Bin), + write_streamed_attachment(Stream, F, LenLeft - size(Bin)); + eof -> + ok + end. enum_docs_since_reduce_to_count(Reds) -> couch_btree:final_reduce( diff --git a/src/couchdb/couch_rep_att.erl b/src/couchdb/couch_rep_att.erl index 28b8945c..367afbb5 100644 --- a/src/couchdb/couch_rep_att.erl +++ b/src/couchdb/couch_rep_att.erl @@ -81,8 +81,12 @@ receive_data(Ref, ReqId, ContentEncoding) -> % ?LOG_DEBUG("got ~p bytes for ~p", [size(Data), ReqId]), Data; {ibrowse_async_response_end, ReqId} -> - ?LOG_ERROR("streaming att. ended but more data requested ~p", [ReqId]), - throw({attachment_request_failed, premature_end}) + % This means ibrowse received all the data it was supposed to. + % In case of not receiving the whole data, due to a network link + % failure for example, we would have received an error message. + % In other words, this message doesn't represent an error - look into + % ibrowse_http_client.erl. + eof after 31000 -> throw({attachment_request_failed, timeout}) end. -- cgit v1.2.3 From 2cb0b6ba823521792ad481d6faf8e52af09c531f Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Fri, 16 Jul 2010 22:55:14 +0000 Subject: Revert revision 964956 (COUCHDB-827) - solution had secondary effects. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@964969 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_db.erl | 16 +++------------- src/couchdb/couch_rep_att.erl | 8 ++------ 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl index b445046f..80f0d7bf 100644 --- a/src/couchdb/couch_db.erl +++ b/src/couchdb/couch_db.erl @@ -901,20 +901,10 @@ with_stream(Fd, #att{md5=InMd5,type=Type,encoding=Enc}=Att, Fun) -> write_streamed_attachment(_Stream, _F, 0) -> ok; -% LenLeft might be different from the total size of what function F returns. -% This happens when doing a pull replication of compressed attachments from a -% 0.11.0 server, where LenLeft will match the uncompressed size but we end up -% receiving the attachment compressed (therefore a size different from LenLeft). -% This is because 0.11.0 doesn't understand the query parameter -% "?att_encoding_info=true" when we do a doc request (GET /somedb/somedoc). write_streamed_attachment(Stream, F, LenLeft) -> - case F() of - Bin when is_binary(Bin) -> - ok = couch_stream:write(Stream, Bin), - write_streamed_attachment(Stream, F, LenLeft - size(Bin)); - eof -> - ok - end. + Bin = F(), + ok = couch_stream:write(Stream, Bin), + write_streamed_attachment(Stream, F, LenLeft - size(Bin)). enum_docs_since_reduce_to_count(Reds) -> couch_btree:final_reduce( diff --git a/src/couchdb/couch_rep_att.erl b/src/couchdb/couch_rep_att.erl index 367afbb5..28b8945c 100644 --- a/src/couchdb/couch_rep_att.erl +++ b/src/couchdb/couch_rep_att.erl @@ -81,12 +81,8 @@ receive_data(Ref, ReqId, ContentEncoding) -> % ?LOG_DEBUG("got ~p bytes for ~p", [size(Data), ReqId]), Data; {ibrowse_async_response_end, ReqId} -> - % This means ibrowse received all the data it was supposed to. - % In case of not receiving the whole data, due to a network link - % failure for example, we would have received an error message. - % In other words, this message doesn't represent an error - look into - % ibrowse_http_client.erl. - eof + ?LOG_ERROR("streaming att. ended but more data requested ~p", [ReqId]), + throw({attachment_request_failed, premature_end}) after 31000 -> throw({attachment_request_failed, timeout}) end. -- cgit v1.2.3 From 66ab269ccf9945a72f7108f07454eca14060b632 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Sat, 17 Jul 2010 00:11:00 +0000 Subject: note about firefox support in test suite git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@964987 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/couch_tests.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/share/www/couch_tests.html b/share/www/couch_tests.html index 46893d71..f10bad23 100644 --- a/share/www/couch_tests.html +++ b/share/www/couch_tests.html @@ -68,7 +68,10 @@ specific language governing permissions and limitations under the License. Note: Each of the tests will block the browser. If the connection to your CouchDB server is slow, running the tests will take some time, and you'll not be able to do much with your browser while - a test is being executed. + a test is being executed. Also: The test suite is designed + to work with Firefox (with Firebug disabled). Patches are welcome for + convenience compatibility with other browsers, but official support is + for Firefox (latest stable version) only.

-- cgit v1.2.3 From 4c780ad5eb81e0e4c66b45fd70fbb623c3a19be6 Mon Sep 17 00:00:00 2001 From: Robert Newson Date: Mon, 19 Jul 2010 00:10:11 +0000 Subject: COUCHDB-810: Adds port to replication checkpoints. New replication checkpoints now include the port number, which allows for efficient replication between multiple couchdb instances running on the same machine. Old replication checkpoints are recognized (Full replication is not induced) and they are automatically migrated to the new checkpoint format. Thanks to Randall Leeds for the patch. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@965331 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_rep.erl | 77 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/src/couchdb/couch_rep.erl b/src/couchdb/couch_rep.erl index b741f86f..4f6fb673 100644 --- a/src/couchdb/couch_rep.erl +++ b/src/couchdb/couch_rep.erl @@ -19,6 +19,8 @@ -include("couch_db.hrl"). +-define(REP_ID_VERSION, 2). + -record(state, { changes_feed, missing_revs, @@ -59,7 +61,9 @@ replicate(Source, Target) when is_binary(Source), is_binary(Target) -> %% function handling POST to _replicate replicate({Props}=PostBody, UserCtx) -> - {BaseId, Extension} = make_replication_id(PostBody, UserCtx), + BaseId = make_replication_id({Props}, UserCtx, ?REP_ID_VERSION), + Extension = maybe_append_options( + [<<"continuous">>, <<"create_target">>], Props), Replicator = {BaseId ++ Extension, {gen_server, start_link, [?MODULE, [BaseId, PostBody, UserCtx], []]}, temporary, @@ -144,8 +148,9 @@ do_init([RepId, {PostProps}, UserCtx] = InitArgs) -> _ -> % Replication using the _changes API (DB sequence update numbers). - SourceLog = open_replication_log(Source, RepId), - TargetLog = open_replication_log(Target, RepId), + + [SourceLog, TargetLog] = find_replication_logs( + [Source, Target], RepId, {PostProps}, UserCtx), {StartSeq, History} = compare_replication_logs(SourceLog, TargetLog), @@ -451,13 +456,23 @@ maybe_append_options(Options, Props) -> end end, [], Options). -make_replication_id({Props}, UserCtx) -> - %% funky algorithm to preserve backwards compatibility +% Versioned clauses for generating replication ids +% If a change is made to how replications are identified +% add a new clause and increase ?REP_ID_VERSION at the top +make_replication_id({Props}, UserCtx, 2) -> + {ok, HostName} = inet:gethostname(), + Port = mochiweb_socket_server:get(couch_httpd, port), + Src = get_rep_endpoint(UserCtx, couch_util:get_value(<<"source">>, Props)), + Tgt = get_rep_endpoint(UserCtx, couch_util:get_value(<<"target">>, Props)), + maybe_append_filters({Props}, [HostName, Port, Src, Tgt]); +make_replication_id({Props}, UserCtx, 1) -> {ok, HostName} = inet:gethostname(), - % Port = mochiweb_socket_server:get(couch_httpd, port), Src = get_rep_endpoint(UserCtx, couch_util:get_value(<<"source">>, Props)), Tgt = get_rep_endpoint(UserCtx, couch_util:get_value(<<"target">>, Props)), - Base = [HostName, Src, Tgt] ++ + maybe_append_filters({Props}, [HostName, Src, Tgt]). + +maybe_append_filters({Props}, Base) -> + Base2 = Base ++ case couch_util:get_value(<<"filter">>, Props) of undefined -> case couch_util:get_value(<<"doc_ids">>, Props) of @@ -469,9 +484,7 @@ make_replication_id({Props}, UserCtx) -> Filter -> [Filter, couch_util:get_value(<<"query_params">>, Props, {[]})] end, - Extension = maybe_append_options( - [<<"continuous">>, <<"create_target">>], Props), - {couch_util:to_hex(couch_util:md5(term_to_binary(Base))), Extension}. + couch_util:to_hex(couch_util:md5(term_to_binary(Base2))). maybe_add_trailing_slash(Url) -> re:replace(Url, "[^/]$", "&/", [{return, list}]). @@ -493,26 +506,52 @@ get_rep_endpoint(_UserCtx, <<"https://",_/binary>>=Url) -> get_rep_endpoint(UserCtx, <>) -> {local, DbName, UserCtx}. -open_replication_log(#http_db{}=Db, RepId) -> - DocId = ?LOCAL_DOC_PREFIX ++ RepId, - Req = Db#http_db{resource=couch_util:url_encode(DocId)}, +find_replication_logs(Logs, RepId, {Props}, UserCtx) -> + LogId = ?l2b(?LOCAL_DOC_PREFIX ++ RepId), + fold_replication_logs(Logs, ?REP_ID_VERSION, + LogId, LogId, {Props}, UserCtx, []). + +% Accumulate the replication logs +% Falls back to older log document ids and migrates them +fold_replication_logs([], _Vsn, _LogId, _NewId, {_Props}, _UserCtx, Acc) -> + lists:reverse(Acc); +fold_replication_logs([Db|Rest]=Dbs, Vsn, LogId, NewId, + {Props}, UserCtx, Acc) -> + case open_replication_log(Db, LogId) of + {error, not_found} when Vsn > 1 -> + OldRepId = make_replication_id({Props}, UserCtx, Vsn - 1), + fold_replication_logs(Dbs, Vsn - 1, + ?l2b(?LOCAL_DOC_PREFIX ++ OldRepId), NewId, {Props}, UserCtx, Acc); + {error, not_found} -> + fold_replication_logs(Rest, ?REP_ID_VERSION, NewId, NewId, + {Props}, UserCtx, [#doc{id=NewId}|Acc]); + {ok, Doc} when LogId =:= NewId -> + fold_replication_logs(Rest, ?REP_ID_VERSION, NewId, NewId, + {Props}, UserCtx, [Doc|Acc]); + {ok, Doc} -> + MigratedLog = #doc{id=NewId,body=Doc#doc.body}, + fold_replication_logs(Rest, ?REP_ID_VERSION, NewId, NewId, + {Props}, UserCtx, [MigratedLog|Acc]) + end. + +open_replication_log(#http_db{}=Db, DocId) -> + Req = Db#http_db{resource=couch_util:url_encode(?b2l(DocId))}, case couch_rep_httpc:request(Req) of {[{<<"error">>, _}, {<<"reason">>, _}]} -> ?LOG_DEBUG("didn't find a replication log for ~s", [Db#http_db.url]), - #doc{id=?l2b(DocId)}; + {error, not_found}; Doc -> ?LOG_DEBUG("found a replication log for ~s", [Db#http_db.url]), - couch_doc:from_json_obj(Doc) + {ok, couch_doc:from_json_obj(Doc)} end; -open_replication_log(Db, RepId) -> - DocId = ?l2b(?LOCAL_DOC_PREFIX ++ RepId), +open_replication_log(Db, DocId) -> case couch_db:open_doc(Db, DocId, []) of {ok, Doc} -> ?LOG_DEBUG("found a replication log for ~s", [Db#db.name]), - Doc; + {ok, Doc}; _ -> ?LOG_DEBUG("didn't find a replication log for ~s", [Db#db.name]), - #doc{id=DocId} + {error, not_found} end. open_db(Props, UserCtx) -> -- cgit v1.2.3 From 11aa4f9db32ef33ef8b9e17ee2a685c95bc553b2 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Mon, 19 Jul 2010 11:32:46 +0000 Subject: Fix possible bad match error - WriteFun might not return the atom 'ok'. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@965434 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_doc.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/couchdb/couch_doc.erl b/src/couchdb/couch_doc.erl index acae0c55..a6d23a91 100644 --- a/src/couchdb/couch_doc.erl +++ b/src/couchdb/couch_doc.erl @@ -437,7 +437,7 @@ atts_to_mp([Att | RestAtts], Boundary, WriteFun, true -> fun att_foldl/3 end, - AttFun(Att, fun(Data, ok) -> WriteFun(Data) end, ok), + AttFun(Att, fun(Data, _) -> WriteFun(Data) end, ok), WriteFun(<<"\r\n--", Boundary/binary>>), atts_to_mp(RestAtts, Boundary, WriteFun, SendEncodedAtts). -- cgit v1.2.3 From 662bf6812ef0a4fa80cf137761f9b1b5a93821c0 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Mon, 19 Jul 2010 22:46:14 +0000 Subject: remove unguarded atom creation to prevent DOS attacks. closes COUCHDB-829 git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@965667 13f79535-47bb-0310-9956-ffa450edef68 --- src/couchdb/couch_db.hrl | 1 - src/couchdb/couch_doc.erl | 2 +- src/couchdb/couch_httpd.erl | 2 +- src/couchdb/couch_httpd_rewrite.erl | 66 +++++++++++++++--------------- src/couchdb/couch_httpd_stats_handlers.erl | 3 +- src/couchdb/couch_httpd_view.erl | 4 +- src/couchdb/couch_os_process.erl | 4 +- src/couchdb/couch_rep.erl | 2 +- src/couchdb/couch_util.erl | 4 +- 9 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index 4497dc8f..a35745ef 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -20,7 +20,6 @@ -define(JSON_ENCODE(V), couch_util:json_encode(V)). -define(JSON_DECODE(V), couch_util:json_decode(V)). --define(b2a(V), list_to_atom(binary_to_list(V))). -define(b2l(V), binary_to_list(V)). -define(l2b(V), list_to_binary(V)). diff --git a/src/couchdb/couch_doc.erl b/src/couchdb/couch_doc.erl index a6d23a91..d15cd7de 100644 --- a/src/couchdb/couch_doc.erl +++ b/src/couchdb/couch_doc.erl @@ -267,7 +267,7 @@ att_encoding_info(BinProps) -> {identity, DiskLen}; Enc -> EncodedLen = couch_util:get_value(<<"encoded_length">>, BinProps, DiskLen), - {list_to_atom(?b2l(Enc)), EncodedLen} + {list_to_existing_atom(?b2l(Enc)), EncodedLen} end. to_doc_info(FullDocInfo) -> diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index 079b9367..faf14bcc 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -225,7 +225,7 @@ handle_request_int(MochiReq, DefaultFun, true -> ?LOG_INFO("MethodOverride: ~s (real method was ~s)", [MethodOverride, Method1]), case Method1 of - 'POST' -> list_to_atom(MethodOverride); + 'POST' -> couch_util:to_existing_atom(MethodOverride); _ -> % Ignore X-HTTP-Method-Override when the original verb isn't POST. % I'd like to send a 406 error to the client, but that'd require a nasty refactor. diff --git a/src/couchdb/couch_httpd_rewrite.erl b/src/couchdb/couch_httpd_rewrite.erl index 6c909994..ca4ac1f0 100644 --- a/src/couchdb/couch_httpd_rewrite.erl +++ b/src/couchdb/couch_httpd_rewrite.erl @@ -20,7 +20,7 @@ -include("couch_db.hrl"). -define(SEPARATOR, $\/). --define(MATCH_ALL, '*'). +-define(MATCH_ALL, {bind, <<"*">>}). %% doc The http rewrite handler. All rewriting is done from @@ -118,7 +118,7 @@ handle_rewrite_req(#httpd{ DesignId = <<"_design/", DesignName/binary>>, Prefix = <<"/", DbName/binary, "/", DesignId/binary>>, QueryList = couch_httpd:qs(Req), - QueryList1 = [{to_atom(K), V} || {K, V} <- QueryList], + QueryList1 = [{to_binding(K), V} || {K, V} <- QueryList], #doc{body={Props}} = DDoc, @@ -132,12 +132,12 @@ handle_rewrite_req(#httpd{ DispatchList = [make_rule(Rule) || {Rule} <- Rules], %% get raw path by matching url to a rule. - RawPath = case try_bind_path(DispatchList, Method, PathParts, + RawPath = case try_bind_path(DispatchList, couch_util:to_binary(Method), PathParts, QueryList1) of no_dispatch_path -> throw(not_found); {NewPathParts, Bindings} -> - Parts = [mochiweb_util:quote_plus(X) || X <- NewPathParts], + Parts = [quote_plus(X) || X <- NewPathParts], % build new path, reencode query args, eventually convert % them to json @@ -183,7 +183,10 @@ handle_rewrite_req(#httpd{ UrlHandlers, DbUrlHandlers, DesignUrlHandlers) end. - +quote_plus({bind, X}) -> + mochiweb_util:quote_plus(X); +quote_plus(X) -> + mochiweb_util:quote_plus(X). %% @doc Try to find a rule matching current url. If none is found %% 404 error not_found is raised @@ -196,15 +199,13 @@ try_bind_path([Dispatch|Rest], Method, PathParts, QueryList) -> case bind_path(PathParts1, PathParts, []) of {ok, Remaining, Bindings} -> Bindings1 = Bindings ++ QueryList, - % we parse query args from the rule and fill % it eventually with bindings vars QueryArgs1 = make_query_list(QueryArgs, Bindings1, []), - % remove params in QueryLists1 that are already in % QueryArgs1 Bindings2 = lists:foldl(fun({K, V}, Acc) -> - K1 = to_atom(K), + K1 = to_binding(K), KV = case couch_util:get_value(K1, QueryArgs1) of undefined -> [{K1, V}]; _V1 -> [] @@ -230,15 +231,15 @@ make_query_list([], _Bindings, Acc) -> Acc; make_query_list([{Key, {Value}}|Rest], Bindings, Acc) -> Value1 = to_json({Value}), - make_query_list(Rest, Bindings, [{to_atom(Key), Value1}|Acc]); + make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]); make_query_list([{Key, Value}|Rest], Bindings, Acc) when is_binary(Value) -> Value1 = replace_var(Key, Value, Bindings), - make_query_list(Rest, Bindings, [{to_atom(Key), Value1}|Acc]); + make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]); make_query_list([{Key, Value}|Rest], Bindings, Acc) when is_list(Value) -> Value1 = replace_var(Key, Value, Bindings), - make_query_list(Rest, Bindings, [{to_atom(Key), Value1}|Acc]); + make_query_list(Rest, Bindings, [{to_binding(Key), Value1}|Acc]); make_query_list([{Key, Value}|Rest], Bindings, Acc) -> - make_query_list(Rest, Bindings, [{to_atom(Key), Value}|Acc]). + make_query_list(Rest, Bindings, [{to_binding(Key), Value}|Acc]). replace_var(Key, Value, Bindings) -> case Value of @@ -274,7 +275,7 @@ replace_var(Key, Value, Bindings) -> get_var(VarName, Props, Default) -> - VarName1 = list_to_atom(binary_to_list(VarName)), + VarName1 = to_binding(VarName), couch_util:get_value(VarName1, Props, Default). %% doc: build new patch from bindings. bindings are query args @@ -288,8 +289,8 @@ make_new_path([?MATCH_ALL], _Bindings, Remaining, Acc) -> make_new_path([?MATCH_ALL|_Rest], _Bindings, Remaining, Acc) -> Acc1 = lists:reverse(Acc) ++ Remaining, Acc1; -make_new_path([P|Rest], Bindings, Remaining, Acc) when is_atom(P) -> - P2 = case couch_util:get_value(P, Bindings) of +make_new_path([{bind, P}|Rest], Bindings, Remaining, Acc) -> + P2 = case couch_util:get_value({bind, P}, Bindings) of undefined -> << "undefined">>; P1 -> P1 end, @@ -304,7 +305,7 @@ make_new_path([P|Rest], Bindings, Remaining, Acc) -> %% depending on HTTP method. bind_method(?MATCH_ALL, _Method) -> true; -bind_method(Method, Method) -> +bind_method({bind, Method}, Method) -> true; bind_method(_, _) -> false. @@ -318,8 +319,8 @@ bind_path([?MATCH_ALL], Rest, Bindings) when is_list(Rest) -> {ok, Rest, Bindings}; bind_path(_, [], _) -> fail; -bind_path([Token|RestToken],[Match|RestMatch],Bindings) when is_atom(Token) -> - bind_path(RestToken, RestMatch, [{Token, Match}|Bindings]); +bind_path([{bind, Token}|RestToken],[Match|RestMatch],Bindings) -> + bind_path(RestToken, RestMatch, [{{bind, Token}, Match}|Bindings]); bind_path([Token|RestToken], [Token|RestMatch], Bindings) -> bind_path(RestToken, RestMatch, Bindings); bind_path(_, _, _) -> @@ -350,15 +351,15 @@ normalize_path1([Path|Rest], Acc) -> %% @doc transform json rule in erlang for pattern matching make_rule(Rule) -> Method = case couch_util:get_value(<<"method">>, Rule) of - undefined -> '*'; - M -> list_to_atom(?b2l(M)) + undefined -> ?MATCH_ALL; + M -> to_binding(M) end, QueryArgs = case couch_util:get_value(<<"query">>, Rule) of undefined -> []; {Args} -> Args end, FromParts = case couch_util:get_value(<<"from">>, Rule) of - undefined -> ['*']; + undefined -> [?MATCH_ALL]; From -> parse_path(From) end, @@ -396,30 +397,29 @@ path_to_list([<<"..">>|R], Acc, DotDotCount) -> path_to_list([P|R], Acc, DotDotCount) -> P1 = case P of <<":", Var/binary>> -> - list_to_atom(binary_to_list(Var)); + to_binding(Var); _ -> P end, path_to_list(R, [P1|Acc], DotDotCount). encode_query(Props) -> - Props1 = lists:foldl(fun ({K, V}, Acc) -> - V1 = case is_list(V) of + Props1 = lists:foldl(fun ({{bind, K}, V}, Acc) -> + V1 = case is_list(V) orelse is_binary(V) of true -> V; - false when is_binary(V) -> - V; false -> - mochiweb_util:quote_plus(V) + % probably it's a number + quote_plus(V) end, [{K, V1} | Acc] end, [], Props), lists:flatten(mochiweb_util:urlencode(Props1)). -to_atom(V) when is_atom(V) -> - V; -to_atom(V) when is_binary(V) -> - to_atom(?b2l(V)); -to_atom(V) -> - list_to_atom(V). +to_binding({bind, V}) -> + {bind, V}; +to_binding(V) when is_list(V) -> + to_binding(?l2b(V)); +to_binding(V) -> + {bind, V}. to_json(V) -> iolist_to_binary(?JSON_ENCODE(V)). diff --git a/src/couchdb/couch_httpd_stats_handlers.erl b/src/couchdb/couch_httpd_stats_handlers.erl index 26c8891d..41aeaed0 100644 --- a/src/couchdb/couch_httpd_stats_handlers.erl +++ b/src/couchdb/couch_httpd_stats_handlers.erl @@ -29,7 +29,8 @@ handle_stats_req(#httpd{method='GET', path_parts=[_, _Mod]}) -> handle_stats_req(#httpd{method='GET', path_parts=[_, Mod, Key]}=Req) -> flush(Req), - Stats = couch_stats_aggregator:get_json({?b2a(Mod), ?b2a(Key)}, range(Req)), + Stats = couch_stats_aggregator:get_json({list_to_atom(binary_to_list(Mod)), + list_to_atom(binary_to_list(Key))}, range(Req)), send_json(Req, {[{Mod, {[{Key, Stats}]}}]}); handle_stats_req(#httpd{method='GET', path_parts=[_, _Mod, _Key | _Extra]}) -> diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl index 704136ca..65aa364a 100644 --- a/src/couchdb/couch_httpd_view.erl +++ b/src/couchdb/couch_httpd_view.erl @@ -196,10 +196,10 @@ reverse_key_default(?MAX_STR) -> ?MIN_STR; reverse_key_default(Key) -> Key. get_stale_type(Req) -> - list_to_atom(couch_httpd:qs_value(Req, "stale", "nil")). + list_to_existing_atom(couch_httpd:qs_value(Req, "stale", "nil")). get_reduce_type(Req) -> - list_to_atom(couch_httpd:qs_value(Req, "reduce", "true")). + list_to_existing_atom(couch_httpd:qs_value(Req, "reduce", "true")). load_view(Req, Db, {ViewDesignId, ViewName}, Keys) -> Stale = get_stale_type(Req), diff --git a/src/couchdb/couch_os_process.erl b/src/couchdb/couch_os_process.erl index 070b86fc..5776776b 100644 --- a/src/couchdb/couch_os_process.erl +++ b/src/couchdb/couch_os_process.erl @@ -93,10 +93,10 @@ readjson(OsProc) when is_record(OsProc, os_proc) -> ?LOG_INFO("OS Process ~p Log :: ~s", [OsProc#os_proc.port, Msg]), readjson(OsProc); [<<"error">>, Id, Reason] -> - throw({list_to_atom(binary_to_list(Id)),Reason}); + throw({couch_util:to_existing_atom(Id),Reason}); [<<"fatal">>, Id, Reason] -> ?LOG_INFO("OS Process ~p Fatal Error :: ~s ~p",[OsProc#os_proc.port, Id, Reason]), - throw({list_to_atom(binary_to_list(Id)),Reason}); + throw({couch_util:to_existing_atom(Id),Reason}); Result -> Result end. diff --git a/src/couchdb/couch_rep.erl b/src/couchdb/couch_rep.erl index 4f6fb673..1e36f58e 100644 --- a/src/couchdb/couch_rep.erl +++ b/src/couchdb/couch_rep.erl @@ -367,7 +367,7 @@ strip_password(Url) -> dbinfo(#http_db{} = Db) -> {DbProps} = couch_rep_httpc:request(Db), - [{list_to_atom(?b2l(K)), V} || {K,V} <- DbProps]; + [{list_to_existing_atom(?b2l(K)), V} || {K,V} <- DbProps]; dbinfo(Db) -> {ok, Info} = couch_db:get_db_info(Db), Info. diff --git a/src/couchdb/couch_util.erl b/src/couchdb/couch_util.erl index 2aa69af8..f2bf6297 100644 --- a/src/couchdb/couch_util.erl +++ b/src/couchdb/couch_util.erl @@ -70,9 +70,9 @@ normparts([Part | RestParts], Acc) -> % works like list_to_existing_atom, except can be list or binary and it % gives you the original value instead of an error if no existing atom. to_existing_atom(V) when is_list(V) -> - try list_to_existing_atom(V) catch _ -> V end; + try list_to_existing_atom(V) catch _:_ -> V end; to_existing_atom(V) when is_binary(V) -> - try list_to_existing_atom(?b2l(V)) catch _ -> V end; + try list_to_existing_atom(?b2l(V)) catch _:_ -> V end; to_existing_atom(V) when is_atom(V) -> V. -- cgit v1.2.3 From ae1499d95d5337870da57620130eab79cc0b8490 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Tue, 20 Jul 2010 01:03:10 +0000 Subject: require application/json content-type in the remaining places where a POST has side-effects git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@965700 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/test/view_errors.js | 4 ++-- src/couchdb/couch_httpd_db.erl | 1 + src/couchdb/couch_httpd_misc_handlers.erl | 3 +++ src/couchdb/couch_httpd_view.erl | 2 ++ src/couchdb/couch_rep.erl | 5 +++-- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/share/www/script/test/view_errors.js b/share/www/script/test/view_errors.js index c6607ad4..a211c061 100644 --- a/share/www/script/test/view_errors.js +++ b/share/www/script/test/view_errors.js @@ -56,14 +56,14 @@ couchTests.view_errors = function(debug) { }); T(JSON.parse(xhr.responseText).error == "bad_request"); - // views should ignore Content-Type, like the rest of CouchDB + // content type must be json var xhr = CouchDB.request("POST", "/test_suite_db/_temp_view", { headers: {"Content-Type": "application/x-www-form-urlencoded"}, body: JSON.stringify({language: "javascript", map : "function(doc){}" }) }); - T(xhr.status == 200); + T(xhr.status == 415); var map = function (doc) {emit(doc.integer, doc.integer);}; diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 05979e46..29b20c21 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -242,6 +242,7 @@ db_req(#httpd{path_parts=[_DbName]}=Req, _Db) -> send_method_not_allowed(Req, "DELETE,GET,HEAD,POST"); db_req(#httpd{method='POST',path_parts=[_,<<"_ensure_full_commit">>]}=Req, Db) -> + couch_httpd:validate_ctype(Req, "application/json"), UpdateSeq = couch_db:get_update_seq(Db), CommittedSeq = couch_db:get_committed_update_seq(Db), {ok, StartTime} = diff --git a/src/couchdb/couch_httpd_misc_handlers.erl b/src/couchdb/couch_httpd_misc_handlers.erl index 205ebc6f..0a6f4a42 100644 --- a/src/couchdb/couch_httpd_misc_handlers.erl +++ b/src/couchdb/couch_httpd_misc_handlers.erl @@ -79,6 +79,7 @@ handle_task_status_req(Req) -> send_method_not_allowed(Req, "GET,HEAD"). handle_replicate_req(#httpd{method='POST'}=Req) -> + couch_httpd:validate_ctype(Req, "application/json"), PostBody = couch_httpd:json_body_obj(Req), try couch_rep:replicate(PostBody, Req#httpd.user_ctx) of {ok, {continuous, RepId}} -> @@ -102,6 +103,7 @@ handle_replicate_req(Req) -> handle_restart_req(#httpd{method='POST'}=Req) -> + couch_httpd:validate_ctype(Req, "application/json"), ok = couch_httpd:verify_is_server_admin(Req), couch_server_sup:restart_core_server(), send_json(Req, 200, {[{ok, true}]}); @@ -189,6 +191,7 @@ handle_config_req(Req) -> % httpd db handlers increment_update_seq_req(#httpd{method='POST'}=Req, Db) -> + couch_httpd:validate_ctype(Req, "application/json"), {ok, NewSeq} = couch_db:increment_update_seq(Db), send_json(Req, {[{ok, true}, {update_seq, NewSeq} diff --git a/src/couchdb/couch_httpd_view.erl b/src/couchdb/couch_httpd_view.erl index 65aa364a..fc0c24a8 100644 --- a/src/couchdb/couch_httpd_view.erl +++ b/src/couchdb/couch_httpd_view.erl @@ -61,6 +61,7 @@ handle_view_req(#httpd{method='GET', handle_view_req(#httpd{method='POST', path_parts=[_, _, DName, _, ViewName]}=Req, Db, _DDoc) -> + couch_httpd:validate_ctype(Req, "application/json"), {Fields} = couch_httpd:json_body_obj(Req), case couch_util:get_value(<<"keys">>, Fields, nil) of nil -> @@ -77,6 +78,7 @@ handle_view_req(Req, _Db, _DDoc) -> send_method_not_allowed(Req, "GET,POST,HEAD"). handle_temp_view_req(#httpd{method='POST'}=Req, Db) -> + couch_httpd:validate_ctype(Req, "application/json"), ok = couch_db:check_is_admin(Db), couch_stats_collector:increment({httpd, temporary_view_reads}), {Props} = couch_httpd:json_body_obj(Req), diff --git a/src/couchdb/couch_rep.erl b/src/couchdb/couch_rep.erl index 1e36f58e..07ee8416 100644 --- a/src/couchdb/couch_rep.erl +++ b/src/couchdb/couch_rep.erl @@ -696,8 +696,9 @@ ensure_full_commit(#http_db{headers = Headers} = Target) -> Req = Target#http_db{ resource = "_ensure_full_commit", method = post, - headers = [{"content-type", "application/json"} | Headers] + headers = couch_util:proplist_apply_field({"Content-Type", "application/json"}, Headers) }, + ?LOG_ERROR("Req ~p",[Req]), {ResultProps} = couch_rep_httpc:request(Req), true = couch_util:get_value(<<"ok">>, ResultProps), couch_util:get_value(<<"instance_start_time">>, ResultProps); @@ -722,7 +723,7 @@ ensure_full_commit(#http_db{headers = Headers} = Source, RequiredSeq) -> resource = "_ensure_full_commit", method = post, qs = [{seq, RequiredSeq}], - headers = [{"content-type", "application/json"} | Headers] + headers = couch_util:proplist_apply_field({"Content-Type", "application/json"}, Headers) }, {ResultProps} = couch_rep_httpc:request(Req), case couch_util:get_value(<<"ok">>, ResultProps) of -- cgit v1.2.3 From 5fc1e706ee5015e6a555f46a5eea601b4ec3bf63 Mon Sep 17 00:00:00 2001 From: Jan Lehnardt Date: Tue, 20 Jul 2010 10:42:21 +0000 Subject: add link to the wiki about the messed up ubuntu install situation. Closes COUCHDB-830 git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@965790 13f79535-47bb-0310-9956-ffa450edef68 --- INSTALL.Unix | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/INSTALL.Unix b/INSTALL.Unix index 93b4803d..768e3846 100644 --- a/INSTALL.Unix +++ b/INSTALL.Unix @@ -18,8 +18,17 @@ You will need the following installed: It is recommended that you install Erlang OTP R12B-5 or above where possible. -Debian-based (inc. Ubuntu) Systems -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Ubuntu +~~~~~~ + +See + + http://wiki.apache.org/couchdb/Installing_on_Ubuntu + +for updated instructions on how to install on Ubuntu. + +Debian-based Systems +~~~~~~~~~~~~~~~~~~~~ You can install the build tools by running: -- cgit v1.2.3 From 6bd11d1195f63380bcfd4a1ac6aa6a10700ad5f2 Mon Sep 17 00:00:00 2001 From: Filipe David Borba Manana Date: Wed, 21 Jul 2010 15:43:07 +0000 Subject: Add "Stale views" checkbox to Futon's database pages. Closes COUCHDB-550. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@966283 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/database.html | 11 +++++++++++ share/www/script/futon.browse.js | 8 +++++++- share/www/style/layout.css | 7 +++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/share/www/database.html b/share/www/database.html index 5142ed1b..39507398 100644 --- a/share/www/database.html +++ b/share/www/database.html @@ -81,6 +81,9 @@ specific language governing permissions and limitations under the License. $("#perpage").val($.futon.storage.get("per_page")); + var staleViews = $.futon.storage.get("stale"); + $("#staleviews :checkbox")[0].checked = staleViews; + page.populateViewsMenu(); page.populateViewEditor(); if (page.isTempView) { @@ -94,6 +97,9 @@ specific language governing permissions and limitations under the License. location.href = "?" + encodeURIComponent(page.db.name) + (viewName ? "/" + viewName : ""); }); + $("#staleviews :checkbox").click(function() { + $.futon.storage.set("stale", this.checked); + }); $("#documents thead th.key span").click(function() { $(this).closest("th").toggleClass("desc"); page.updateDocumentListing(); @@ -148,6 +154,11 @@ specific language governing permissions and limitations under the License.
+
+ +
+
+ +    +
+ + + + diff --git a/1.1.x/share/www/database.html b/1.1.x/share/www/database.html new file mode 100644 index 00000000..9a9f121e --- /dev/null +++ b/1.1.x/share/www/database.html @@ -0,0 +1,267 @@ + + + + + Browse Database + + + + + + + + + + + + + + + +
+

+ Overview + ? + +

+
+
+ +
+
+ +
+
+ +
+
    +
  • +
  • +
  • +
  • +
+ + +

+ Warning: Please note that temporary views are not + suitable for use in production, as they are really slow for any + database with more than a few dozen documents. You can use a temporary + view to experiment with view functions, but switch to a permanent view + before using them in an application. +

+ + + + + + + + + + + + + + + + +
Documents
+ + Key + + + Value +
+
+ | + | + +
+ +
+
+
+ diff --git a/1.1.x/share/www/dialog/_admin_party.html b/1.1.x/share/www/dialog/_admin_party.html new file mode 100644 index 00000000..ea9fb15a --- /dev/null +++ b/1.1.x/share/www/dialog/_admin_party.html @@ -0,0 +1,33 @@ + +
+

Admin Party!

+
+

+ The test suite requires CouchDB to be in Admin Party mode. This + mode give all users admin capabilities. This is the least secure mode of + operation. Do not run the tests on production servers, as you'll impact + both performance and security. +

+

+ Clicking “Remove Admins” will remove all admins from the configuration. You will + have to recreate any admins by hand after the tests have finished. +

+
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_change_password.html b/1.1.x/share/www/dialog/_change_password.html new file mode 100644 index 00000000..40601d9a --- /dev/null +++ b/1.1.x/share/www/dialog/_change_password.html @@ -0,0 +1,31 @@ + +
+

Change Password

+
+ + + + + + + +
+
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_compact_cleanup.html b/1.1.x/share/www/dialog/_compact_cleanup.html new file mode 100644 index 00000000..506417f4 --- /dev/null +++ b/1.1.x/share/www/dialog/_compact_cleanup.html @@ -0,0 +1,51 @@ + +
+

Compact & Cleanup

+
+ +

+ Compacting a database removes deleted documents and previous revisions. + It is an irreversible operation and may take + a while to complete for large databases. +

+
+ +

+ View compaction will affect all views in this design document. This + operation may take some time to complete. Your views will still operate + normally during compaction. +

+
+ +

+ Cleaning up views in a database removes old view files still stored + on the filesystem. It is an irreversible operation. +

+
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_create_admin.html b/1.1.x/share/www/dialog/_create_admin.html new file mode 100644 index 00000000..d4aec95a --- /dev/null +++ b/1.1.x/share/www/dialog/_create_admin.html @@ -0,0 +1,50 @@ + +
+

Create Server Admin

+
+

+ Before a server admin is configured, all clients have admin privileges. + This is fine when HTTP access is restricted + to trusted users. If end-users will be accessing this CouchDB, you must + create an admin account to prevent accidental (or malicious) data loss. +

+

Server admins can create and destroy databases, install + and update _design documents, run the test suite, and edit all aspects of CouchDB + configuration. +

+ + + + + + + +
+

Non-admin users have read and write access to all databases, which + are controlled by validation functions. CouchDB can be configured to block all + access to anonymous users. +

+

About Authentication

+

+ Couch has a pluggable authentication mechanism. Futon exposes a user friendly cookie-auth which handles login and logout, so app developers can relax. Just use $.couch.session() to load the current user's info. +

+ +
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_create_config.html b/1.1.x/share/www/dialog/_create_config.html new file mode 100644 index 00000000..79e08b08 --- /dev/null +++ b/1.1.x/share/www/dialog/_create_config.html @@ -0,0 +1,42 @@ + +
+

Create New Config Option

+
+

+ Please enter the section, option, and value. +

+ + + + + + + + + + + + + + + +
+
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_create_database.html b/1.1.x/share/www/dialog/_create_database.html new file mode 100644 index 00000000..74e7ea61 --- /dev/null +++ b/1.1.x/share/www/dialog/_create_database.html @@ -0,0 +1,33 @@ + +
+

Create New Database

+
+

+ Please enter the name of the database. Note that only lowercase + characters (a-z), digits (0-9), or any of the + characters _, $, (, ), +, + -, and / are allowed. +

+ + + +
+
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_database_security.html b/1.1.x/share/www/dialog/_database_security.html new file mode 100644 index 00000000..d63fa787 --- /dev/null +++ b/1.1.x/share/www/dialog/_database_security.html @@ -0,0 +1,50 @@ + +
+

Security

+
+

+ Each database contains lists of admins and readers. + Admins and readers are each defined by names and roles, which are lists of strings. +

+ +

Admins

+

Database admins can update design documents and edit the readers list.

+ + + + + + + +
+ +

Readers

+

Database readers can access the database. If no readers are defined, the database is public.

+ + + + + + + +
+ +
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_delete_database.html b/1.1.x/share/www/dialog/_delete_database.html new file mode 100644 index 00000000..039ba39b --- /dev/null +++ b/1.1.x/share/www/dialog/_delete_database.html @@ -0,0 +1,27 @@ + +
+

Delete Database

+
+

+ Are you sure you want to delete this database? Note that this is an + irreversible operation! +

+
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_delete_document.html b/1.1.x/share/www/dialog/_delete_document.html new file mode 100644 index 00000000..8ae89710 --- /dev/null +++ b/1.1.x/share/www/dialog/_delete_document.html @@ -0,0 +1,26 @@ + +
+

Delete Document

+
+

+ Are you sure you want to delete this document? +

+
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_login.html b/1.1.x/share/www/dialog/_login.html new file mode 100644 index 00000000..f05a5fdc --- /dev/null +++ b/1.1.x/share/www/dialog/_login.html @@ -0,0 +1,34 @@ + +
+

Login

+
+

+ Login to CouchDB with your name and password. +

+ + + + + + + +
+
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_save_view_as.html b/1.1.x/share/www/dialog/_save_view_as.html new file mode 100644 index 00000000..d59122bf --- /dev/null +++ b/1.1.x/share/www/dialog/_save_view_as.html @@ -0,0 +1,35 @@ + +
+

Save View As…

+
+

+ You can save this function code as a permanent view in the database. Just + enter or select the design document and the name of the view below. Note + that if you choose an existing view, it will be overwritten! +

+ + + + + + +
_design/
+
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_share_test_reports.html b/1.1.x/share/www/dialog/_share_test_reports.html new file mode 100644 index 00000000..82b49a74 --- /dev/null +++ b/1.1.x/share/www/dialog/_share_test_reports.html @@ -0,0 +1,42 @@ + +
+

Share Test Reports

+
+

+ After each test run, a results summary document is stored in + your local + test_suite_reports database. The data has no personally + identifying information, just details about the test run and your CouchDB + and browser versions. (Click the red link above to see what's stored.) + The data remains private until you click the "share" button below. +

+

+ Test reports are very valuable to the CouchDB community, and are easy to share. + Clicking the "share" button below triggers replication from + your local test_suite_reports database, to a database hosted by the + project. +

+

+ + Browse test reports shared by other users. + Thank you for sharing! +

+
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_signup.html b/1.1.x/share/www/dialog/_signup.html new file mode 100644 index 00000000..7ba3448a --- /dev/null +++ b/1.1.x/share/www/dialog/_signup.html @@ -0,0 +1,35 @@ + +
+

Create User Account

+
+

+ Create a user document on this CouchDB. You will be logged in as this + user after the document is created. +

+ + + + + + + +
+
+
+ + +
+
diff --git a/1.1.x/share/www/dialog/_upload_attachment.html b/1.1.x/share/www/dialog/_upload_attachment.html new file mode 100644 index 00000000..50b7e1fa --- /dev/null +++ b/1.1.x/share/www/dialog/_upload_attachment.html @@ -0,0 +1,36 @@ + +
+

Upload Attachment

+
+

+ Please select the file you want to upload as an attachment to this + document. Please note that this will result in the immediate creation of + a new revision of the document, so it's not necessary to save the + document after the upload. +

+ + + + + +
 
+
+
+ + + +
+
diff --git a/1.1.x/share/www/document.html b/1.1.x/share/www/document.html new file mode 100644 index 00000000..b6f42018 --- /dev/null +++ b/1.1.x/share/www/document.html @@ -0,0 +1,114 @@ + + + + + View Document + + + + + + + + + + + + + + + + + +
+

+ Overview + ? + ? + +

+
+
    +
  • +
  • +
  • +
  • +
+ + + + + + + + + + + + + + + + + + + + + +
Fields
FieldValue
+ + +
+ +
+
+ diff --git a/1.1.x/share/www/favicon.ico b/1.1.x/share/www/favicon.ico new file mode 100644 index 00000000..34bfaa86 Binary files /dev/null and b/1.1.x/share/www/favicon.ico differ diff --git a/1.1.x/share/www/image/add.png b/1.1.x/share/www/image/add.png new file mode 100644 index 00000000..34e8c7d7 Binary files /dev/null and b/1.1.x/share/www/image/add.png differ diff --git a/1.1.x/share/www/image/apply.gif b/1.1.x/share/www/image/apply.gif new file mode 100644 index 00000000..63de0d53 Binary files /dev/null and b/1.1.x/share/www/image/apply.gif differ diff --git a/1.1.x/share/www/image/bg.png b/1.1.x/share/www/image/bg.png new file mode 100644 index 00000000..ec815244 Binary files /dev/null and b/1.1.x/share/www/image/bg.png differ diff --git a/1.1.x/share/www/image/cancel.gif b/1.1.x/share/www/image/cancel.gif new file mode 100644 index 00000000..4329076e Binary files /dev/null and b/1.1.x/share/www/image/cancel.gif differ diff --git a/1.1.x/share/www/image/compact.png b/1.1.x/share/www/image/compact.png new file mode 100644 index 00000000..ea8985dc Binary files /dev/null and b/1.1.x/share/www/image/compact.png differ diff --git a/1.1.x/share/www/image/delete-mini.png b/1.1.x/share/www/image/delete-mini.png new file mode 100644 index 00000000..ad5588d9 Binary files /dev/null and b/1.1.x/share/www/image/delete-mini.png differ diff --git a/1.1.x/share/www/image/delete.png b/1.1.x/share/www/image/delete.png new file mode 100644 index 00000000..e8384017 Binary files /dev/null and b/1.1.x/share/www/image/delete.png differ diff --git a/1.1.x/share/www/image/grippie.gif b/1.1.x/share/www/image/grippie.gif new file mode 100644 index 00000000..a8807896 Binary files /dev/null and b/1.1.x/share/www/image/grippie.gif differ diff --git a/1.1.x/share/www/image/hgrad.gif b/1.1.x/share/www/image/hgrad.gif new file mode 100644 index 00000000..08aa80ca Binary files /dev/null and b/1.1.x/share/www/image/hgrad.gif differ diff --git a/1.1.x/share/www/image/key.png b/1.1.x/share/www/image/key.png new file mode 100644 index 00000000..e04ed108 Binary files /dev/null and b/1.1.x/share/www/image/key.png differ diff --git a/1.1.x/share/www/image/load.png b/1.1.x/share/www/image/load.png new file mode 100644 index 00000000..07b4f791 Binary files /dev/null and b/1.1.x/share/www/image/load.png differ diff --git a/1.1.x/share/www/image/logo.png b/1.1.x/share/www/image/logo.png new file mode 100644 index 00000000..d21ac025 Binary files /dev/null and b/1.1.x/share/www/image/logo.png differ diff --git a/1.1.x/share/www/image/order-asc.gif b/1.1.x/share/www/image/order-asc.gif new file mode 100644 index 00000000..d2a237ae Binary files /dev/null and b/1.1.x/share/www/image/order-asc.gif differ diff --git a/1.1.x/share/www/image/order-desc.gif b/1.1.x/share/www/image/order-desc.gif new file mode 100644 index 00000000..1043b499 Binary files /dev/null and b/1.1.x/share/www/image/order-desc.gif differ diff --git a/1.1.x/share/www/image/path.gif b/1.1.x/share/www/image/path.gif new file mode 100644 index 00000000..01ec717e Binary files /dev/null and b/1.1.x/share/www/image/path.gif differ diff --git a/1.1.x/share/www/image/progress.gif b/1.1.x/share/www/image/progress.gif new file mode 100644 index 00000000..d84f6537 Binary files /dev/null and b/1.1.x/share/www/image/progress.gif differ diff --git a/1.1.x/share/www/image/rarrow.png b/1.1.x/share/www/image/rarrow.png new file mode 100644 index 00000000..507e87e7 Binary files /dev/null and b/1.1.x/share/www/image/rarrow.png differ diff --git a/1.1.x/share/www/image/run-mini.png b/1.1.x/share/www/image/run-mini.png new file mode 100644 index 00000000..b2fcbd82 Binary files /dev/null and b/1.1.x/share/www/image/run-mini.png differ diff --git a/1.1.x/share/www/image/run.png b/1.1.x/share/www/image/run.png new file mode 100644 index 00000000..a1d79f65 Binary files /dev/null and b/1.1.x/share/www/image/run.png differ diff --git a/1.1.x/share/www/image/running.png b/1.1.x/share/www/image/running.png new file mode 100644 index 00000000..9b50cd67 Binary files /dev/null and b/1.1.x/share/www/image/running.png differ diff --git a/1.1.x/share/www/image/save.png b/1.1.x/share/www/image/save.png new file mode 100644 index 00000000..a04e4bcc Binary files /dev/null and b/1.1.x/share/www/image/save.png differ diff --git a/1.1.x/share/www/image/sidebar-toggle.png b/1.1.x/share/www/image/sidebar-toggle.png new file mode 100644 index 00000000..3ea32ffe Binary files /dev/null and b/1.1.x/share/www/image/sidebar-toggle.png differ diff --git a/1.1.x/share/www/image/spinner.gif b/1.1.x/share/www/image/spinner.gif new file mode 100644 index 00000000..6239655e Binary files /dev/null and b/1.1.x/share/www/image/spinner.gif differ diff --git a/1.1.x/share/www/image/spinner_33.gif b/1.1.x/share/www/image/spinner_33.gif new file mode 100644 index 00000000..5ad51927 Binary files /dev/null and b/1.1.x/share/www/image/spinner_33.gif differ diff --git a/1.1.x/share/www/image/spinner_6b.gif b/1.1.x/share/www/image/spinner_6b.gif new file mode 100644 index 00000000..4e3d9725 Binary files /dev/null and b/1.1.x/share/www/image/spinner_6b.gif differ diff --git a/1.1.x/share/www/image/test_failure.gif b/1.1.x/share/www/image/test_failure.gif new file mode 100644 index 00000000..2a873b24 Binary files /dev/null and b/1.1.x/share/www/image/test_failure.gif differ diff --git a/1.1.x/share/www/image/test_success.gif b/1.1.x/share/www/image/test_success.gif new file mode 100644 index 00000000..6df8bae2 Binary files /dev/null and b/1.1.x/share/www/image/test_success.gif differ diff --git a/1.1.x/share/www/image/thead-key.gif b/1.1.x/share/www/image/thead-key.gif new file mode 100644 index 00000000..42a43b58 Binary files /dev/null and b/1.1.x/share/www/image/thead-key.gif differ diff --git a/1.1.x/share/www/image/thead.gif b/1.1.x/share/www/image/thead.gif new file mode 100644 index 00000000..1587b1f2 Binary files /dev/null and b/1.1.x/share/www/image/thead.gif differ diff --git a/1.1.x/share/www/image/toggle-collapse.gif b/1.1.x/share/www/image/toggle-collapse.gif new file mode 100644 index 00000000..f0979304 Binary files /dev/null and b/1.1.x/share/www/image/toggle-collapse.gif differ diff --git a/1.1.x/share/www/image/toggle-expand.gif b/1.1.x/share/www/image/toggle-expand.gif new file mode 100644 index 00000000..03fa8360 Binary files /dev/null and b/1.1.x/share/www/image/toggle-expand.gif differ diff --git a/1.1.x/share/www/image/twisty.gif b/1.1.x/share/www/image/twisty.gif new file mode 100644 index 00000000..5ba57a1a Binary files /dev/null and b/1.1.x/share/www/image/twisty.gif differ diff --git a/1.1.x/share/www/index.html b/1.1.x/share/www/index.html new file mode 100644 index 00000000..975f5986 --- /dev/null +++ b/1.1.x/share/www/index.html @@ -0,0 +1,94 @@ + + + + + Overview + + + + + + + + + + + + + +
+

Overview

+
+
    +
  • +
+ + + + + + + + + + + + + + + + + + +
Databases
NameSizeNumber of DocumentsUpdate Seq
+
+ | + | + +
+ +
+
+ +
+ + diff --git a/1.1.x/share/www/replicator.html b/1.1.x/share/www/replicator.html new file mode 100644 index 00000000..16c0940b --- /dev/null +++ b/1.1.x/share/www/replicator.html @@ -0,0 +1,184 @@ + + + + + Replicator + + + + + + + + + + + + +
+

+ Overview + Replicator +

+
+ +
+
+ Replicate changes from: +

+ + + +

+ + + +

+
+

+
+ to: +

+ + + +

+ + + +

+
+

+ + +

+
+ + + + + + + + + + +
Replication History
Event
No replication
+ +
+
+ diff --git a/1.1.x/share/www/script/base64.js b/1.1.x/share/www/script/base64.js new file mode 100644 index 00000000..e0aab303 --- /dev/null +++ b/1.1.x/share/www/script/base64.js @@ -0,0 +1,124 @@ +/* Copyright (C) 1999 Masanao Izumo + * Version: 1.0 + * LastModified: Dec 25 1999 + * This library is free. You can redistribute it and/or modify it. + */ + /* Modified by Chris Anderson to not use CommonJS */ + /* Modified by Dan Webb not to require Narwhal's binary library */ + +var Base64 = {}; +(function(exports) { + + var encodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var decodeChars = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 + ]; + + exports.encode = function (str) { + var out, i, length; + var c1, c2, c3; + + length = len(str); + i = 0; + out = []; + while(i < length) { + c1 = str.charCodeAt(i++) & 0xff; + if(i == length) + { + out.push(encodeChars.charCodeAt(c1 >> 2)); + out.push(encodeChars.charCodeAt((c1 & 0x3) << 4)); + out.push("=".charCodeAt(0)); + out.push("=".charCodeAt(0)); + break; + } + c2 = str.charCodeAt(i++); + if(i == length) + { + out.push(encodeChars.charCodeAt(c1 >> 2)); + out.push(encodeChars.charCodeAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4))); + out.push(encodeChars.charCodeAt((c2 & 0xF) << 2)); + out.push("=".charCodeAt(0)); + break; + } + c3 = str.charCodeAt(i++); + out.push(encodeChars.charCodeAt(c1 >> 2)); + out.push(encodeChars.charCodeAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4))); + out.push(encodeChars.charCodeAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6))); + out.push(encodeChars.charCodeAt(c3 & 0x3F)); + } + + var str = ""; + out.forEach(function(chr) { str += String.fromCharCode(chr) }); + return str; + }; + + exports.decode = function (str) { + var c1, c2, c3, c4; + var i, length, out; + + length = len(str); + i = 0; + out = []; + while(i < length) { + /* c1 */ + do { + c1 = decodeChars[str.charCodeAt(i++) & 0xff]; + } while(i < length && c1 == -1); + if(c1 == -1) + break; + + /* c2 */ + do { + c2 = decodeChars[str.charCodeAt(i++) & 0xff]; + } while(i < length && c2 == -1); + if(c2 == -1) + break; + + out.push(String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4))); + + /* c3 */ + do { + c3 = str.charCodeAt(i++) & 0xff; + if(c3 == 61) + return out.join(''); + c3 = decodeChars[c3]; + } while(i < length && c3 == -1); + if(c3 == -1) + break; + + out.push(String.fromCharCode(((c2 & 0xF) << 4) | ((c3 & 0x3C) >> 2))); + + /* c4 */ + do { + c4 = str.charCodeAt(i++) & 0xff; + if(c4 == 61) + return out.join(''); + c4 = decodeChars[c4]; + } while(i < length && c4 == -1); + + if(c4 == -1) + break; + + out.push(String.fromCharCode(((c3 & 0x03) << 6) | c4)); + } + + return out.join(''); + }; + + var len = function (object) { + if (object.length !== undefined) { + return object.length; + } else if (object.getLength !== undefined) { + return object.getLength(); + } else { + return undefined; + } + }; +})(Base64); diff --git a/1.1.x/share/www/script/couch.js b/1.1.x/share/www/script/couch.js new file mode 100644 index 00000000..bcc19652 --- /dev/null +++ b/1.1.x/share/www/script/couch.js @@ -0,0 +1,473 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// A simple class to represent a database. Uses XMLHttpRequest to interface with +// the CouchDB server. + +function CouchDB(name, httpHeaders) { + this.name = name; + this.uri = "/" + encodeURIComponent(name) + "/"; + + // The XMLHttpRequest object from the most recent request. Callers can + // use this to check result http status and headers. + this.last_req = null; + + this.request = function(method, uri, requestOptions) { + requestOptions = requestOptions || {}; + requestOptions.headers = combine(requestOptions.headers, httpHeaders); + return CouchDB.request(method, uri, requestOptions); + }; + + // Creates the database on the server + this.createDb = function() { + this.last_req = this.request("PUT", this.uri); + CouchDB.maybeThrowError(this.last_req); + return JSON.parse(this.last_req.responseText); + }; + + // Deletes the database on the server + this.deleteDb = function() { + this.last_req = this.request("DELETE", this.uri); + if (this.last_req.status == 404) { + return false; + } + CouchDB.maybeThrowError(this.last_req); + return JSON.parse(this.last_req.responseText); + }; + + // Save a document to the database + this.save = function(doc, options) { + if (doc._id == undefined) { + doc._id = CouchDB.newUuids(1)[0]; + } + + this.last_req = this.request("PUT", this.uri + + encodeURIComponent(doc._id) + encodeOptions(options), + {body: JSON.stringify(doc)}); + CouchDB.maybeThrowError(this.last_req); + var result = JSON.parse(this.last_req.responseText); + doc._rev = result.rev; + return result; + }; + + // Open a document from the database + this.open = function(docId, options) { + this.last_req = this.request("GET", this.uri + encodeURIComponent(docId) + + encodeOptions(options)); + if (this.last_req.status == 404) { + return null; + } + CouchDB.maybeThrowError(this.last_req); + return JSON.parse(this.last_req.responseText); + }; + + // Deletes a document from the database + this.deleteDoc = function(doc) { + this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + + "?rev=" + doc._rev); + CouchDB.maybeThrowError(this.last_req); + var result = JSON.parse(this.last_req.responseText); + doc._rev = result.rev; //record rev in input document + doc._deleted = true; + return result; + }; + + // Deletes an attachment from a document + this.deleteDocAttachment = function(doc, attachment_name) { + this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + + "/" + attachment_name + "?rev=" + doc._rev); + CouchDB.maybeThrowError(this.last_req); + var result = JSON.parse(this.last_req.responseText); + doc._rev = result.rev; //record rev in input document + return result; + }; + + this.bulkSave = function(docs, options) { + // first prepoulate the UUIDs for new documents + var newCount = 0; + for (var i=0; i= n) { + var uuids = CouchDB.uuids_cache.slice(CouchDB.uuids_cache.length - n); + if(CouchDB.uuids_cache.length - n == 0) { + CouchDB.uuids_cache = []; + } else { + CouchDB.uuids_cache = + CouchDB.uuids_cache.slice(0, CouchDB.uuids_cache.length - n); + } + return uuids; + } else { + CouchDB.last_req = CouchDB.request("GET", "/_uuids?count=" + (buf + n)); + CouchDB.maybeThrowError(CouchDB.last_req); + var result = JSON.parse(CouchDB.last_req.responseText); + CouchDB.uuids_cache = + CouchDB.uuids_cache.concat(result.uuids.slice(0, buf)); + return result.uuids.slice(buf); + } +}; + +CouchDB.maybeThrowError = function(req) { + if (req.status >= 400) { + try { + var result = JSON.parse(req.responseText); + } catch (ParseError) { + var result = {error:"unknown", reason:req.responseText}; + } + throw result; + } +} + +CouchDB.params = function(options) { + options = options || {}; + var returnArray = []; + for(var key in options) { + var value = options[key]; + returnArray.push(key + "=" + value); + } + return returnArray.join("&"); +}; diff --git a/1.1.x/share/www/script/couch_test_runner.js b/1.1.x/share/www/script/couch_test_runner.js new file mode 100644 index 00000000..55a6533f --- /dev/null +++ b/1.1.x/share/www/script/couch_test_runner.js @@ -0,0 +1,437 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// *********************** Test Framework of Sorts ************************* // + + +function loadScript(url) { + // disallow loading remote URLs + if((url.substr(0, 7) == "http://") + || (url.substr(0, 2) == "//") + || (url.substr(0, 5) == "data:") + || (url.substr(0, 11) == "javascript:")) { + throw "Not loading remote test scripts"; + } + if (typeof document != "undefined") document.write(''); +}; + +function patchTest(fun) { + var source = fun.toString(); + var output = ""; + var i = 0; + var testMarker = "T("; + while (i < source.length) { + var testStart = source.indexOf(testMarker, i); + if (testStart == -1) { + output = output + source.substring(i, source.length); + break; + } + var testEnd = source.indexOf(");", testStart); + var testCode = source.substring(testStart + testMarker.length, testEnd); + output += source.substring(i, testStart) + "T(" + testCode + "," + JSON.stringify(testCode); + i = testEnd; + } + try { + return eval("(" + output + ")"); + } catch (e) { + return null; + } +} + +function runAllTests() { + var rows = $("#tests tbody.content tr"); + $("td", rows).text(""); + $("td.status", rows).removeClass("error").removeClass("failure").removeClass("success").text("not run"); + var offset = 0; + function runNext() { + if (offset < rows.length) { + var row = rows.get(offset); + runTest($("th button", row).get(0), function() { + offset += 1; + setTimeout(runNext, 100); + }, false, true); + } else { + saveTestReport(); + } + } + runNext(); +} + +var numFailures = 0; +var currentRow = null; + +function runTest(button, callback, debug, noSave) { + + // offer to save admins + if (currentRow != null) { + alert("Can not run multiple tests simultaneously."); + return; + } + var row = currentRow = $(button).parents("tr").get(0); + $("td.status", row).removeClass("error").removeClass("failure").removeClass("success"); + $("td", row).text(""); + $("#toolbar li.current").text("Running: "+row.id); + var testFun = couchTests[row.id]; + function run() { + numFailures = 0; + var start = new Date().getTime(); + try { + if (debug == undefined || !debug) { + testFun = patchTest(testFun) || testFun; + } + testFun(debug); + var status = numFailures > 0 ? "failure" : "success"; + } catch (e) { + var status = "error"; + if ($("td.details ol", row).length == 0) { + $("
    ").appendTo($("td.details", row)); + } + $("
  1. Exception raised:
  2. ") + .find("code").text(JSON.stringify(e)).end() + .appendTo($("td.details ol", row)); + if (debug) { + currentRow = null; + throw e; + } + } + if ($("td.details ol", row).length) { + $("Run with debugger").click(function() { + runTest(this, undefined, true); + }).prependTo($("td.details ol", row)); + } + var duration = new Date().getTime() - start; + $("td.status", row).removeClass("running").addClass(status).text(status); + $("td.duration", row).text(duration + "ms"); + $("#toolbar li.current").text("Finished: "+row.id); + updateTestsFooter(); + currentRow = null; + if (callback) callback(); + if (!noSave) saveTestReport(); + } + $("td.status", row).addClass("running").text("running…"); + setTimeout(run, 100); +} + +function showSource(cell) { + var name = $(cell).text(); + var win = window.open("", name, "width=700,height=500,resizable=yes,scrollbars=yes"); + win.document.location = "script/test/" + name + ".js"; +} + +var readyToRun; +function setupAdminParty(fun) { + if (readyToRun) { + fun(); + } else { + function removeAdmins(confs, doneFun) { + // iterate through the config and remove current user last + // current user is at front of list + var remove = confs.pop(); + if (remove) { + $.couch.config({ + success : function() { + removeAdmins(confs, doneFun); + } + }, "admins", remove[0], null); + } else { + doneFun(); + } + }; + $.couch.session({ + success : function(resp) { + var userCtx = resp.userCtx; + if (userCtx.name && userCtx.roles.indexOf("_admin") != -1) { + // admin but not admin party. dialog offering to make admin party + $.showDialog("dialog/_admin_party.html", { + submit: function(data, callback) { + $.couch.config({ + success : function(conf) { + var meAdmin, adminConfs = []; + for (var name in conf) { + if (name == userCtx.name) { + meAdmin = [name, conf[name]]; + } else { + adminConfs.push([name, conf[name]]); + } + } + adminConfs.unshift(meAdmin); + removeAdmins(adminConfs, function() { + callback(); + $.futon.session.sidebar(); + readyToRun = true; + setTimeout(fun, 500); + }); + } + }, "admins"); + } + }); + } else if (userCtx.roles.indexOf("_admin") != -1) { + // admin party! + readyToRun = true; + fun(); + } else { + // not an admin + alert("Error: You need to be an admin to run the tests."); + }; + } + }); + } +}; + +function updateTestsListing() { + for (var name in couchTests) { + var testFunction = couchTests[name]; + var row = $("") + .find("th").text(name).attr("title", "Show source").click(function() { + showSource(this); + }).end() + .find("td:nth(0)").addClass("status").text("not run").end() + .find("td:nth(1)").addClass("duration").end() + .find("td:nth(2)").addClass("details").end(); + $("").click(function() { + this.blur(); + var self = this; + // check for admin party + setupAdminParty(function() { + runTest(self); + }); + return false; + }).prependTo(row.find("th")); + row.attr("id", name).appendTo("#tests tbody.content"); + } + $("#tests tr").removeClass("odd").filter(":odd").addClass("odd"); + updateTestsFooter(); +} + +function updateTestsFooter() { + var tests = $("#tests tbody.content tr td.status"); + var testsRun = tests.filter(".success, .error, .failure"); + var testsFailed = testsRun.not(".success"); + var totalDuration = 0; + $("#tests tbody.content tr td.duration:contains('ms')").each(function() { + var text = $(this).text(); + totalDuration += parseInt(text.substr(0, text.length - 2), 10); + }); + $("#tests tbody.footer td").html(""+testsRun.length + " of " + tests.length + + " test(s) run, " + testsFailed.length + " failures (" + + totalDuration + " ms) "); +} + +// make report and save to local db +// display how many reports need replicating to the mothership +// have button to replicate them + +function saveTestReport(report) { + var report = makeTestReport(); + if (report) { + var db = $.couch.db("test_suite_reports"); + var saveReport = function(db_info) { + report.db = db_info; + $.couch.info({success : function(node_info) { + report.node = node_info; + db.saveDoc(report); + }}); + }; + var createDb = function() { + db.create({success: function() { + db.info({success:saveReport}); + }}); + }; + db.info({error: createDb, success:saveReport}); + } +}; + +function makeTestReport() { + var report = {}; + report.summary = $("#tests tbody.footer td").text(); + report.platform = testPlatform(); + var date = new Date(); + report.timestamp = date.getTime(); + report.timezone = date.getTimezoneOffset(); + report.tests = []; + $("#tests tbody.content tr").each(function() { + var status = $("td.status", this).text(); + if (status != "not run") { + var test = {}; + test.name = this.id; + test.status = status; + test.duration = parseInt($("td.duration", this).text()); + test.details = []; + $("td.details li", this).each(function() { + test.details.push($(this).text()); + }); + if (test.details.length == 0) { + delete test.details; + } + report.tests.push(test); + } + }); + if (report.tests.length > 0) return report; +}; + +function testPlatform() { + var b = $.browser; + var bs = ["mozilla", "msie", "opera", "safari"]; + for (var i=0; i < bs.length; i++) { + if (b[bs[i]]) { + return {"browser" : bs[i], "version" : b.version}; + } + }; + return {"browser" : "undetected"}; +} + + +function reportTests() { + // replicate the database to couchdb.couchdb.org +} + +// Use T to perform a test that returns false on failure and if the test fails, +// display the line that failed. +// Example: +// T(MyValue==1); +function T(arg1, arg2, testName) { + if (!arg1) { + if (currentRow) { + if ($("td.details ol", currentRow).length == 0) { + $("
      ").appendTo($("td.details", currentRow)); + } + var message = (arg2 != null ? arg2 : arg1).toString(); + $("
    1. Assertion " + (testName ? "'" + testName + "'" : "") + " failed:
    2. ") + .find("code").text(message).end() + .appendTo($("td.details ol", currentRow)); + } + numFailures += 1; + } +} + +function TEquals(expected, actual, testName) { + T(equals(expected, actual), "expected '" + repr(expected) + + "', got '" + repr(actual) + "'", testName); +} + +function TEqualsIgnoreCase(expected, actual, testName) { + T(equals(expected.toUpperCase(), actual.toUpperCase()), "expected '" + repr(expected) + + "', got '" + repr(actual) + "'", testName); +} + +function equals(a,b) { + if (a === b) return true; + try { + return repr(a) === repr(b); + } catch (e) { + return false; + } +} + +function repr(val) { + if (val === undefined) { + return null; + } else if (val === null) { + return "null"; + } else { + return JSON.stringify(val); + } +} + +function makeDocs(start, end, templateDoc) { + var templateDocSrc = templateDoc ? JSON.stringify(templateDoc) : "{}"; + if (end === undefined) { + end = start; + start = 0; + } + var docs = []; + for (var i = start; i < end; i++) { + var newDoc = eval("(" + templateDocSrc + ")"); + newDoc._id = (i).toString(); + newDoc.integer = i; + newDoc.string = (i).toString(); + docs.push(newDoc); + } + return docs; +} + +function run_on_modified_server(settings, fun) { + try { + // set the settings + for(var i=0; i < settings.length; i++) { + var s = settings[i]; + var xhr = CouchDB.request("PUT", "/_config/" + s.section + "/" + s.key, { + body: JSON.stringify(s.value), + headers: {"X-Couch-Persist": "false"} + }); + CouchDB.maybeThrowError(xhr); + s.oldValue = xhr.responseText; + } + // run the thing + fun(); + } finally { + // unset the settings + for(var j=0; j < i; j++) { + var s = settings[j]; + if(s.oldValue == "\"\"\n") { // unset value + CouchDB.request("DELETE", "/_config/" + s.section + "/" + s.key, { + headers: {"X-Couch-Persist": "false"} + }); + } else { + CouchDB.request("PUT", "/_config/" + s.section + "/" + s.key, { + body: s.oldValue, + headers: {"X-Couch-Persist": "false"} + }); + } + } + } +} + +function stringFun(fun) { + var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")"; + return string; +} + +function waitForSuccess(fun, tag) { + var start = new Date(); + while(true) { + if (new Date() - start > 5000) { + throw("timeout: "+tag); + } else { + try { + fun(); + break; + } catch (e) {} + // sync http req allow async req to happen + CouchDB.request("GET", "/test_suite_db/?tag="+encodeURIComponent(tag)); + } + } +} + +function waitForRestart() { + var waiting = true; + while (waiting) { + try { + CouchDB.request("GET", "/"); + CouchDB.request("GET", "/"); + waiting = false; + } catch(e) { + // the request will fail until restart completes + } + } +}; + +function restartServer() { + var xhr; + try { + CouchDB.request("POST", "/_restart"); + } catch(e) { + // this request may sometimes fail + } + waitForRestart(); +} + diff --git a/1.1.x/share/www/script/couch_tests.js b/1.1.x/share/www/script/couch_tests.js new file mode 100644 index 00000000..eb573526 --- /dev/null +++ b/1.1.x/share/www/script/couch_tests.js @@ -0,0 +1,105 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// Used by replication test +if (typeof window == 'undefined' || !window) { + CouchDB.host = "127.0.0.1:5984"; + CouchDB.protocol = "http://"; + CouchDB.inBrowser = false; +} else { + CouchDB.host = window.location.host; + CouchDB.inBrowser = true; + CouchDB.protocol = window.location.protocol + "//"; +} + +CouchDB.urlPrefix = ".."; +var couchTests = {}; + +function loadTest(file) { + loadScript("script/test/"+file); +}; +// keep first +loadTest("basics.js"); + +// keep sorted +loadTest("all_docs.js"); +loadTest("attachments.js"); +loadTest("attachments_multipart.js"); +loadTest("attachment_conflicts.js"); +loadTest("attachment_names.js"); +loadTest("attachment_paths.js"); +loadTest("attachment_ranges.js"); +loadTest("attachment_views.js"); +loadTest("auth_cache.js"); +loadTest("batch_save.js"); +loadTest("bulk_docs.js"); +loadTest("changes.js"); +loadTest("compact.js"); +loadTest("config.js"); +loadTest("conflicts.js"); +loadTest("content_negotiation.js"); +loadTest("cookie_auth.js"); +loadTest("copy_doc.js"); +loadTest("delayed_commits.js"); +loadTest("design_docs.js"); +loadTest("design_options.js"); +loadTest("design_paths.js"); +loadTest("erlang_views.js"); +loadTest("etags_head.js"); +loadTest("etags_views.js"); +loadTest("form_submit.js"); +loadTest("http.js"); +loadTest("invalid_docids.js"); +loadTest("jsonp.js"); +loadTest("large_docs.js"); +loadTest("list_views.js"); +loadTest("lots_of_docs.js"); +loadTest("method_override.js"); +loadTest("multiple_rows.js"); +loadScript("script/oauth.js"); +loadScript("script/sha1.js"); +loadTest("oauth.js"); +loadTest("proxyauth.js"); +loadTest("purge.js"); +loadTest("reader_acl.js"); +loadTest("recreate_doc.js"); +loadTest("reduce.js"); +loadTest("reduce_builtin.js"); +loadTest("reduce_false.js"); +loadTest("reduce_false_temp.js"); +loadTest("replication.js"); +loadTest("replicator_db.js"); +loadTest("rev_stemming.js"); +loadTest("rewrite.js"); +loadTest("security_validation.js"); +loadTest("show_documents.js"); +loadTest("stats.js"); +loadTest("update_documents.js"); +loadTest("users_db.js"); +loadTest("utf8.js"); +loadTest("uuids.js"); +loadTest("view_collation.js"); +loadTest("view_collation_raw.js"); +loadTest("view_conflicts.js"); +loadTest("view_compaction.js"); +loadTest("view_errors.js"); +loadTest("view_include_docs.js"); +loadTest("view_multi_key_all_docs.js"); +loadTest("view_multi_key_design.js"); +loadTest("view_multi_key_temp.js"); +loadTest("view_offsets.js"); +loadTest("view_pagination.js"); +loadTest("view_sandboxing.js"); +loadTest("view_update_seq.js"); +loadTest("view_xml.js"); +// keep sorted + diff --git a/1.1.x/share/www/script/futon.browse.js b/1.1.x/share/www/script/futon.browse.js new file mode 100644 index 00000000..56435ae4 --- /dev/null +++ b/1.1.x/share/www/script/futon.browse.js @@ -0,0 +1,1290 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +(function($) { + $.futon = $.futon || {}; + $.extend($.futon, { + + // Page class for browse/index.html + CouchIndexPage: function() { + page = this; + + $.futon.storage.declare("per_page", {defaultValue: 10}); + + this.addDatabase = function() { + $.showDialog("dialog/_create_database.html", { + submit: function(data, callback) { + if (!data.name || data.name.length == 0) { + callback({name: "Please enter a name."}); + return; + } + $.couch.db(data.name).create({ + error: function(status, id, reason) { callback({name: reason}) }, + success: function(resp) { + location.href = "database.html?" + encodeURIComponent(data.name); + callback(); + } + }); + } + }); + return false; + } + + this.updateDatabaseListing = function(offset) { + offset |= 0; + var maxPerPage = parseInt($("#perpage").val(), 10); + + $.couch.allDbs({ + success: function(dbs) { + $("#paging a").unbind(); + $("#databases tbody.content").empty(); + + var dbsOnPage = dbs.slice(offset, offset + maxPerPage); + + $.each(dbsOnPage, function(idx, dbName) { + $("#databases tbody.content").append("" + + "" + + dbName + "" + + "" + + ""); + $.couch.db(dbName).info({ + success: function(info) { + $("#databases tbody.content tr:eq(" + idx + ")") + .find("td.size").text($.futon.formatSize(info.disk_size)).end() + .find("td.count").text(info.doc_count).end() + .find("td.seq").text(info.update_seq); + }, + error : function() {} + }); + }); + $("#databases tbody tr:odd").addClass("odd"); + + if (offset > 0) { + $("#paging a.prev").attr("href", "#" + (offset - maxPerPage)).click(function() { + page.updateDatabaseListing(offset - maxPerPage); + }); + } else { + $("#paging a.prev").removeAttr("href"); + } + if (offset + maxPerPage < dbs.length) { + $("#paging a.next").attr("href", "#" + (offset + maxPerPage)).click(function() { + page.updateDatabaseListing(offset + maxPerPage); + }); + } else { + $("#paging a.next").removeAttr("href"); + } + + var firstNum = offset + 1; + var lastNum = firstNum + dbsOnPage.length - 1; + $("#databases tbody.footer tr td span").text( + "Showing " + firstNum + "-" + lastNum + " of " + dbs.length + + " databases"); + } + }); + } + + }, + + // Page class for browse/database.html + CouchDatabasePage: function() { + var urlParts = location.search.substr(1).split("/"); + var dbName = decodeURIComponent(urlParts.shift()) + + var dbNameRegExp = new RegExp("[^a-z0-9\_\$\(\)\+\/\-]", "g"); + dbName = dbName.replace(dbNameRegExp, ""); + + $.futon.storage.declareWithPrefix(dbName + ".", { + desc: {}, + language: {defaultValue: "javascript"}, + map_fun: {defaultValue: ""}, + reduce_fun: {defaultValue: ""}, + reduce: {}, + group_level: {defaultValue: 100}, + per_page: {defaultValue: 10}, + view: {defaultValue: ""}, + stale: {defaultValue: false} + }); + + var viewName = (urlParts.length > 0) ? urlParts.join("/") : null; + if (viewName) { + $.futon.storage.set("view", decodeURIComponent(viewName)); + } else { + viewName = $.futon.storage.get("view"); + if (viewName) { + this.redirecting = true; + location.href = "database.html?" + encodeURIComponent(dbName) + + "/" + encodeURIComponent(viewName); + } + } + var db = $.couch.db(dbName); + + this.dbName = dbName; + viewName = decodeURIComponent(viewName); + this.viewName = viewName; + this.viewLanguage = "javascript"; + this.db = db; + this.isDirty = false; + this.isTempView = viewName == "_temp_view"; + page = this; + + var templates = { + javascript: "function(doc) {\n emit(null, doc);\n}", + python: "def fun(doc):\n yield None, doc", + ruby: "lambda {|doc|\n emit(nil, doc);\n}" + } + + this.newDocument = function() { + location.href = "document.html?" + encodeURIComponent(db.name); + } + + this.compactAndCleanup = function() { + $.showDialog("dialog/_compact_cleanup.html", { + submit: function(data, callback) { + switch (data.action) { + case "compact_database": + db.compact({success: function(resp) { callback() }}); + break; + case "compact_views": + var idx = page.viewName.indexOf("/_view"); + if (idx == -1) { + alert("Compact Views requires focus on a view!"); + } else { + var groupname = page.viewName.substring(8, idx); + db.compactView(groupname, {success: function(resp) { callback() }}); + } + break; + case "view_cleanup": + db.viewCleanup({success: function(resp) { callback() }}); + break; + } + } + }); + } + + this.deleteDatabase = function() { + $.showDialog("dialog/_delete_database.html", { + submit: function(data, callback) { + db.drop({ + success: function(resp) { + callback(); + location.href = "index.html"; + if (window !== null) { + $("#dbs li").filter(function(index) { + return $("a", this).text() == dbName; + }).remove(); + $.futon.navigation.removeDatabase(dbName); + } + } + }); + } + }); + } + + this.databaseSecurity = function() { + $.showDialog("dialog/_database_security.html", { + load : function(d) { + db.getDbProperty("_security", { + success: function(r) { + ["admin", "reader"].forEach(function(key) { + var names = []; + var roles = []; + + if (r && typeof r[key + "s"] === "object") { + if ($.isArray(r[key + "s"]["names"])) { + names = r[key + "s"]["names"]; + } + if ($.isArray(r[key + "s"]["roles"])) { + roles = r[key + "s"]["roles"]; + } + } + + $("input[name=" + key + "_names]", d).val(JSON.stringify(names)); + $("input[name=" + key + "_roles]", d).val(JSON.stringify(roles)); + }); + } + }); + }, + // maybe this should be 2 forms + submit: function(data, callback) { + var errors = {}; + var secObj = { + admins: { + names: [], + roles: [] + }, + readers: { + names: [], + roles: [] + } + }; + + ["admin", "reader"].forEach(function(key) { + var names, roles; + + try { + names = JSON.parse(data[key + "_names"]); + } catch(e) { } + try { + roles = JSON.parse(data[key + "_roles"]); + } catch(e) { } + + if ($.isArray(names)) { + secObj[key + "s"]["names"] = names; + } else { + errors[key + "_names"] = "The " + key + + " names must be an array of strings"; + } + if ($.isArray(roles)) { + secObj[key + "s"]["roles"] = roles; + } else { + errors[key + "_roles"] = "The " + key + + " roles must be an array of strings"; + } + }); + + if ($.isEmptyObject(errors)) { + db.setDbProperty("_security", secObj); + } + callback(errors); + } + }); + } + + this.populateViewEditor = function() { + if (viewName.match(/^_design\//)) { + page.revertViewChanges(function() { + var dirtyTimeout = null; + function updateDirtyState() { + clearTimeout(dirtyTimeout); + dirtyTimeout = setTimeout(function() { + var buttons = $("#viewcode button.save, #viewcode button.revert"); + var viewCode = { + map: $("#viewcode_map").val(), + reduce: $("#viewcode_reduce").val() + }; + $("#reduce, #grouplevel").toggle(!!viewCode.reduce); + page.isDirty = (viewCode.map != page.storedViewCode.map) + || (viewCode.reduce != (page.storedViewCode.reduce || "")) + || page.viewLanguage != page.storedViewLanguage; + if (page.isDirty) { + buttons.removeAttr("disabled"); + } else { + buttons.attr("disabled", "disabled"); + } + }, 100); + } + $("#viewcode textarea").enableTabInsertion() + .bind("input", updateDirtyState); + if ($.browser.msie || $.browser.safari) { + $("#viewcode textarea").bind("paste", updateDirtyState) + .bind("change", updateDirtyState) + .bind("keydown", updateDirtyState) + .bind("keypress", updateDirtyState) + .bind("keyup", updateDirtyState) + .bind("textInput", updateDirtyState); + } + $("#language").change(updateDirtyState); + page.updateDocumentListing(); + }); + } else if (viewName == "_temp_view") { + $("#viewcode textarea").enableTabInsertion(); + page.viewLanguage = $.futon.storage.get("language"); + page.updateViewEditor( + $.futon.storage.get("map_fun", templates[page.viewLanguage]), + $.futon.storage.get("reduce_fun") + ); + } else { + $("#grouplevel, #reduce").hide(); + page.updateDocumentListing(); + } + page.populateLanguagesMenu(); + if (this.isTempView) { + $("#tempwarn").show(); + } + } + + // Populate the languages dropdown, and listen to selection changes + this.populateLanguagesMenu = function() { + var all_langs = {}; + fill_language = function() { + var select = $("#language"); + for (var language in all_langs) { + var option = $(document.createElement("option")) + .attr("value", language).text(language) + .appendTo(select); + } + if (select[0].options.length == 1) { + select[0].disabled = true; + } else { + select[0].disabled = false; + select.val(page.viewLanguage); + select.change(function() { + var language = $("#language").val(); + if (language != page.viewLanguage) { + var mapFun = $("#viewcode_map").val(); + if (mapFun == "" || mapFun == templates[page.viewLanguage]) { + // no edits made, so change to the new default + $("#viewcode_map").val(templates[language]); + } + page.viewLanguage = language; + $("#viewcode_map")[0].focus(); + } + return false; + }); + } + } + $.couch.config({ + success: function(resp) { + for (var language in resp) { + all_langs[language] = resp[language]; + } + + $.couch.config({ + success: function(resp) { + for (var language in resp) { + all_langs[language] = resp[language]; + } + fill_language(); + } + }, "native_query_servers"); + }, + error : function() {} + }, "query_servers"); + } + + this.populateViewsMenu = function() { + var select = $("#switch select"); + db.allDocs({startkey: "_design/", endkey: "_design0", + include_docs: true, + success: function(resp) { + select[0].options.length = 3; + for (var i = 0; i < resp.rows.length; i++) { + var doc = resp.rows[i].doc; + var optGroup = $(document.createElement("optgroup")) + .attr("label", doc._id.substr(8)).appendTo(select); + var viewNames = []; + for (var name in doc.views) { + viewNames.push(name); + } + viewNames.sort(); + for (var j = 0; j < viewNames.length; j++) { + var path = $.couch.encodeDocId(doc._id) + "/_view/" + + encodeURIComponent(viewNames[j]); + var option = $(document.createElement("option")) + .attr("value", path).text(encodeURIComponent(viewNames[j])) + .appendTo(optGroup); + if (path == viewName) { + option[0].selected = true; + } + } + } + } + }); + if (!viewName.match(/^_design\//)) { + $.each(["_all_docs", "_design_docs", "_temp_view"], function(idx, name) { + if (viewName == name) { + select[0].options[idx].selected = true; + } + }); + } + } + + this.revertViewChanges = function(callback) { + if (!page.storedViewCode) { + var viewNameParts = viewName.split("/"); + var designDocId = decodeURIComponent(viewNameParts[1]); + var localViewName = decodeURIComponent(viewNameParts[3]); + db.openDoc("_design/" + designDocId, { + error: function(status, error, reason) { + if (status == 404) { + $.futon.storage.del("view"); + location.href = "database.html?" + encodeURIComponent(db.name); + } + }, + success: function(resp) { + if(!resp.views || !resp.views[localViewName]) { + $.futon.storage.del("view"); + location.href = "database.html?" + encodeURIComponent(db.name); + } + var viewCode = resp.views[localViewName]; + page.viewLanguage = resp.language || "javascript"; + $("#language").val(encodeURIComponent(page.viewLanguage)); + page.updateViewEditor(viewCode.map, viewCode.reduce || ""); + $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled"); + page.storedViewCode = viewCode; + page.storedViewLanguage = page.viewLanguage; + if (callback) callback(); + } + }, {async: false}); + } else { + page.updateViewEditor(page.storedViewCode.map, + page.storedViewCode.reduce || ""); + page.viewLanguage = page.storedViewLanguage; + $("#language").val(encodeURIComponent(page.viewLanguage)); + $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled"); + page.isDirty = false; + if (callback) callback(); + } + } + + this.updateViewEditor = function(mapFun, reduceFun) { + if (!mapFun) return; + $("#viewcode_map").val(mapFun); + $("#viewcode_reduce").val(reduceFun); + var lines = Math.max( + mapFun.split("\n").length, + reduceFun.split("\n").length + ); + $("#reduce, #grouplevel").toggle(!!reduceFun); + $("#viewcode textarea").attr("rows", Math.min(15, Math.max(3, lines))); + } + + this.saveViewAs = function() { + if (viewName && /^_design/.test(viewName)) { + var viewNameParts = viewName.split("/"); + var designDocId = decodeURIComponent(viewNameParts[1]); + var localViewName = decodeURIComponent(viewNameParts[3]); + } else { + var designDocId = "", localViewName = ""; + } + $.showDialog("dialog/_save_view_as.html", { + load: function(elem) { + $("#input_docid", elem).val(designDocId).suggest(function(text, callback) { + db.allDocs({ + limit: 10, startkey: "_design/" + text, endkey: "_design0", + success: function(docs) { + var matches = []; + for (var i = 0; i < docs.rows.length; i++) { + var docName = docs.rows[i].id.substr(8); + if (docName.indexOf(text) == 0) { + matches[i] = docName; + } + } + callback(matches); + } + }); + }); + $("#input_name", elem).val(localViewName).suggest(function(text, callback) { + db.openDoc("_design/" + $("#input_docid").val(), { + error: function() {}, // ignore + success: function(doc) { + var matches = []; + if (!doc.views) return; + for (var viewName in doc.views) { + if (viewName.indexOf(text) == 0) { + matches.push(viewName); + } + } + callback(matches); + } + }); + }); + }, + submit: function(data, callback) { + if (!data.docid || !data.name) { + var errors = {}; + if (!data.docid) errors.docid = "Please enter a document ID"; + if (!data.name) errors.name = "Please enter a view name"; + callback(errors); + } else { + var viewCode = { + map: $("#viewcode_map").val(), + reduce: $("#viewcode_reduce").val() || undefined + }; + var docId = ["_design", data.docid].join("/"); + function save(doc) { + if (!doc) { + doc = {_id: docId, language: page.viewLanguage}; + } else { + var numViews = 0; + for (var viewName in (doc.views || {})) { + if (viewName != data.name) numViews++; + } + if (numViews > 0 && page.viewLanguage != doc.language) { + callback({ + docid: "Cannot save to " + data.docid + + " because its language is \"" + doc.language + + "\", not \"" + + encodeURIComponent(page.viewLanguage) + "\"." + }); + return; + } + doc.language = page.viewLanguage; + } + if (doc.views === undefined) doc.views = {}; + doc.views[data.name] = viewCode; + db.saveDoc(doc, { + success: function(resp) { + callback(); + page.isDirty = false; + location.href = "database.html?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(doc._id) + + "/_view/" + encodeURIComponent(data.name); + } + }); + } + db.openDoc(docId, { + error: function(status, error, reason) { + if (status == 404) save(null); + else alert(reason); + }, + success: function(doc) { + save(doc); + } + }); + } + } + }); + } + + this.saveViewChanges = function() { + var viewNameParts = viewName.split("/"); + var designDocId = decodeURIComponent(viewNameParts[1]); + var localViewName = decodeURIComponent(viewNameParts[3]); + db.openDoc("_design/" + designDocId, { + success: function(doc) { + var numViews = 0; + for (var viewName in (doc.views || {})) { + if (viewName != localViewName) numViews++; + } + if (numViews > 0 && page.viewLanguage != doc.language) { + alert("Cannot save view because the design document language " + + "is \"" + doc.language + "\", not \"" + + page.viewLanguage + "\"."); + return; + } + doc.language = page.viewLanguage; + var viewDef = doc.views[localViewName]; + viewDef.map = $("#viewcode_map").val(); + viewDef.reduce = $("#viewcode_reduce").val() || undefined; + db.saveDoc(doc, { + success: function(resp) { + page.isDirty = false; + $("#viewcode button.revert, #viewcode button.save") + .attr("disabled", "disabled"); + } + }); + } + }); + } + + this.updateDesignDocLink = function() { + if (viewName && /^_design/.test(viewName)) { + var docId = "_design/" + encodeURIComponent(decodeURIComponent(viewName).split("/")[1]); + $("#designdoc-link").attr("href", "document.html?" + + encodeURIComponent(dbName) + "/" + $.couch.encodeDocId(docId)).text(docId); + } else { + $("#designdoc-link").removeAttr("href").text(""); + } + } + + this.jumpToDocument = function(docId) { + if (docId != "") { + location.href = 'document.html?' + encodeURIComponent(db.name) + + "/" + $.couch.encodeDocId(docId); + } + } + + this.updateDocumentListing = function(options) { + if (options === undefined) options = {}; + if (options.limit === undefined) { + var perPage = parseInt($("#perpage").val(), 10) + // Fetch an extra row so we know when we're on the last page for + // reduce views + options.limit = perPage + 1; + } else { + perPage = options.limit - 1; + } + if ($("#documents thead th.key").is(".desc")) { + if (typeof options.descending == 'undefined') options.descending = true; + var descend = true; + $.futon.storage.set("desc", "1"); + } else { + var descend = false; + $.futon.storage.del("desc"); + } + $("#paging a").unbind(); + $("#documents").find("tbody.content").empty().end().show(); + page.updateDesignDocLink(); + + options.success = function(resp) { + if (resp.offset === undefined) { + resp.offset = 0; + } + var descending_reverse = ((options.descending && !descend) || (descend && (options.descending === false))); + var has_reduce_prev = resp.total_rows === undefined && (descending_reverse ? resp.rows.length > perPage : options.startkey !== undefined); + if (descending_reverse && resp.rows) { + resp.rows = resp.rows.reverse(); + if (resp.rows.length > perPage) { + resp.rows.push(resp.rows.shift()); + } + } + if (resp.rows !== null && (has_reduce_prev || (descending_reverse ? + (resp.total_rows - resp.offset > perPage) : + (resp.offset > 0)))) { + $("#paging a.prev").attr("href", "#" + (resp.offset - perPage)).click(function() { + var opt = { + descending: !descend, + limit: options.limit + }; + if (resp.rows.length > 0) { + var firstDoc = resp.rows[0]; + opt.startkey = firstDoc.key !== undefined ? firstDoc.key : null; + if (firstDoc.id !== undefined) { + opt.startkey_docid = firstDoc.id; + } + opt.skip = 1; + } + page.updateDocumentListing(opt); + return false; + }); + } else { + $("#paging a.prev").removeAttr("href"); + } + var has_reduce_next = resp.total_rows === undefined && (descending_reverse ? options.startkey !== undefined : resp.rows.length > perPage); + if (resp.rows !== null && (has_reduce_next || (descending_reverse ? + (resp.offset - resp.total_rows < perPage) : + (resp.total_rows - resp.offset > perPage)))) { + $("#paging a.next").attr("href", "#" + (resp.offset + perPage)).click(function() { + var opt = { + descending: descend, + limit: options.limit + }; + if (resp.rows.length > 0) { + var lastDoc = resp.rows[Math.min(perPage, resp.rows.length) - 1]; + opt.startkey = lastDoc.key !== undefined ? lastDoc.key : null; + if (lastDoc.id !== undefined) { + opt.startkey_docid = lastDoc.id; + } + opt.skip = 1; + } + page.updateDocumentListing(opt); + return false; + }); + } else { + $("#paging a.next").removeAttr("href"); + } + + for (var i = 0; i < Math.min(perPage, resp.rows.length); i++) { + var row = resp.rows[i]; + var tr = $(""); + var key = "null"; + if (row.key !== null) { + key = $.futon.formatJSON(row.key, {indent: 0, linesep: ""}); + } + if (row.id) { + key = key.replace(/\\"/, '"'); + var rowlink = encodeURIComponent(db.name) + + "/" + $.couch.encodeDocId(row.id); + $("" + + $.futon.escape(key) + "
      " + + "ID: " + $.futon.escape(row.id) + "
      ") + .appendTo(tr); + } else { + $("") + .find("strong").text(key).end() + .appendTo(tr); + } + var value = "null"; + if (row.value !== null) { + value = $.futon.formatJSON(row.value, { + html: true, indent: 0, linesep: "", quoteKeys: false + }); + } + $("
      ").find("div").html(value).end() + .appendTo(tr).dblclick(function() { + location.href = this.previousSibling.firstChild.href; + }); + tr.appendTo("#documents tbody.content"); + } + var firstNum = 1; + var lastNum = totalNum = Math.min(perPage, resp.rows.length); + if (resp.total_rows != null) { + if (descending_reverse) { + lastNum = Math.min(resp.total_rows, resp.total_rows - resp.offset); + firstNum = lastNum - totalNum + 1; + } else { + firstNum = Math.min(resp.total_rows, resp.offset + 1); + lastNum = firstNum + totalNum - 1; + } + totalNum = resp.total_rows; + } else { + totalNum = "unknown"; + } + $("#paging").show(); + + $("#documents tbody.footer td span").text( + "Showing " + firstNum + "-" + lastNum + " of " + totalNum + + " row" + (firstNum != lastNum || totalNum == "unknown" ? "s" : "")); + $("#documents tbody tr:odd").addClass("odd"); + } + options.error = function(status, error, reason) { + alert("Error: " + error + "\n\n" + reason); + } + + if (!viewName || viewName == "_all_docs") { + $("#switch select")[0].selectedIndex = 0; + db.allDocs(options); + } else { + if (viewName == "_temp_view") { + $("#viewcode").show().removeClass("collapsed"); + var mapFun = $("#viewcode_map").val(); + $.futon.storage.set("map_fun", mapFun); + var reduceFun = $.trim($("#viewcode_reduce").val()) || null; + if (reduceFun) { + $.futon.storage.set("reduce_fun", reduceFun); + if ($("#reduce :checked").length) { + var level = parseInt($("#grouplevel select").val(), 10); + options.group = level > 0; + if (options.group && level < 100) { + options.group_level = level; + } + } else { + options.reduce = false; + } + } + $.futon.storage.set("language", page.viewLanguage); + db.query(mapFun, reduceFun, page.viewLanguage, options); + } else if (viewName == "_design_docs") { + options.startkey = options.descending ? "_design0" : "_design"; + options.endkey = options.descending ? "_design" : "_design0"; + db.allDocs(options); + } else { + $("button.compactview").show(); + $("#viewcode").show(); + var currentMapCode = $("#viewcode_map").val(); + var currentReduceCode = $.trim($("#viewcode_reduce").val()) || null; + if (currentReduceCode) { + if ($("#reduce :checked").length) { + var level = parseInt($("#grouplevel select").val(), 10); + options.group = level > 0; + if (options.group && level < 100) { + options.group_level = level; + } + } else { + options.reduce = false; + } + } + if (page.isDirty) { + db.query(currentMapCode, currentReduceCode, page.viewLanguage, options); + } else { + var viewParts = decodeURIComponent(viewName).split('/'); + if ($.futon.storage.get("stale")) { + options.stale = "ok"; + } + + db.view(viewParts[1] + "/" + viewParts[3], options); + } + } + } + } + + window.onbeforeunload = function() { + $("#switch select").val(viewName); + if (page.isDirty) { + return "You've made changes to the view code that have not been " + + "saved yet."; + } + } + + }, + + // Page class for browse/document.html + CouchDocumentPage: function() { + var urlParts = location.search.substr(1).split("/"); + var dbName = decodeURIComponent(urlParts.shift()); + if (urlParts.length) { + var idParts = urlParts.join("/").split("@", 2); + var docId = decodeURIComponent(idParts[0]); + var docRev = (idParts.length > 1) ? idParts[1] : null; + this.isNew = false; + } else { + var docId = $.couch.newUUID(); + var docRev = null; + this.isNew = true; + } + var db = $.couch.db(dbName); + + $.futon.storage.declare("tab", {defaultValue: "tabular", scope: "cookie"}); + + this.dbName = dbName; + this.db = db; + this.docId = docId; + this.doc = null; + this.isDirty = this.isNew; + page = this; + + this.activateTabularView = function() { + if ($("#fields tbody.source textarea").length > 0) + return; + + $.futon.storage.set("tab", "tabular"); + $("#tabs li").removeClass("active").filter(".tabular").addClass("active"); + $("#fields thead th:first").text("Field").attr("colspan", 1).next().show(); + $("#fields tbody.content").show(); + $("#fields tbody.source").hide(); + return false; + } + + this.activateSourceView = function() { + $.futon.storage.set("tab", "source"); + $("#tabs li").removeClass("active").filter(".source").addClass("active"); + $("#fields thead th:first").text("Source").attr("colspan", 2).next().hide(); + $("#fields tbody.content").hide(); + $("#fields tbody.source").find("td").each(function() { + $(this).html($("
      ").html($.futon.formatJSON(page.doc, {html: true})))
      +            .makeEditable({allowEmpty: false,
      +              createInput: function(value) {
      +                var rows = value.split("\n").length;
      +                return $("").enableTabInsertion();
      +              },
      +              prepareInput: function(input) {
      +                $(input).makeResizable({vertical: true});
      +              },
      +              end: function() {
      +                $(this).html($("
      ").html($.futon.formatJSON(page.doc, {html: true})));
      +              },
      +              accept: function(newValue) {
      +                page.doc = JSON.parse(newValue);
      +                page.isDirty = true;
      +                page.updateFieldListing(true);
      +              },
      +              populate: function(value) {
      +                return $.futon.formatJSON(page.doc);
      +              },
      +              validate: function(value) {
      +                try {
      +                  var doc = JSON.parse(value);
      +                  if (typeof doc != "object")
      +                    throw new SyntaxError("Please enter a valid JSON document (for example, {}).");
      +                  return true;
      +                } catch (err) {
      +                  var msg = err.message;
      +                  if (msg == "parseJSON" || msg == "JSON.parse") {
      +                    msg = "There is a syntax error in the document.";
      +                  }
      +                  $("
      ").text(msg).appendTo(this); + return false; + } + } + }); + }).end().show(); + return false; + } + + this.addField = function() { + if (!$("#fields tbody.content:visible").length) { + location.hash = "#tabular"; + page.activateTabularView(); + } + var fieldName = "unnamed"; + var fieldIdx = 1; + while (page.doc.hasOwnProperty(fieldName)) { + fieldName = "unnamed " + fieldIdx++; + } + page.doc[fieldName] = null; + var row = _addRowForField(page.doc, fieldName); + page.isDirty = true; + row.find("th b").dblclick(); + } + + var _sortFields = function(a, b) { + var a0 = a.charAt(0), b0 = b.charAt(0); + if (a0 == "_" && b0 != "_") { + return -1; + } else if (a0 != "_" && b0 == "_") { + return 1; + } else if (a == "_attachments" || b == "_attachments") { + return a0 == "_attachments" ? 1 : -1; + } else { + return a < b ? -1 : a != b ? 1 : 0; + } + } + + this.updateFieldListing = function(noReload) { + $("#fields tbody.content").empty(); + + function handleResult(doc, revs) { + page.doc = doc; + var propNames = []; + for (var prop in doc) { + propNames.push(prop); + } + // Order properties alphabetically, but put internal fields first + propNames.sort(_sortFields); + for (var pi = 0; pi < propNames.length; pi++) { + _addRowForField(doc, propNames[pi]); + } + if (revs.length > 1) { + var currentIndex = 0; + for (var i = 0; i < revs.length; i++) { + if (revs[i].rev == doc._rev) { + currentIndex = i; + break; + } + } + if (currentIndex < revs.length - 1) { + var prevRev = revs[currentIndex + 1].rev; + $("#paging a.prev").attr("href", "?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(docId) + "@" + prevRev); + } + if (currentIndex > 0) { + var nextRev = revs[currentIndex - 1].rev; + $("#paging a.next").attr("href", "?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(docId) + "@" + nextRev); + } + $("#fields tbody.footer td span").text("Showing revision " + + (revs.length - currentIndex) + " of " + revs.length); + } + if ($.futon.storage.get("tab") == "source") { + page.activateSourceView(); + } + } + + if (noReload) { + handleResult(page.doc, []); + return; + } + + if (!page.isNew) { + db.openDoc(docId, {revs_info: true, + success: function(doc) { + var revs = doc._revs_info || []; + delete doc._revs_info; + if (docRev != null) { + db.openDoc(docId, {rev: docRev, + error: function(status, error, reason) { + alert("The requested revision was not found. You will " + + "be redirected back to the latest revision."); + location.href = "?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(docId); + }, + success: function(doc) { + handleResult(doc, revs); + } + }); + } else { + handleResult(doc, revs); + } + } + }); + } else { + handleResult({_id: docId}, []); + $("#fields tbody td").dblclick(); + } + } + + this.deleteDocument = function() { + $.showDialog("dialog/_delete_document.html", { + submit: function(data, callback) { + db.removeDoc(page.doc, { + success: function(resp) { + callback(); + location.href = "database.html?" + encodeURIComponent(dbName); + } + }); + } + }); + } + + this.saveDocument = function() { + db.saveDoc(page.doc, { + error: function(status, error, reason) { + alert("Error: " + error + "\n\n" + reason); + }, + success: function(resp) { + page.isDirty = false; + location.href = "?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(page.docId); + } + }); + } + + this.uploadAttachment = function() { + if (page.isDirty) { + alert("You need to save or revert any changes you have made to the " + + "document before you can attach a new file."); + return false; + } + $.showDialog("dialog/_upload_attachment.html", { + load: function(elem) { + $("input[name='_rev']", elem).val(page.doc._rev); + }, + submit: function(data, callback) { + if (!data._attachments || data._attachments.length == 0) { + callback({_attachments: "Please select a file to upload."}); + return; + } + var form = $("#upload-form"); + form.find("#progress").css("visibility", "visible"); + form.ajaxSubmit({ + url: db.uri + $.couch.encodeDocId(page.docId), + success: function(resp) { + form.find("#progress").css("visibility", "hidden"); + page.isDirty = false; + location.href = "?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(page.docId); + } + }); + } + }); + } + + window.onbeforeunload = function() { + if (page.isDirty) { + return "You've made changes to this document that have not been " + + "saved yet."; + } + } + + function _addRowForField(doc, fieldName) { + var row = $("") + .find("th").append($("").text(fieldName)).end() + .appendTo("#fields tbody.content"); + if (fieldName == "_attachments") { + row.find("td").append(_renderAttachmentList(doc[fieldName])); + } else { + row.find("td").append(_renderValue(doc[fieldName])); + _initKey(doc, row, fieldName); + _initValue(doc, row, fieldName); + } + $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd"); + row.data("name", fieldName); + return row; + } + + function _initKey(doc, row, fieldName) { + if (fieldName == "_id" || fieldName == "_rev") { + return; + } + + var cell = row.find("th"); + + $("").click(function() { + delete doc[fieldName]; + row.remove(); + page.isDirty = true; + $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd"); + }).prependTo(cell); + + cell.find("b").makeEditable({allowEmpty: false, + accept: function(newName, oldName) { + doc[newName] = doc[oldName]; + delete doc[oldName]; + row.data("name", newName); + $(this).text(newName); + page.isDirty = true; + }, + begin: function() { + row.find("th button.delete").hide(); + return true; + }, + end: function(keyCode) { + row.find("th button.delete").show(); + if (keyCode == 9) { // tab, move to editing the value + row.find("td").dblclick(); + } + }, + validate: function(newName, oldName) { + $("div.error", this).remove(); + if (newName != oldName && doc[newName] !== undefined) { + $("
      Already have field with that name.
      ") + .appendTo(this); + return false; + } + return true; + } + }); + } + + function _initValue(doc, row, fieldName) { + if ((fieldName == "_id" && !page.isNew) || fieldName == "_rev") { + return; + } + + row.find("td").makeEditable({acceptOnBlur: false, allowEmpty: true, + createInput: function(value) { + value = doc[row.data("name")]; + var elem = $(this); + if (elem.find("dl").length > 0 || + elem.find("code").is(".array, .object") || + typeof(value) == "string" && (value.length > 60 || value.match(/\n/))) { + return $(""); + } + return $(""); + }, + end: function() { + $(this).children().remove(); + $(this).append(_renderValue(doc[row.data("name")])); + }, + prepareInput: function(input) { + if ($(input).is("textarea")) { + var height = Math.min(input.scrollHeight, document.body.clientHeight - 100); + $(input).height(height).makeResizable({vertical: true}).enableTabInsertion(); + } + }, + accept: function(newValue) { + var fieldName = row.data("name"); + try { + doc[fieldName] = JSON.parse(newValue); + } catch (err) { + doc[fieldName] = newValue; + } + page.isDirty = true; + if (fieldName == "_id") { + page.docId = page.doc._id = doc[fieldName]; + $("h1 strong").text(page.docId); + } + }, + populate: function(value) { + value = doc[row.data("name")]; + if (typeof(value) == "string") { + return value; + } + return $.futon.formatJSON(value); + }, + validate: function(value) { + $("div.error", this).remove(); + try { + var parsed = JSON.parse(value); + if (row.data("name") == "_id" && typeof(parsed) != "string") { + $("
      The document ID must be a string.
      ") + .appendTo(this); + return false; + } + return true; + } catch (err) { + return true; + } + } + }); + } + + function _renderValue(value) { + function isNullOrEmpty(val) { + if (val == null) return true; + for (var i in val) return false; + return true; + } + function render(val) { + var type = typeof(val); + if (type == "object" && !isNullOrEmpty(val)) { + var list = $("
      "); + for (var i in val) { + $("
      ").text(i).appendTo(list); + $("
      ").append(render(val[i])).appendTo(list); + } + return list; + } else { + var html = $.futon.formatJSON(val, { + html: true, + escapeStrings: false + }); + var n = $(html); + if (n.text().length > 140) { + // This code reduces a long string in to a summarized string with a link to expand it. + // Someone, somewhere, is doing something nasty with the event after it leaves these handlers. + // At this time I can't track down the offender, it might actually be a jQuery propogation issue. + var fulltext = n.text(); + var mintext = n.text().slice(0, 140); + var e = $('...'); + var m = $('X'); + var expand = function (evt) { + n.empty(); + n.text(fulltext); + n.append(m); + evt.stopPropagation(); + evt.stopImmediatePropagation(); + evt.preventDefault(); + } + var minimize = function (evt) { + n.empty(); + n.text(mintext); + // For some reason the old element's handler won't fire after removed and added again. + e = $('...'); + e.click(expand); + n.append(e); + evt.stopPropagation(); + evt.stopImmediatePropagation(); + evt.preventDefault(); + } + e.click(expand); + n.click(minimize); + n.text(mintext); + n.append(e) + } + return n; + } + } + var elem = render(value); + + elem.find("dd:has(dl)").hide().prev("dt").addClass("collapsed"); + elem.find("dd:not(:has(dl))").addClass("inline").prev().addClass("inline"); + elem.find("dt.collapsed").click(function() { + $(this).toggleClass("collapsed").next().toggle(); + }); + + return elem; + } + + function _renderAttachmentList(attachments) { + var ul = $("
        ").addClass("attachments"); + $.each(attachments, function(idx, attachment) { + _renderAttachmentItem(idx, attachment).appendTo(ul); + }); + return ul; + } + + function _renderAttachmentItem(name, attachment) { + var attachmentHref = db.uri + $.couch.encodeDocId(page.docId) + + "/" + encodeAttachment(name); + var li = $("
      • "); + $("").text(name) + .attr("href", attachmentHref) + .wrapInner("").appendTo(li); + $("()").text("" + $.futon.formatSize(attachment.length) + + ", " + attachment.content_type).addClass("info").appendTo(li); + if (name == "tests.js") { + li.find('span.info').append(', open in test runner'); + } + _initAttachmentItem(name, attachment, li); + return li; + } + + function _initAttachmentItem(name, attachment, li) { + $("").click(function() { + if (!li.siblings("li").length) { + delete page.doc._attachments; + li.parents("tr").remove(); + $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd"); + } else { + delete page.doc._attachments[name]; + li.remove(); + } + page.isDirty = true; + return false; + }).prependTo($("a", li)); + } + }, + + }); + + function encodeAttachment(name) { + var encoded = [], parts = name.split('/'); + for (var i=0; i < parts.length; i++) { + encoded.push(encodeURIComponent(parts[i])); + }; + return encoded.join('%2f'); + } + +})(jQuery); diff --git a/1.1.x/share/www/script/futon.format.js b/1.1.x/share/www/script/futon.format.js new file mode 100644 index 00000000..0eb9b104 --- /dev/null +++ b/1.1.x/share/www/script/futon.format.js @@ -0,0 +1,146 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +(function($) { + $.futon = $.futon || {}; + $.extend($.futon, { + escape: function(string) { + return string.replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + ; + }, + + // JSON pretty printing + formatJSON: function(val, options) { + options = $.extend({ + escapeStrings: true, + indent: 4, + linesep: "\n", + quoteKeys: true + }, options || {}); + var itemsep = options.linesep.length ? "," + options.linesep : ", "; + + function format(val, depth) { + var tab = []; + for (var i = 0; i < options.indent * depth; i++) tab.push(""); + tab = tab.join(" "); + + var type = typeof val; + switch (type) { + case "boolean": + case "number": + case "string": + var retval = val; + if (type == "string" && !options.escapeStrings) { + retval = indentLines(retval.replace(/\r\n/g, "\n"), tab.substr(options.indent)); + } else { + if (options.html) { + retval = $.futon.escape(JSON.stringify(val)); + } else { + retval = JSON.stringify(val); + } + } + if (options.html) { + retval = "" + retval + ""; + } + return retval; + + case "object": { + if (val === null) { + if (options.html) { + return "null"; + } + return "null"; + } + if (val.constructor == Date) { + return JSON.stringify(val); + } + + var buf = []; + + if (val.constructor == Array) { + buf.push("["); + for (var index = 0; index < val.length; index++) { + buf.push(index > 0 ? itemsep : options.linesep); + buf.push(tab, format(val[index], depth + 1)); + } + if (index >= 0) { + buf.push(options.linesep, tab.substr(options.indent)); + } + buf.push("]"); + if (options.html) { + return "" + buf.join("") + ""; + } + + } else { + buf.push("{"); + var index = 0; + for (var key in val) { + buf.push(index > 0 ? itemsep : options.linesep); + var keyDisplay = options.quoteKeys ? JSON.stringify(key) : key; + if (options.html) { + if (options.quoteKeys) { + keyDisplay = keyDisplay.substr(1, keyDisplay.length - 2); + } + keyDisplay = "" + $.futon.escape(keyDisplay) + ""; + if (options.quoteKeys) { + keyDisplay = '"' + keyDisplay + '"'; + } + } + buf.push(tab, keyDisplay, + ": ", format(val[key], depth + 1)); + index++; + } + if (index >= 0) { + buf.push(options.linesep, tab.substr(options.indent)); + } + buf.push("}"); + if (options.html) { + return "" + buf.join("") + ""; + } + } + + return buf.join(""); + } + } + } + + function indentLines(text, tab) { + var lines = text.split("\n"); + for (var i in lines) { + lines[i] = (i > 0 ? tab : "") + $.futon.escape(lines[i]); + } + return lines.join("
        "); + } + + return format(val, 1); + }, + + // File size pretty printing + formatSize: function(size) { + var jump = 512; + if (size < jump) return size + " bytes"; + var units = ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + var i = 0; + while (size >= jump && i < units.length) { + i += 1; + size /= 1024 + } + return size.toFixed(1) + ' ' + units[i - 1]; + } + + }); + +})(jQuery); diff --git a/1.1.x/share/www/script/futon.js b/1.1.x/share/www/script/futon.js new file mode 100644 index 00000000..fb73e3c9 --- /dev/null +++ b/1.1.x/share/www/script/futon.js @@ -0,0 +1,535 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/ +function $$(node) { + var data = $(node).data("$$"); + if (data) { + return data; + } else { + data = {}; + $(node).data("$$", data); + return data; + } +}; + +(function($) { + + function Session() { + + function doLogin(name, password, callback) { + $.couch.login({ + name : name, + password : password, + success : function() { + $.futon.session.sidebar(); + callback(); + }, + error : function(code, error, reason) { + $.futon.session.sidebar(); + callback({name : "Error logging in: "+reason}); + } + }); + }; + + function doSignup(name, password, callback, runLogin) { + $.couch.signup({ + name : name + }, password, { + success : function() { + if (runLogin) { + doLogin(name, password, callback); + } else { + callback(); + } + }, + error : function(status, error, reason) { + $.futon.session.sidebar(); + if (error == "conflict") { + callback({name : "Name '"+name+"' is taken"}); + } else { + callback({name : "Signup error: "+reason}); + } + } + }); + }; + + function validateUsernameAndPassword(data, callback) { + if (!data.name || data.name.length == 0) { + callback({name: "Please enter a name."}); + return false; + }; + return validatePassword(data, callback); + }; + + function validatePassword(data, callback) { + if (!data.password || data.password.length == 0) { + callback({password: "Please enter a password."}); + return false; + }; + return true; + }; + + function createAdmin() { + $.showDialog("dialog/_create_admin.html", { + submit: function(data, callback) { + if (!validateUsernameAndPassword(data, callback)) return; + $.couch.config({ + success : function() { + doLogin(data.name, data.password, function(errors) { + if(!$.isEmptyObject(errors)) { + callback(errors); + return; + } + doSignup(data.name, null, function(errors) { + if (errors && errors.name && errors.name.indexOf && errors.name.indexOf("taken") == -1) { + callback(errors); + } else { + callback(); + } + }, false); + }); + } + }, "admins", data.name, data.password); + } + }); + return false; + }; + + function login() { + $.showDialog("dialog/_login.html", { + submit: function(data, callback) { + if (!validateUsernameAndPassword(data, callback)) return; + doLogin(data.name, data.password, callback); + } + }); + return false; + }; + + function logout() { + $.couch.logout({ + success : function(resp) { + $.futon.session.sidebar(); + } + }) + }; + + function signup() { + $.showDialog("dialog/_signup.html", { + submit: function(data, callback) { + if (!validateUsernameAndPassword(data, callback)) return; + doSignup(data.name, data.password, callback, true); + } + }); + return false; + }; + + function changePassword () { + $.showDialog("dialog/_change_password.html", { + submit: function(data, callback) { + if (validatePassword(data, callback)) { + if (data.password != data.verify_password) { + callback({verify_password: "Passwords don't match."}); + return false; + } + } else { + return false; + } + $.couch.session({success: function (resp) { + if (resp.userCtx.roles.indexOf("_admin") > -1) { + $.couch.config({ + success : function () { + doLogin(resp.userCtx.name, data.password, function(errors) { + if(!$.isEmptyObject(errors)) { + callback(errors); + return; + } else { + location.reload(); + } + }); + } + }, "admins", resp.userCtx.name, data.password); + } else { + $.couch.db(resp.info.authentication_db).openDoc("org.couchdb.user:"+resp.userCtx.name, { + success: function (user) { + $.couch.db(resp.info.authentication_db).saveDoc($.couch.prepareUserDoc(user, data.password), { + success: function() { + doLogin(user.name, data.password, function(errors) { + if(!$.isEmptyObject(errors)) { + callback(errors); + return; + } else { + location.reload(); + } + }); + } + }); + } + }); + } + }}); + } + }); + return false; + }; + + this.setupSidebar = function() { + $("#userCtx .login").click(login); + $("#userCtx .logout").click(logout); + $("#userCtx .signup").click(signup); + $("#userCtx .createadmin").click(createAdmin); + $("#userCtx .changepass").click(changePassword); + }; + + this.sidebar = function() { + // get users db info? + $("#userCtx span").hide(); + $.couch.session({ + success : function(r) { + var userCtx = r.userCtx; + $$("#userCtx").userCtx = userCtx; + if (userCtx.name) { + $("#userCtx .name").text(userCtx.name).attr({href : $.couch.urlPrefix + "/_utils/document.html?"+encodeURIComponent(r.info.authentication_db)+"/org.couchdb.user%3A"+encodeURIComponent(userCtx.name)}); + if (userCtx.roles.indexOf("_admin") != -1) { + $("#userCtx .loggedin").show(); + $("#userCtx .loggedinadmin").show(); + } else { + $("#userCtx .loggedin").show(); + } + } else if (userCtx.roles.indexOf("_admin") != -1) { + $("#userCtx .adminparty").show(); + } else { + $("#userCtx .loggedout").show(); + }; + } + }) + }; + }; + + function Navigation() { + var nav = this; + this.loaded = false; + this.eventHandlers = { + load: [] + }; + + this.ready = function(callback) { + if (callback) { + if (this.loaded) { + callback.apply(this); + } + this.eventHandlers["load"].push(callback); + } else { + this.loaded = true; + callbacks = this.eventHandlers["load"]; + for (var i = 0; i < callbacks.length; i++) { + callbacks[i].apply(this); + } + } + } + + this.addDatabase = function(name) { + var current = $.futon.storage.get("recent", ""); + var recentDbs = current ? current.split(",") : []; + if ($.inArray(name, recentDbs) == -1) { + recentDbs.unshift(name); + if (recentDbs.length > 10) recentDbs.length = 10; + $.futon.storage.set("recent", recentDbs.join(",")); + this.updateDatabases(); + } + } + + this.removeDatabase = function(name) { + // remove database from recent databases list + var current = $.futon.storage.get("recent", ""); + var recentDbs = current ? current.split(",") : []; + var recentIdx = $.inArray(name, recentDbs); + if (recentIdx >= 0) { + recentDbs.splice(recentIdx, 1); + $.futon.storage.set("recent", recentDbs.join(",")); + this.updateDatabases(); + } + } + + this.updateDatabases = function() { + var selection = null; + $("#dbs .selected a").each(function() { + selection = [this.pathname, this.search]; + }); + $("#dbs").empty(); + var recentDbs = $.futon.storage.get("recent").split(","); + recentDbs.sort(); + $.each(recentDbs, function(idx, name) { + if (name) { + name = encodeURIComponent(name); + $("#dbs").append("
      • " + + "" + + "" + name + + "
      • "); + } + }); + if (selection) { + this.updateSelection(selection[0], selection[1]); + } + $("#dbs button.remove").click(function() { + nav.removeDatabase(this.value); + return false; + }); + } + + this.updateSelection = function(path, queryString) { + function fixupPath(path) { // hack for IE/Win + return (path.charAt(0) != "/") ? ("/" + path) : path; + } + if (!path) { + path = location.pathname; + if (!queryString) { + queryString = location.search; + } + } else if (!queryString) { + queryString = ""; + } + var href = fixupPath(path + queryString); + $("#nav li").removeClass("selected"); + $("#nav li a").each(function() { + if (fixupPath(this.pathname) + this.search != href) return; + $(this).parent("li").addClass("selected").parents("li").addClass("selected"); + }); + } + + this.toggle = function(speed) { + if (speed === undefined) { + speed = 500; + } + var sidebar = $("#sidebar").stop(true, true); + var hidden = !$(sidebar).is(".hidden"); + + $("#wrap").animate({ + marginRight: hidden ? 0 : 210 + }, speed, function() { + $(document.body).toggleClass("fullwidth", hidden); + }); + sidebar.toggleClass("hidden").animate({ + width: hidden ? 26 : 210, + height: hidden ? $("h1").outerHeight() - 1 : "100%", + right: hidden ? 0 : -210 + }, speed).children(":not(#sidebar-toggle)").animate({ + opacity: "toggle" + }, speed); + $("h1").animate({marginRight: hidden ? 26 : 0}, speed); + + $("#sidebar-toggle") + .attr("title", hidden ? "Show Sidebar" : "Hide Sidebar"); + $.futon.storage.set("sidebar", hidden ? "hidden" : "show"); + }; + } + + function Storage() { + var storage = this; + this.decls = {}; + + this.declare = function(name, options) { + this.decls[name] = $.extend({}, { + scope: "window", + defaultValue: null, + prefix: "" + }, options || {}); + } + + this.declareWithPrefix = function(prefix, decls) { + for (var name in decls) { + var options = decls[name]; + options.prefix = prefix; + storage.declare(name, options); + } + } + + this.del = function(name) { + lookup(name, function(decl) { + handlers[decl.scope].del(decl.prefix + name); + }); + } + + this.get = function(name, defaultValue) { + return lookup(name, function(decl) { + var value = handlers[decl.scope].get(decl.prefix + name); + if (value !== undefined) { + return value; + } + if (defaultValue !== undefined) { + return defaultValue; + } + return decl.defaultValue; + }); + } + + this.set = function(name, value) { + lookup(name, function(decl) { + if (value == decl.defaultValue) { + handlers[decl.scope].del(decl.prefix + name); + } else { + handlers[decl.scope].set(decl.prefix + name, value); + } + }); + } + + function lookup(name, callback) { + var decl = storage.decls[name]; + if (decl === undefined) { + return decl; + } + return callback(decl); + } + + function windowName() { + try { + return JSON.parse(window.name || "{}"); + } catch (e) { + return {}; + } + } + + // add suffix to cookie names to be able to separate between ports + var cookiePrefix = location.port + "_"; + + var handlers = { + + "cookie": { + get: function(name) { + var nameEq = cookiePrefix + name + "="; + var parts = document.cookie.split(';'); + for (var i = 0; i < parts.length; i++) { + var part = parts[i].replace(/^\s+/, ""); + if (part.indexOf(nameEq) == 0) { + return unescape(part.substring(nameEq.length, part.length)); + } + } + }, + set: function(name, value) { + var date = new Date(); + date.setTime(date.getTime() + 14*24*60*60*1000); // two weeks + document.cookie = cookiePrefix + name + "=" + escape(value) + + "; expires=" + date.toGMTString(); + }, + del: function(name) { + var date = new Date(); + date.setTime(date.getTime() - 24*60*60*1000); // yesterday + document.cookie = cookiePrefix + name + "=" + + "; expires=" + date.toGMTString(); + } + }, + + "window": { + get: function(name) { + return windowName()[name]; + }, + set: function(name, value) { + var obj = windowName(); + obj[name] = value || null; + window.name = JSON.stringify(obj); + }, + del: function(name) { + var obj = windowName(); + delete obj[name]; + window.name = JSON.stringify(obj); + } + } + + }; + + } + + $.couch.urlPrefix = ".."; + $.futon = $.futon || {}; + $.extend($.futon, { + navigation: new Navigation(), + session : new Session(), + storage: new Storage() + }); + + $.fn.addPlaceholder = function() { + if (this[0] && "placeholder" in document.createElement("input")) { + return; // found native placeholder support + } + return this.live('focusin', function() { + var input = $(this); + if (input.val() === input.attr("placeholder")) { + input.removeClass("placeholder").val(""); + } + }).live("focusout", function() { + var input = $(this); + if (input.val() === "") { + input.val(input.attr("placeholder")).addClass("placeholder"); + } + }).trigger("focusout"); + } + + $.fn.enableTabInsertion = function(chars) { + chars = chars || "\t"; + var width = chars.length; + return this.keydown(function(evt) { + if (evt.keyCode == 9) { + var v = this.value; + var start = this.selectionStart; + var scrollTop = this.scrollTop; + if (start !== undefined) { + this.value = v.slice(0, start) + chars + v.slice(start); + this.selectionStart = this.selectionEnd = start + width; + } else { + document.selection.createRange().text = chars; + this.caretPos += width; + } + return false; + } + }); + } + + $(document) + .ajaxStart(function() { $(this.body).addClass("loading"); }) + .ajaxStop(function() { $(this.body).removeClass("loading"); }); + + $.futon.storage.declare("sidebar", {scope: "cookie", defaultValue: "show"}); + $.futon.storage.declare("recent", {scope: "cookie", defaultValue: ""}); + + $(function() { + document.title = "Apache CouchDB - Futon: " + document.title; + if ($.futon.storage.get("sidebar") == "hidden") { + // doing this as early as possible prevents flickering + $(document.body).addClass("fullwidth"); + } + $("input[placeholder]").addPlaceholder(); + + $.get("_sidebar.html", function(resp) { + $("#wrap").append(resp) + .find("#sidebar-toggle").click(function(e) { + $.futon.navigation.toggle(e.shiftKey ? 2500 : 500); + return false; + }); + if ($.futon.storage.get("sidebar") == "hidden") { + $.futon.navigation.toggle(0); + } + + $.futon.navigation.updateDatabases(); + $.futon.navigation.updateSelection(); + $.futon.navigation.ready(); + $.futon.session.setupSidebar(); + $.futon.session.sidebar(); + + $.couch.info({ + success: function(info, status) { + $("#version").text(info.version); + } + }); + }); + }); + +})(jQuery); diff --git a/1.1.x/share/www/script/jquery-ui-1.8.11.custom.min.js b/1.1.x/share/www/script/jquery-ui-1.8.11.custom.min.js new file mode 100644 index 00000000..45b927e0 --- /dev/null +++ b/1.1.x/share/www/script/jquery-ui-1.8.11.custom.min.js @@ -0,0 +1,81 @@ +/*! + * jQuery UI 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI + */ +(function(c,j){function k(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.11",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106, +NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this, +"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position"); +if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f, +"border"+this+"Width",true))||0;if(m)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h, +d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}}); +c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a0?b.left-d:Math.max(b.left-a.collisionPosition.left,b.left)},top:function(b,a){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();b.top=d>0?b.top-d:Math.max(b.top-a.collisionPosition.top,b.top)}},flip:{left:function(b,a){if(a.at[0]!=="center"){var d=c(window);d=a.collisionPosition.left+a.collisionWidth-d.width()-d.scrollLeft();var g=a.my[0]==="left"?-a.elemWidth:a.my[0]==="right"?a.elemWidth:0,e=a.at[0]==="left"?a.targetWidth:-a.targetWidth,h=-2*a.offset[0];b.left+= +a.collisionPosition.left<0?g+e+h:d>0?g+e+h:0}},top:function(b,a){if(a.at[1]!=="center"){var d=c(window);d=a.collisionPosition.top+a.collisionHeight-d.height()-d.scrollTop();var g=a.my[1]==="top"?-a.elemHeight:a.my[1]==="bottom"?a.elemHeight:0,e=a.at[1]==="top"?a.targetHeight:-a.targetHeight,h=-2*a.offset[1];b.top+=a.collisionPosition.top<0?g+e+h:d>0?g+e+h:0}}}};if(!c.offset.setOffset){c.offset.setOffset=function(b,a){if(/static/.test(c.curCSS(b,"position")))b.style.position="relative";var d=c(b), +g=d.offset(),e=parseInt(c.curCSS(b,"top",true),10)||0,h=parseInt(c.curCSS(b,"left",true),10)||0;g={top:a.top-g.top+e,left:a.left-g.left+h};"using"in a?a.using.call(b,g):d.css(g)};c.fn.offset=function(b){var a=this[0];if(!a||!a.ownerDocument)return null;if(b)return this.each(function(){c.offset.setOffset(this,b)});return u.call(this)}}})(jQuery); +;/* + * jQuery UI Autocomplete 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.position.js + */ +(function(d){var e=0;d.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:false,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var a=this,b=this.element[0].ownerDocument,g;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!(a.options.disabled||a.element.attr("readonly"))){g= +false;var f=d.ui.keyCode;switch(c.keyCode){case f.PAGE_UP:a._move("previousPage",c);break;case f.PAGE_DOWN:a._move("nextPage",c);break;case f.UP:a._move("previous",c);c.preventDefault();break;case f.DOWN:a._move("next",c);c.preventDefault();break;case f.ENTER:case f.NUMPAD_ENTER:if(a.menu.active){g=true;c.preventDefault()}case f.TAB:if(!a.menu.active)return;a.menu.select(c);break;case f.ESCAPE:a.element.val(a.term);a.close(c);break;default:clearTimeout(a.searching);a.searching=setTimeout(function(){if(a.term!= +a.element.val()){a.selectedItem=null;a.search(null,c)}},a.options.delay);break}}}).bind("keypress.autocomplete",function(c){if(g){g=false;c.preventDefault()}}).bind("focus.autocomplete",function(){if(!a.options.disabled){a.selectedItem=null;a.previous=a.element.val()}}).bind("blur.autocomplete",function(c){if(!a.options.disabled){clearTimeout(a.searching);a.closing=setTimeout(function(){a.close(c);a._change(c)},150)}});this._initSource();this.response=function(){return a._response.apply(a,arguments)}; +this.menu=d("
          ").addClass("ui-autocomplete").appendTo(d(this.options.appendTo||"body",b)[0]).mousedown(function(c){var f=a.menu.element[0];d(c.target).closest(".ui-menu-item").length||setTimeout(function(){d(document).one("mousedown",function(h){h.target!==a.element[0]&&h.target!==f&&!d.ui.contains(f,h.target)&&a.close()})},1);setTimeout(function(){clearTimeout(a.closing)},13)}).menu({focus:function(c,f){f=f.item.data("item.autocomplete");false!==a._trigger("focus",c,{item:f})&&/^key/.test(c.originalEvent.type)&& +a.element.val(f.value)},selected:function(c,f){var h=f.item.data("item.autocomplete"),i=a.previous;if(a.element[0]!==b.activeElement){a.element.focus();a.previous=i;setTimeout(function(){a.previous=i;a.selectedItem=h},1)}false!==a._trigger("select",c,{item:h})&&a.element.val(h.value);a.term=a.element.val();a.close(c);a.selectedItem=h},blur:function(){a.menu.element.is(":visible")&&a.element.val()!==a.term&&a.element.val(a.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"); +d.fn.bgiframe&&this.menu.element.bgiframe()},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup");this.menu.element.remove();d.Widget.prototype.destroy.call(this)},_setOption:function(a,b){d.Widget.prototype._setOption.apply(this,arguments);a==="source"&&this._initSource();if(a==="appendTo")this.menu.element.appendTo(d(b||"body",this.element[0].ownerDocument)[0]);a==="disabled"&& +b&&this.xhr&&this.xhr.abort()},_initSource:function(){var a=this,b,g;if(d.isArray(this.options.source)){b=this.options.source;this.source=function(c,f){f(d.ui.autocomplete.filter(b,c.term))}}else if(typeof this.options.source==="string"){g=this.options.source;this.source=function(c,f){a.xhr&&a.xhr.abort();a.xhr=d.ajax({url:g,data:c,dataType:"json",autocompleteRequest:++e,success:function(h){this.autocompleteRequest===e&&f(h)},error:function(){this.autocompleteRequest===e&&f([])}})}}else this.source= +this.options.source},search:function(a,b){a=a!=null?a:this.element.val();this.term=this.element.val();if(a.length").data("item.autocomplete",b).append(d("").text(b.label)).appendTo(a)},_move:function(a,b){if(this.menu.element.is(":visible"))if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term);this.menu.deactivate()}else this.menu[a](b);else this.search(null,b)},widget:function(){return this.menu.element}});d.extend(d.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, +"\\$&")},filter:function(a,b){var g=new RegExp(d.ui.autocomplete.escapeRegex(b),"i");return d.grep(a,function(c){return g.test(c.label||c.value||c)})}})})(jQuery); +(function(d){d.widget("ui.menu",{_create:function(){var e=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(a){if(d(a.target).closest(".ui-menu-item a").length){a.preventDefault();e.select(a)}});this.refresh()},refresh:function(){var e=this;this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem").children("a").addClass("ui-corner-all").attr("tabindex", +-1).mouseenter(function(a){e.activate(a,d(this).parent())}).mouseleave(function(){e.deactivate()})},activate:function(e,a){this.deactivate();if(this.hasScroll()){var b=a.offset().top-this.element.offset().top,g=this.element.attr("scrollTop"),c=this.element.height();if(b<0)this.element.attr("scrollTop",g+b);else b>=c&&this.element.attr("scrollTop",g+b-c+a.height())}this.active=a.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end();this._trigger("focus",e,{item:a})}, +deactivate:function(){if(this.active){this.active.children("a").removeClass("ui-state-hover").removeAttr("id");this._trigger("blur");this.active=null}},next:function(e){this.move("next",".ui-menu-item:first",e)},previous:function(e){this.move("prev",".ui-menu-item:last",e)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(e,a,b){if(this.active){e=this.active[e+"All"](".ui-menu-item").eq(0); +e.length?this.activate(b,e):this.activate(b,this.element.children(a))}else this.activate(b,this.element.children(a))},nextPage:function(e){if(this.hasScroll())if(!this.active||this.last())this.activate(e,this.element.children(".ui-menu-item:first"));else{var a=this.active.offset().top,b=this.element.height(),g=this.element.children(".ui-menu-item").filter(function(){var c=d(this).offset().top-a-b+d(this).height();return c<10&&c>-10});g.length||(g=this.element.children(".ui-menu-item:last"));this.activate(e, +g)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(e){if(this.hasScroll())if(!this.active||this.first())this.activate(e,this.element.children(".ui-menu-item:last"));else{var a=this.active.offset().top,b=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var g=d(this).offset().top-a+b-d(this).height();return g<10&&g>-10});result.length||(result=this.element.children(".ui-menu-item:first")); +this.activate(e,result)}else this.activate(e,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()= 0, + data = xml ? xhr.responseXML : xhr.responseText; + + if ( xml && data.documentElement.nodeName === "parsererror" ) { + $.error( "parsererror" ); + } + if ( s && s.dataFilter ) { + data = s.dataFilter( data, type ); + } + if ( typeof data === "string" ) { + if ( type === "json" || !type && ct.indexOf("json") >= 0 ) { + data = $.parseJSON( data ); + } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) { + $.globalEval( data ); + } + } + return data; + }; + + function ajax(obj, options, errorMessage, ajaxOptions) { + options = $.extend({successStatus: 200}, options); + ajaxOptions = $.extend({contentType: "application/json"}, ajaxOptions); + errorMessage = errorMessage || "Unknown error"; + $.ajax($.extend($.extend({ + type: "GET", dataType: "json", cache : !$.browser.msie, + beforeSend: function(xhr){ + if(ajaxOptions && ajaxOptions.headers){ + for (var header in ajaxOptions.headers){ + xhr.setRequestHeader(header, ajaxOptions.headers[header]); + } + } + }, + complete: function(req) { + try { + var resp = httpData(req, "json"); + } catch(e) { + if (options.error) { + options.error(req.status, req, e); + } else { + alert(errorMessage + ": " + e); + } + return; + } + if (options.ajaxStart) { + options.ajaxStart(resp); + } + if (req.status == options.successStatus) { + if (options.beforeSuccess) options.beforeSuccess(req, resp); + if (options.success) options.success(resp); + } else if (options.error) { + options.error(req.status, resp && resp.error || errorMessage, resp && resp.reason || "no response"); + } else { + alert(errorMessage + ": " + resp.reason); + } + } + }, obj), ajaxOptions)); + } + + function fullCommit(options) { + var options = options || {}; + if (typeof options.ensure_full_commit !== "undefined") { + var commit = options.ensure_full_commit; + delete options.ensure_full_commit; + return function(xhr) { + xhr.setRequestHeader('Accept', 'application/json'); + xhr.setRequestHeader("X-Couch-Full-Commit", commit.toString()); + }; + } + }; + + // Convert a options object to an url query string. + // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"' + function encodeOptions(options) { + var buf = []; + if (typeof(options) === "object" && options !== null) { + for (var name in options) { + if ($.inArray(name, ["error", "success", "beforeSuccess", "ajaxStart"]) >= 0) + continue; + var value = options[name]; + if ($.inArray(name, ["key", "startkey", "endkey"]) >= 0) { + value = toJSON(value); + } + buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value)); + } + } + return buf.length ? "?" + buf.join("&") : ""; + } + + function toJSON(obj) { + return obj !== null ? JSON.stringify(obj) : null; + } + +})(jQuery); diff --git a/1.1.x/share/www/script/jquery.dialog.js b/1.1.x/share/www/script/jquery.dialog.js new file mode 100644 index 00000000..02c0c497 --- /dev/null +++ b/1.1.x/share/www/script/jquery.dialog.js @@ -0,0 +1,96 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +(function($) { + + $.fn.centerBox = function() { + return this.each(function() { + var s = this.style; + s.left = (($(window).width() - $(this).width()) / 2) + "px"; + s.top = (($(window).height() - $(this).height()) / 2) + "px"; + }); + } + + $.showDialog = function(url, options) { + options = options || {}; + options.load = options.load || function() {}; + options.cancel = options.cancel || function() {}; + options.validate = options.validate || function() { return true }; + options.submit = options.submit || function() {}; + + var overlay = $('
          ') + .css("opacity", "0"); + var dialog = $(''); + if ($.browser.msie) { + var frame = $('') + .css("opacity", "0").appendTo(document.body); + if (parseInt($.browser.version)<7) { + dialog.css("position", "absolute"); + overlay.css("position", "absolute"); + $("html,body").css({width: "100%", height: "100%"}); + } + } + overlay.appendTo(document.body).fadeTo(100, 0.6); + dialog.appendTo(document.body).addClass("loading").centerBox().fadeIn(400); + + $(document).keydown(function(e) { + if (e.keyCode == 27) dismiss(); // dismiss on escape key + }); + function dismiss() { + dialog.fadeOut("fast", function() { + $("#dialog, #overlay, #overlay-frame").remove(); + }); + $(document).unbind("keydown"); + } + overlay.click(function() { dismiss(); }); + + function showError(name, message) { + var input = dialog.find(":input[name=" + name + "]"); + input.addClass("error").next("div.error").remove(); + $('
          ').text(message).insertAfter(input); + } + + $.get(url, function(html) { + $(html).appendTo(dialog); + dialog.removeClass("loading").addClass("loaded").centerBox().each(function() { + options.load(dialog.children()[0]); + $(":input:first", dialog).each(function() { this.focus() }); + $("button.cancel", dialog).click(function() { // dismiss on cancel + dismiss(); + options.cancel(); + }); + $("form", dialog).submit(function(e) { // invoke callback on submit + e.preventDefault(); + dialog.find("div.error").remove().end().find(".error").removeClass("error"); + var data = {}; + $.each($("form :input", dialog).serializeArray(), function(i, field) { + data[field.name] = field.value; + }); + $("form :file", dialog).each(function() { + data[this.name] = this.value; // file inputs need special handling + }); + options.submit(data, function callback(errors) { + if ($.isEmptyObject(errors)) { + dismiss(); + } else { + for (var name in errors) { + showError(name, errors[name]); + } + } + }); + return false; + }); + }); + }); + } + +})(jQuery); diff --git a/1.1.x/share/www/script/jquery.editinline.js b/1.1.x/share/www/script/jquery.editinline.js new file mode 100644 index 00000000..b48607d4 --- /dev/null +++ b/1.1.x/share/www/script/jquery.editinline.js @@ -0,0 +1,114 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +(function($) { + + function startEditing(elem, options) { + var editable = $(elem); + var origHtml = editable.html(); + var origText = options.populate($.trim(editable.text())); + + if (!options.begin.apply(elem, [origText])) { + return; + } + + var input = options.createInput.apply(elem, [origText]) + .addClass("editinline").val(origText) + .dblclick(function() { return false; }) + .keydown(function(evt) { + switch (evt.keyCode) { + case 13: { // return + if (!input.is("textarea")) applyChange(evt.keyCode); + break; + } + case 27: { // escape + cancelChange(evt.keyCode); + break; + } + case 9: { // tab + if (!input.is("textarea")) { + applyChange(evt.keyCode); + return false; + } + } + } + }); + if (options.acceptOnBlur) { + input.blur(function() { + return applyChange(); + }); + } + + function applyChange(keyCode) { + var newText = input.val(); + if (newText == origText) { + cancelChange(keyCode); + return true; + } + if ((!options.allowEmpty && !newText.length) || + !options.validate.apply(elem, [newText, origText])) { + input.addClass("invalid"); + return false; + } + input.remove(); + tools.remove(); + options.accept.apply(elem, [newText, origText]); + editable.removeClass("editinline-container"); + options.end.apply(elem, [keyCode]); + return true; + } + + function cancelChange(keyCode) { + options.cancel.apply(elem, [origText]); + editable.html(origHtml).removeClass("editinline-container"); + options.end.apply(elem, [keyCode]); + } + + var tools = $(""); + $("") + .text(options.acceptLabel).click(applyChange).appendTo(tools); + $("") + .text(options.cancelLabel).click(cancelChange).appendTo(tools) + + editable.html("").append(tools).append(input) + .addClass("editinline-container"); + options.prepareInput.apply(elem, [input[0]]); + input.each(function() { this.focus(); this.select(); }); + } + + $.fn.makeEditable = function(options) { + options = $.extend({ + allowEmpty: true, + acceptLabel: "", + cancelLabel: "", + toolTip: "Double click to edit", + acceptOnBlur: true, + + // callbacks + begin: function() { return true }, + accept: function(newValue, oldValue) {}, + cancel: function(oldValue) {}, + createInput: function(value) { return $("") }, + prepareInput: function(input) {}, + end: function(keyCode) {}, + populate: function(value) { return value }, + validate: function() { return true } + }, options || {}); + + return this.each(function() { + $(this).attr("title", options.toolTip).dblclick(function() { + startEditing(this, options); + }); + }); + } + +})(jQuery); diff --git a/1.1.x/share/www/script/jquery.form.js b/1.1.x/share/www/script/jquery.form.js new file mode 100644 index 00000000..dde39427 --- /dev/null +++ b/1.1.x/share/www/script/jquery.form.js @@ -0,0 +1,660 @@ +/* + * jQuery Form Plugin + * version: 2.36 (07-NOV-2009) + * @requires jQuery v1.2.6 or later + * + * Examples and documentation at: http://malsup.com/jquery/form/ + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ +;(function($) { + +/* + Usage Note: + ----------- + Do not use both ajaxSubmit and ajaxForm on the same form. These + functions are intended to be exclusive. Use ajaxSubmit if you want + to bind your own submit handler to the form. For example, + + $(document).ready(function() { + $('#myForm').bind('submit', function() { + $(this).ajaxSubmit({ + target: '#output' + }); + return false; // <-- important! + }); + }); + + Use ajaxForm when you want the plugin to manage all the event binding + for you. For example, + + $(document).ready(function() { + $('#myForm').ajaxForm({ + target: '#output' + }); + }); + + When using ajaxForm, the ajaxSubmit function will be invoked for you + at the appropriate time. +*/ + +/** + * ajaxSubmit() provides a mechanism for immediately submitting + * an HTML form using AJAX. + */ +$.fn.ajaxSubmit = function(options) { + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) + if (!this.length) { + log('ajaxSubmit: skipping submit process - no element selected'); + return this; + } + + if (typeof options == 'function') + options = { success: options }; + + var url = $.trim(this.attr('action')); + if (url) { + // clean url (don't include hash vaue) + url = (url.match(/^([^#]+)/)||[])[1]; + } + url = url || window.location.href || ''; + + options = $.extend({ + url: url, + type: this.attr('method') || 'GET', + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' + }, options || {}); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger('form-pre-serialize', [this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); + return this; + } + + // provide opportunity to alter form data before it is serialized + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSerialize callback'); + return this; + } + + var a = this.formToArray(options.semantic); + if (options.data) { + options.extraData = options.data; + for (var n in options.data) { + if(options.data[n] instanceof Array) { + for (var k in options.data[n]) + a.push( { name: n, value: options.data[n][k] } ); + } + else + a.push( { name: n, value: options.data[n] } ); + } + } + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSubmit callback'); + return this; + } + + // fire vetoable 'validate' event + this.trigger('form-submit-validate', [a, this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); + return this; + } + + var q = $.param(a); + + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else + options.data = q; // data is the query string for 'post' + + var $form = this, callbacks = []; + if (options.resetForm) callbacks.push(function() { $form.resetForm(); }); + if (options.clearForm) callbacks.push(function() { $form.clearForm(); }); + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function(){}; + callbacks.push(function(data) { + $(options.target).html(data).each(oldSuccess, arguments); + }); + } + else if (options.success) + callbacks.push(options.success); + + options.success = function(data, status) { + for (var i=0, max=callbacks.length; i < max; i++) + callbacks[i].apply(options, [data, status, $form]); + }; + + // are there files to upload? + var files = $('input:file', this).fieldValue(); + var found = false; + for (var j=0; j < files.length; j++) + if (files[j]) + found = true; + + var multipart = false; +// var mp = 'multipart/form-data'; +// multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); + + // options.iframe allows user to force iframe mode + // 06-NOV-09: now defaulting to iframe mode if file input is detected + if ((files.length && options.iframe !== false) || options.iframe || found || multipart) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if (options.closeKeepAlive) + $.get(options.closeKeepAlive, fileUpload); + else + fileUpload(); + } + else + $.ajax(options); + + // fire 'notify' event + this.trigger('form-submit-notify', [this, options]); + return this; + + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUpload() { + var form = $form[0]; + + if ($(':input[name=submit]', form).length) { + alert('Error: Form elements must not be named "submit".'); + return; + } + + var opts = $.extend({}, $.ajaxSettings, options); + var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts); + + var id = 'jqFormIO' + (new Date().getTime()); + var $io = $('