smugmug 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY +2 -0
- data/LICENSE +19 -0
- data/MANIFEST +48 -0
- data/README +30 -0
- data/Rakefile +100 -0
- data/bin/smcli +225 -0
- data/bin/smugmug2sql +158 -0
- data/doc/API +310 -0
- data/doc/TODO +32 -0
- data/lib/net/httpz.rb +31 -0
- data/lib/smugmug.rb +179 -0
- data/lib/smugmug/album/info.rb +131 -0
- data/lib/smugmug/album/stats.rb +31 -0
- data/lib/smugmug/albums.rb +39 -0
- data/lib/smugmug/base.rb +104 -0
- data/lib/smugmug/cache.rb +33 -0
- data/lib/smugmug/config.rb +48 -0
- data/lib/smugmug/image/exif.rb +72 -0
- data/lib/smugmug/image/info.rb +88 -0
- data/lib/smugmug/image/stats.rb +32 -0
- data/lib/smugmug/images.rb +52 -0
- data/lib/smugmug/table.rb +133 -0
- data/lib/smugmug/util.rb +12 -0
- data/test/album.rb +359 -0
- data/test/config.rb +39 -0
- data/test/httpz.rb +120 -0
- data/test/image.rb +540 -0
- data/test/login.rb +24 -0
- data/test/runner.rb +83 -0
- data/test/servlet.rb +257 -0
- data/test/table.rb +113 -0
- data/xml/canned +212 -0
- data/xml/fail/empty.set.xml +4 -0
- data/xml/fail/invalid.apikey.xml +4 -0
- data/xml/fail/invalid.login.xml +4 -0
- data/xml/fail/invalid.method.xml +4 -0
- data/xml/fail/invalid.user.xml +4 -0
- data/xml/fail/system.error.xml +4 -0
- data/xml/standard/albums.get.xml +24 -0
- data/xml/standard/albums.getInfo.xml +38 -0
- data/xml/standard/albums.getStats.xml +43 -0
- data/xml/standard/categories.get.xml +213 -0
- data/xml/standard/images.get.xml +9 -0
- data/xml/standard/images.getEXIF.xml +34 -0
- data/xml/standard/images.getInfo.xml +29 -0
- data/xml/standard/images.getStats.xml +15 -0
- data/xml/standard/login.withHash.xml +7 -0
- data/xml/standard/login.withPassword.xml +10 -0
- metadata +103 -0
data/doc/API
ADDED
@@ -0,0 +1,310 @@
|
|
1
|
+
# -*- text -*-
|
2
|
+
# $Hg$
|
3
|
+
|
4
|
+
= Introduction
|
5
|
+
|
6
|
+
The majority of the SmugMug's function are auto-generated. This means rdoc
|
7
|
+
documentation is useless because none of the functions are visible. It is
|
8
|
+
probably still preferable to document the function within class files, but for
|
9
|
+
now I am going to document them here. This way everything is in one location.
|
10
|
+
|
11
|
+
== Conventions
|
12
|
+
|
13
|
+
* SmugMug refers to the company, or web site.
|
14
|
+
* smugmug refers to this library.
|
15
|
+
|
16
|
+
=== Method
|
17
|
+
==== Naming
|
18
|
+
|
19
|
+
SmugMug and ruby do not share the same naming convetions. For example, a
|
20
|
+
function to return an album's ID is called AlbumID by Smugmug, but ruby would
|
21
|
+
name it album_id. To keep both factions happy smugmug provides both naming
|
22
|
+
conventions. When data are boolean types the ruby method names have a trailing
|
23
|
+
question mark. This convention is used throughout ruby.
|
24
|
+
|
25
|
+
==== Return Types
|
26
|
+
|
27
|
+
There are four data types that are returned by SmugMug. They are boolean, int,
|
28
|
+
string, and float. The smugmug library ensures that all data returned is cast
|
29
|
+
to the appropriate type. For example, the data returned from smugmug.com for
|
30
|
+
AlbumID is a string, but smugmug returns a Fixnum (integer). This convention
|
31
|
+
is broken with dealing with EXIF data. Camera manufacturers may encode unit
|
32
|
+
information in their data, thereby preventing the casting of data. For
|
33
|
+
example, the method focal length for my Canon SD500 return 7mm.
|
34
|
+
|
35
|
+
The type information for the methods can be found on SmugMug's wiki at
|
36
|
+
http://smugmug.jot.com/WikiHome/API/Versions/1.1.1.
|
37
|
+
|
38
|
+
= SmugMug::SmugMug
|
39
|
+
|
40
|
+
The first time you use the library you need to login in SmugMug. This requires
|
41
|
+
a username and password, all subsequent login attempts do not require a
|
42
|
+
username, and password. The first time you login SmugMug returns your UserID,
|
43
|
+
and a password hash. These data are stored in ~/.smugmugrc by default, and
|
44
|
+
used by smugmug for subsequent login attempts.
|
45
|
+
|
46
|
+
== Login
|
47
|
+
|
48
|
+
=== Security
|
49
|
+
|
50
|
+
Logins are carried out over https, but all other operations are carried out
|
51
|
+
over http. This is currently not user configurable, but may be in future
|
52
|
+
versions.
|
53
|
+
|
54
|
+
=== First Login
|
55
|
+
|
56
|
+
The first login attempt requires and username, and password.
|
57
|
+
|
58
|
+
sm = SmugMug::SmugMug.new('user@example.net', 'secret')
|
59
|
+
|
60
|
+
=== Subsequent Login
|
61
|
+
|
62
|
+
Subsequent logins do not require a username/password if ~/.smugmugrc has good
|
63
|
+
data.
|
64
|
+
|
65
|
+
sm = SmugMug::SmugMug.new
|
66
|
+
|
67
|
+
=== Logging
|
68
|
+
|
69
|
+
All session data can be logged by for debugging purposes. By default, all
|
70
|
+
logging information is sent to /dev/null, or a StringIO if /dev/null does not
|
71
|
+
exist. (Note: Need a better solution that StringIO)
|
72
|
+
|
73
|
+
To create a logging instance do the following before logging into SmugMug.
|
74
|
+
|
75
|
+
$log = Logger.new('smugmug.log')
|
76
|
+
sm = SmugMug::SmugMug.new
|
77
|
+
|
78
|
+
NOTE: If logging is enabled personal information is captured, most notable the
|
79
|
+
username and password, or UserID and password hash depending how login was
|
80
|
+
handled.
|
81
|
+
|
82
|
+
= Albums
|
83
|
+
|
84
|
+
Use the each method to iterate over your albums. You can also use any?, and
|
85
|
+
size to determine if there are albums, and the number of albums. Because the
|
86
|
+
each method is provided, you can use any enumerable method.
|
87
|
+
|
88
|
+
sm = SmugMug::SmugMug.new
|
89
|
+
|
90
|
+
if sm.any?
|
91
|
+
puts "You have albums."
|
92
|
+
else
|
93
|
+
puts "You have no albums."
|
94
|
+
end
|
95
|
+
|
96
|
+
puts "There are #{sm.size} albums"
|
97
|
+
|
98
|
+
sm.each do |album|
|
99
|
+
puts albums.title
|
100
|
+
end
|
101
|
+
|
102
|
+
== Methods
|
103
|
+
|
104
|
+
Each albums has the following methods. The alias for each method is also shown
|
105
|
+
for completeness.
|
106
|
+
|
107
|
+
(Some of these functions are self explanatory, but others are not. I need to
|
108
|
+
clarify these.)
|
109
|
+
|
110
|
+
=== Standard
|
111
|
+
|
112
|
+
The following methods are available for Standard, Power, and Pro, and act on
|
113
|
+
the current album.
|
114
|
+
|
115
|
+
* AlbumID (album_id): returns the Album ID.
|
116
|
+
* HighlightID (hightlight_id): returns the hightlight ID.
|
117
|
+
* CategoryID (category_id): returns the category ID.
|
118
|
+
* SubCategoryID (sub_category_id): returns the sub category ID.
|
119
|
+
* Position (position): returns the position.
|
120
|
+
* SortMethod (sort_method): return sort method: Position, Caption, Filenames, Date Uploaded, Date Modified, Date Taken.
|
121
|
+
* SortDirection (sort_direction?): sort ascending (true), or descending (false).
|
122
|
+
* ImageCount (image_count): returns the number of images.
|
123
|
+
* Keywords (keywords): returns the keywords.
|
124
|
+
* Description (description): returns the description.
|
125
|
+
* LastUpdate (last_updated): returns the date and time when the album was last updated.
|
126
|
+
* CommunityID (community_id): returns the community id.
|
127
|
+
* Public (public?): show this gallery on your homepage?
|
128
|
+
* Password (password): returns the password.
|
129
|
+
* PasswordHint (password_hint): return password hint.
|
130
|
+
* CanRank (can_rank?): allow visitors to vote your photos most popular?
|
131
|
+
* Printable (printable?): can people buy prints of these photos?
|
132
|
+
* Filenames (filenames?): for photos without captions, show the filename instead of nothing?
|
133
|
+
* Comments (comments?): can visitors read and post comments?
|
134
|
+
* External (external?): allow external links?
|
135
|
+
* Originals (originals?): can people view your original full-size photos?
|
136
|
+
* FamilyEdit (family_edit?): can your family edit captions and keywords?
|
137
|
+
* FriendEdit (friend_edit?): can your friends edit captions and keywords?
|
138
|
+
* HideOwnder (hide_owner?): hide your name, navigation, and look and feel?
|
139
|
+
* EXIF (exif?): show extra camera info?
|
140
|
+
* Share (share?): do you want the 'share photo: links formus, blogs' link?
|
141
|
+
* Header (header?): (???)
|
142
|
+
* SmugSearchable (smug_searchable?): do you want your photos in SmugMug's search?
|
143
|
+
* WorldSearchable (world_searchable?): can Google find your smug mug?
|
144
|
+
|
145
|
+
puts album.image_count
|
146
|
+
puts album.SortMethod
|
147
|
+
|
148
|
+
=== Power
|
149
|
+
|
150
|
+
The following methods are available for Power, and Pro. (Need help with these.)
|
151
|
+
|
152
|
+
* Header (header?): (???)
|
153
|
+
* TemplateID (template_id): (???)
|
154
|
+
|
155
|
+
puts album.header
|
156
|
+
|
157
|
+
=== Pro
|
158
|
+
|
159
|
+
The following methods are available for Pro. (Need help with these.)
|
160
|
+
|
161
|
+
* Larges (larges?): (???)
|
162
|
+
* DefaultColor (default_color?): (???)
|
163
|
+
* Clean (clean?): (???)
|
164
|
+
* Protected (protected?): (???)
|
165
|
+
* Watermark (watermark?): (???)
|
166
|
+
|
167
|
+
puts album.Watermark
|
168
|
+
puts album.watermark?
|
169
|
+
|
170
|
+
== Stats
|
171
|
+
|
172
|
+
Every album has statistics associted with it for the current month. SmugMug
|
173
|
+
supports the retreival of statistics from up to two months previous, but
|
174
|
+
smugmug only supports the current month.
|
175
|
+
|
176
|
+
stats = album.stats
|
177
|
+
puts stats.bytes
|
178
|
+
puts album.stats.small
|
179
|
+
|
180
|
+
=== Methods
|
181
|
+
|
182
|
+
The following methods are supported for the an Album's statistics. (I need to
|
183
|
+
verify if these definitions are correct.)
|
184
|
+
|
185
|
+
* Bytes (bytes): return bytes downloaded for this album.
|
186
|
+
* Tiny (tiny): retuns the number of tiny images fetched.
|
187
|
+
* Thumb (thumb): retuns the number of thumb images fetched.
|
188
|
+
* Small (small): retuns the number of small images fetched.
|
189
|
+
* Medium (medium): retuns the number of medium images fetched.
|
190
|
+
* Large (large): retuns the number of large images fetched.
|
191
|
+
* Original (original): retuns the number of original images fetched.
|
192
|
+
|
193
|
+
Note: album statistics also have a status, but that information is not returned
|
194
|
+
by smugmug. (I am not really sure what status means.)
|
195
|
+
|
196
|
+
= Images
|
197
|
+
|
198
|
+
Just like the Album class the methods any?, size, and each are available.
|
199
|
+
These methods are attached to an album instance. Likewise, album instances
|
200
|
+
include Enumerable and can be used with any of the enumerable methods, such as
|
201
|
+
inject.
|
202
|
+
|
203
|
+
if album.any?
|
204
|
+
puts "This album contains images."
|
205
|
+
else
|
206
|
+
puts "This album contains no images."
|
207
|
+
end
|
208
|
+
|
209
|
+
puts "This album contains #{album.size} images."
|
210
|
+
puts "This album contains #{album.image_count} images."
|
211
|
+
|
212
|
+
album.each do |image|
|
213
|
+
puts "#{image.file_name} has MD5 Sum #{image.md5_sum}"
|
214
|
+
end
|
215
|
+
|
216
|
+
=== Methods
|
217
|
+
|
218
|
+
Just like with Album there are two functions to access the same values. One
|
219
|
+
based on SmugMug's name, and based on ruby's convention.
|
220
|
+
|
221
|
+
* AlbumID (album_id): returns this image's album ID.
|
222
|
+
* ImageID (image_id): returns this image's ID.
|
223
|
+
* Position (position): returns this image's position.
|
224
|
+
* Serial (serial): returns this image's serial.
|
225
|
+
* Size (size): returns this image's size in bytes.
|
226
|
+
* Width (width): return this image's width in pixels.
|
227
|
+
* Height (height): return this image's height in pixels.
|
228
|
+
* LastUpdated (last_updated): returns the date and time the last time this image was updated.
|
229
|
+
* Caption (caption): returns the caption of this image.
|
230
|
+
* FileName (file_name): returns this image's file name.
|
231
|
+
* MD5Sum (md5_sum): returns this image's MD5 sum.
|
232
|
+
* Watermark (watermark): returns this image's watermark.
|
233
|
+
* Format (format): returns this image's format, such as JPG.
|
234
|
+
* Keywords (keywords): returns this image's keywords.
|
235
|
+
* Date (date): returns the date and time this image was taken (???)
|
236
|
+
* OriginalURL (original_url): return a URL pointing this image's original.
|
237
|
+
* LargeURL (Large_url): return a URL pointing a large version of this image's.
|
238
|
+
* MediumURL (medium_url): return a URL pointing a medium version of this image's.
|
239
|
+
* SmallURL (small_url): return a URL pointing a small version of this image's.
|
240
|
+
* ThumbURL (thumb_url): return a URL pointing a thumb version of this image's.
|
241
|
+
* TinyURL (tiny_url): return a URL pointing a tiny version of this image's.
|
242
|
+
* AlbumURL (album_url): return a URL pointing to this image's album.
|
243
|
+
|
244
|
+
puts "This image is #{image.size} bytes."
|
245
|
+
|
246
|
+
== EXIF
|
247
|
+
|
248
|
+
The method exif is attached to every image instance, and returns the EXIF class
|
249
|
+
attached to this image.
|
250
|
+
|
251
|
+
exif = image.exif
|
252
|
+
puts "The camera that took this image was #{exif.model}."
|
253
|
+
|
254
|
+
=== Methods
|
255
|
+
|
256
|
+
EXIF methods are unique in that SmugMug does not guarantee that all methods are
|
257
|
+
available. This make sense because not all camera manfucaturers report the
|
258
|
+
same data. If you attempt to access data that does not exist the value nil is
|
259
|
+
returned.
|
260
|
+
|
261
|
+
if image.exif.iso.nil?
|
262
|
+
puts "Sorry, no EXIF data is available for 'ISO'"
|
263
|
+
end
|
264
|
+
|
265
|
+
All functions are in the context of this image.
|
266
|
+
|
267
|
+
* ImageId (image_id): returns the image ID.
|
268
|
+
* DateTime (date_time): returns the date and time.
|
269
|
+
* DateTimeOriginal (date_time_original): (???)
|
270
|
+
* DateTimeDigitized (date_time_digitized): returns the date and time when this image was digitized.
|
271
|
+
* Make (make): returns the make of the camera that took.
|
272
|
+
* Model (model): returns the model of the camera that took.
|
273
|
+
* ExposureTime (exposure_time): returns the exposure time.
|
274
|
+
* Aperture (aperture): returns the aperture.
|
275
|
+
* ISO (iso): returns the ISO.
|
276
|
+
* FocalLength (focal_lenght): returns the focal length.
|
277
|
+
* FocalLength35mmFilm (focal_length35mm_film): (???)
|
278
|
+
* CCDWidth (ccdwidth): return the CCD width
|
279
|
+
* CompressedBitsPerPixel (compressed_bits_per_pixel): returns the compressed bits per pixel.
|
280
|
+
* Flash (flash): (???)
|
281
|
+
* Metering (metering): (???)
|
282
|
+
* ExposureProgram (exposure_program): (???)
|
283
|
+
* SubjectDistance (subject_distance): return the distance to the subject.
|
284
|
+
* SubjectDistanceRange (subject_distance_range): return an range of the distance to the subject.
|
285
|
+
* SensingMethod (sensing_method): returns the sensing method.
|
286
|
+
* ColorSpace (color_space): returns the color space.
|
287
|
+
* Brightness (brightness): returns the brigtness.
|
288
|
+
|
289
|
+
exif = image.exif
|
290
|
+
puts "This image was taked on #{exif.date_time}."
|
291
|
+
|
292
|
+
== Stats
|
293
|
+
|
294
|
+
Image stats are identical to album stats except that they apply to a specific
|
295
|
+
image.
|
296
|
+
|
297
|
+
=== Methods
|
298
|
+
|
299
|
+
The following methods are supported for the an image's statistics. (I need to
|
300
|
+
verify if these definitions are correct.)
|
301
|
+
|
302
|
+
* Bytes (bytes): return bytes downloaded.
|
303
|
+
* Tiny (tiny): downloads of the tiny representation.
|
304
|
+
* Thumb (thumb): downloads of the thumb representation.
|
305
|
+
* Small (small): downloads of the small representation.
|
306
|
+
* Medium (medium): downloads of the medium representation.
|
307
|
+
* Large (large): downloads of the large representation.
|
308
|
+
* Original (original): downloads of the original.
|
309
|
+
|
310
|
+
puts "The original of this image was downloaded #{image.stats.original} times for #{Time.now.month}/#{Time.now.year}"
|
data/doc/TODO
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- text -*-
|
2
|
+
# $Hg$
|
3
|
+
|
4
|
+
== Documentation
|
5
|
+
|
6
|
+
* You'll never have enough!
|
7
|
+
|
8
|
+
== Library
|
9
|
+
|
10
|
+
* More testing
|
11
|
+
a. extend the SmugMug servlet
|
12
|
+
a. make login more realistic
|
13
|
+
a. automatically generate fake test data
|
14
|
+
a. use SQLite has the backend for the servlet
|
15
|
+
* Add write support
|
16
|
+
a. adding new albums
|
17
|
+
a. uploading new images
|
18
|
+
a. modifying album, and image attributes
|
19
|
+
* Add support for categories, and sub categories
|
20
|
+
* Add support for password protected galleries
|
21
|
+
* Add support for a date type to MethodTable.
|
22
|
+
* Add support for explicitly specifying an alias for a MethodTable.
|
23
|
+
* Add support for scrubbing the data generated by canned.
|
24
|
+
* Factor StatsBase out into base/stats.rb
|
25
|
+
|
26
|
+
== Scripts
|
27
|
+
|
28
|
+
* Add a script to query *all* of the SmugMug data and stick it in a
|
29
|
+
SQLite database.
|
30
|
+
* Add a script to verify the integrity of one's photos.
|
31
|
+
* Add a poor man's rsync script to upload missing photos to SmugMug.
|
32
|
+
* Add a script to move photos between SmugMug and Picasa.
|
data/lib/net/httpz.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# $Hg$
|
3
|
+
|
4
|
+
require 'net/https'
|
5
|
+
require 'stringio'
|
6
|
+
require 'zlib'
|
7
|
+
|
8
|
+
module Net
|
9
|
+
class HTTP
|
10
|
+
alias original_get get
|
11
|
+
def get(path, initheader = nil, dest = nil, &block)
|
12
|
+
headers = { 'Accept-Encoding' => 'gzip' }
|
13
|
+
headers.merge!(initheader) unless initheader.nil?
|
14
|
+
|
15
|
+
return original_get(path, headers, dest, &block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class HTTPResponse
|
20
|
+
def gzip?
|
21
|
+
return false unless @header.has_key?('content-encoding')
|
22
|
+
return @header['content-encoding'].to_s == 'gzip'
|
23
|
+
end
|
24
|
+
|
25
|
+
alias original_body body
|
26
|
+
def body
|
27
|
+
data = original_body
|
28
|
+
return (gzip?) ? Zlib::GzipReader.new(StringIO.new(data)).read : data
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/smugmug.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# $Hg: smugmug.rb,v c21b2f4bec8c 2007/08/11 08:00:21 boumenot $
|
3
|
+
|
4
|
+
require 'forwardable'
|
5
|
+
require 'logger'
|
6
|
+
require 'net/httpz'
|
7
|
+
require 'rexml/document'
|
8
|
+
require 'stringio'
|
9
|
+
require 'time'
|
10
|
+
require 'uri'
|
11
|
+
require 'zlib'
|
12
|
+
|
13
|
+
require 'smugmug/albums'
|
14
|
+
require 'smugmug/cache'
|
15
|
+
require 'smugmug/config'
|
16
|
+
require 'smugmug/util'
|
17
|
+
|
18
|
+
module SmugMug
|
19
|
+
|
20
|
+
API_VERSION='1.1.1'
|
21
|
+
API_KEY='dydlCYDzmykqoLkVGgECEIP5WEuwWFzc'
|
22
|
+
URL="https://api.smugmug.com/hack/rest/#{API_VERSION}/"
|
23
|
+
|
24
|
+
VERSION = '0.0.1'
|
25
|
+
USERAGENT = "SmugMug Ruby Library - #{VERSION} " + '($HgRev: c21b2f4bec8ce187d5bcb5cd75a6076c40c0cf5f $)'
|
26
|
+
|
27
|
+
class SmugMugError < RuntimeError
|
28
|
+
end
|
29
|
+
|
30
|
+
class NotModified < SmugMugError
|
31
|
+
end
|
32
|
+
|
33
|
+
class SmugMug
|
34
|
+
include Enumerable
|
35
|
+
|
36
|
+
extend Forwardable
|
37
|
+
def_delegator :@config, :AccountType, :account_type
|
38
|
+
def_delegator :albums, :each, :each
|
39
|
+
def_delegator :albums, :any?, :any?
|
40
|
+
def_delegator :albums, :size, :size
|
41
|
+
|
42
|
+
attr_reader :base, :session_id
|
43
|
+
|
44
|
+
def initialize(*args)
|
45
|
+
if $log.nil?
|
46
|
+
$log = if File.exists?('/dev/null')
|
47
|
+
Logger.new('/dev/null')
|
48
|
+
else
|
49
|
+
Logger.new(StringIO.new)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@base = ENV.has_key?('SMUGMUG_URL') ? ENV['SMUGMUG_URL'] : URL
|
54
|
+
@config = Config.new()
|
55
|
+
login(*args)
|
56
|
+
end
|
57
|
+
|
58
|
+
def fetch(params={})
|
59
|
+
query = build_url(params)
|
60
|
+
data = get(query)
|
61
|
+
doc = REXML::Document.new(data)
|
62
|
+
|
63
|
+
if doc.root.attributes['stat'] == 'fail'
|
64
|
+
raise SmugMugError,
|
65
|
+
"#{doc.root.elements['err'].attributes['code']}" +
|
66
|
+
"- #{doc.root.elements['err'].attributes['msg']}"
|
67
|
+
end
|
68
|
+
|
69
|
+
return doc
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def albums
|
75
|
+
@albums = Albums.new(self) if @albums.nil?
|
76
|
+
return @albums
|
77
|
+
end
|
78
|
+
|
79
|
+
def build_url(params)
|
80
|
+
query = base + '?'
|
81
|
+
|
82
|
+
$log.debug('SmugMug#build_url') { "Params:" }
|
83
|
+
params.each do |k,v|
|
84
|
+
$log.debug('SmugMug#build_url') { " #{k}=#{v}" }
|
85
|
+
query += "#{k}=" + URI.encode(v.to_s) + '&'
|
86
|
+
end
|
87
|
+
|
88
|
+
return query
|
89
|
+
end
|
90
|
+
|
91
|
+
public
|
92
|
+
|
93
|
+
def get(url, headers={})
|
94
|
+
# XXX: this is a hack, think of a better solution
|
95
|
+
url.sub!(/^https:/, 'http:') unless url =~ /method=smugmug\.login/
|
96
|
+
$log.info('SmugMug#get') { url }
|
97
|
+
|
98
|
+
headers['User-Agent'] = USERAGENT
|
99
|
+
uri = URI.parse(url)
|
100
|
+
|
101
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
102
|
+
|
103
|
+
if uri.scheme == 'https'
|
104
|
+
http.use_ssl = true
|
105
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
106
|
+
end
|
107
|
+
|
108
|
+
http.start {
|
109
|
+
response = http.get(uri.path + '?' + uri.query, headers)
|
110
|
+
|
111
|
+
$log.info('SmugMug#get') { "HTTP #{response.code} (#{response.message})" }
|
112
|
+
$log.debug('SmugMug#get') { "Headers (response):" }
|
113
|
+
response.each_header { |k,v| $log.debug('SmugMug#get') { " #{k}: #{v}" } }
|
114
|
+
|
115
|
+
case response.code.to_i
|
116
|
+
when 200
|
117
|
+
body = response.body
|
118
|
+
$log.debug('SmugMug#get') { body }
|
119
|
+
return body
|
120
|
+
when 304
|
121
|
+
$log.debug('SmugMug#get') { 'If-Modified-Since is false' }
|
122
|
+
raise NotModified, "#{url} has not changed"
|
123
|
+
else
|
124
|
+
raise SmugMugError, "GET of #{url} failed"
|
125
|
+
end
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
def post(url, body, headers={})
|
130
|
+
end
|
131
|
+
|
132
|
+
def login(*args)
|
133
|
+
params = {}
|
134
|
+
if @config.PasswordHash.nil?
|
135
|
+
raise SmugMugError, "Login by password requires a email address, and password" if args.size != 2
|
136
|
+
|
137
|
+
params[:method] = 'smugmug.login.withPassword'
|
138
|
+
params[:EmailAddress] = args.shift
|
139
|
+
params[:Password] = args.shift
|
140
|
+
|
141
|
+
@config.EmailAddress = params[:EmailAddress]
|
142
|
+
else
|
143
|
+
params[:method] = 'smugmug.login.withHash'
|
144
|
+
%w{UserID PasswordHash}.each do |var|
|
145
|
+
params[var.to_sym] = @config.send(var.to_sym)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
params[:APIKey] = API_KEY
|
150
|
+
@login_doc = fetch(params).root.elements['Login']
|
151
|
+
|
152
|
+
@session_id = @login_doc.elements['SessionID'].text
|
153
|
+
|
154
|
+
Config::VARIABLES.each do |var|
|
155
|
+
@config.send("#{var}=".to_sym, @login_doc.elements[var].text) unless @login_doc.elements[var].nil?
|
156
|
+
end
|
157
|
+
@config.save
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
def main
|
164
|
+
unless ARGV.length == 2
|
165
|
+
puts "#{$0} Usage: <email> <password>"
|
166
|
+
Kernel.exit(1)
|
167
|
+
end
|
168
|
+
|
169
|
+
File.unlink('smugmug.log') if File.exists?('smugmug.log')
|
170
|
+
$log = Logger.new('smugmug.log')
|
171
|
+
sm = SmugMug::SmugMug.new(ARGV.shift, ARGV.shift)
|
172
|
+
sm.each do |album|
|
173
|
+
puts "#{album.id} -> #{album.title} (#{album.ImageCount})"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
if __FILE__ == $0
|
178
|
+
main()
|
179
|
+
end
|