rscribd 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ === 0.0.3 / 2008-02-18
2
+
3
+ * Initial RubyForge project
data/Manifest.txt ADDED
@@ -0,0 +1,14 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/api.rb
6
+ lib/doc.rb
7
+ lib/exceptions.rb
8
+ lib/multipart.rb
9
+ lib/resource.rb
10
+ lib/rscribd.rb
11
+ lib/user.rb
12
+ sample/01_upload.rb
13
+ sample/02_user.rb
14
+ sample/test.txt
data/README.txt ADDED
@@ -0,0 +1,83 @@
1
+ = rscribd
2
+
3
+ * 1.0.3 (Jan 3, 2007)
4
+
5
+ == DESCRIPTION:
6
+
7
+ This gem provides a simple and powerful library for the Scribd API, allowing you
8
+ to write Ruby applications or Ruby on Rails websites that upload, convert,
9
+ display, search, and control documents in many formats.
10
+
11
+ == FEATURES/PROBLEMS:
12
+
13
+ * Upload your documents to Scribd's servers and access them using the gem
14
+ * Upload local files or from remote web sites
15
+ * Search, tag, and organize documents
16
+ * Associate documents with your users' accounts
17
+
18
+ == SYNOPSIS:
19
+
20
+ This API allows you to use Scribd's Flash viewer on your website. You'll be able
21
+ to take advantage of Scribd's scalable document conversion system to convert
22
+ your documents into platform-independent formats. You can leverage Scribd's
23
+ storage system to store your documents in accessible manner. Scribd's ad system
24
+ will help you monetize your documents easily.
25
+
26
+ First, you'll need to get a Scribd API account. Visit
27
+ http://www.scribd.com/platform to apply for a platform account.
28
+
29
+ On the Platform site you will be given an API key and secret. The API object
30
+ will need these to authenticate you:
31
+
32
+ require 'rscribd'
33
+ Scribd::API.instance.key = 'your API key'
34
+ Scribd::API.instance.secret = 'your API secret'
35
+
36
+ Next, log into the Scribd website:
37
+
38
+ Scribd::User.login 'username', 'password'
39
+
40
+ You are now ready to use Scribd to manage your documents. For instance, to
41
+ upload a document:
42
+
43
+ doc = Scribd::Document.upload(:file => 'your-file.pdf')
44
+
45
+ For more information, please see the documentation for the Scribd::API,
46
+ Scribd::User, and Scribd::Document classes. (You can also check out the docs for
47
+ the other classes for a more in-depth look at this gem's features).
48
+
49
+ == REQUIREMENTS:
50
+
51
+ * A Scribd API account
52
+ * mime-types gem
53
+
54
+ == INSTALL:
55
+
56
+ * The client library is a RubyGem called *rscribd*. To install, type:
57
+
58
+ sudo gem install rscribd
59
+
60
+ == LICENSE:
61
+
62
+ (The MIT License)
63
+
64
+ Copyright (c) 2007-2008 The Scribd Team
65
+
66
+ Permission is hereby granted, free of charge, to any person obtaining
67
+ a copy of this software and associated documentation files (the
68
+ 'Software'), to deal in the Software without restriction, including
69
+ without limitation the rights to use, copy, modify, merge, publish,
70
+ distribute, sublicense, and/or sell copies of the Software, and to
71
+ permit persons to whom the Software is furnished to do so, subject to
72
+ the following conditions:
73
+
74
+ The above copyright notice and this permission notice shall be
75
+ included in all copies or substantial portions of the Software.
76
+
77
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
78
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
79
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
80
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
81
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
82
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
83
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.new('rscribd', '0.0.3') do |p|
7
+ p.rubyforge_name = 'rscribd'
8
+ p.author = 'Jared Friedman'
9
+ p.email = 'api@scribd.com'
10
+ p.summary = 'Ruby client library for the Scribd API'
11
+ p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
12
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
13
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
14
+ p.extra_deps << [ 'mime-types', '>0.0.0' ]
15
+ p.remote_rdoc_dir = ''
16
+ end
17
+
18
+ # vim: syntax=Ruby
data/lib/api.rb ADDED
@@ -0,0 +1,219 @@
1
+ require 'singleton'
2
+ require 'md5'
3
+ require 'rexml/document'
4
+
5
+ module Scribd
6
+
7
+ # This class acts as the top-level interface between Scribd and your
8
+ # application. Before you can begin using the Scribd API, you must specify for
9
+ # this object your API key and API secret. They are available on your
10
+ # Platform home page.
11
+ #
12
+ # This class is a singleton. Its only instance is accessed using the
13
+ # +instance+ class method.
14
+ #
15
+ # To begin, first specify your API key and secret:
16
+ #
17
+ # Scribd::API.instance.key = 'your API key here'
18
+ # Scribd::API.instance.secret = 'your API secret here'
19
+ #
20
+ # (If you set the +SCRIBD_API_KEY+ and +SCRIBD_API_SECRET+ Ruby environment
21
+ # variables before loading the gem, these values will be set automatically for
22
+ # you.)
23
+ #
24
+ # Next, you should log in to Scribd, or create a new account through the gem.
25
+ #
26
+ # user = Scribd::User.login 'login', 'password'
27
+ #
28
+ # You are now free to use the Scribd::User or Scribd::Document classes to work
29
+ # with Scribd documents or your user account.
30
+ #
31
+ # If you need the Scribd::User instance for the currently logged in user at a
32
+ # later point in time, you can retrieve it using the +user+ attribute:
33
+ #
34
+ # user = Scribd::API.instance.user
35
+ #
36
+ # In addition, you can save and restore sessions by simply storing this user
37
+ # instance and assigning it to the API at a later time. For example, to
38
+ # restore the session retrieved in the previous example:
39
+ #
40
+ # Scribd::API.instance.user = user
41
+ #
42
+ # In addition to working with Scribd users, you can also work with your own
43
+ # website's user accounts. To do this, set the Scribd API user to a string
44
+ # containing a unique identifier for that user (perhaps a login name or a user
45
+ # ID):
46
+ #
47
+ # Scribd::API.instance.user = my_user_object.mangled_user_id
48
+ #
49
+ # A "phantom" Scribd user will be set up with that ID, so you any documents
50
+ # you upload will be associated with that account.
51
+ #
52
+ # For more hints on what you can do with the Scribd API, please see the
53
+ # Scribd::Document class.
54
+
55
+ class API
56
+ include Singleton
57
+
58
+ HOST = 'api.scribd.com' #:nodoc:
59
+ PORT = 80 #:nodoc:
60
+ REQUEST_PATH = '/api' #:nodoc:
61
+ TRIES = 3 #:nodoc:
62
+
63
+ attr_accessor :key # The API key you were given when you created a Platform account.
64
+ attr_accessor :secret # The API secret used to validate your key (also provided with your account).
65
+ attr_accessor :user # The currently logged in user.
66
+ attr_accessor :asynchronous # If true, requests are processed asynchronously. If false, requests are blocking.
67
+ attr_accessor :debug # If true, extended debugging information is printed
68
+
69
+ def initialize #:nodoc:
70
+ @asychronous = false
71
+ @key = ENV['SCRIBD_API_KEY']
72
+ @secret = ENV['SCRIBD_API_SECRET']
73
+ end
74
+
75
+ def send_request(method, fields={}) #:nodoc:
76
+ raise NotReadyError unless @key and @secret
77
+ # See if method is given
78
+ raise ArgumentError, "Method should be given" if method.nil? || method.empty?
79
+
80
+ debug("** Remote method call: #{method}; fields: #{fields.inspect}")
81
+
82
+ # replace pesky hashes to prevent accidents
83
+ fields = fields.stringify_keys
84
+
85
+ # Complete fields with the method name
86
+ fields['method'] = method
87
+ fields['api_key'] = @key
88
+
89
+ if fields['session_key'].nil? and fields['my_user_id'].nil? then
90
+ if @user.kind_of? Scribd::User then
91
+ fields['session_key'] = @user.session_key
92
+ elsif @user.kind_of? String then
93
+ fields['my_user_id'] = @user
94
+ end
95
+ end
96
+
97
+ nil_fields = fields.keys.select { |key| fields[key] == nil }
98
+ nil_fields.each { |key| fields.delete key }
99
+
100
+ # Don't include file in parameters to calculate signature
101
+ sign_fields = {}
102
+ fields.map {|k,v| sign_fields[k] = v if k != 'file' }
103
+ #fields.delete_if {|k,v| k=='file'}
104
+
105
+ fields['api_sig'] = sign(sign_fields)
106
+ debug("** POST parameters: #{fields.inspect}")
107
+
108
+ # Create the connection
109
+ http = Net::HTTP.new(HOST, PORT)
110
+ # TODO configure timeouts through the properties
111
+
112
+ # API methods can be SLOW. Make sure this is set to something big to prevent spurious timeouts
113
+ http.read_timeout = 15*60
114
+
115
+ request = Net::HTTP::Post.new(REQUEST_PATH)
116
+ request.multipart_params = fields
117
+
118
+ tries = TRIES
119
+ begin
120
+ tries -= 1
121
+ res = http.request(request)
122
+ rescue Exception
123
+ $stderr.puts "Request encountered error, will retry #{tries} more."
124
+ if tries > 0
125
+ # Retrying several times with sleeps is recommended.
126
+ # This will prevent temporary downtimes at Scribd from breaking API applications
127
+ sleep(20)
128
+ retry
129
+ end
130
+ raise $!
131
+ end
132
+
133
+ debug "** Response:"
134
+ debug(res.body)
135
+ debug "** End response"
136
+
137
+ # Convert response into XML
138
+ xml = REXML::Document.new(res.body)
139
+ raise MalformedResponseError, "The response received from the remote host could not be interpreted" unless xml.elements['/rsp']
140
+
141
+ # See if there was an error and raise an exception
142
+ if xml.elements['/rsp'].attributes['stat'] == 'fail'
143
+ # Load default code and error
144
+ code, message = -1, "Unidentified error:\n#{res.body}"
145
+
146
+ # Get actual error code and message
147
+ err = xml.elements['/rsp/error']
148
+ code, message = err.attributes['code'], err.attributes['message'] if err
149
+
150
+ # Add more data
151
+ message = "Method: #{method} Response: code=#{code} message=#{message}"
152
+
153
+ raise ResponseError.new(code), message
154
+ end
155
+
156
+ return xml
157
+ end
158
+
159
+ private
160
+
161
+ # FIXME: Since we don't need XMLRPC, the exception could be different
162
+ # TODO: It would probably be better if we wrapped the fault
163
+ # in something more meaningful. At the very least, a broad
164
+ # division of errors, such as retryable and fatal.
165
+ def error(el) #:nodoc:
166
+ att = el.attributes
167
+ fe = XMLRPC::FaultException.new(att['code'].to_i, att['msg'])
168
+ $stderr.puts "ERR: #{fe.faultString} (#{fe.faultCode})"
169
+ raise fe
170
+ end
171
+
172
+ # Checks if a string parameter is given and not empty.
173
+ #
174
+ # Parameters:
175
+ # name - parameter name for an error message.
176
+ # value - value.
177
+ #
178
+ # Raises:
179
+ # ArgumentError if the value is nil, or empty.
180
+ #
181
+ def check_not_empty(name, value) #:nodoc:
182
+ check_given(name, value)
183
+ raise ArgumentError, "#{name} must not be empty" if value.to_s.empty?
184
+ end
185
+
186
+ # Checks if the value is given.
187
+ #
188
+ # Parameters:
189
+ # name - parameter name for an error message.
190
+ # value - value.
191
+ #
192
+ # Raises:
193
+ # ArgumentError if the value is nil.
194
+ #
195
+ def check_given(name, value) #:nodoc:
196
+ raise ArgumentError, "#{name} must be given" if value.nil?
197
+ end
198
+
199
+ # Sign the arguments hash with our very own signature.
200
+ #
201
+ # Parameters:
202
+ # args - method arguments to be sent to the server API
203
+ #
204
+ # Returns:
205
+ # signature
206
+ #
207
+ def sign(args)
208
+ return MD5.md5(@secret + args.sort.flatten.join).to_s
209
+ end
210
+
211
+ # Outputs whatever is given into the $stderr if debugging is enabled.
212
+ #
213
+ # Parameters:
214
+ # args - content to output
215
+ def debug(*args)
216
+ $stderr.puts(sprintf(*args)) if @debug
217
+ end
218
+ end
219
+ end
data/lib/doc.rb ADDED
@@ -0,0 +1,233 @@
1
+ require "uri"
2
+
3
+ module Scribd
4
+
5
+ # A document as shown on the Scribd website. API programs can upload documents
6
+ # from files or URL's, tag them, and change their settings. An API program can
7
+ # access any document, but it can only modify documents owned by the logged-in
8
+ # user.
9
+ #
10
+ # To upload a new document to Scribd, you must create a new Document instance,
11
+ # set the +file+ attribute to the file's path, and then save the document:
12
+ #
13
+ # doc = Scribd::Document.new
14
+ # doc.file = '/path/or/URL/of/file.txt'
15
+ # doc.save
16
+ #
17
+ # You can do this more simply with one line of code:
18
+ #
19
+ # doc = Scribd::Document.create :file => '/path/or/URL/of/file.txt'
20
+ #
21
+ # If you are uploading a file that does not have an extension (like ".txt"),
22
+ # you need to specify the +type+ attribute as well:
23
+ #
24
+ # doc = Scribd::Document.upload :file => 'CHANGELOG', :type => 'txt'
25
+ #
26
+ # Aside from these two attributes, you can set other attributes that affect
27
+ # how the file is displayed on Scribd. See the API documentation online for a
28
+ # list of attributes, at http://www.scribd.com/platform/documentation?method_name=docs.search&subtab=api
29
+ # (consult the "Result explanation" section).
30
+ #
31
+ # These attributes can be accessed or changed directly
32
+ # (<tt>doc.title = 'Title'</tt>). You must save a document after changing its
33
+ # attributes in order for those changes to take effect. Not all attributes can
34
+ # be modified; see the API documentation online for details.
35
+ #
36
+ # A document can be associated with a Scribd::User via the +owner+ attribute.
37
+ # This is not always the case, however. Documents retrieved from the find
38
+ # method will not be associated with their owners.
39
+ #
40
+ # The +owner+ attribute is read/write, however, changes made to it only apply
41
+ # _before_ the document is saved. Once it is saved, the owner is set in stone
42
+ # and cannot be modified:
43
+ #
44
+ # doc = Scribd::Document.new :file => 'test.txt'
45
+ # doc.user = Scribd::User.signup(:username => 'newuser', :password => 'newpass', :email => 'your@email.com')
46
+ # doc.save #=> Uploads the document as "newuser", regardless of who the Scribd API user is
47
+ # doc.user = Scribd::API.instance.user #=> raises NotImplementedError
48
+
49
+ class Document < Resource
50
+
51
+ # Creates a new, unsaved document with the given attributes. The document
52
+ # must be saved before it will appear on the website.
53
+
54
+ def initialize(options={})
55
+ super
56
+ if options[:xml] then
57
+ load_attributes(options[:xml])
58
+ @attributes[:owner] = options[:owner]
59
+ @saved = true
60
+ @created = true
61
+ else
62
+ @attributes = options
63
+ end
64
+ end
65
+
66
+ # For document objects that have not been saved for the first time, uploads
67
+ # the document, sets its attributes, and saves it. Otherwise, updates any
68
+ # changed attributes and saves it. Returns true if the save completed
69
+ # successfully. Throws an exception if save fails.
70
+ #
71
+ # For first-time saves, you must have specified a +file+ attribute. This can
72
+ # either be a local path to a file, or an HTTP, HTTPS, or FTP URL. In either
73
+ # case, the file at that location will be uploaded to create the document.
74
+ #
75
+ # If you create a document, specify the +file+ attribute again, and then
76
+ # save it, Scribd replaces the existing document with the file given, while
77
+ # keeping all other properties (title, description, etc.) the same, in a
78
+ # process called _revisioning_.
79
+ #
80
+ # This method can throw a +Timeout+ exception if the connection is slow or
81
+ # inaccessible. A ResponseError will be thrown if a remote problem occurs.
82
+ # A PrivilegeError will be thrown if you try to upload a new revision for a
83
+ # document with no associated user (i.e., one retrieved from the find
84
+ # method).
85
+ #
86
+ # You must specify the +type+ attribute alongside the +file+ attribute if
87
+ # the file's type cannot be determined from its name.
88
+
89
+ def save
90
+ if not created? and @attributes[:file].nil? then
91
+ raise "'file' attribute must be specified for new documents"
92
+ return false
93
+ end
94
+
95
+ if created? and @attributes[:file] and (@attributes[:owner].nil? or @attributes[:owner].session_key.nil?) then
96
+ raise PrivilegeError, "The current API user is not the owner of this document"
97
+ end
98
+
99
+ # Make a request form
100
+ fields = Hash.new
101
+ if @attributes[:file] then
102
+ ext = @attributes[:file].split('.').last unless @attributes[:file].index('.').nil?
103
+ fields[:doc_type] = @attributes[:type]
104
+ fields[:doc_type] ||= ext
105
+ fields[:rev_id] = @attributes[:doc_id]
106
+ end
107
+ fields[:session_key] = @attributes[:owner].session_key if @attributes[:owner]
108
+ response = nil
109
+
110
+ if @attributes[:file] then
111
+ uri = nil
112
+ begin
113
+ uri = URI.parse @attributes[:file]
114
+ rescue URI::InvalidURIError
115
+ uri = nil # Some valid file paths are not valid URI's (but some are)
116
+ end
117
+ if uri.kind_of? URI::HTTP or uri.kind_of? URI::HTTPS or uri.kind_of? URI::FTP then
118
+ fields[:url] = @attributes[:file]
119
+ response = API.instance.send_request 'docs.uploadFromUrl', fields
120
+ elsif uri.kind_of? URI::Generic or uri.nil? then
121
+ File.open(@attributes[:file]) do |file|
122
+ fields[:file] = file
123
+ response = API.instance.send_request 'docs.upload', fields
124
+ end
125
+ end
126
+ end
127
+
128
+ fields = @attributes.dup # fields is what we send to the server
129
+
130
+ if response then
131
+ # Extract our response
132
+ xml = response.get_elements('/rsp')[0]
133
+ load_attributes(xml)
134
+ @created = true
135
+ end
136
+
137
+ fields.delete :file
138
+ fields.delete :type
139
+
140
+ changed_attributes = fields.dup # changed_attributes is what we will stick into @attributes once we update remotely
141
+
142
+ fields[:session_key] = fields[:owner].session_key if fields[:owner]
143
+ changed_attributes[:owner] ||= API.instance.user
144
+ fields[:doc_ids] = self.id
145
+
146
+ API.instance.send_request('docs.changeSettings', fields)
147
+
148
+ @attributes.update(changed_attributes)
149
+
150
+ @saved = true
151
+ return true
152
+ end
153
+
154
+ # Quickly updates an array of documents with the given attributes. The
155
+ # documents can have different owners, but all of them must be modifiable.
156
+
157
+ def self.update_all(docs, options)
158
+ raise ArgumentError, "docs must be an array" unless docs.kind_of? Array
159
+ raise ArgumentError, "docs must consist of Scribd::Document objects" unless docs.all? { |doc| doc.kind_of? Document }
160
+ raise ArgumentError, "You can't modify one or more documents" if docs.any? { |doc| doc.owner.nil? }
161
+ raise ArgumentError, "options must be a hash" unless options.kind_of? Hash
162
+
163
+ docs_by_user = docs.inject(Hash.new { |hash, key| hash[key] = Array.new }) { |hash, doc| hash[doc.owner] << doc; hash }
164
+ docs_by_user.each { |user, doc_list| API.instance.send_request 'docs.changeSettings', options.merge(:doc_ids => doc_list.collect { |doc| doc.id }.join(','), :session_key => user.session_key) }
165
+ end
166
+
167
+ # Searches for documents matching a query. This method is called with a hash
168
+ # of options to documents by their content. You must at a minimum supply a
169
+ # +query+ option, with a string that will become the full-text search query.
170
+ # For a list of other supported options, please see the online API
171
+ # documentation at http://www.scribd.com/platform/documentation?method_name=docs.search&subtab=api
172
+ #
173
+ # Documents returned from this method will have their +owner+ attributes set
174
+ # to nil.
175
+
176
+ def self.find(options)
177
+ raise ArgumentError, "The find method takes a hash of options" unless options.kind_of? Hash
178
+ raise ArgumentError, "You must specify the query option" unless options[:query]
179
+
180
+ options[:scope] ||= 'all'
181
+
182
+ response = API.instance.send_request('docs.search', options)
183
+ documents = []
184
+ response.elements['/rsp/result_set'].elements.each do |doc|
185
+ documents << Document.new(:xml => doc)
186
+ end
187
+ return documents
188
+ end
189
+
190
+ # Synonym for create.
191
+
192
+ def self.upload(options={})
193
+ create options
194
+ end
195
+
196
+ # Returns the conversion status of this document. When a document is
197
+ # uploaded it must be converted before it can be used. The conversion is
198
+ # non-blocking; you can query this method to determine whether the document
199
+ # is ready to be displayed.
200
+ #
201
+ # The conversion status is returned as a string. For a full list of
202
+ # conversion statuses, see the online API documentation at
203
+ # http://www.scribd.com/platform/documentation?method_name=docs.getConversionStatus&subtab=api
204
+ #
205
+ # Unlike other properties of a document, this is retrieved from the server
206
+ # every time it's queried.
207
+
208
+ def conversion_status
209
+ response = API.instance.send_request('docs.getConversionStatus', :doc_id => self.id)
210
+ response.elements['/rsp/conversion_status'].text
211
+ end
212
+
213
+ # Deletes a document. Returns true if successful.
214
+
215
+ def destroy
216
+ response = API.instance.send_request('docs.delete', :doc_id => self.id)
217
+ return response.elements['/rsp'].attributes['stat'] == 'ok'
218
+ end
219
+
220
+ # Returns the +doc_id+ attribute.
221
+
222
+ def id
223
+ self.doc_id
224
+ end
225
+
226
+ # Ensures that the +owner+ attribute cannot be set once the document is
227
+ # saved.
228
+
229
+ def owner=(newuser) #:nodoc:
230
+ saved? ? raise(NotImplementedError, "Cannot change a document's owner once the document has been saved") : super
231
+ end
232
+ end
233
+ end
data/lib/exceptions.rb ADDED
@@ -0,0 +1,33 @@
1
+ module Scribd
2
+
3
+ # Raised when API calls are made before a key and secret are specified.
4
+
5
+ class NotReadyError < StandardError; end
6
+
7
+ # Raised when the XML returned by Scribd is malformed.
8
+
9
+ class MalformedResponseError < StandardError; end
10
+
11
+ # Raised when trying to perform an action that isn't allowed for the current
12
+ # active user. Note that this exception is thrown only if the error originates
13
+ # locally. If the request must go out to the Scribd server before the
14
+ # privilege error occurs, a ResponseError will be thrown. Unless a method's
15
+ # documentation indicates otherwise, assume that the error will originate
16
+ # remotely and a ResponseError will be thrown.
17
+
18
+ class PrivilegeError < StandardError; end
19
+
20
+ # Raised when a remote error occurs. Remote errors are referenced by numerical
21
+ # code. The online API documentation has a list of possible error codes and
22
+ # their descriptions for each API method.
23
+
24
+ class ResponseError < RuntimeError
25
+ attr_reader :code # The error code.
26
+
27
+ # Initializes the error with a given code.
28
+
29
+ def initialize(code)
30
+ @code = code
31
+ end
32
+ end
33
+ end
data/lib/multipart.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'net/https'
2
+ require "rubygems"
3
+ require "mime/types" # Requires gem install mime-types
4
+ require "base64"
5
+ require 'cgi'
6
+
7
+ # This is based on a great snippet from Pivot Labs, used with permission:
8
+ # http://pivots.pivotallabs.com/users/damon/blog/articles/227-standup-04-27-07-testing-file-uploads
9
+ module Net #:nodoc:
10
+ class HTTP::Post
11
+ def multipart_params=(param_hash={})
12
+ boundary_token = [Array.new(8) {rand(256)}].join
13
+ self.content_type = "multipart/form-data; boundary=#{boundary_token}"
14
+ boundary_marker = "--#{boundary_token}\r\n"
15
+ self.body = param_hash.map { |param_name, param_value|
16
+ boundary_marker + case param_value
17
+ when File
18
+ file_to_multipart(param_name, param_value)
19
+ else
20
+ text_to_multipart(param_name, param_value.to_s)
21
+ end
22
+ }.join('') + "--#{boundary_token}--\r\n"
23
+ end
24
+
25
+ protected
26
+ def file_to_multipart(key,file)
27
+ filename = File.basename(file.path)
28
+ mime_types = MIME::Types.of(filename)
29
+ mime_type = mime_types.empty? ? "application/octet-stream" : mime_types.first.content_type
30
+ part = %Q|Content-Disposition: form-data; name="#{key}"; filename="#{filename}"\r\n|
31
+ part += "Content-Transfer-Encoding: binary\r\n"
32
+ part += "Content-Type: #{mime_type}\r\n\r\n#{file.read}\r\n"
33
+ end
34
+
35
+ def text_to_multipart(key,value)
36
+ "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n#{value}\r\n"
37
+ end
38
+ end
39
+ end