dewey 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,8 +1,37 @@
1
+ ## 0.2.0 (October 20, 2010)
2
+
3
+ Additions:
4
+
5
+ - Class-wide authentication. You only have to set up authentication once and
6
+ then utilize that in all successive calls.
7
+ - All file operations are stateless (Dewey.put, Dewey.get, etc)
8
+ - Store multiple authorizations simultaneously.
9
+
10
+ Changes:
11
+
12
+ - Convert API change. Format is required, not an option.
13
+ - No longer supports upload or download, instead use put or get.
14
+
15
+ ## 0.1.4 (October 19, 2010)
16
+
17
+ Additions:
18
+
19
+ - Handle bad mimetypes.
20
+ - Modular validation
21
+ - Removed service option, needless.
22
+ - Automatic implicit authorization, removes need to call authorize! manually.
23
+
24
+ Bugfixes:
25
+
26
+ - Prevent peer certificate warnings in 1.9
27
+ - Fixed id extraction regex that prevented resources with dashes or underscores
28
+ from being pulled.
29
+
1
30
  ## 0.1.3 (June 29, 2010)
2
31
 
3
32
  Bugfixes:
4
33
 
5
- - Handle mime type changes for files with no extension
34
+ - Handle mime type changes for files with no extension.
6
35
 
7
36
  ## 0.1.2 (June 28, 2010)
8
37
 
data/README.md CHANGED
@@ -15,32 +15,49 @@ Let Google do all of the hard work of converting your documents!
15
15
 
16
16
  Dewey is in alpha. It is not recommended you use this in production code.
17
17
 
18
- ## Getting Started
18
+ ## Authorization
19
19
 
20
- First, create a new Dewey::Document instance using your google docs account and
21
- password.
20
+ You can configure Dewey to connect with either ClientLogin or OAuth. If you choose
21
+ OAuth you'll only have to authenticate the first time you connect and subsequent
22
+ connections will use the saved token.
22
23
 
23
- dewey = Dewey::Document.new(:account => 'example.account', :password => 'example')
24
+ ClientLogin
24
25
 
25
- Next, choose a local document to upload or convert.
26
+ Dewey.authorization :client, :email => 'example@gmail.com', :password => 'password'
27
+
28
+ OAuth
26
29
 
27
- document = File.new('my_document.doc')
28
- spreadsheet = File.new('my_spreadsheet.xls')
30
+ Dewey.authorization :oauth, :idontknowwhatgoeshereyet
31
+
32
+ ## File Operations
33
+
34
+ You can put, get, delete and convert documents, spreadsheets or presentations in
35
+ any of the formats that Docs accepts. There is a full listing in dewey/validations.rb
36
+ or available here: http://code.google.com/apis/documents/faq.html#WhatKindOfFilesCanIUpload
29
37
 
30
- Finally, handle the upload, download, and delete manually or use the convenient
31
- `convert` method.
38
+ Be sure to set up authorization before attempting any file operations! You don't
39
+ need to explictely call authorize though, as it will attempt to do that on the
40
+ first operation.
32
41
 
33
- dewey.convert(document, :html)
34
- dewey.convert(spreadsheet, :csv)
42
+ Putting a document:
43
+
44
+ document = File.new('my_document.doc')
45
+ resource = Dewey.put(document, 'My First Upload') # Returns the id when successful
35
46
 
36
- ## Testing
47
+ Getting a document:
37
48
 
38
- Until testing is converted to an offline solution you will have to provide some
39
- credentials for testing. All that is required is a valid Google Docs account, no
40
- fussing about with getting an application API.
49
+ # Upload your file
50
+ id = Dewey.put(File.new('my_document.doc'), 'My Second Upload')
51
+
52
+ # Get it in various formats
53
+ original = Dewey.get(id) # -> Tempfile
54
+ pdf = Dewey.get(id, :pdf) # -> Tempfile
55
+ html = Dewey.get(id, :html) # -> Tempfile
56
+
57
+ # A tempfile is returned, so you'll have to move it
58
+ FileUtils.mv html.path, 'path/to/destination'
41
59
 
42
- By default the spec will look for a file called dewey.yml inside of the spec
43
- folder. The file should contain two lines:
60
+ Deleting a document:
44
61
 
45
- email: <your gmail account>
46
- password: <your gmail password>
62
+ id = Dewey.put(File.new('my_spreadsheet.xls'))
63
+ result = Dewey.delete(id) # -> true
data/lib/dewey.rb CHANGED
@@ -1,13 +1,3 @@
1
- require 'uri'
2
- require 'net/https'
3
- require 'open-uri'
4
- require 'rexml/document'
5
- require 'tempfile'
6
- require 'dewey/https'
7
- require 'dewey/mime'
8
- require 'dewey/utils'
9
- require 'dewey/validation'
10
-
11
1
  module Dewey
12
2
  GOOGLE_DOCS_URL = "https://docs.google.com"
13
3
  GOOGLE_SPRD_URL = "https://spreadsheets.google.com"
@@ -17,193 +7,14 @@ module Dewey
17
7
  GOOGLE_DOCUMENT_URL = GOOGLE_DOCS_URL + "/feeds/download/documents/Export"
18
8
  GOOGLE_SPREADSHEET_URL = GOOGLE_SPRD_URL + "/feeds/download/spreadsheets/Export"
19
9
 
20
- class DeweyException < Exception
21
- end
22
-
23
- # Doc
24
- # This base class handles authentication and requests
25
- #
26
- class Document
27
- attr_accessor :account, :password, :token
28
-
29
- # Create a new Doc object
30
- # Options specified in +opts+ consist of:
31
- #
32
- # * :account - The Google Doc's account that will be used for authentication.
33
- # This will most typically be a gmail account, i.e. +example@gmail.com+
34
- # * :password - The password for the Google Doc's account.
35
- def initialize(options = {})
36
- @account = options[:account]
37
- @password = options[:password]
38
- end
39
-
40
- # Returns true if this instance has been authorized
41
- def authorized?
42
- !! @token
43
- end
44
-
45
- # Gets an authorization token for this instance. Raises an error if no
46
- # credentials have been provided, +false+ if authorization fails, and +true+ if
47
- # authorization is successful.
48
- def authorize!
49
- if @account.nil? || @password.nil?
50
- raise DeweyException, "Account or password missing."
51
- end
52
-
53
- url = URI.parse(GOOGLE_LOGIN_URL)
54
- params = { 'accountType' => 'HOSTED_OR_GOOGLE', 'Email' => @account,
55
- 'Passwd' => @password, 'service'=> 'writely' }
56
-
57
- response = Net::HTTPS.post_form(url, params)
58
-
59
- case response
60
- when Net::HTTPSuccess
61
- @token = response.body.split(/=/).last
62
- true
63
- when Net::HTTPForbidden
64
- false
65
- else
66
- raise DeweyException, "Unexpected response: #{response}"
67
- end
68
- end
69
-
70
- # Upload a file to the account. A successful upload will return the resource
71
- # id, which is useful for downloading the file without doing a title search.
72
- # * file - A File reference
73
- # * title - An alternative title, to be used instead of the filename
74
- def put(file, title = nil)
75
- authorize! unless authorized?
76
-
77
- extension = File.extname(file.path).sub('.', '')
78
- basename = File.basename(file.path, ".#{extension}")
79
- mimetype = Dewey::Mime.mime_type(file)
80
- service = Dewey::Mime.guess_service(mimetype)
81
-
82
- title ||= basename
83
-
84
- raise DeweyException, "Invalid file type: #{extension}" unless Dewey::Validation.valid_upload_format?(extension, service)
85
-
86
- headers = base_headers
87
- headers['Content-Length'] = File.size?(file).to_s
88
- headers['Slug'] = Dewey::Utils.escape(title)
89
- headers['Content-Type'] = mimetype unless mimetype =~ /Can't expand summary_info/
90
-
91
- # Rewind the file in the case of multiple uploads, or conversions
92
- file.rewind
93
-
94
- response = post_request(GOOGLE_FEED_URL, file.read.to_s, headers)
95
-
96
- case response
97
- when Net::HTTPCreated
98
- extract_rid(response.body)
99
- else
100
- nil
101
- end
102
- end
103
-
104
- alias :upload :put
105
-
106
- # Download, or export more accurately, a file to a specified format
107
- # * rid - A resource id, for example +document:12345+
108
- # * format - The output format, see *_EXPORT_FORMATS for possiblibilites
109
- def get(rid, format = nil)
110
- authorize! unless authorized?
111
-
112
- spreadsheet = !! rid.match(/^spreadsheet/)
113
- id = rid.sub(/[a-z]+:/, '')
114
-
115
- url = ''
116
- url << (spreadsheet ? GOOGLE_SPREADSHEET_URL : GOOGLE_DOCUMENT_URL)
117
- url << (spreadsheet ? "?key=#{id}" : "?docID=#{id}")
118
- url << "&exportFormat=#{format.to_s}" unless format.nil?
119
-
120
- headers = base_headers
121
-
122
- file = Tempfile.new([rid, format].join('.'))
123
- file.binmode
124
-
125
- open(url, headers) { |data| file.write data.read }
126
-
127
- file
128
- end
129
-
130
- alias :download :get
131
-
132
- # Deletes a document referenced either by resource id or by name.
133
- # * id - A resource id or exact file name matching a document in the account
134
- def delete(id)
135
- authorize! unless authorized?
136
-
137
- headers = base_headers
138
- headers['If-Match'] = '*' # We don't care if others have modified
139
-
140
- url = GOOGLE_FEED_URL + "/#{Dewey::Utils.escape(id)}?delete=true"
141
- response = delete_request(url, headers)
142
-
143
- case response
144
- when Net::HTTPOK
145
- true
146
- else
147
- false
148
- end
149
- end
150
-
151
- # Convenience method for +upload+, +download+, +delete+. Returns a Tempfile
152
- # with in the provided type.
153
- # * file - The file that will be converted
154
- # * options - Takes :title and :format. See +upload+ for title, and +download+
155
- # for format.
156
- def convert(file, options = {})
157
- rid = upload(file, options[:title])
158
- con = download(rid, options[:format])
159
-
160
- delete(rid)
161
-
162
- con
163
- end
164
-
165
- private
166
-
167
- def post_request(url, data, headers) #:nodoc:
168
- http_request(:post, url, headers, data)
169
- end
170
-
171
- def delete_request(url, headers) #:nodoc:
172
- http_request(:delete, url, headers)
173
- end
174
-
175
- def http_request(method, url, headers, data = nil) #:nodoc:
176
- url = URI.parse(url) if url.kind_of? String
10
+ class DeweyException < Exception; end
11
+ class DeweyAuthorizationException < Exception; end
12
+ end
177
13
 
178
- connection = (url.scheme == 'https') ? Net::HTTPS.new(url.host, url.port) : Net::HTTP.new(url.host, url.port)
179
-
180
- case method
181
- when :post
182
- connection.post(url.path, data, headers)
183
- when :delete
184
- connection.delete(url.path, headers)
185
- else
186
- raise DeweyException, "Invalid request type. Valid options are :post and :delete"
187
- end
188
- end
189
-
190
- def base_headers #:nodoc:
191
- base = {}
192
- base['GData-Version'] = '3.0'
193
- base['Content-Type'] = 'application/x-www-form-urlencoded'
194
- base['Authorization'] = "GoogleLogin auth=#{@token}" if authorized?
195
-
196
- base
197
- end
198
-
199
- def extract_rid(response) #:nodoc:
200
- xml = REXML::Document.new(response)
201
-
202
- begin
203
- "#{$1}:#{$2}" if xml.elements['//id'].text =~ /.+(document|spreadsheet|presentation)%3A([0-9a-zA-Z_-]+)$/
204
- rescue
205
- raise DeweyException, "id could not be extracted from: #{response}"
206
- end
207
- end
208
- end
209
- end
14
+ require 'dewey/core'
15
+ require 'dewey/client_auth'
16
+ require 'dewey/https'
17
+ require 'dewey/mime'
18
+ require 'dewey/utils'
19
+ require 'dewey/validation'
20
+ require 'dewey/version'
@@ -0,0 +1,48 @@
1
+ module Dewey
2
+ class ClientAuth
3
+ attr_reader :authentications
4
+
5
+ def initialize(email, password)
6
+ @email = email
7
+ @password = password
8
+ @authentications = {}
9
+ end
10
+
11
+ def authenticated?(service = nil)
12
+ if service
13
+ @authentications.has_key?(service)
14
+ else
15
+ @authentications.any?
16
+ end
17
+ end
18
+
19
+ def authenticate!(service = nil)
20
+ service ||= :writely
21
+
22
+
23
+ params = { 'accountType' => 'HOSTED_OR_GOOGLE',
24
+ 'Email' => @email,
25
+ 'Passwd' => @password,
26
+ 'service' => service.to_s
27
+ }
28
+
29
+ url = URI.parse(Dewey::GOOGLE_LOGIN_URL)
30
+ response = Net::HTTPS.post_form(url, params)
31
+
32
+ case response
33
+ when Net::HTTPSuccess
34
+ @authentications[service] = response.body.split('=').last
35
+ true
36
+ when Net::HTTPForbidden
37
+ false
38
+ else
39
+ raise DeweyAuthorizationException, "Unexpected response: #{response}"
40
+ end
41
+ end
42
+
43
+ def token(service = nil)
44
+ @authentications[service || :writely]
45
+ end
46
+ end
47
+ end
48
+
data/lib/dewey/core.rb ADDED
@@ -0,0 +1,158 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+ require 'open-uri'
4
+ require 'rexml/document'
5
+ require 'tempfile'
6
+
7
+ module Dewey
8
+ class << self
9
+ def authentication(strategy, options)
10
+ case strategy
11
+ when :client
12
+ @@authenticator = Dewey::ClientAuth.new(options[:email], options[:password])
13
+ end
14
+ end
15
+
16
+ def authenticated?
17
+ !@@authenticator.nil? && @@authenticator.authenticated?
18
+ end
19
+
20
+ def authenticate!
21
+ @@authenticator.authenticate!
22
+ end
23
+
24
+ # Upload a file to the account. A successful upload will return the resource
25
+ # id, which is useful for downloading the file without doing a title search.
26
+ # * file - A File reference
27
+ # * title - An alternative title, to be used instead of the filename
28
+ def put(file, title = nil)
29
+ authenticate! unless authenticated?
30
+
31
+ extension = File.extname(file.path).sub('.', '')
32
+ basename = File.basename(file.path, ".#{extension}")
33
+ mimetype = Dewey::Mime.mime_type(file)
34
+ service = Dewey::Mime.guess_service(mimetype)
35
+
36
+ title ||= basename
37
+
38
+ raise DeweyException, "Invalid file type: #{extension}" unless Dewey::Validation.valid_upload_format?(extension, service)
39
+
40
+ headers = base_headers
41
+ headers['Content-Length'] = File.size?(file).to_s
42
+ headers['Slug'] = Dewey::Utils.escape(title)
43
+ headers['Content-Type'] = mimetype unless mimetype =~ /Can't expand summary_info/
44
+
45
+ # Rewind the file in the case of multiple uploads, or conversions
46
+ file.rewind
47
+
48
+ response = post_request(GOOGLE_FEED_URL, file.read.to_s, headers)
49
+
50
+ case response
51
+ when Net::HTTPCreated
52
+ extract_rid(response.body)
53
+ else
54
+ false
55
+ end
56
+ end
57
+
58
+ # Download, or export more accurately, a file to a specified format
59
+ # * rid - A resource id, for example +document:12345+
60
+ # * format - The output format, see *_EXPORT_FORMATS for possiblibilites
61
+ def get(rid, format = nil)
62
+ authenticate! unless authenticated?
63
+
64
+ spreadsheet = !! rid.match(/^spreadsheet/)
65
+ id = rid.sub(/[a-z]+:/, '')
66
+
67
+ url = ''
68
+ url << (spreadsheet ? GOOGLE_SPREADSHEET_URL : GOOGLE_DOCUMENT_URL)
69
+ url << (spreadsheet ? "?key=#{id}" : "?docID=#{id}")
70
+ url << "&exportFormat=#{format.to_s}" unless format.nil?
71
+
72
+ headers = base_headers
73
+
74
+ file = Tempfile.new([rid, format].join('.'))
75
+ file.binmode
76
+
77
+ open(url, headers) { |data| file.write data.read }
78
+
79
+ file
80
+ end
81
+
82
+ # Deletes a document referenced either by resource id or by name.
83
+ # * id - A resource id or exact file name matching a document in the account
84
+ def delete(id)
85
+ authenticate! unless authenticated?
86
+
87
+ headers = base_headers
88
+ headers['If-Match'] = '*' # We don't care if others have modified
89
+
90
+ url = GOOGLE_FEED_URL + "/#{Dewey::Utils.escape(id)}?delete=true"
91
+ response = delete_request(url, headers)
92
+
93
+ case response
94
+ when Net::HTTPOK
95
+ true
96
+ else
97
+ false
98
+ end
99
+ end
100
+
101
+ # Convenience method for +put+, +get+, +delete+. Returns a Tempfile
102
+ # with in the provided type. Note that if you omit the format option it will
103
+ # simply upload the file and return it.
104
+ # * file - The file that will be converted
105
+ # * options - Takes :title and :format. See +upload+ for title, and +download+
106
+ # for format.
107
+ def convert(file, options = {})
108
+ rid = put(file, options[:title])
109
+ con = get(rid, options[:format])
110
+
111
+ delete(rid)
112
+
113
+ con
114
+ end
115
+
116
+ def post_request(url, data, headers) #:nodoc:
117
+ http_request(:post, url, headers, data)
118
+ end
119
+
120
+ def delete_request(url, headers) #:nodoc:
121
+ http_request(:delete, url, headers)
122
+ end
123
+
124
+ def http_request(method, url, headers, data = nil) #:nodoc:
125
+ url = URI.parse(url) if url.kind_of? String
126
+
127
+ connection = (url.scheme == 'https') ? Net::HTTPS.new(url.host, url.port) : Net::HTTP.new(url.host, url.port)
128
+
129
+ case method
130
+ when :post
131
+ connection.post(url.path, data, headers)
132
+ when :delete
133
+ connection.delete(url.path, headers)
134
+ else
135
+ raise DeweyException, "Invalid request type. Valid options are :post and :delete"
136
+ end
137
+ end
138
+
139
+ def base_headers #:nodoc:
140
+ base = {}
141
+ base['GData-Version'] = '3.0'
142
+ base['Content-Type'] = 'application/x-www-form-urlencoded'
143
+ base['Authorization'] = "GoogleLogin auth=#{@@authenticator.token}" if authenticated?
144
+
145
+ base
146
+ end
147
+
148
+ def extract_rid(response) #:nodoc:
149
+ xml = REXML::Document.new(response)
150
+
151
+ begin
152
+ "#{$1}:#{$2}" if xml.elements['//id'].text =~ /.*(document|spreadsheet|presentation)%3A([0-9a-zA-Z_-]+)$/
153
+ rescue
154
+ raise DeweyException, "id could not be extracted from: #{response}"
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,3 @@
1
+ module Dewey
2
+ VERSION = '0.2.0'
3
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
8
- - 4
9
- version: 0.1.4
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Parker Selbert
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-10-19 00:00:00 -05:00
17
+ date: 2010-10-23 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -40,14 +40,16 @@ extensions: []
40
40
  extra_rdoc_files: []
41
41
 
42
42
  files:
43
+ - lib/dewey/client_auth.rb
44
+ - lib/dewey/core.rb
43
45
  - lib/dewey/https.rb
44
46
  - lib/dewey/mime.rb
45
47
  - lib/dewey/utils.rb
46
48
  - lib/dewey/validation.rb
49
+ - lib/dewey/version.rb
47
50
  - lib/dewey.rb
48
51
  - README.md
49
52
  - CHANGELOG.md
50
- - TODO.md
51
53
  has_rdoc: true
52
54
  homepage: http://github.com/sorentwo/dewey
53
55
  licenses: []
data/TODO.md DELETED
@@ -1,5 +0,0 @@
1
- ## Dewey TODO List
2
- - Implement the OAuth gem to manage service switching
3
- - Add presentation support
4
- - Documents: list
5
- - Add support for folders