rscribd 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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