From f0e69144eee2276da2c6a035750032454c0de474 Mon Sep 17 00:00:00 2001 From: Marcel Jackwerth Date: Tue, 9 Nov 2010 13:16:03 +0100 Subject: [PATCH] upload-functionality restored --- Rakefile | 23 +++--- lib/vimeo.rb | 2 + lib/vimeo/advanced/upload.rb | 139 +++++++++++++++++++++++++---------- vimeo.gemspec | 11 ++- 4 files changed, 121 insertions(+), 54 deletions(-) diff --git a/Rakefile b/Rakefile index fb9bd7b..27c466d 100644 --- a/Rakefile +++ b/Rakefile @@ -15,16 +15,17 @@ begin gem.add_development_dependency "fakeweb", ">= 1.2.6" gem.add_development_dependency "crack", ">= 0.1.4" gem.add_development_dependency "ruby-prof", ">= 0.9.2" - + gem.has_rdoc = true - + gem.rdoc_options = ['--main', 'README.rdoc', '--inline-source', '--charset=UTF-8'] gem.extra_rdoc_files = ['README.rdoc', 'LICENSE', 'CHANGELOG.rdoc'] - + gem.add_dependency "httparty", ">= 0.4.5" gem.add_dependency "json", ">= 1.1.9" - gem.add_dependency "oauth", ">= 0.3.6" + gem.add_dependency "oauth", ">= 0.4.3" gem.add_dependency "httpclient", ">= 2.1.5.2" + gem.add_dependency "multipart-post", ">= 1.0.1" end Jeweler::GemcutterTasks.new rescue LoadError @@ -59,27 +60,27 @@ namespace :vimeo do desc "Multi-step wizard to acquire an access_token. CONSUMER_KEY and CONSUMER_SECRET required." task :auth do require 'vimeo' - + def ask(message) print message STDOUT.flush STDIN.gets.chomp end - + consumer_key = ENV['CONSUMER_KEY'] consumer_secret = ENV['CONSUMER_SECRET'] base = Vimeo::Advanced::Base.new(consumer_key, consumer_secret) - + request_token = base.get_request_token oauth_secret = request_token.secret - + puts "Please visit: #{base.authorize_url}" - + oauth_token = ask("oauth_token=") oauth_verifier = ask("oauth_verifier=") - + access_token = base.get_access_token(oauth_token, oauth_secret, oauth_verifier) - + puts "token: #{access_token.token}" puts "secret: #{access_token.secret}" end diff --git a/lib/vimeo.rb b/lib/vimeo.rb index e142fae..c3b3aae 100644 --- a/lib/vimeo.rb +++ b/lib/vimeo.rb @@ -2,6 +2,8 @@ require 'httparty' require 'digest/md5' +require 'net/http/post/multipart' + $:.unshift(File.dirname(__FILE__)) require 'vimeo/simple' require 'vimeo/advanced' diff --git a/lib/vimeo/advanced/upload.rb b/lib/vimeo/advanced/upload.rb index c8f7ac3..f359417 100644 --- a/lib/vimeo/advanced/upload.rb +++ b/lib/vimeo/advanced/upload.rb @@ -1,10 +1,16 @@ -require 'httpclient' require 'json' module Vimeo module Advanced class Upload < Vimeo::Advanced::Base + class UploadError < RuntimeError; end + + # 2 megabytes + # CHUNK_SIZE = 2 * 1024 * 1024 + CHUNK_SIZE = 6000 + + BOUNDARY = "-----------RubyMultipartPost" # Check to make sure an upload ticket is still valid. create_api_method :check_ticket, @@ -14,7 +20,7 @@ class Upload < Vimeo::Advanced::Base # Complete the upload process. create_api_method :complete, "vimeo.videos.upload.complete", - :required => [:filename, :ticket_id] + :required => [:ticket_id, :filename] # Returns an upload ticket. create_api_method :get_ticket, @@ -31,50 +37,105 @@ class Upload < Vimeo::Advanced::Base :required => [:ticket_id] - def upload_chunk(data, endpoint, options = {}) - pp @oauth_consumer.request(:post, endpoint, get_access_token, {}, options).body + # Uploads data (IO streams or files) to Vimeo. + def upload(uploadable) + case uploadable + when File + upload_file(uploadable) + when String + upload_file(File.new(uploadable)) + else + upload_io(uploadable) + end end - # Upload +file+ to vimeo with +ticket_id+ and +auth_token+ - # Returns the json manifest necessary to confirm the upload. - def upload(file_path) - size = File.size(file_path) - basename = File.basename(file_path) - file = File.open(file_path) - file.binmode - - chunk_size = 2 * 1024 * 1024 # 2 megabytes - + protected + + def upload_chunk(chunk_id, data, endpoint, filename) + endpoint += "&chunk_id=#{chunk_id}" + + response = @oauth_consumer.request(:post, endpoint, get_access_token, {}, {}) do |req| + req.set_content_type("multipart/form-data", { "boundary" => BOUNDARY }) + + io = StringIO.new(data) + io.instance_variable_set :"@original_filename", filename + def io.original_filename; @original_filename; end + def io.content_type; "application/octet-stream"; end + + parts = [] + parts << Parts::FilePart.new(BOUNDARY, "file_data", io) + parts << Parts::EpiloguePart.new(BOUNDARY) + + ios = parts.map{|p| p.to_io } + req.content_length = parts.inject(0) {|sum,i| sum + i.length } + req.body_stream = CompositeReadIO.new(*ios) + + :continue + end + + response.body + end + + def upload_io(io, size, filename = 'io.data') + raise "#{io.inspect} must respond to #read" unless io.respond_to?(:read) + + quota_response = get_quota + user = quota_response["user"] + upload_space = user["upload_space"] + free = upload_space["free"].to_i + + raise UploadError.new, "file size exceeds quota. required: #{size}, free: #{free}" if size > free + ticket_response = get_ticket - pp ticket_response - ticket = ticket_response["ticket"] - ticket_id = ticket["id"] - endpoint = ticket["endpoint"] - - chunk_count = (size.to_f / chunk_size.to_f).ceil + ticket = ticket_response["ticket"] + max_file_size = ticket["max_file_size"].to_i + ticket_id = ticket["id"] + endpoint = ticket["endpoint"] + + raise UploadError.new, "file was too big: #{size}, maximum: #{max_file_size}" if size > max_file_size + chunk_sizes = {} - - chunk_count.times do |chunk_index| - last = (chunk_index == chunk_count - 1) - data = last ? file.read : file.read(chunk_size) - - chunk_sizes[chunk_index] = data.size - upload_chunk(data, endpoint, :chunk_id => chunk_index, :ticket_id => ticket_id) + chunk_index = 0 + + while (chunk = io.read(CHUNK_SIZE)) do + + chunk_id = upload_chunk(chunk_index, chunk, endpoint, filename) + chunk_sizes[chunk_id] = chunk.length + chunk_index += 1 end - - verification = verify_chunks(:ticket_id => ticket_id)["ticket"] - received_chunks = Hash[(verification["chunk"] || []).map do |chunk| - [chunk["id"], chunk["size"]] - end] - - chunk_sizes.all? do |id, size| - received_chunks[id] == size + + validate_chunks_after_upload(ticket_id, chunk_sizes) + + complete(ticket_id, filename) + end + + def upload_file(file) + file_path = file.path + + size = File.size(file_path) + basename = File.basename(file_path) + io = File.open(file_path) + io.binmode + + upload_io(io, size, basename).tap do + io.close end - - complete(:ticket_id => ticket_id, :filename => basename).tap do - file.close + end + + def validate_chunks_after_upload(ticket_id, chunk_sizes) + verification = verify_chunks(ticket_id) + ticket = verification["ticket"] + chunk_list = Array(ticket["chunks"]["chunk"]) + received_chunks = Hash[chunk_list.map { |chunk| [chunk["id"], chunk["size"].to_i] }] + + chunk_sizes.each do |id, size| + vimeo_size = received_chunks[id] + + if vimeo_size != size + raise UploadError.new, "Chunk (id: #{id}) was invalid - was: #{vimeo_size}, should be: #{size}." + end end end end # Upload end # Advanced -end # Vimeo \ No newline at end of file +end # Vimeo diff --git a/vimeo.gemspec b/vimeo.gemspec index 9e6b7ad..9290d0a 100644 --- a/vimeo.gemspec +++ b/vimeo.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Matt Hooks"] - s.date = %q{2010-09-18} + s.date = %q{2010-11-09} s.description = %q{A full featured Ruby implementation of the Vimeo API.} s.email = %q{matthooks@gmail.com} s.extra_rdoc_files = [ @@ -227,8 +227,9 @@ Gem::Specification.new do |s| s.add_development_dependency(%q, [">= 0.9.2"]) s.add_runtime_dependency(%q, [">= 0.4.5"]) s.add_runtime_dependency(%q, [">= 1.1.9"]) - s.add_runtime_dependency(%q, [">= 0.3.6"]) + s.add_runtime_dependency(%q, [">= 0.4.3"]) s.add_runtime_dependency(%q, [">= 2.1.5.2"]) + s.add_runtime_dependency(%q, [">= 1.0.1"]) else s.add_dependency(%q, [">= 2.11.3"]) s.add_dependency(%q, [">= 1.2.6"]) @@ -236,8 +237,9 @@ Gem::Specification.new do |s| s.add_dependency(%q, [">= 0.9.2"]) s.add_dependency(%q, [">= 0.4.5"]) s.add_dependency(%q, [">= 1.1.9"]) - s.add_dependency(%q, [">= 0.3.6"]) + s.add_dependency(%q, [">= 0.4.3"]) s.add_dependency(%q, [">= 2.1.5.2"]) + s.add_dependency(%q, [">= 1.0.1"]) end else s.add_dependency(%q, [">= 2.11.3"]) @@ -246,8 +248,9 @@ Gem::Specification.new do |s| s.add_dependency(%q, [">= 0.9.2"]) s.add_dependency(%q, [">= 0.4.5"]) s.add_dependency(%q, [">= 1.1.9"]) - s.add_dependency(%q, [">= 0.3.6"]) + s.add_dependency(%q, [">= 0.4.3"]) s.add_dependency(%q, [">= 2.1.5.2"]) + s.add_dependency(%q, [">= 1.0.1"]) end end