1
- require 'httpclient'
2
1
require 'json'
3
2
4
3
module Vimeo
5
4
module Advanced
6
5
7
6
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"
8
14
9
15
# Check to make sure an upload ticket is still valid.
10
16
create_api_method :check_ticket ,
@@ -14,7 +20,7 @@ class Upload < Vimeo::Advanced::Base
14
20
# Complete the upload process.
15
21
create_api_method :complete ,
16
22
"vimeo.videos.upload.complete" ,
17
- :required => [ :filename , :ticket_id ]
23
+ :required => [ :ticket_id , :filename ]
18
24
19
25
# Returns an upload ticket.
20
26
create_api_method :get_ticket ,
@@ -31,50 +37,105 @@ class Upload < Vimeo::Advanced::Base
31
37
:required => [ :ticket_id ]
32
38
33
39
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
36
50
end
37
51
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
+
48
89
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
+
55
97
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
63
105
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
72
122
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
76
137
end
77
138
end
78
139
end # Upload
79
140
end # Advanced
80
- end # Vimeo
141
+ end # Vimeo
0 commit comments