smugmugr 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Rob Sterner
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,60 @@
1
+ h1. smugmugr
2
+
3
+ A (he's the DJ, I'm the) wrapper for v1.2.2 of the SmugMug "JSON API":http://wiki.smugmug.net/display/API/API+1.2.2.
4
+
5
+ Originally inspired by Scott White's simple_smugmug Rails plugin:
6
+ http://github.com/scottwhite/simple_smugmug/tree/master
7
+
8
+ Current version: 0.2.0
9
+
10
+ h2. Roadmap
11
+
12
+ h3. v1.0
13
+
14
+ * read-only (with and without login (password-based and OAuth)) bits
15
+
16
+ h3. v2.0
17
+
18
+ * read-write
19
+
20
+ h2. Usage
21
+
22
+ The current highly opinionated way to use this gem is:
23
+
24
+ user = Smugmugr::User.new(api_key, :email => 'foo@example.com', :password => 'secret')
25
+ albums = user.albums
26
+ images = albums.first.images
27
+
28
+ As of v1.2.2 of the API, Smugmug allows the passing of an Extras parameter to allow customization of a call to your liking:
29
+
30
+ "http://www.dgrin.com/showthread.php?t=90636":http://www.dgrin.com/showthread.php?t=90636
31
+
32
+ Taking advantage of this is a cinch with smugmugr:
33
+
34
+ albums = user.albums(:SmugSearchable,:LastUpdated,:ImageCount)
35
+
36
+ will yield:
37
+
38
+ albums.first.extras => {:ImageCount=>29, :LastUpdated=>"2009-05-16 15:23:11", :SmugSearchable=>true}
39
+
40
+ Also:
41
+
42
+ albums.first.last_updated => "2009-05-16 15:23:11"
43
+ albums.first.LastUpdated => "2009-05-16 15:23:11"
44
+
45
+ albums.first.image_count => 29
46
+ albums.first.ImageCount => 29
47
+
48
+ h3. TODO
49
+ * TESTS: added rspec requires and shell spec classes.
50
+ * TBD
51
+
52
+ h3. Author(s)
53
+
54
+ Written by "Rob Sterner":http://github.com/fermion = "robsterner.com":http://robsterner.com
55
+
56
+ h3. Copyright
57
+
58
+ Released under the MIT license.
59
+
60
+ Copyright (c) 2009 Rob Sterner. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'spec/rake/spectask'
4
+ require 'rake/rdoctask'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = "smugmugr"
10
+ gem.summary = %Q{smugmugr, the smugmug API wrapper}
11
+ gem.email = "rob.sterner@gmail.com"
12
+ gem.homepage = "http://github.com/fermion/smugmugr"
13
+ gem.authors = ["Rob Sterner"]
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ desc 'Default: run unit specs.'
22
+ task :default => :spec
23
+
24
+ desc 'Test the smugmugr gem.'
25
+ Spec::Rake::SpecTask.new('spec') do |t|
26
+ t.spec_files = FileList['spec/**/*_spec.rb']
27
+ t.spec_opts = ["-c"]
28
+ end
29
+
30
+ Rake::RDocTask.new do |rdoc|
31
+ if File.exist?('VERSION.yml')
32
+ config = YAML.load(File.read('VERSION.yml'))
33
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
34
+ else
35
+ version = ""
36
+ end
37
+
38
+ rdoc.rdoc_dir = 'rdoc'
39
+ rdoc.title = "smugmugr #{version}"
40
+ rdoc.rdoc_files.include('README*')
41
+ rdoc.rdoc_files.include('lib/**/*.rb')
42
+ end
43
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,134 @@
1
+ module Smugmugr
2
+ class Album < Smugmugr::Base
3
+ attr_accessor :user, :id, :title, :key, :pub_key, :session_id
4
+
5
+ #############################################
6
+ # all attributes available on an Album
7
+ #############################################
8
+ class_inheritable_reader :attributes
9
+ write_inheritable_attribute :attributes, [:id,:Key,:Backprinting,:CanRank,:Category,:Clean,:ColorCorrection,:Comments,:Community,
10
+ :Description,:EXIF,:External,:FamilyEdit,:Filenames,:FriendEdit,:Geography,:Header,:HideOwner,:Highlight,:ImageCount,:Larges,:LastUpdated,
11
+ :Originals,:Password,:PasswordHint,:Position,:Printable,:ProofDays,:Protected,:Public,:Share,:SmugSearchable,:SortDirection,
12
+ :SortMethod,:SquareThumbs,:SubCategory,:Template,:Theme,:Title,:UnsharpAmount,:UnsharpRadius,:UnsharpSigma,:UnsharpThreshold,
13
+ :Watermark,:Watermarking,:WorldSearchable,:X2Larges,:X3Larges,:XLarges]
14
+
15
+ class_inheritable_reader :complex_attributes
16
+ write_inheritable_attribute :complex_attributes, [:Album, :Category, :Community, :Highlight, :SubCategory, :Template, :Theme, :Watermark]
17
+
18
+ def initialize(user)
19
+ @extras = {}
20
+ @user = user
21
+ end
22
+
23
+ #############################################
24
+ # class
25
+ #############################################
26
+ class << self
27
+ def get(user, args={})
28
+ args[:heavy] ||= false
29
+ args[:extras] ||= []
30
+ params = {
31
+ :method => smug_method + '.get'
32
+ }
33
+ params.merge!(:Heavy => args[:heavy].to_s) if args[:heavy]
34
+ params.merge!(:Extras => args[:extras].collect(&:to_s).join(','))
35
+
36
+ json = user.call(params)
37
+ return albums_from_json(json, user, args[:extras])
38
+ end
39
+
40
+ def albums_from_json(json, user, extra_params=[])
41
+ json["Albums"].map do |album|
42
+ album_from_json(Smugmugr::Album.new(user), album, user, extra_params)
43
+ end
44
+ end
45
+
46
+ def album_from_json(album, json, user, extras=[])
47
+ album.id = json['id']
48
+ album.title = json['Title']
49
+ album.key = json['Key']
50
+ album.extras = {}
51
+ self.attributes.each{ |key|
52
+ next unless json.has_key?(key.to_s)
53
+
54
+ album.extras[key] = if self.complex_attributes.include?(key)
55
+ Object.class_eval("Smugmugr::Album::#{key}").new(json[key.to_s])
56
+ else
57
+ json[key.to_s]
58
+ end
59
+ }
60
+ album
61
+ end
62
+
63
+ def smug_method
64
+ "smugmug.albums"
65
+ end
66
+ end
67
+
68
+ #############################################
69
+ # instance
70
+ #############################################
71
+ def images(*extras)
72
+ extras = [extras] unless extras.is_a?(Array)
73
+ Smugmugr::Image.get(self, :extras => extras)
74
+ end
75
+
76
+ def get_info
77
+ params = {
78
+ :method => Smugmugr::Album.smug_method + '.getInfo',
79
+ :AlbumID => self.id,
80
+ :AlbumKey => self.key
81
+ }
82
+ json = call(params)
83
+ return Smugmugr::Album.album_from_json(self, json["Album"], user, Smugmugr::Album.attributes)
84
+ end
85
+
86
+ def call(params)
87
+ @user.call(params)
88
+ end
89
+
90
+ class Simple
91
+ attr_accessor :id, :name
92
+ def initialize(json)
93
+ self.id = json["id"]
94
+ self.name = json["Name"]
95
+ end
96
+ end
97
+
98
+ class Category < Simple
99
+ end
100
+
101
+ class Community < Simple
102
+ end
103
+
104
+ class Highlight
105
+ attr_accessor :id, :key
106
+ def initialize(json)
107
+ self.id = json["id"]
108
+ self.key = json["Key"]
109
+ end
110
+ end
111
+
112
+ class SubCategory < Simple
113
+ end
114
+
115
+ class Template
116
+ attr_accessor :id
117
+ def initialize(json)
118
+ self.id = json["id"]
119
+ end
120
+ end
121
+
122
+ class Theme
123
+ attr_accessor :id, :name, :type
124
+ def initialize(json)
125
+ self.id = json["id"]
126
+ self.name = json["Name"]
127
+ self.type = json["Type"]
128
+ end
129
+ end
130
+
131
+ class Watermark < Simple
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,25 @@
1
+ module Smugmugr
2
+ class Base
3
+ class_inheritable_reader :attributes
4
+ write_inheritable_attribute :attributes, []
5
+
6
+ class_inheritable_reader :complex_attributes
7
+ write_inheritable_attribute :complex_attributes, []
8
+
9
+ attr_accessor :extras
10
+
11
+ def method_missing(method)
12
+ # check extras for method_name, MethodName
13
+ methods = [method.to_s.classify, method.to_s, method.to_s.downcase, method.to_s.upcase]
14
+ methods.each do |extras_attribute|
15
+ return self.extras[extras_attribute.to_sym] if self.extras.has_key?(extras_attribute.to_sym)
16
+ end
17
+
18
+ # if it in @@attributes but not populated, return nil
19
+ return nil unless (methods.collect(&:to_sym) & self.class.attributes).empty?
20
+
21
+ # otherwise, move along
22
+ super(method)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ module Smugmugr
2
+ class Category < Smugmugr::Base
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Smugmugr
2
+ class Community < Smugmugr::Base
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Smugmugr
2
+ class Family < Smugmugr::Base
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Smugmugr
2
+ class Friend < Smugmugr::Base
3
+
4
+ end
5
+ end
@@ -0,0 +1,89 @@
1
+ module Smugmugr
2
+ class Image < Smugmugr::Base
3
+ attr_accessor :album, :id, :key
4
+
5
+ class_inheritable_reader :attributes
6
+ write_inheritable_attribute :attributes, [:id,:Key,:Album,:Altitude,:Caption,:Date,:Duration,:FileName,:Format,:Height,:Hidden,
7
+ :Keywords,:LargeURL,:LastUpdated,:Latitude,:Longitude,:MD5Sum,:MediumURL,:OriginalURL,:Position,:Serial,
8
+ :Size,:SmallURL,:ThumbURL,:TinyURL,:Video320URL,:Video640URL,:Video960URL,:Video1280URL,:Video1920URL,:Width,
9
+ :X2LargeURL,:X3LargeURL,:XLargeURL]
10
+
11
+ class_inheritable_reader :complex_attributes
12
+ write_inheritable_attribute :complex_attributes, [:Album]
13
+
14
+ def initialize(album)
15
+ @extras = {}
16
+ @album = album
17
+ end
18
+
19
+ def get(*extras)
20
+ extras = [extras] unless extras.is_a?(Array)
21
+ Smugmugr::Image.get(self.album, :extras => extras)
22
+ end
23
+
24
+ class << self
25
+ def get(album, args={})
26
+ args[:heavy] ||= false
27
+ args[:extras] ||= []
28
+ params = {
29
+ :method => smug_method + 'get',
30
+ :AlbumID => album.id,
31
+ :AlbumKey => album.key
32
+ }
33
+ params.merge!(:Heavy => args[:heavy]) if args[:heavy]
34
+ params.merge!(:Extras => args[:extras].collect(&:to_s).join(',')) if args[:extras].any?
35
+
36
+ json = album.call(params)
37
+ images_from_json(json, album, params[:Extras])
38
+ end
39
+
40
+ def smug_method
41
+ return "smugmug.images."
42
+ end
43
+
44
+ def images_from_json(json, album, extra_params=[])
45
+ json['Album']['Images'].map{ |image_json|
46
+ image_from_json(Smugmugr::Image.new(album), image_json, extra_params)
47
+ }
48
+ end
49
+
50
+ def image_from_json(image, json, extras=[])
51
+ image.id = json['id']
52
+ image.key = json['Key']
53
+ image.extras = {}
54
+ self.attributes.each{ |key|
55
+ next unless json.has_key?(key.to_s)
56
+
57
+ image.extras[key] = if self.complex_attributes.include?(key)
58
+ Object.class_eval("Smugmugr::Image::#{key}").new(json[key.to_s])
59
+ else
60
+ json[key.to_s]
61
+ end
62
+ }
63
+ image
64
+ end
65
+ end
66
+
67
+ #############################################
68
+ # instance
69
+ #############################################
70
+ def get_info
71
+ params = {
72
+ :method => Smugmugr::Image.smug_method + '.getInfo',
73
+ :ImageID => self.id,
74
+ :ImageKey => self.key
75
+ }
76
+ json = @album.call(params)
77
+ return Smugmugr::Image.image_from_json(self, json["Image"], Smugmugr::Image.attributes)
78
+ end
79
+
80
+ class Album
81
+ attr_accessor :id, :key, :url
82
+ def initialize(json)
83
+ self.id = json["id"]
84
+ self.key = json["Key"]
85
+ self.url = json["URL"]
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,126 @@
1
+ require 'curl'
2
+ require 'json'
3
+ require 'cgi'
4
+
5
+ module Smugmugr
6
+ class Session
7
+ attr_accessor :params, :host, :port, :timeout, :retry, :use_ssl, :session_id
8
+ attr_accessor :api_path, :session, :api_key, :user, :attributes, :headers
9
+
10
+ def initialize(user, args={})
11
+ @user = user
12
+ @api_key = user.api_key
13
+ # TODO: externalize, make overrideable from ~/smugmugr.yml or RAILS_ROOT/config/smugmugr.yml
14
+ attributes = {
15
+ :host => 'api.smugmug.com',
16
+ :port => 443,
17
+ :timeout => 10,
18
+ :retry => 1, # no retry for now
19
+ :use_ssl => true,
20
+ :api_path => '/services/api/json/1.2.2/',
21
+ :headers => {
22
+ "User-Agent" => 'smugmugr v0.0.1'
23
+ }
24
+ }
25
+ attributes.merge!(args)
26
+ attributes.each{|k,v|
27
+ self.send("#{k}=", v) if self.respond_to?(k)
28
+ }
29
+ end
30
+
31
+ def establish_session
32
+ if user.email && user.password
33
+ establish_session_with_login
34
+ else
35
+ establish_anonymous_session
36
+ end
37
+ return self
38
+ end
39
+
40
+ def establish_anonymous_session
41
+ establish_new_session({:method => 'smugmug.login.anonymously'})
42
+ end
43
+
44
+ def establish_session_with_login
45
+ establish_new_session({:method => 'smugmug.login.withPassword',
46
+ :EmailAddress => "#{user.email}",
47
+ :Password => "#{user.password}"})
48
+ end
49
+
50
+ def establish_session_with_open_auth
51
+ raise "unimplemented"
52
+ end
53
+
54
+ def get_session_id
55
+ @session ||= establish_session
56
+ return @session.session_id
57
+ end
58
+
59
+ def call(params={}, with_session_id=true)
60
+ params.merge!(:SessionID => "#{get_session_id}") if with_session_id
61
+ data = nil
62
+ begin
63
+ url = protocol + @host + build_url_request(params)
64
+ # puts "\nsending: #{url}\n"
65
+ response = Curl::Easy.perform(url) do |easy|
66
+ easy.headers.merge!(headers)
67
+ easy.timeout = @timeout
68
+ end
69
+
70
+ data = response.body_str unless response.nil?
71
+ rescue Timeout::Error => e
72
+ # TODO: custom exception
73
+ raise e
74
+ end
75
+ # check status of response
76
+ json = JSON.parse(data)
77
+
78
+ unless json['stat'] == 'ok'
79
+ # TODO: custom exception
80
+ raise "error from smugmug: #{json.inspect}"
81
+ end
82
+
83
+ return json
84
+ end
85
+
86
+ def establish_new_session(params)
87
+ begin
88
+ json = call(params, false)
89
+
90
+ set_user_data(json["Login"])
91
+ self.session_id = json["Login"]["Session"]["id"]
92
+ rescue => e
93
+ # TODO: custom exception
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def set_user_data(json)
100
+ @user.user_id = json["User"]["id"]
101
+ @user.nickname = json["User"]["NickName"]
102
+ @user.password_hash = json['PasswordHash']
103
+ @user.filesize_limit = json['FileSizeLimit']
104
+ end
105
+
106
+ def protocol
107
+ return "http#{'s' if @use_ssl}://"
108
+ end
109
+
110
+ # {:foo => 'bar', :baz => "blingy things"} => ['foo=bar','baz=blingy+things']
111
+ def encode_params(raw_params)
112
+ return raw_params.map{ |k,v|
113
+ [k.to_s,CGI::escape(v.to_s)].join('=')
114
+ }
115
+ end
116
+
117
+ # returns everything except protocol/host
118
+ def build_url_request(params)
119
+ params.merge!(:APIKey => @api_key)
120
+ param_string = encode_params(params)
121
+ return @api_path + '?' + param_string.join('&')
122
+ end
123
+
124
+ end
125
+ end
126
+
@@ -0,0 +1,5 @@
1
+ module Smugmugr
2
+ class ShareGroup < Smugmugr::Base
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Smugmugr
2
+ class SubCategory < Smugmugr::Base
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Smugmugr
2
+ class Theme < Smugmugr::Base
3
+
4
+ end
5
+ end
@@ -0,0 +1,34 @@
1
+ module Smugmugr
2
+ class User < Smugmugr::Base
3
+ attr_accessor :credentials, :session_id, :session, :email, :password, :api_key
4
+ # set by smugmug response data
5
+ attr_accessor :user_id, :nickname, :password_hash, :filesize_limit
6
+
7
+ def initialize(api_key, args={})
8
+ @credentials = {}
9
+ self.api_key = api_key
10
+ args.each{ |k,v|
11
+ self.send("#{k}=", v) if self.respond_to?(k)
12
+ }
13
+ @session = Smugmugr::Session.new(self, args[:session_args] || {})
14
+ return
15
+ end
16
+
17
+ def send_request_with_session(params)
18
+ @session.send_request_with_session(params)
19
+ end
20
+
21
+ def albums(*extras)
22
+ extras = [extras] unless extras.is_a?(Array)
23
+ Album.get(self, :extras => extras)
24
+ end
25
+
26
+ def session_id
27
+ @session.session_id
28
+ end
29
+
30
+ def call(params={})
31
+ @session.call(params)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module Smugmugr
2
+ class Watermark < Smugmugr::Base
3
+
4
+ end
5
+ end
data/lib/smugmugr.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'rubygems'
2
+ require 'curb'
3
+ # TODO: look for json_pure, then json
4
+ require 'json'
5
+ require 'yaml'
6
+
7
+ require 'smugmugr/base'
8
+ require 'smugmugr/album'
9
+ require 'smugmugr/category'
10
+ require 'smugmugr/community'
11
+ require 'smugmugr/family'
12
+ require 'smugmugr/friend'
13
+ require 'smugmugr/image'
14
+ require 'smugmugr/session'
15
+ require 'smugmugr/share_group'
16
+ require 'smugmugr/sub_category'
17
+ require 'smugmugr/theme'
18
+ require 'smugmugr/user'
19
+ require 'smugmugr/watermark'
20
+
21
+
22
+ module Smugmugr
23
+ # TODO: attempt to load from a configuration file
24
+ end
25
+
26
+ def logger
27
+ @logger ||= if Module.constants.include?('RAILS_DEFAULT_LOGGER')
28
+ RAILS_DEFAULT_LOGGER
29
+ else
30
+ Logger.new('smugmugr.log')
31
+ end
32
+ end