From: Michal Fojtik <[email protected]>
Signed-off-by: Michal fojtik <[email protected]> --- server/lib/sinatra/rabbit.rb | 90 +++++++++++++- server/server.rb | 266 ++++++++++++++++++++++------------------- 2 files changed, 226 insertions(+), 130 deletions(-) diff --git a/server/lib/sinatra/rabbit.rb b/server/lib/sinatra/rabbit.rb index 33a79aa..00bd075 100644 --- a/server/lib/sinatra/rabbit.rb +++ b/server/lib/sinatra/rabbit.rb @@ -59,7 +59,8 @@ module Sinatra def initialize(coll, name, opts, &block) @name = name.to_sym opts = STANDARD[@name].merge(opts) if standard? - @collection = coll + @path_generator = opts[:path_generator] + @collection, @standard = coll, opts[:standard] raise "No method for operation #{name}" unless opts[:method] @method = opts[:method].to_sym @member = opts[:member] @@ -74,7 +75,7 @@ module Sinatra end def standard? - STANDARD.keys.include?(name) + STANDARD.keys.include?(name) || @standard end def form? @@ -125,8 +126,17 @@ module Sinatra end end + def member? + if standard? + @member || STANDARD[name][:member] + else + @member + end + end + def path(args = {}) - if @member + return @path_generator.call(self) if @path_generator + if member? if standard? "#{@collection.name}/:id" else @@ -190,12 +200,12 @@ module Sinatra end class Collection - attr_reader :name, :operations + attr_reader :name, :operations, :subcollections def initialize(name, &block) @name = name @description = "" - @operations = {} + @operations, @subcollections = {}, {} @global = false instance_eval(&block) if block_given? generate_documentation @@ -203,6 +213,10 @@ module Sinatra generate_options end + def subcollection? + self.class == SubCollection + end + # Set/Return description for collection # If first parameter is not present, full description will be # returned. @@ -280,13 +294,21 @@ module Sinatra @operations[name] = Operation.new(self, name, opts, &block) end + def collection(name, opts={}, &block) + if subcollections.keys.include?(name) + raise DuplicateOperationException::new(500, "DuplicateSubcollection", "Subcollection #{name} is already defined", []) + end + subcollections[name] = SubCollection.new(self, name, opts, &block) + subcollections[name].generate + end + def generate operations.values.reject { |op| op.member }.each { |o| o.generate } operations.values.select { |op| op.member }.each { |o| o.generate } app = ::Sinatra::Application collname = name # Work around Ruby's weird scoping/capture app.send(:define_method, "#{name.to_s.singularize}_url") do |id| - api_url_for "#{collname}/#{id}", :full + api_url_for "#{collname}/#{id}", :full end if index_op = operations[:index] app.send(:define_method, "#{name}_url") do @@ -296,12 +318,66 @@ module Sinatra end def check_supported(driver) - unless global? || driver.has_collection?(@name) + unless global? || driver.has_collection?(@name) || self.kind_of?(Sinatra::Rabbit::SubCollection) raise UnsupportedCollectionException end end end + class SubCollection < Collection + + attr_accessor :parent + + def initialize(parent, name, opts={}, &block) + self.parent = parent + super(name, &block) + end + + def operation(name, opts = {}, &block) + if @operations.keys.include?(name) + raise DuplicateOperationException::new(500, "DuplicateOperation", "Operation #{name} is already defined", []) + end + # Preserve self as local variable to workaround Ruby namespace + # weirdness + c = self + path_generator = Proc.new do |obj| + if obj.member? + if obj.standard? + "#{parent.name}/:#{parent.name.to_s.singularize}/:#{c.name.to_s.singularize}" + else + "#{parent.name}/:#{parent.name.to_s.singularize}/:#{c.name.to_s.singularize}/#{name}" + end + else + if obj.form? + "#{parent.name}/:id/:#{parent.name.to_s.singularize}/#{obj.name}" + else + "#{parent.name}/:#{parent.name.to_s.singularize}" + end + end + end + opts.merge!({ + :path_generator => path_generator + }) + @operations[name] = Operation.new(self, name, opts, &block) + end + + def generate + operations.values.reject { |op| op.member }.each { |o| o.generate } + operations.values.select { |op| op.member }.each { |o| o.generate } + app = ::Sinatra::Application + collname = name # Work around Ruby's weird scoping/capture + app.send(:define_method, "#{parent.name.to_s}_#{name.to_s.singularize}_url") do |id, subid| + api_url_for "#{collname}/#{id}/#{subid}", :full + end + if index_op = operations[:index] + app.send(:define_method, "#{parent.name.to_s}_#{name}_url") do + api_url_for index_op.path.gsub(/\/\?$/,''), :full + end + end + end + + end + def collections @collections ||= {} end diff --git a/server/server.rb b/server/server.rb index ae8d63d..1640827 100644 --- a/server/server.rb +++ b/server/server.rb @@ -754,39 +754,6 @@ collection :keys do end -#create a new blob using PUT - streams through deltacloud -put "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob" do - if(env["BLOB_SUCCESS"]) #ie got a 200ok after putting blob - content_type = env["CONTENT_TYPE"] - content_type ||= "" - @blob = driver.blob(credentials, {:id => params[:blob], - 'bucket' => params[:bucket]}) - respond_to do |format| - format.xml { haml :"blobs/show" } - format.html { haml :"blobs/show" } - format.json { convert_to_json(:blob, @blob) } - end - elsif(env["BLOB_FAIL"]) - report_error(500) #OK? - else # small blobs - < 112kb dont hit the streaming monkey patch - use 'normal' create_blob - # also, if running under webrick don't hit the streaming patch (Thin specific) - bucket_id = params[:bucket] - blob_id = params[:blob] - temp_file = Tempfile.new("temp_blob_file") - temp_file.write(env['rack.input'].read) - temp_file.flush - content_type = env['CONTENT_TYPE'] || "" - blob_data = {:tempfile => temp_file, :type => content_type} - user_meta = BlobHelper::extract_blob_metadata_hash(request.env) - @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data, user_meta) - temp_file.delete - respond_to do |format| - format.xml { haml :"blobs/show" } - format.html { haml :"blobs/show"} - end - end -end - #get html form for creating a new blob # The URL for getting the new blob form for the HTML UI looks like the URL @@ -802,107 +769,160 @@ get "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/#{NEW_BLOB_FOR end end -#create a new blob using html interface - NON STREAMING (i.e. browser POST http form data) -post "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket" do - bucket_id = params[:bucket] - blob_id = params['blob_id'] || params['blob'] #keep 'blob' just in case - blob_data = params['blob_data'] - user_meta = {} - #metadata from params (i.e., passed by http form post, e.g. browser) - max = params[:meta_params] - if(max) - (1..max.to_i).each do |i| - key = params[:"meta_name#{i}"] - key = "HTTP_X_Deltacloud_Blobmeta_#{key}" - value = params[:"meta_value#{i}"] - user_meta[key] = value +collection :buckets do + description "Cloud Storage buckets - aka buckets|directories|folders" + + collection :blobs do + description "Blobs associated with given bucket" + + operation :show do + description "Display blob" + control do + @blob = driver.blob(credentials, { :id => params[:blob], 'bucket' => params[:bucket]}) + if @blob + respond_to do |format| + format.xml { haml :"blobs/show" } + format.html { haml :"blobs/show" } + format.json { convert_to_json(:blob, @blob) } + end + else + report_error(404) + end + end + end - end - @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data, user_meta) - respond_to do |format| - format.xml { haml :"blobs/show" } - format.html { haml :"blobs/show"} - end -end -#delete a blob -delete "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob" do - bucket_id = params[:bucket] - blob_id = params[:blob] - driver.delete_blob(credentials, bucket_id, blob_id) - status 204 - respond_to do |format| - format.xml - format.json - format.html { redirect(bucket_url(bucket_id)) } - end -end + operation :create do + description "Create new blob" + control do + bucket_id = params[:bucket] + blob_id = params['blob'] + blob_data = params['blob_data'] + user_meta = {} + #metadata from params (i.e., passed by http form post, e.g. browser) + max = params[:meta_params] + if(max) + (1..max.to_i).each do |i| + key = params[:"meta_name#{i}"] + key = "HTTP_X_Deltacloud_Blobmeta_#{key}" + value = params[:"meta_value#{i}"] + user_meta[key] = value + end + end + @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data, user_meta) + respond_to do |format| + format.xml { haml :"blobs/show" } + format.html { haml :"blobs/show"} + end + end + end -#get blob metadata -head "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob" do - @blob_id = params[:blob] - @blob_metadata = driver.blob_metadata(credentials, {:id => params[:blob], 'bucket' => params[:bucket]}) - if @blob_metadata - @blob_metadata.each do |k,v| - headers["X-Deltacloud-Blobmeta-#{k}"] = v + operation :destroy do + description "Destroy bucket" + control do + bucket_id = params[:bucket] + blob_id = params[:blob] + driver.delete_blob(credentials, bucket_id, blob_id) + status 204 + respond_to do |format| + format.xml + format.json + format.html { redirect(bucket_url(bucket_id)) } + end end - status 204 - respond_to do |format| - format.xml - format.json + end + + operation :stream, :member => true, :standard => true, :method => :put do + description "Get blob metadata" + control do + if(env["BLOB_SUCCESS"]) #ie got a 200ok after putting blob + content_type = env["CONTENT_TYPE"] + content_type ||= "" + @blob = driver.blob(credentials, {:id => params[:blob], + 'bucket' => params[:bucket]}) + respond_to do |format| + format.xml { haml :"blobs/show" } + format.html { haml :"blobs/show" } + format.json { convert_to_json(:blob, @blob) } + end + elsif(env["BLOB_FAIL"]) + report_error(500) #OK? + else # small blobs - < 112kb dont hit the streaming monkey patch - use 'normal' create_blob + # also, if running under webrick don't hit the streaming patch (Thin specific) + bucket_id = params[:bucket] + blob_id = params[:blob] + temp_file = Tempfile.new("temp_blob_file") + temp_file.write(env['rack.input'].read) + temp_file.flush + content_type = env['CONTENT_TYPE'] || "" + blob_data = {:tempfile => temp_file, :type => content_type} + user_meta = BlobHelper::extract_blob_metadata_hash(request.env) + @blob = driver.create_blob(credentials, bucket_id, blob_id, blob_data, user_meta) + temp_file.delete + respond_to do |format| + format.xml { haml :"blobs/show" } + format.html { haml :"blobs/show"} + end + end end - else - report_error(404) - end -end + end -#update blob metadata -post "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob" do - meta_hash = BlobHelper::extract_blob_metadata_hash(request.env) - success = driver.update_blob_metadata(credentials, {'bucket'=>params[:bucket], :id =>params[:blob], 'meta_hash' => meta_hash}) - if(success) - meta_hash.each do |k,v| - headers["X-Deltacloud-Blobmeta-#{k}"] = v + operation :metadata, :member => true, :standard => true, :method => :head do + description "Get blob metadata" + control do + @blob_id = params[:blob] + @blob_metadata = driver.blob_metadata(credentials, {:id => params[:blob], 'bucket' => params[:bucket]}) + if @blob_metadata + @blob_metadata.each do |k,v| + headers["X-Deltacloud-Blobmeta-#{k}"] = v + end + status 204 + respond_to do |format| + format.xml + format.json + end + else + report_error(404) + end + end end - status 204 - respond_to do |format| - format.xml - format.json + + operation :update, :member => true, :method => :post do + description "Update blob metadata" + control do + meta_hash = BlobHelper::extract_blob_metadata_hash(request.env) + success = driver.update_blob_metadata(credentials, {'bucket'=>params[:bucket], :id =>params[:blob], 'meta_hash' => meta_hash}) + if(success) + meta_hash.each do |k,v| + headers["X-Deltacloud-Blobmeta-#{k}"] = v + end + status 204 + respond_to do |format| + format.xml + format.json + end + else + report_error(404) #FIXME is this the right error code? + end + end end - else - report_error(404) #FIXME is this the right error code? - end -end -#Get a particular blob's particulars (not actual blob data) -get "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob" do - @blob = driver.blob(credentials, { :id => params[:blob], 'bucket' => params[:bucket]}) - if @blob - respond_to do |format| - format.xml { haml :"blobs/show" } - format.html { haml :"blobs/show" } - format.json { convert_to_json(:blob, @blob) } + operation :content, :member => true, :method => :get do + description "Download blob content" + control do + @blob = driver.blob(credentials, { :id => params[:blob], 'bucket' => params[:bucket]}) + if @blob + params['content_length'] = @blob.content_length + params['content_type'] = @blob.content_type + params['content_disposition'] = "attachment; filename=#{@blob.id}" + BlobStream.call(env, credentials, params) + else + report_error(404) + end end - else - report_error(404) - end -end + end -#get the content of a particular blob -get "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/:bucket/:blob/content" do - @blob = driver.blob(credentials, { :id => params[:blob], 'bucket' => params[:bucket]}) - if @blob - params['content_length'] = @blob.content_length - params['content_type'] = @blob.content_type - params['content_disposition'] = "attachment; filename=#{@blob.id}" - BlobStream.call(env, credentials, params) - else - report_error(404) end -end - -collection :buckets do - description "Cloud Storage buckets - aka buckets|directories|folders" operation :new do description "A form to create a new bucket resource" -- 1.7.4.1
