smugmug 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/HISTORY +2 -0
  2. data/LICENSE +19 -0
  3. data/MANIFEST +48 -0
  4. data/README +30 -0
  5. data/Rakefile +100 -0
  6. data/bin/smcli +225 -0
  7. data/bin/smugmug2sql +158 -0
  8. data/doc/API +310 -0
  9. data/doc/TODO +32 -0
  10. data/lib/net/httpz.rb +31 -0
  11. data/lib/smugmug.rb +179 -0
  12. data/lib/smugmug/album/info.rb +131 -0
  13. data/lib/smugmug/album/stats.rb +31 -0
  14. data/lib/smugmug/albums.rb +39 -0
  15. data/lib/smugmug/base.rb +104 -0
  16. data/lib/smugmug/cache.rb +33 -0
  17. data/lib/smugmug/config.rb +48 -0
  18. data/lib/smugmug/image/exif.rb +72 -0
  19. data/lib/smugmug/image/info.rb +88 -0
  20. data/lib/smugmug/image/stats.rb +32 -0
  21. data/lib/smugmug/images.rb +52 -0
  22. data/lib/smugmug/table.rb +133 -0
  23. data/lib/smugmug/util.rb +12 -0
  24. data/test/album.rb +359 -0
  25. data/test/config.rb +39 -0
  26. data/test/httpz.rb +120 -0
  27. data/test/image.rb +540 -0
  28. data/test/login.rb +24 -0
  29. data/test/runner.rb +83 -0
  30. data/test/servlet.rb +257 -0
  31. data/test/table.rb +113 -0
  32. data/xml/canned +212 -0
  33. data/xml/fail/empty.set.xml +4 -0
  34. data/xml/fail/invalid.apikey.xml +4 -0
  35. data/xml/fail/invalid.login.xml +4 -0
  36. data/xml/fail/invalid.method.xml +4 -0
  37. data/xml/fail/invalid.user.xml +4 -0
  38. data/xml/fail/system.error.xml +4 -0
  39. data/xml/standard/albums.get.xml +24 -0
  40. data/xml/standard/albums.getInfo.xml +38 -0
  41. data/xml/standard/albums.getStats.xml +43 -0
  42. data/xml/standard/categories.get.xml +213 -0
  43. data/xml/standard/images.get.xml +9 -0
  44. data/xml/standard/images.getEXIF.xml +34 -0
  45. data/xml/standard/images.getInfo.xml +29 -0
  46. data/xml/standard/images.getStats.xml +15 -0
  47. data/xml/standard/login.withHash.xml +7 -0
  48. data/xml/standard/login.withPassword.xml +10 -0
  49. metadata +103 -0
data/test/login.rb ADDED
@@ -0,0 +1,24 @@
1
+ # -*- ruby -*-
2
+ # $Hg$
3
+
4
+ require 'test/unit'
5
+ require 'smugmug'
6
+
7
+ class TC_Login < Test::Unit::TestCase
8
+ def teardown
9
+ File.unlink(ENV['SMUGMUG_RC'])
10
+ end
11
+
12
+ def test_login
13
+ sm = nil
14
+
15
+ assert_nil(sm)
16
+ assert_raise(SmugMug::SmugMugError) { SmugMug::SmugMug.new('foo', 'blank') }
17
+ assert_nothing_raised { SmugMug::SmugMug.new('smugmug@example.net', 'secret') }
18
+
19
+ assert_nothing_raised { sm = SmugMug::SmugMug.new }
20
+ assert_equal('http://localhost:45556/', sm.base)
21
+
22
+ assert_not_nil(sm)
23
+ end
24
+ end
data/test/runner.rb ADDED
@@ -0,0 +1,83 @@
1
+ # -*- ruby -*-
2
+ # $Hg$
3
+
4
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
5
+ require 'test/unit/ui/console/testrunner'
6
+ require 'test/unit'
7
+ require 'logger'
8
+ require 'servlet'
9
+ require 'smugmug'
10
+
11
+ if not (File.exists?('table.rb'))
12
+ raise 'The test suite should only be run from the test directory.'
13
+ end
14
+
15
+ %w{access.log servlet.log test.log}.each do |filename|
16
+ File.unlink(filename) if File.exists?(filename)
17
+ end
18
+
19
+ ENV['SMUGMUG_URL'] = 'http://localhost:45556/'
20
+ ENV['SMUGMUG_RC'] = '.smugmugrc'
21
+
22
+ ## Imitation smugmug.com
23
+ server = WEBrick::HTTPServer.new(
24
+ :BindAddress => '127.0.0.1',
25
+ :Port => 45556,
26
+ :AccessLog => [
27
+ [ File.open('access.log', 'w'), WEBrick::AccessLog::COMBINED_LOG_FORMAT ]
28
+ ],
29
+ :Logger => WEBrick::Log.new('servlet.log')
30
+ )
31
+
32
+ threads = []
33
+ threads << Thread.new do
34
+ server.mount('/', SmugMugServlet)
35
+ server.start
36
+ end
37
+
38
+ %w{INT TERM EXIT}.each do |sig|
39
+ trap(sig) do
40
+ server.shutdown
41
+ threads.each {|t| t.join}
42
+ File.unlink('.smugmugrc') if File.exists?('.smugmugrc')
43
+ end
44
+ end
45
+
46
+ $log = Logger.new('test.log')
47
+ $smugmug = SmugMug::SmugMug.new('smugmug@example.net', 'secret')
48
+ File.unlink(ENV['SMUGMUG_RC'])
49
+
50
+ if ARGV.any?
51
+ ARGV.each {|fn| require(fn) }
52
+ else
53
+ ## Stand alone
54
+ require('config.rb')
55
+ require('httpz.rb')
56
+ require('table.rb')
57
+
58
+ ## SmugMug.com WEBrick
59
+
60
+ require('login.rb')
61
+ require('album.rb')
62
+ require('image.rb')
63
+ end
64
+
65
+ class SmugMugTestSuite < Test::Unit::TestSuite
66
+ def initialize
67
+ super('SmugMug')
68
+ end
69
+ def self.suite
70
+ suite = Test::Unit::TestSuite.new
71
+ ObjectSpace.each_object(Class) do |klass|
72
+ suite << klass.suite if (Test::Unit::TestCase > klass)
73
+ end
74
+ return suite
75
+ end
76
+ end
77
+
78
+ passed = Test::Unit::UI::Console::TestRunner.run(SmugMugTestSuite.suite).passed?
79
+ Kernel.exit(passed ? 0 : 1)
80
+
81
+ # Local Variables:
82
+ # ruby-indent-level: 4
83
+ # End:
data/test/servlet.rb ADDED
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- Mode: ruby; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3
+ # $Hg: servlet.rb,v 3b8b5b808b51 2007/08/11 07:33:22 boumenot $
4
+
5
+ require 'webrick'
6
+ require 'uri'
7
+
8
+ module URI
9
+ class Generic
10
+ def [](key)
11
+ return query_hash[key]
12
+ end
13
+
14
+ def query_hash
15
+ raise RuntimeError if query.nil?
16
+ hash = {}
17
+ query.split('&').each do |s|
18
+ k,v = s.split('=')
19
+ hash[k] = v
20
+ end
21
+ return hash
22
+ end
23
+
24
+ def has_key?(key)
25
+ return query_hash.has_key?(key)
26
+ end
27
+ end
28
+ end
29
+
30
+ class EmptySet < RuntimeError; end
31
+ class InvalidAPIKey < RuntimeError; end
32
+ class InvalidLogin < RuntimeError; end
33
+ class InvalidMethod < RuntimeError; end
34
+ class InvalidUser < RuntimeError; end
35
+ class SystemError < RuntimeError; end
36
+
37
+ class Methods
38
+ attr_accessor :uri
39
+
40
+ attr_reader :account_type, :logger
41
+ attr_reader :api_key, :email_address, :password, :password_hash, :session_id, :user_id
42
+
43
+ @authenticated = false
44
+ class << self
45
+ attr_accessor :authenticated
46
+ def authenticated?() @authenticated ; end
47
+ end
48
+
49
+ def initialize(account_type='Standard')
50
+ @authenticated = false
51
+
52
+ @account_type = account_type
53
+ @api_key = 'dydlCYDzmykqoLkVGgECEIP5WEuwWFzc'
54
+ @email_address = 'smugmug@example.net'
55
+ @password = 'secret'
56
+ @password_hash = '$1$add02c7c2232759874e1c205587017bed'
57
+ @session_id = '42e74d96e8a3454259eed4e2b6a143cf'
58
+ @user_id = 1001
59
+ end
60
+
61
+ ## accessors
62
+
63
+ def authenticated?() self.class.authenticated? ; end
64
+
65
+ ## methods
66
+
67
+ private
68
+
69
+ def api_key?() uri['APIKey'] == api_key ; end
70
+ def email_address?() uri['EmailAddress'] == email_address ; end
71
+ def password?() uri['Password'] == password ; end
72
+ def password_hash?() uri['PasswordHash'] == password_hash ; end
73
+ def session_id?() uri['SessionID'] == session_id ; end
74
+ def user_id?() uri['UserID'].to_i == user_id ; end
75
+
76
+ def login_check
77
+ raise InvalidAPIKey unless uri.has_key?('APIKey')
78
+ raise InvalidAPIKey unless api_key?
79
+ end
80
+
81
+ public
82
+
83
+ ## login
84
+
85
+ def login?()
86
+ return %w{smugmug.login.withPassword smugmug.login.withHash}.include?(uri['method'])
87
+ end
88
+
89
+ def login_withPassword
90
+ raise InvalidLogin unless uri.has_key?('EmailAddress')
91
+ raise InvalidLogin unless uri.has_key?('Password')
92
+
93
+ raise InvalidLogin unless email_address?
94
+ raise InvalidLogin unless password?
95
+
96
+ login_check()
97
+
98
+ self.class.authenticated = true
99
+ return _read('login.withPassword')
100
+ end
101
+
102
+ def login_withHash
103
+ raise InvalidLogin unless uri.has_key?('UserID')
104
+ raise InvalidLogin unless uri.has_key?('PasswordHash')
105
+
106
+ raise InvalidLogin unless user_id?
107
+ raise InvalidLogin unless password_hash?
108
+
109
+ login_check()
110
+
111
+ self.class.authenticated = true
112
+ return _read('login.withHash')
113
+ end
114
+
115
+ ## albums
116
+
117
+ def album_id?()
118
+ return (uri['AlbumID'].to_i >= 1001 and uri['AlbumID'].to_i <= 1003)
119
+ end
120
+
121
+ def albums?()
122
+ raise InvalidUser unless uri.has_key?('AlbumID')
123
+ raise InvalidUser unless album_id?
124
+ end
125
+
126
+ def albums_get
127
+ raise InvalidAPIKey unless session_id?
128
+ return _read('albums.get')
129
+ end
130
+ def albums_getInfo
131
+ raise InvalidAPIKey unless session_id?
132
+ raise SystemError unless uri.has_key?('AlbumID')
133
+ albums?
134
+ return _read('albums.getInfo')
135
+ end
136
+ def albums_getStats
137
+ raise InvalidAPIKey unless session_id?
138
+ albums?
139
+ return _read('albums.getStats')
140
+ end
141
+
142
+ ## images
143
+
144
+ def image_id?()
145
+ return (uri['ImageID'].to_i >= 1001 and uri['ImageID'].to_i <= 1003)
146
+ end
147
+
148
+ def images?()
149
+ raise InvalidUser unless uri.has_key?('ImageID')
150
+ raise InvalidUser unless image_id?
151
+ end
152
+
153
+ def images_get
154
+ albums?
155
+ raise InvalidAPIKey unless session_id?
156
+ return _read('images.get')
157
+ end
158
+ def images_getInfo
159
+ raise InvalidAPIKey unless session_id?
160
+ images?
161
+ return _read('images.getInfo')
162
+ end
163
+ def images_getEXIF
164
+ raise InvalidAPIKey unless session_id?
165
+ images?
166
+ return _read('images.getEXIF')
167
+ end
168
+ def images_getStats
169
+ raise InvalidAPIKey unless session_id?
170
+ images?
171
+ return _read('images.getStats')
172
+ end
173
+
174
+ ## fail
175
+
176
+ def empty_set
177
+ return _read('empty_set', 'fail')
178
+ end
179
+ def invalid_login
180
+ return _read('invalid_login', 'fail')
181
+ end
182
+ def invalid_method
183
+ return _read('invalid_method', 'fail')
184
+ end
185
+
186
+ private
187
+
188
+ def _read(method, type=account_type.downcase)
189
+ method.gsub!('smugmug.', '')
190
+ method.gsub!('_', '.')
191
+
192
+ filename = File.join('..', 'xml', type, "#{method}.xml")
193
+ unless File.exists?(filename)
194
+ raise RuntimeError, "Cannot locate the XML file (#{filename} )for the method #{method}!"
195
+ end
196
+ return IO.read(filename)
197
+ end
198
+ end
199
+
200
+ class SmugMugServlet < WEBrick::HTTPServlet::AbstractServlet
201
+ attr_reader :method
202
+ def initialize(*args)
203
+ super(*args)
204
+ @method = Methods.new
205
+ end
206
+
207
+ def _method
208
+ raise InvalidMethod unless @method.uri.has_key?('method')
209
+ m = @method.uri['method']
210
+ m.gsub!('smugmug.', '')
211
+ m.gsub!('.', '_')
212
+ return m.to_sym
213
+ end
214
+
215
+ def do_GET(req, res)
216
+ @logger.info("URI: " + req.request_uri.to_s)
217
+ begin
218
+ @method.uri = req.request_uri
219
+ raise InvalidMethod.new unless method.respond_to?(_method)
220
+
221
+ unless method.authenticated?
222
+ raise InvalidAPIKey unless method.login?
223
+ end
224
+
225
+ res.body = method.send(_method)
226
+ rescue InvalidLogin
227
+ raise WEBrick::HTTPStatus::Unauthorized, method.invalid_login
228
+ rescue InvalidMethod
229
+ raise WEBrick::HTTPStatus::MethodNotAllowed, method.invalid_method
230
+ rescue
231
+ @logger.info($!.class)
232
+ $!.backtrace.each { |line| @logger.info(line) }
233
+ raise WEBrick::HTTPStatus::InternalServerError
234
+ end
235
+
236
+ return res
237
+ end
238
+
239
+ def do_PUT
240
+ end
241
+
242
+ def do_POST
243
+ end
244
+ end
245
+
246
+ if __FILE__ == $0
247
+ s = WEBrick::HTTPServer.new(
248
+ :BindAddress => '127.0.0.1',
249
+ :Port => 45556,
250
+ :Logger => WEBrick::Log.new('servlet.log', WEBrick::Log::DEBUG),
251
+ :AccessLog => [ [ File.open('access.log', 'w'), WEBrick::AccessLog::COMBINED_LOG_FORMAT ] ]
252
+ )
253
+ s.mount('/', SmugMugServlet)
254
+ trap("INT") { s.shutdown }
255
+ trap("TERM") { s.shutdown }
256
+ s.start
257
+ end
data/test/table.rb ADDED
@@ -0,0 +1,113 @@
1
+ # -*- ruby -*-
2
+ # $Hg: table.rb,v 1a7959f46f5c 2007/08/11 05:56:18 boumenot $
3
+
4
+ require 'test/unit'
5
+ require 'smugmug/table'
6
+
7
+
8
+ class TC_MethodTable < Test::Unit::TestCase
9
+ attr_reader :std, :pow, :poW, :pro, :sup
10
+
11
+ def setup
12
+ table_xml = %q{
13
+ <smugmug>
14
+ <TestId type="int" xpath="//TestId/attribute::id"/>
15
+ <String/>
16
+ <Int type="int"/>
17
+ <False type="boolean"/>
18
+ <True type="boolean"/>
19
+ <Power account="power"/>
20
+ <Pro account="pro"/>
21
+ </smugmug>
22
+ }
23
+
24
+ resp_xml = %q{
25
+ <test>
26
+ <TestId id="1234"/>
27
+ <String>foo</String>
28
+ <Int>4567</Int>
29
+ <False>0</False>
30
+ <True>1</True>
31
+ <Power>power</Power>
32
+ <Pro>pro</Pro>
33
+ </test>
34
+ }
35
+
36
+ @doc = REXML::Document.new(resp_xml)
37
+
38
+ @std = SmugMug::MethodTable.new(table_xml, 'standard', 'foo')
39
+ @pow = SmugMug::MethodTable.new(table_xml, 'power', 'foo')
40
+ @poW = SmugMug::MethodTable.new(table_xml, 'PoWeR', 'foo')
41
+ @pro = SmugMug::MethodTable.new(table_xml, 'pro', 'foo')
42
+ @sup = SmugMug::MethodTable.new(table_xml, 'super', 'foo')
43
+ end
44
+
45
+ def test_klass
46
+ %w{std pow poW pro sup}.each do |inst|
47
+ assert_equal('foo', self.send(inst.to_sym).klass)
48
+ end
49
+ end
50
+
51
+ def test_bad_permission
52
+ assert_nothing_raised { std.get(@doc, :String) }
53
+ assert_nothing_raised { pow.get(@doc, :String) }
54
+ assert_nothing_raised { pro.get(@doc, :String) }
55
+
56
+ assert_raise(RuntimeError) { std.get(@doc, :Power) }
57
+ assert_raise(RuntimeError) { std.get(@doc, :Pro) }
58
+ assert_raise(RuntimeError) { pow.get(@doc, :Pro) }
59
+ end
60
+
61
+ def test_convert_to_type
62
+ assert_instance_of String, std.get(@doc, :String)
63
+ assert_instance_of Fixnum, std.get(@doc, :Int)
64
+ assert_instance_of Fixnum, std.get(@doc, :TestId)
65
+ assert_instance_of FalseClass, std.get(@doc, :False)
66
+ assert_instance_of TrueClass, std.get(@doc, :True)
67
+ end
68
+
69
+ def test_mixed_case_account_type
70
+ assert_nothing_raised { poW.get(@doc, :String) }
71
+ end
72
+
73
+ def test_return_values
74
+ %w{std pow poW pro}.each do |inst|
75
+ assert_equal 1234, self.send(inst.to_sym).get(@doc, :TestId)
76
+ assert_equal 'foo', self.send(inst.to_sym).get(@doc, :String)
77
+ assert_equal 4567, self.send(inst.to_sym).get(@doc, :Int)
78
+ assert_equal false, self.send(inst.to_sym).get(@doc, :False)
79
+ assert_equal true, self.send(inst.to_sym).get(@doc, :True)
80
+ end
81
+
82
+ assert_equal 'power', pow.get(@doc, :Power)
83
+ assert_equal 'power', poW.get(@doc, :Power)
84
+ assert_equal 'power', pro.get(@doc, :Power)
85
+ assert_equal 'pro', pro.get(@doc, :Pro)
86
+ end
87
+
88
+ def test_undefined_methods
89
+ assert_raise(NoMethodError) { std.get(@doc, :fart) }
90
+ assert_raise(NoMethodError) { pow.get(@doc, :fart) }
91
+ assert_raise(NoMethodError) { pro.get(@doc, :fart) }
92
+ end
93
+
94
+ def test_unknown_account_type
95
+ assert_raise(RuntimeError) { sup.get(@doc, :Power) }
96
+ assert_raise(RuntimeError) { sup.get(@doc, :Pro) }
97
+ end
98
+
99
+ def test_aliases
100
+ %w{std pow poW pro}.each do |inst|
101
+ assert_equal 1234, self.send(inst.to_sym).get(@doc, :test_id)
102
+ assert_equal 'foo', self.send(inst.to_sym).get(@doc, :string)
103
+ assert_equal 4567, self.send(inst.to_sym).get(@doc, :int)
104
+ assert_equal false, self.send(inst.to_sym).get(@doc, :false?)
105
+ assert_equal true, self.send(inst.to_sym).get(@doc, :true?)
106
+ end
107
+ end
108
+ end
109
+
110
+ # Local Variables:
111
+ # ruby-indent-level: 4
112
+ # End:
113
+