Class: SmugMugAPI

Inherits:
Object
  • Object
show all
Defined in:
lib/smugmugapi.rb,
lib/smugmug/core.rb,
lib/smugmug/logon.rb,
lib/smugmug/upload.rb,
lib/smugmug/utility.rb,
lib/smugmug/exception.rb,
lib/smugmug/xmlstruct.rb

Defined Under Namespace

Classes: Error, XMLStruct

Constant Summary collapse

VERSION =
"0.9.5"
USERAGENT =
"SumMug Ruby API Library"

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base = "smugmug", feed = false) ⇒ SmugMugAPI

Returns a new instance of SmugMugAPI.



16
17
18
19
# File 'lib/smugmugapi.rb', line 16

def initialize(base="smugmug", feed=false)
  @base = base
  @feed = feed
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *params) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/smugmugapi.rb', line 21

def method_missing(method, *params)
  cmd = @base + "." + SmugMugAPI.camelize(method, false)
  if SmugMugAPI.groups.include?(method.to_s)
    SmugMugAPI.new(cmd, @feed)
  elsif [:global_feed, :user_feed].include?(method)
    SmugMugAPI.new(cmd, method)
  else
    
    if @feed
      process_feed(method, params)
    else
      process_api(cmd, params)
    end
    
  end  
end

Class Attribute Details

.agentObject



41
42
43
# File 'lib/smugmug/core.rb', line 41

def agent
  @agent || USERAGENT
end

.api_keyObject



27
28
29
# File 'lib/smugmug/core.rb', line 27

def api_key
  @api_key ||= 'RXcw7ywveg9pEj1n6HBJfuDXqsFsx4jw'
end

.api_versionObject



23
24
25
# File 'lib/smugmug/core.rb', line 23

def api_version
  @api_version ||= '1.2.0'
end

.cookiesObject



15
16
17
# File 'lib/smugmug/core.rb', line 15

def cookies
  @cookies ||= Hash.new
end

.default_paramsObject



19
20
21
# File 'lib/smugmug/core.rb', line 19

def default_params
  @default_params ||= { :APIKey => api_key }
end

.nicknameObject

Returns the value of attribute nickname.



13
14
15
# File 'lib/smugmug/core.rb', line 13

def nickname
  @nickname
end

Class Method Details

.api_pathObject



31
32
33
34
35
36
37
38
39
# File 'lib/smugmug/core.rb', line 31

def api_path
  if @url
    @url
  elsif api_version == '1.2.1'
    "api.smugmug.com/services/api/rest/#{api_version}/" 
  else 
    "api.smugmug.com/hack/rest/#{api_version}/"
  end
end

.build_uri(passed_params, scheme = 'http') ⇒ Object



55
56
57
58
59
60
61
62
# File 'lib/smugmug/core.rb', line 55

def build_uri(passed_params, scheme='http')
  params = default_params.merge(passed_params)

  url = scheme + "://" + api_path + 
    "?" +
    params.map { |k,v| URI.encode("#{SmugMugAPI.camelize(k)}=#{v}") }.join("&")
  uri = URI.parse(url)
end

.call(params = {}, scheme = "http") ⇒ Object



142
143
144
145
# File 'lib/smugmug/core.rb', line 142

def call(params = {}, scheme = "http")
  uri = build_uri(params, scheme)
  parse_response(get(uri, {}))
end

.camelize(str, upcase_first_char = true) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
# File 'lib/smugmug/utility.rb', line 2

def self.camelize(str, upcase_first_char = true)
  if upcase_first_char
    str = str.to_s.gsub(/(^|_)(.)/) { $2.upcase }
  else
    str = str.to_s.gsub(/_(.)/) { $1.upcase }
  end
  str = str.gsub(/URL/i, "URL").gsub(/EXIF/i, "EXIF")
  str = str.gsub(/(?!^)ID$/i, "ID")
  str = str.gsub(/EXIF/i, "EXIF")
  str = str.gsub(/MD5Sum/i, "MD5Sum")
  str = str.gsub(/X(.?)Large/i) { "X#{$1}Large" }
  str = "method" if str == "Method" #special case
  str
end

.get(uri, headers = {}) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/smugmug/core.rb', line 64

def get(uri, headers={})
  headers['User-Agent'] ||= agent
  headers['Accept-Encoding'] ||= 'gzip, deflate'

  http = Net::HTTP.new(uri.host, uri.port)
  if uri.scheme == 'https'
    http.use_ssl     = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  end

  http.start do
    http.get(uri.path + '?' + uri.query, headers)
  end
end

.get_feed(feed) ⇒ Object



134
135
136
137
138
139
140
# File 'lib/smugmug/core.rb', line 134

def get_feed(feed)
  uri = URI.parse(feed)
  
  # process to support passworded feeds
  headers= { 'Cookie' => SmugMugAPI.cookies.map { |k,v| "#{k}=#{v}"}.join("; ") }
  parse_response(get(uri, headers), true)
end

.groupsObject



45
46
47
48
49
50
51
52
53
# File 'lib/smugmug/core.rb', line 45

def groups
  # it would be better to have a smugmug reflection method to 
  # get this information but this is a fairly exhaustive list 
  # and it will be easy to add to this as well
  %w(albums albumtemplates categories images login subcategories users 

  communities family friends orders pricing propricing sharegroups 
  styles themes watermarks)
end

.http_call(params = {}) ⇒ Object



130
131
132
# File 'lib/smugmug/core.rb', line 130

def http_call(params={})
  call(params, 'http')
end

.https_call(params = {}) ⇒ Object



126
127
128
# File 'lib/smugmug/core.rb', line 126

def https_call(params={})
  call(params, 'https')
end

.parse_response(response, feed = false) ⇒ Object

Raises:



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/smugmug/core.rb', line 79

def parse_response(response, feed = false)
  puts "HTTP #{response.code}: #{response.message}" if $DEBUG
  if $DEBUG
    response.each_header do |k,v|
      puts "#{k}: #{v}"
      p k if k.to_s.match(/cook/i)
    end
  end

  raise SmugMugAPI::Error.new("HTTP #{response.code}") unless response.code.to_i == 200

  body = case response['content-encoding']
  when 'gzip'
    Zlib::GzipReader.new(StringIO.new(response.body)).read 
  when 'deflate'
    Zlib::Inflate.inflate(response.body)
  when nil
    response.body
  else
    raise "Unknown encoding #{response['content-encoding']}"
  end

  if new_cookies = response.get_fields('Set-Cookie')
    # not quite RFC 2109, but should be good enough for our needs
    new_cookies.join(',').split(/,(?=[^;,]*=)|,$/).each do |cookie|
      if cookie.match(/([^=]+)=([^;]+)/)
        self.cookies[$1] = $2
      end
    end
  end
  
  doc = REXML::Document.new(body)
  if !feed && doc.root.attributes['stat'] != 'ok'
    err = doc.root.elements['/rsp/err']
    code = err.attributes['code']
    msg = err.attributes['msg']
    puts body if $DEBUG
    raise SmugMugAPI::Error.new("#{code}: #{msg}")
  end

  if feed
    doc = doc.root
  end
  
  XMLStruct.new(doc, feed)      
end

Instance Method Details

#login(email, password) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/smugmug/logon.rb', line 3

def (email, password)
  self.nickname = email # it works here, or you can just set it manually
  
  res = SmugMugAPI.https_call(
  :method => 'smugmug.login.withPassword',
  :EmailAddress => email,
  :Password => password
  )

  SmugMugAPI.default_params[:SessionID] = res.session.id

  if block_given?
    begin
      yield res
    ensure
      logout
    end
  else
    res
  end
end

#login_with_hash(user, hash) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/smugmug/logon.rb', line 25

def (user, hash)
  res = SmugMugAPI.https_call(
    :method       => 'smugmug.login.withHash',
    :UserID       => user,
    :PasswordHash => hash
  )

  SmugMugAPI.default_params[:SessionID] = res.session.id

  if block_given?
    begin
      yield res
    ensure
      logout
    end
  else
    res
  end
end

#logoutObject



45
46
47
48
49
# File 'lib/smugmug/logon.rb', line 45

def logout
  result = SmugMugAPI.call(:method => 'smugmug.logout')
  SmugMugAPI.default_params.delete(:SessionID)
  result
end

#nickname=(name) ⇒ Object



12
13
14
# File 'lib/smugmugapi.rb', line 12

def nickname=(name)
  SmugMugAPI.nickname = name
end

#process_api(cmd, params) ⇒ Object



38
39
40
41
# File 'lib/smugmugapi.rb', line 38

def process_api(cmd, params)
  param_hash = params.first || {}
  SmugMugAPI.call( {:method => cmd}.merge(param_hash))
end

#upload(fname, params = {}) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/smugmug/upload.rb', line 7

def upload(fname, params={})
  base_name = File.basename(fname)
  uri = URI.parse("http://upload.smugmug.com/#{base_name}")
  image = IO::read(fname)

  Net::HTTP.start(uri.host, uri.port) do |http|
    headers = {
      'Content-Type'  => MIME::Types.type_for(base_name).to_s,
      'Content-Lenth' => image.size.to_s,
      'Content-MD5'   => Digest::MD5.hexdigest(image),
      'X-Smug-SessionID' => SmugMugAPI.default_params[:SessionID],
      'X-Smug-Version'   => SmugMugAPI.api_version,
      'X-Smug-ResponseType' => 'REST',
      'X-Smug-FileName' => base_name
    }

    adjusted_headers = Hash[*params.map { |k,v| [ "X-Smug-" + SmugMugAPI.camelize(k), v.to_s ] }.flatten ]
    headers = headers.merge(adjusted_headers)

    resp = http.send_request('PUT', uri.request_uri, image, headers)
    SmugMugAPI.parse_response(resp)
  end
end