From f47712d803811f06b4371f50fabdfc1fefffe4cc Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 19 Dec 2013 15:59:55 +0100 Subject: add design docs on db creation, use Pathname Pathname makes dealing with files easier than String. Tapicero will look for design documents in design directory in the tapicero path for now. --- bin/tapicero | 5 +++-- lib/tapicero.rb | 2 +- lib/tapicero/user_database.rb | 19 +++++++++++++++++++ lib/tapicero_daemon.rb | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/bin/tapicero b/bin/tapicero index 9a5a7ee..b8b88f1 100755 --- a/bin/tapicero +++ b/bin/tapicero @@ -1,10 +1,11 @@ #!/usr/bin/ruby +require 'pathname' # # Tapicero Daemon # -BASE_DIR = File.expand_path('../..', File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__) +BASE_DIR = Pathname.new(__FILE__).realpath + '../..' begin # @@ -17,7 +18,7 @@ rescue LoadError # require "#{BASE_DIR}/lib/tapicero/version.rb" Tapicero::REQUIRE_PATHS.each do |path| - path = File.expand_path(path, BASE_DIR) + path = BASE_DIR + path $LOAD_PATH.unshift path unless $LOAD_PATH.include?(path) end require 'rubygems' diff --git a/lib/tapicero.rb b/lib/tapicero.rb index aba9fad..3923c65 100644 --- a/lib/tapicero.rb +++ b/lib/tapicero.rb @@ -1,5 +1,5 @@ unless defined? BASE_DIR - BASE_DIR = File.expand_path('../..', __FILE__) + BASE_DIR = Pathname.new(__FILE__) + '../..' end unless defined? TAPICERO_CONFIG TAPICERO_CONFIG = '/etc/leap/tapicero.yaml' diff --git a/lib/tapicero/user_database.rb b/lib/tapicero/user_database.rb index 8f461ef..b08558f 100644 --- a/lib/tapicero/user_database.rb +++ b/lib/tapicero/user_database.rb @@ -23,6 +23,21 @@ module Tapicero CouchRest.put security_url, security end + def add_design_docs + pattern = BASE_DIR + 'designs' + '*.json' + Tapicero.logger.debug "looking for design docs in #{pattern}" + Pathname.glob(pattern).each do |file| + upload_design_doc(file) + end + end + + def upload_design_doc(file) + url = design_url(file.basename) + Tapicero.logger.debug "uploading design doc #{file.basename} to #{url}" + CouchRest.put url, JSON.parse(file.read) + end + + def destroy db = CouchRest.new(host).database(name) db.delete! if db @@ -40,6 +55,10 @@ module Tapicero "#{host}/#{name}/_security" end + def design_url(doc_name) + "#{host}/#{name}/_design/#{doc_name}" + end + attr_reader :host, :name end end diff --git a/lib/tapicero_daemon.rb b/lib/tapicero_daemon.rb index 9020fa2..67eee88 100644 --- a/lib/tapicero_daemon.rb +++ b/lib/tapicero_daemon.rb @@ -15,6 +15,7 @@ module Tapicero db = user_database(hash['id']) db.create db.secure(config.options[:security]) + db.add_design_docs end users.deleted do |hash| -- cgit v1.2.3 From d8c28a0c3cba76fadd0495a79f67efd61458218c Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 20 Dec 2013 12:58:53 +0100 Subject: add design docs to be included in each user database Also strip the .json extention because soledad does not expect it --- designs/docs.json | 12 ++++++++++++ designs/syncs.json | 11 +++++++++++ designs/transactions.json | 12 ++++++++++++ lib/tapicero/user_database.rb | 2 +- 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 designs/docs.json create mode 100644 designs/syncs.json create mode 100644 designs/transactions.json diff --git a/designs/docs.json b/designs/docs.json new file mode 100644 index 0000000..4aad02a --- /dev/null +++ b/designs/docs.json @@ -0,0 +1,12 @@ +{ + "views" : { + "get" : { + "map" : "function(doc) {\n if (doc.u1db_rev) {\n var is_tombstone = true;\n var has_conflicts = false;\n if (doc._attachments) {\n if (doc._attachments.u1db_content)\n is_tombstone = false;\n if (doc._attachments.u1db_conflicts)\n has_conflicts = true;\n }\n emit(doc._id,\n {\n \"couch_rev\": doc._rev,\n \"u1db_rev\": doc.u1db_rev,\n \"is_tombstone\": is_tombstone,\n \"has_conflicts\": has_conflicts,\n }\n );\n }\n}\n" + } + }, + "_id" : "_design/docs", + "updates" : { + "resolve_doc" : "function(doc, req){\n /* we expect to receive the following in `req.body`:\n * {\n * 'couch_rev': '',\n * 'conflicts': '',\n * }\n */\n var body = JSON.parse(req.body);\n\n // fail if no document was given\n if (!doc) {\n return [null, 'document does not exist']\n } \n\n // fail if couch revisions do not match\n if (body['couch_rev'] != null\n && doc['_rev'] != body['couch_rev']) {\n return [null, 'revision conflict']\n }\n\n // fail if conflicts were not sent\n if (body['conflicts'] == null)\n return [null, 'missing conflicts']\n\n // save conflicts as attachment if they were sent\n if (body['conflicts'] != null) {\n if (!doc._attachments)\n doc._attachments = {};\n doc._attachments.u1db_conflicts = {\n content_type: \"application/octet-stream\",\n data: body['conflicts'] // should be base64 encoded\n }\n }\n // or delete attachment if there are no conflicts\n else if (doc._attachments && doc._attachments.u1db_conflicts)\n delete doc._attachments.u1db_conflicts;\n\n return [doc, 'ok'];\n}\n", + "put" : "function(doc, req){\n /* we expect to receive the following in `req.body`:\n * {\n * 'couch_rev': '',\n * 'u1db_rev': '',\n * 'content': '',\n * 'trans_id': ''\n * 'conflicts': '',\n * 'update_conflicts': \n * }\n */\n var body = JSON.parse(req.body);\n\n // create a new document document\n if (!doc) {\n doc = {}\n doc['_id'] = req['id'];\n }\n // or fail if couch revisions do not match\n else if (doc['_rev'] != body['couch_rev']) {\n // of fail if revisions do not match\n return [null, 'revision conflict']\n }\n\n // store u1db rev\n doc.u1db_rev = body['u1db_rev'];\n\n // save content as attachment\n if (body['content'] != null) {\n // save u1db content as attachment\n if (!doc._attachments)\n doc._attachments = {};\n doc._attachments.u1db_content = {\n content_type: \"application/octet-stream\",\n data: body['content'] // should be base64 encoded\n };\n }\n // or delete the attachment if document is tombstone\n else if (doc._attachments &&\n doc._attachments.u1db_content)\n delete doc._attachments.u1db_content;\n\n // store the transaction id\n if (!doc.u1db_transactions)\n doc.u1db_transactions = [];\n var d = new Date();\n doc.u1db_transactions.push([d.getTime(), body['trans_id']]);\n\n // save conflicts as attachment if they were sent\n if (body['update_conflicts'])\n if (body['conflicts'] != null) {\n if (!doc._attachments)\n doc._attachments = {};\n doc._attachments.u1db_conflicts = {\n content_type: \"application/octet-stream\",\n data: body['conflicts'] // should be base64 encoded\n }\n } else {\n if(doc._attachments && doc._attachments.u1db_conflicts)\n delete doc._attachments.u1db_conflicts\n }\n\n return [doc, 'ok'];\n}\n" + } +} diff --git a/designs/syncs.json b/designs/syncs.json new file mode 100644 index 0000000..0df5ff7 --- /dev/null +++ b/designs/syncs.json @@ -0,0 +1,11 @@ +{ + "views" : { + "log" : { + "map" : "function(doc) {\n if (doc._id == 'u1db_sync_log') {\n if (doc.syncs)\n doc.syncs.forEach(function (entry) {\n emit(entry[0],\n {\n 'known_generation': entry[1],\n 'known_transaction_id': entry[2]\n });\n });\n }\n}\n" + } + }, + "_id" : "_design/syncs", + "updates" : { + "put" : "function(doc, req){\n if (!doc) {\n doc = {}\n doc['_id'] = 'u1db_sync_log';\n doc['syncs'] = [];\n }\n body = JSON.parse(req.body);\n // remove outdated info\n doc['syncs'] = doc['syncs'].filter(\n function (entry) {\n return entry[0] != body['other_replica_uid'];\n }\n );\n // store u1db rev\n doc['syncs'].push([\n body['other_replica_uid'],\n body['other_generation'],\n body['other_transaction_id']\n ]);\n return [doc, 'ok'];\n}\n\n" + } +} diff --git a/designs/transactions.json b/designs/transactions.json new file mode 100644 index 0000000..8fcb84d --- /dev/null +++ b/designs/transactions.json @@ -0,0 +1,12 @@ +{ + "lists" : { + "generation" : "function(head, req) {\n var row;\n var rows=[];\n // fetch all rows\n while(row = getRow()) {\n rows.push(row);\n }\n if (rows.length > 0)\n send(JSON.stringify({\n \"generation\": rows.length,\n \"doc_id\": rows[rows.length-1]['id'],\n \"transaction_id\": rows[rows.length-1]['value']\n }));\n else\n send(JSON.stringify({\n \"generation\": 0,\n \"doc_id\": \"\",\n \"transaction_id\": \"\",\n }));\n}\n", + "whats_changed" : "function(head, req) {\n var row;\n var gen = 1;\n var old_gen = 0;\n if (req.query.old_gen)\n old_gen = parseInt(req.query['old_gen']);\n send('{\"transactions\":[\\n');\n // fetch all rows\n while(row = getRow()) {\n if (gen > old_gen) {\n if (gen > old_gen+1)\n send(',\\n');\n send(JSON.stringify({\n \"generation\": gen,\n \"doc_id\": row[\"id\"],\n \"transaction_id\": row[\"value\"]\n }));\n }\n gen++;\n }\n send('\\n]}');\n}\n", + "trans_id_for_gen" : "function(head, req) {\n var row;\n var rows=[];\n var i = 1;\n var gen = 1;\n if (req.query.gen)\n gen = parseInt(req.query['gen']);\n // fetch all rows\n while(row = getRow())\n rows.push(row);\n if (gen <= rows.length)\n send(JSON.stringify({\n \"generation\": gen,\n \"doc_id\": rows[gen-1]['id'],\n \"transaction_id\": rows[gen-1]['value'],\n }));\n else\n send('{}');\n}\n" + }, + "views" : { + "log" : { + "map" : "function(doc) {\n if (doc.u1db_transactions)\n doc.u1db_transactions.forEach(function(t) {\n emit(t[0], // use timestamp as key so the results are ordered\n t[1]); // value is the transaction_id\n });\n}\n" + } + } +} diff --git a/lib/tapicero/user_database.rb b/lib/tapicero/user_database.rb index b08558f..dc28aa9 100644 --- a/lib/tapicero/user_database.rb +++ b/lib/tapicero/user_database.rb @@ -32,7 +32,7 @@ module Tapicero end def upload_design_doc(file) - url = design_url(file.basename) + url = design_url(file.basename('.json')) Tapicero.logger.debug "uploading design doc #{file.basename} to #{url}" CouchRest.put url, JSON.parse(file.read) end -- cgit v1.2.3 From 1bf3ee50638ba1e06c7df297635f28b6c8715eb1 Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 20 Dec 2013 15:22:16 +0100 Subject: proceed if design docs are already there We'll add a flag to overwrite designs and / or security later. For now just make sure this does not crash tapicero. --- lib/tapicero/user_database.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/tapicero/user_database.rb b/lib/tapicero/user_database.rb index dc28aa9..efd723a 100644 --- a/lib/tapicero/user_database.rb +++ b/lib/tapicero/user_database.rb @@ -33,8 +33,9 @@ module Tapicero def upload_design_doc(file) url = design_url(file.basename('.json')) - Tapicero.logger.debug "uploading design doc #{file.basename} to #{url}" CouchRest.put url, JSON.parse(file.read) + Tapicero.logger.debug "uploaded design doc #{file.basename} to #{url}" + rescue RestClient::Conflict end -- cgit v1.2.3 From c1e6eea1eb2a9fbdb92e806929806acef32ed8dc Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 20 Dec 2013 15:35:35 +0100 Subject: Version 0.3.0: include design docs on created databases --- lib/tapicero/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tapicero/version.rb b/lib/tapicero/version.rb index 3688f62..2c4bc00 100644 --- a/lib/tapicero/version.rb +++ b/lib/tapicero/version.rb @@ -1,4 +1,4 @@ module Tapicero - VERSION = "0.2.0" + VERSION = "0.3.0" REQUIRE_PATHS = ['lib'] end -- cgit v1.2.3