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
@@ -0,0 +1,88 @@
1
+ # -*- Mode: ruby; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2
+ # $Hg: info.rb,v ecd3a25582a8 2007/08/11 07:17:53 boumenot $
3
+
4
+ require 'smugmug/base'
5
+ require 'smugmug/table'
6
+ require 'smugmug/image/exif'
7
+
8
+ module SmugMug
9
+ module Image
10
+ class Info < Base
11
+ attr_reader :album, :doc
12
+
13
+ def initialize(smugmug, album, doc)
14
+ super(smugmug)
15
+ @album, @doc = album, doc
16
+ self.class.table = MethodTable.new(Info.data(), smugmug.account_type, self)
17
+ end
18
+
19
+ ## overrides
20
+
21
+ def method
22
+ return 'smugmug.images.getInfo'
23
+ end
24
+
25
+ def fetch
26
+ params = {}
27
+ # params[:Password] = album.password
28
+ params[:ImageID] = image_id
29
+
30
+ return super(params)
31
+ end
32
+
33
+ ## methods
34
+
35
+ def <=>(rhs)
36
+ return self.file_name <=> rhs.file_name
37
+ end
38
+
39
+ def delete
40
+ raise RuntimeError, "method is not yet implemented"
41
+ end
42
+
43
+ def stats
44
+ @stats = Stats.new(@smugmug, self) if @stats.nil?
45
+ return @stats
46
+ end
47
+
48
+ def exif
49
+ @exif = Exif.new(@smugmug, self) if @exif.nil?
50
+ return @exif
51
+ end
52
+
53
+ ## accessors
54
+
55
+ # FIXME: needed to prevent infinite recursion
56
+ def image_id() return self.class.table.get(@doc, :image_id) ; end
57
+
58
+ def Info.data
59
+ return %q{
60
+ <smugmug>
61
+ <AlbumID type="int" xpath="//Album/attribute::id"/>
62
+ <ImageID type="int" xpath="//Image/attribute::id"/>
63
+ <Position type="int"/>
64
+ <Serial type="int"/>
65
+ <Size type="int"/>
66
+ <Width type="int"/>
67
+ <Height type="int"/>
68
+ <LastUpdated/>
69
+ <Caption/>
70
+ <FileName/>
71
+ <MD5Sum/>
72
+ <Watermark type="int"/>
73
+ <Format/>
74
+ <Keywords/>
75
+ <Date/>
76
+ <OriginalURL/>
77
+ <LargeURL/>
78
+ <MediumURL/>
79
+ <SmallURL/>
80
+ <ThumbURL/>
81
+ <TinyURL/>
82
+ <AlbumURL/>
83
+ </smugmug>
84
+ }
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,32 @@
1
+ # -*- Mode: ruby; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2
+ # $Hg$
3
+
4
+ require 'smugmug/base'
5
+
6
+ module SmugMug
7
+ module Image
8
+ class Stats < BaseStats
9
+ attr_reader :doc, :image
10
+ def initialize(smugmug, image)
11
+ super(smugmug)
12
+ @image = image
13
+ end
14
+
15
+ ## overrides
16
+
17
+ def method
18
+ return 'smugmug.images.getStats'
19
+ end
20
+
21
+ def fetch
22
+ params = {}
23
+ params[:ImageID] = image.image_id
24
+
25
+ super(params)
26
+ end
27
+
28
+ ## methods
29
+ ## accessors
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,52 @@
1
+ # -*- ruby -*-
2
+ # $Hg: images.rb,v a116fa71e3fb 2007/08/11 07:43:48 boumenot $
3
+
4
+ require 'forwardable'
5
+ require 'smugmug/base'
6
+ require 'smugmug/image/info'
7
+
8
+ module SmugMug
9
+ class Images < Base
10
+ include Enumerable
11
+
12
+ extend Forwardable
13
+ def_delegator :@images, :each, :each
14
+ def_delegator :@images, :any?, :any?
15
+ def_delegator :@images, :size, :size
16
+
17
+ attr_reader :album, :doc
18
+
19
+ def initialize(smugmug, album)
20
+ super(smugmug)
21
+
22
+ @album = album
23
+ @images = []
24
+
25
+ @doc = fetch()
26
+
27
+ @doc.elements.each('//Images/Image') do |x|
28
+ @images << Image::Info.new(smugmug, album, REXML::Document.new(x.to_s))
29
+ $log.info('Images#initialize') { "Added image ID #{@images[-1].image_id}" }
30
+ end
31
+ end
32
+
33
+ ## overrides
34
+
35
+ def method
36
+ return 'smugmug.images.get'
37
+ end
38
+
39
+ def method_missing(sym, *args)
40
+ raise NoMethodError.new("undefined method `#{sym}' for " +
41
+ "#{self.inspect}:#{self.class.name}")
42
+ end
43
+
44
+ def fetch
45
+ params = {}
46
+ # params[:Password] = XXX
47
+ params[:AlbumID] = album.album_id
48
+
49
+ return super(params)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,133 @@
1
+ # -*- Mode: ruby; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2
+ # $Hg: table.rb,v f2af7d25fd90 2007/08/11 07:20:47 boumenot $
3
+
4
+ require 'rexml/document'
5
+
6
+ module SmugMug
7
+ class MethodTable
8
+ include Enumerable
9
+
10
+ attr_reader :account_type, :klass, :table
11
+ def initialize(xml, account_type, klass)
12
+ parse(xml)
13
+ @account_type = account_type
14
+ @klass = klass
15
+ end
16
+
17
+ def get(doc, sym)
18
+ unless element?(sym)
19
+ raise NoMethodError.new("undefined method '#{sym}' for " +
20
+ "#{self.inspect}:#{self.class.name}")
21
+ end
22
+
23
+ method = alias?(sym) ? alias_to_method(sym) : sym
24
+
25
+ # XXX: add an exception to handle this case!
26
+ unless permission?(method)
27
+ raise "an account type of #{account_type} cannot access this method, " +
28
+ "which requires at least #{account(method)}"
29
+ end
30
+
31
+ val = REXML::XPath.first(doc, table[method][:xpath])
32
+ return nil if val.nil?
33
+ return convert_to_type(method, val.text.to_s) if val.respond_to?(:text)
34
+ return convert_to_type(method, val.to_s)
35
+ end
36
+
37
+ def each
38
+ @table.sort {|a,b| a.to_s <=> b.to_s }.each do |k,v|
39
+ next if @table[k].has_key?(:alias)
40
+ next unless permission?(k)
41
+
42
+ yield k, v
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def standard?
49
+ return account_type.downcase == 'standard'
50
+ end
51
+
52
+ def power?
53
+ return account_type.downcase == 'power'
54
+ end
55
+
56
+ def pro?
57
+ return account_type.downcase == 'pro'
58
+ end
59
+
60
+ def parse(xml)
61
+ @table = {}
62
+
63
+ doc = REXML::Document.new(xml)
64
+ doc.root.elements.each do |element|
65
+ sym = element.name.to_sym
66
+ @table[sym] ||= {}
67
+
68
+ element.attributes.each do |k,v|
69
+ @table[sym][k.to_sym] = v.to_s
70
+ end
71
+
72
+ @table[sym][:xpath] ||= "//#{sym.to_s}"
73
+ @table[sym][:type] ||= "string"
74
+ @table[sym][:account] ||= "standard"
75
+
76
+ _alias(sym)
77
+ end
78
+ end
79
+
80
+ def _alias(method)
81
+ sym = method.to_s.gsub(/([a-z0-9])([A-Z])/) { "#{$1}_#{$2}" }.downcase
82
+ sym += '?' if @table[method][:type] == 'boolean'
83
+ sym = sym.to_sym
84
+
85
+ @table[sym] = {}
86
+ @table[sym][:alias] = method
87
+ end
88
+
89
+ def alias?(method)
90
+ return @table[method].has_key?(:alias)
91
+ end
92
+
93
+ def alias_to_method(method)
94
+ return @table[method][:alias]
95
+ end
96
+
97
+ def convert_to_type(sym, val)
98
+ puts "sym=#{sym.to_s} val=#{val}" if sym.to_s == 'test_id'
99
+ case xtype(sym)
100
+ when 'boolean': return (val.to_i == 0) ? false : true
101
+ when 'float': return val.to_f
102
+ when 'int': return val.to_i
103
+ else return val.to_s
104
+ end
105
+ end
106
+
107
+ def permission?(sym)
108
+ case account(sym)
109
+ when 'standard': return (standard? or power? or pro?)
110
+ when 'power': return (power? or pro?)
111
+ when 'pro': return pro?
112
+ else
113
+ raise "Do not understand an account type of '#{account(sym)}' for method #{sym.to_s}!"
114
+ end
115
+ end
116
+
117
+ def element?(sym)
118
+ return table.has_key?(sym)
119
+ end
120
+
121
+ def xpath(sym)
122
+ return @table[sym][:xpath]
123
+ end
124
+
125
+ def xtype(sym)
126
+ return @table[sym][:type]
127
+ end
128
+
129
+ def account(sym)
130
+ return @table[sym][:account]
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+ # $Hg$
3
+
4
+ class File
5
+ def File.write(filename, data, offset=0)
6
+
7
+ File.open(filename, 'w') do |fh|
8
+ fh.seek(offset)
9
+ fh.write(data)
10
+ end
11
+ end
12
+ end
data/test/album.rb ADDED
@@ -0,0 +1,359 @@
1
+ # -*- Mode: ruby; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2
+ # $Hg$
3
+
4
+ require 'test/unit'
5
+ require 'smugmug'
6
+ require 'smugmug/album/info'
7
+ require 'smugmug/album/stats'
8
+
9
+
10
+ class TC_AlbumInfo < Test::Unit::TestCase
11
+ def setup
12
+ $smugmug.each do |album|
13
+ @album = album
14
+ break
15
+ end
16
+ end
17
+
18
+ def teardown
19
+ end
20
+
21
+ def test_raise_no_method
22
+ assert_raises(NoMethodError) { @album.No_SmugMug_Method }
23
+ end
24
+
25
+ ## ----------
26
+ ## accessors
27
+
28
+ def test_method
29
+ assert_equal('smugmug.albums.getInfo', @album.method)
30
+ end
31
+
32
+ def test_each_album
33
+ arr = %w{One Two Three}
34
+ $smugmug.each do |album|
35
+ assert_equal("Gallery #{arr.shift}", album.title)
36
+ end
37
+ end
38
+
39
+ def test_delegation
40
+ assert_equal(3, $smugmug.size)
41
+ assert($smugmug.any?)
42
+ assert_equal(3, @album.size)
43
+ assert(@album.any?)
44
+ end
45
+
46
+ ## ----------
47
+ ## dynamic accessors (method_missing)
48
+
49
+ ## Boolean ###############################################
50
+
51
+ def test_Public
52
+ val = @album.Public
53
+ assert_equal(true, val)
54
+ assert_kind_of(TrueClass, val)
55
+ assert_equal(val, @album.public?)
56
+ end
57
+
58
+ def test_Printable
59
+ val = @album.Printable
60
+ assert_equal(true, val)
61
+ assert_kind_of(TrueClass, val)
62
+ assert_kind_of(TrueClass, @album.printable?)
63
+ assert_equal(val, @album.printable?)
64
+ end
65
+
66
+ def test_Filenames
67
+ val = @album.Filenames
68
+ assert_equal(false, val)
69
+ assert_kind_of(FalseClass, val)
70
+ assert_kind_of(FalseClass, @album.filenames?)
71
+ assert_equal(val, @album.filenames?)
72
+ end
73
+
74
+ def test_Comments
75
+ val = @album.Comments
76
+ assert_equal(true, val)
77
+ assert_kind_of(TrueClass, val)
78
+ assert_kind_of(TrueClass, @album.comments?)
79
+ assert_equal(val, @album.comments?)
80
+ end
81
+
82
+ def test_External
83
+ val = @album.External
84
+ assert_equal(false, val)
85
+ assert_kind_of(FalseClass, val)
86
+ assert_kind_of(FalseClass, @album.external?)
87
+ assert_equal(val, @album.external?)
88
+ end
89
+
90
+ def test_Originals
91
+ val = @album.Originals
92
+ assert_equal(true, val)
93
+ assert_kind_of(TrueClass, val)
94
+ assert_kind_of(TrueClass, @album.originals?)
95
+ assert_equal(val, @album.originals?)
96
+ end
97
+
98
+ def test_EXIF
99
+ val = @album.EXIF
100
+ assert_equal(true, val)
101
+ assert_kind_of(TrueClass, val)
102
+ assert_kind_of(TrueClass, @album.exif?)
103
+ assert_equal(val, @album.exif?)
104
+ end
105
+
106
+ def test_Share
107
+ val = @album.Share
108
+ assert_equal(true, val)
109
+ assert_kind_of(TrueClass, val)
110
+ assert_kind_of(TrueClass, @album.share?)
111
+ assert_equal(val, @album.share?)
112
+ end
113
+
114
+ def test_SortDirection
115
+ val = @album.SortDirection
116
+ assert_equal(false, val)
117
+ assert_kind_of(FalseClass, val)
118
+ assert_kind_of(FalseClass, @album.sort_direction?)
119
+ assert_equal(val, @album.sort_direction?)
120
+ end
121
+
122
+ def test_FamilyEdit
123
+ val = @album.FamilyEdit
124
+ assert_equal(false, val)
125
+ assert_kind_of(FalseClass, val)
126
+ assert_kind_of(FalseClass, @album.family_edit?)
127
+ assert_equal(val, @album.family_edit?)
128
+ end
129
+
130
+ def test_FriendEdit
131
+ val = @album.FriendEdit
132
+ assert_equal(false, val)
133
+ assert_kind_of(FalseClass, val)
134
+ assert_kind_of(FalseClass, @album.friend_edit?)
135
+ assert_equal(val, @album.friend_edit?)
136
+ end
137
+
138
+ def test_HideOwner
139
+ val = @album.HideOwner
140
+ assert_equal(false, val)
141
+ assert_kind_of(FalseClass, val)
142
+ assert_kind_of(FalseClass, @album.hide_owner?)
143
+ assert_equal(val, @album.hide_owner?)
144
+ end
145
+
146
+ def test_CanRank
147
+ val = @album.CanRank
148
+ assert_equal(true, val)
149
+ assert_kind_of(TrueClass, val)
150
+ assert_kind_of(TrueClass, @album.can_rank?)
151
+ assert_equal(val, @album.can_rank?)
152
+ end
153
+
154
+ def test_Clean
155
+ assert_raises(RuntimeError) { @album.Clean }
156
+ end
157
+
158
+ def test_SmugSearchable
159
+ val = @album.SmugSearchable
160
+ assert_equal(false, val)
161
+ assert_kind_of(FalseClass, val)
162
+ assert_kind_of(FalseClass, @album.smug_searchable?)
163
+ assert_equal(val, @album.smug_searchable?)
164
+ end
165
+
166
+ def test_WorldSearchable
167
+ val = @album.WorldSearchable
168
+ assert_equal(false, val)
169
+ assert_kind_of(FalseClass, val)
170
+ assert_kind_of(FalseClass, @album.world_searchable?)
171
+ assert_equal(val, @album.world_searchable?)
172
+ end
173
+
174
+
175
+ ## Fixnum ################################################
176
+
177
+ def test_AlbumID
178
+ val = @album.AlbumID
179
+ assert_equal(1001, val)
180
+ assert_kind_of(Fixnum, val)
181
+ assert_kind_of(Fixnum, @album.album_id)
182
+ assert_equal(val, @album.album_id)
183
+ end
184
+
185
+ def test_HighlightID
186
+ val = @album.HighlightID
187
+ assert_equal(0, val)
188
+ assert_kind_of(Fixnum, val)
189
+ assert_kind_of(Fixnum, @album.highlight_id)
190
+ assert_equal(val, @album.highlight_id)
191
+ end
192
+
193
+ def test_CommunityID
194
+ val = @album.CommunityID
195
+ assert_equal(0, val)
196
+ assert_kind_of(Fixnum, val)
197
+ assert_kind_of(Fixnum, @album.community_id)
198
+ assert_equal(val, @album.community_id)
199
+ end
200
+
201
+ def test_CategoryID
202
+ val = @album.CategoryID
203
+ assert_equal(47, val)
204
+ assert_kind_of(Fixnum, val)
205
+ assert_kind_of(Fixnum, @album.category_id)
206
+ assert_equal(val, @album.category_id)
207
+ end
208
+
209
+ def test_ImageCount
210
+ val = @album.ImageCount
211
+ assert_equal(3, val)
212
+ assert_kind_of(Fixnum, val)
213
+ assert_kind_of(Fixnum, @album.image_count)
214
+ assert_equal(val, @album.image_count)
215
+ end
216
+
217
+ def test_Position
218
+ val = @album.Position
219
+ assert_equal(1, val)
220
+ assert_kind_of(Fixnum, val)
221
+ assert_kind_of(Fixnum, @album.position)
222
+ assert_equal(val, @album.position)
223
+ end
224
+
225
+
226
+ ## String ################################################
227
+
228
+ # def test_CategoryTitle
229
+ # val = @album.CategoryTitle
230
+ # assert_equal('Children', val)
231
+ # assert_kind_of(String, val)
232
+ # assert_kind_of(String, @album.category_title)
233
+ # assert_equal(val, @album.category_title)
234
+ # end
235
+
236
+ def test_Description
237
+ val = @album.Description
238
+ assert_equal('description', val)
239
+ assert_kind_of(String, val)
240
+ assert_kind_of(String, @album.description)
241
+ assert_equal(val, @album.description)
242
+ end
243
+
244
+ def test_Keywords
245
+ val = @album.Keywords
246
+ assert_equal('keywords', val)
247
+ assert_kind_of(String, val)
248
+ assert_kind_of(String, @album.keywords)
249
+ assert_equal(val, @album.keywords)
250
+ end
251
+
252
+ def test_LastUpdated
253
+ val = @album.LastUpdated
254
+ assert_equal('1970-01-01 00:00:00', val)
255
+ assert_kind_of(String, val)
256
+ assert_kind_of(String, @album.last_updated)
257
+ assert_equal(val, @album.last_updated)
258
+ end
259
+
260
+
261
+ def test_Password
262
+ val = @album.Password
263
+ assert_equal('', val)
264
+ assert_kind_of(String, val)
265
+ assert_kind_of(String, @album.password)
266
+ assert_equal(val, @album.password)
267
+ end
268
+
269
+ def test_PasswordHint
270
+ val = @album.PasswordHint
271
+ assert_equal('', val)
272
+ assert_kind_of(String, val)
273
+ assert_kind_of(String, @album.password_hint)
274
+ assert_equal(val, @album.password_hint)
275
+ end
276
+
277
+ def test_SortMethod
278
+ val = @album.SortMethod
279
+ assert_equal('Position', val)
280
+ assert_kind_of(String, val)
281
+ assert_kind_of(String, @album.sort_method)
282
+ assert_equal(val, @album.sort_method)
283
+ end
284
+
285
+ def test_Title
286
+ val = @album.Title
287
+ assert_equal('Gallery One', val)
288
+ assert_kind_of(String, val)
289
+ assert_kind_of(String, @album.title)
290
+ assert_equal(val, @album.title)
291
+ end
292
+ end
293
+
294
+ class TC_AlbumStats < Test::Unit::TestCase
295
+ def setup
296
+ $smugmug.each do |album|
297
+ @stats = album.stats
298
+ break
299
+ end
300
+
301
+ end
302
+
303
+ def teardown
304
+ end
305
+
306
+ def test_method
307
+ assert('smugmug.albums.getStats', @stats.method)
308
+ end
309
+
310
+ def test_bytes
311
+ assert_kind_of(Fixnum, @stats.bytes)
312
+ assert_equal(234659, @stats.bytes)
313
+ assert_kind_of(Fixnum, @stats.Bytes)
314
+ assert_equal(234659, @stats.Bytes)
315
+ end
316
+
317
+ def test_medium
318
+ assert_kind_of(Fixnum, @stats.medium)
319
+ assert_equal(2, @stats.medium)
320
+ assert_kind_of(Fixnum, @stats.Medium)
321
+ assert_equal(2, @stats.Medium)
322
+ end
323
+
324
+ def test_original
325
+ assert_kind_of(Float, @stats.original)
326
+ assert_equal(0, @stats.original)
327
+ assert_kind_of(Float, @stats.original)
328
+ assert_equal(0, @stats.original)
329
+ end
330
+
331
+ def test_small
332
+ assert_kind_of(Fixnum, @stats.small)
333
+ assert_equal(0, @stats.small)
334
+ assert_kind_of(Fixnum, @stats.Small)
335
+ assert_equal(0, @stats.Small)
336
+ end
337
+
338
+ ## XXX: should I support this?
339
+ # def test_status
340
+ # assert_kind_of(String, @stats.status)
341
+ # assert_equal('Open', @stats.status)
342
+ # assert_kind_of(String, @stats.Status)
343
+ # assert_equal('Open', @stats.Status)
344
+ # end
345
+
346
+ def test_thumb
347
+ assert_kind_of(Fixnum, @stats.thumb)
348
+ assert_equal(2, @stats.thumb)
349
+ assert_kind_of(Fixnum, @stats.Thumb)
350
+ assert_equal(2, @stats.Thumb)
351
+ end
352
+
353
+ def test_tiny
354
+ assert_kind_of(Fixnum, @stats.tiny)
355
+ assert_equal(20, @stats.tiny)
356
+ assert_kind_of(Fixnum, @stats.Tiny)
357
+ assert_equal(20, @stats.Tiny)
358
+ end
359
+ end