Skip to content

Commit f0e6914

Browse files
committed
upload-functionality restored
1 parent ca24ebd commit f0e6914

File tree

4 files changed

+121
-54
lines changed

4 files changed

+121
-54
lines changed

Rakefile

+12-11
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@ begin
1515
gem.add_development_dependency "fakeweb", ">= 1.2.6"
1616
gem.add_development_dependency "crack", ">= 0.1.4"
1717
gem.add_development_dependency "ruby-prof", ">= 0.9.2"
18-
18+
1919
gem.has_rdoc = true
20-
20+
2121
gem.rdoc_options = ['--main', 'README.rdoc', '--inline-source', '--charset=UTF-8']
2222
gem.extra_rdoc_files = ['README.rdoc', 'LICENSE', 'CHANGELOG.rdoc']
23-
23+
2424
gem.add_dependency "httparty", ">= 0.4.5"
2525
gem.add_dependency "json", ">= 1.1.9"
26-
gem.add_dependency "oauth", ">= 0.3.6"
26+
gem.add_dependency "oauth", ">= 0.4.3"
2727
gem.add_dependency "httpclient", ">= 2.1.5.2"
28+
gem.add_dependency "multipart-post", ">= 1.0.1"
2829
end
2930
Jeweler::GemcutterTasks.new
3031
rescue LoadError
@@ -59,27 +60,27 @@ namespace :vimeo do
5960
desc "Multi-step wizard to acquire an access_token. CONSUMER_KEY and CONSUMER_SECRET required."
6061
task :auth do
6162
require 'vimeo'
62-
63+
6364
def ask(message)
6465
print message
6566
STDOUT.flush
6667
STDIN.gets.chomp
6768
end
68-
69+
6970
consumer_key = ENV['CONSUMER_KEY']
7071
consumer_secret = ENV['CONSUMER_SECRET']
7172
base = Vimeo::Advanced::Base.new(consumer_key, consumer_secret)
72-
73+
7374
request_token = base.get_request_token
7475
oauth_secret = request_token.secret
75-
76+
7677
puts "Please visit: #{base.authorize_url}"
77-
78+
7879
oauth_token = ask("oauth_token=")
7980
oauth_verifier = ask("oauth_verifier=")
80-
81+
8182
access_token = base.get_access_token(oauth_token, oauth_secret, oauth_verifier)
82-
83+
8384
puts "token: #{access_token.token}"
8485
puts "secret: #{access_token.secret}"
8586
end

lib/vimeo.rb

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
require 'httparty'
33
require 'digest/md5'
44

5+
require 'net/http/post/multipart'
6+
57
$:.unshift(File.dirname(__FILE__))
68
require 'vimeo/simple'
79
require 'vimeo/advanced'

lib/vimeo/advanced/upload.rb

+100-39
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
require 'httpclient'
21
require 'json'
32

43
module Vimeo
54
module Advanced
65

76
class Upload < Vimeo::Advanced::Base
7+
class UploadError < RuntimeError; end
8+
9+
# 2 megabytes
10+
# CHUNK_SIZE = 2 * 1024 * 1024
11+
CHUNK_SIZE = 6000
12+
13+
BOUNDARY = "-----------RubyMultipartPost"
814

915
# Check to make sure an upload ticket is still valid.
1016
create_api_method :check_ticket,
@@ -14,7 +20,7 @@ class Upload < Vimeo::Advanced::Base
1420
# Complete the upload process.
1521
create_api_method :complete,
1622
"vimeo.videos.upload.complete",
17-
:required => [:filename, :ticket_id]
23+
:required => [:ticket_id, :filename]
1824

1925
# Returns an upload ticket.
2026
create_api_method :get_ticket,
@@ -31,50 +37,105 @@ class Upload < Vimeo::Advanced::Base
3137
:required => [:ticket_id]
3238

3339

34-
def upload_chunk(data, endpoint, options = {})
35-
pp @oauth_consumer.request(:post, endpoint, get_access_token, {}, options).body
40+
# Uploads data (IO streams or files) to Vimeo.
41+
def upload(uploadable)
42+
case uploadable
43+
when File
44+
upload_file(uploadable)
45+
when String
46+
upload_file(File.new(uploadable))
47+
else
48+
upload_io(uploadable)
49+
end
3650
end
3751

38-
# Upload +file+ to vimeo with +ticket_id+ and +auth_token+
39-
# Returns the json manifest necessary to confirm the upload.
40-
def upload(file_path)
41-
size = File.size(file_path)
42-
basename = File.basename(file_path)
43-
file = File.open(file_path)
44-
file.binmode
45-
46-
chunk_size = 2 * 1024 * 1024 # 2 megabytes
47-
52+
protected
53+
54+
def upload_chunk(chunk_id, data, endpoint, filename)
55+
endpoint += "&chunk_id=#{chunk_id}"
56+
57+
response = @oauth_consumer.request(:post, endpoint, get_access_token, {}, {}) do |req|
58+
req.set_content_type("multipart/form-data", { "boundary" => BOUNDARY })
59+
60+
io = StringIO.new(data)
61+
io.instance_variable_set :"@original_filename", filename
62+
def io.original_filename; @original_filename; end
63+
def io.content_type; "application/octet-stream"; end
64+
65+
parts = []
66+
parts << Parts::FilePart.new(BOUNDARY, "file_data", io)
67+
parts << Parts::EpiloguePart.new(BOUNDARY)
68+
69+
ios = parts.map{|p| p.to_io }
70+
req.content_length = parts.inject(0) {|sum,i| sum + i.length }
71+
req.body_stream = CompositeReadIO.new(*ios)
72+
73+
:continue
74+
end
75+
76+
response.body
77+
end
78+
79+
def upload_io(io, size, filename = 'io.data')
80+
raise "#{io.inspect} must respond to #read" unless io.respond_to?(:read)
81+
82+
quota_response = get_quota
83+
user = quota_response["user"]
84+
upload_space = user["upload_space"]
85+
free = upload_space["free"].to_i
86+
87+
raise UploadError.new, "file size exceeds quota. required: #{size}, free: #{free}" if size > free
88+
4889
ticket_response = get_ticket
49-
pp ticket_response
50-
ticket = ticket_response["ticket"]
51-
ticket_id = ticket["id"]
52-
endpoint = ticket["endpoint"]
53-
54-
chunk_count = (size.to_f / chunk_size.to_f).ceil
90+
ticket = ticket_response["ticket"]
91+
max_file_size = ticket["max_file_size"].to_i
92+
ticket_id = ticket["id"]
93+
endpoint = ticket["endpoint"]
94+
95+
raise UploadError.new, "file was too big: #{size}, maximum: #{max_file_size}" if size > max_file_size
96+
5597
chunk_sizes = {}
56-
57-
chunk_count.times do |chunk_index|
58-
last = (chunk_index == chunk_count - 1)
59-
data = last ? file.read : file.read(chunk_size)
60-
61-
chunk_sizes[chunk_index] = data.size
62-
upload_chunk(data, endpoint, :chunk_id => chunk_index, :ticket_id => ticket_id)
98+
chunk_index = 0
99+
100+
while (chunk = io.read(CHUNK_SIZE)) do
101+
102+
chunk_id = upload_chunk(chunk_index, chunk, endpoint, filename)
103+
chunk_sizes[chunk_id] = chunk.length
104+
chunk_index += 1
63105
end
64-
65-
verification = verify_chunks(:ticket_id => ticket_id)["ticket"]
66-
received_chunks = Hash[(verification["chunk"] || []).map do |chunk|
67-
[chunk["id"], chunk["size"]]
68-
end]
69-
70-
chunk_sizes.all? do |id, size|
71-
received_chunks[id] == size
106+
107+
validate_chunks_after_upload(ticket_id, chunk_sizes)
108+
109+
complete(ticket_id, filename)
110+
end
111+
112+
def upload_file(file)
113+
file_path = file.path
114+
115+
size = File.size(file_path)
116+
basename = File.basename(file_path)
117+
io = File.open(file_path)
118+
io.binmode
119+
120+
upload_io(io, size, basename).tap do
121+
io.close
72122
end
73-
74-
complete(:ticket_id => ticket_id, :filename => basename).tap do
75-
file.close
123+
end
124+
125+
def validate_chunks_after_upload(ticket_id, chunk_sizes)
126+
verification = verify_chunks(ticket_id)
127+
ticket = verification["ticket"]
128+
chunk_list = Array(ticket["chunks"]["chunk"])
129+
received_chunks = Hash[chunk_list.map { |chunk| [chunk["id"], chunk["size"].to_i] }]
130+
131+
chunk_sizes.each do |id, size|
132+
vimeo_size = received_chunks[id]
133+
134+
if vimeo_size != size
135+
raise UploadError.new, "Chunk (id: #{id}) was invalid - was: #{vimeo_size}, should be: #{size}."
136+
end
76137
end
77138
end
78139
end # Upload
79140
end # Advanced
80-
end # Vimeo
141+
end # Vimeo

vimeo.gemspec

+7-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
99

1010
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
1111
s.authors = ["Matt Hooks"]
12-
s.date = %q{2010-09-18}
12+
s.date = %q{2010-11-09}
1313
s.description = %q{A full featured Ruby implementation of the Vimeo API.}
1414
s.email = %q{[email protected]}
1515
s.extra_rdoc_files = [
@@ -227,17 +227,19 @@ Gem::Specification.new do |s|
227227
s.add_development_dependency(%q<ruby-prof>, [">= 0.9.2"])
228228
s.add_runtime_dependency(%q<httparty>, [">= 0.4.5"])
229229
s.add_runtime_dependency(%q<json>, [">= 1.1.9"])
230-
s.add_runtime_dependency(%q<oauth>, [">= 0.3.6"])
230+
s.add_runtime_dependency(%q<oauth>, [">= 0.4.3"])
231231
s.add_runtime_dependency(%q<httpclient>, [">= 2.1.5.2"])
232+
s.add_runtime_dependency(%q<multipart-post>, [">= 1.0.1"])
232233
else
233234
s.add_dependency(%q<shoulda>, [">= 2.11.3"])
234235
s.add_dependency(%q<fakeweb>, [">= 1.2.6"])
235236
s.add_dependency(%q<crack>, [">= 0.1.4"])
236237
s.add_dependency(%q<ruby-prof>, [">= 0.9.2"])
237238
s.add_dependency(%q<httparty>, [">= 0.4.5"])
238239
s.add_dependency(%q<json>, [">= 1.1.9"])
239-
s.add_dependency(%q<oauth>, [">= 0.3.6"])
240+
s.add_dependency(%q<oauth>, [">= 0.4.3"])
240241
s.add_dependency(%q<httpclient>, [">= 2.1.5.2"])
242+
s.add_dependency(%q<multipart-post>, [">= 1.0.1"])
241243
end
242244
else
243245
s.add_dependency(%q<shoulda>, [">= 2.11.3"])
@@ -246,8 +248,9 @@ Gem::Specification.new do |s|
246248
s.add_dependency(%q<ruby-prof>, [">= 0.9.2"])
247249
s.add_dependency(%q<httparty>, [">= 0.4.5"])
248250
s.add_dependency(%q<json>, [">= 1.1.9"])
249-
s.add_dependency(%q<oauth>, [">= 0.3.6"])
251+
s.add_dependency(%q<oauth>, [">= 0.4.3"])
250252
s.add_dependency(%q<httpclient>, [">= 2.1.5.2"])
253+
s.add_dependency(%q<multipart-post>, [">= 1.0.1"])
251254
end
252255
end
253256

0 commit comments

Comments
 (0)