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/HISTORY ADDED
@@ -0,0 +1,2 @@
1
+ === 0.0.1 (08/10/2007)
2
+ * Initial relase.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2007 Christopher Boumenot
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/MANIFEST ADDED
@@ -0,0 +1,48 @@
1
+ HISTORY
2
+ LICENSE
3
+ MANIFEST
4
+ README
5
+ Rakefile
6
+ bin/smcli
7
+ bin/smugmug2sql
8
+ doc/API
9
+ doc/TODO
10
+ lib/net/httpz.rb
11
+ lib/smugmug.rb
12
+ lib/smugmug/album/info.rb
13
+ lib/smugmug/album/stats.rb
14
+ lib/smugmug/albums.rb
15
+ lib/smugmug/base.rb
16
+ lib/smugmug/cache.rb
17
+ lib/smugmug/config.rb
18
+ lib/smugmug/image/exif.rb
19
+ lib/smugmug/image/info.rb
20
+ lib/smugmug/image/stats.rb
21
+ lib/smugmug/images.rb
22
+ lib/smugmug/table.rb
23
+ lib/smugmug/util.rb
24
+ test/album.rb
25
+ test/config.rb
26
+ test/httpz.rb
27
+ test/image.rb
28
+ test/login.rb
29
+ test/runner.rb
30
+ test/servlet.rb
31
+ test/table.rb
32
+ xml/canned
33
+ xml/fail/empty.set.xml
34
+ xml/fail/invalid.apikey.xml
35
+ xml/fail/invalid.login.xml
36
+ xml/fail/invalid.method.xml
37
+ xml/fail/invalid.user.xml
38
+ xml/fail/system.error.xml
39
+ xml/standard/albums.get.xml
40
+ xml/standard/albums.getInfo.xml
41
+ xml/standard/albums.getStats.xml
42
+ xml/standard/categories.get.xml
43
+ xml/standard/images.get.xml
44
+ xml/standard/images.getEXIF.xml
45
+ xml/standard/images.getInfo.xml
46
+ xml/standard/images.getStats.xml
47
+ xml/standard/login.withHash.xml
48
+ xml/standard/login.withPassword.xml
data/README ADDED
@@ -0,0 +1,30 @@
1
+ = SmugMug
2
+
3
+ * http://www.rubyforge.org/project/smugmug
4
+ * mailto:boumenot+smugmug@gmail.com
5
+
6
+ == DESCRIPTION:
7
+
8
+ An interface to smugmug.com using their REST interface.
9
+
10
+ == FEATURES/PROBLEMS:
11
+
12
+ * smugmug currently support read only access, read-write support should follow
13
+ soon.
14
+
15
+ == SYNOPSIS:
16
+
17
+ sm = SmugMug::SmugMug.new('user@example.net', 'secret')
18
+ sm.each do |album|
19
+ puts album.title
20
+ album.each do |image|
21
+ puts "\t#{image.file_name}"
22
+ end
23
+ end
24
+
25
+ == REQUIREMENTS:
26
+ == INSTALL:
27
+
28
+ == LICENSE:
29
+
30
+ :include:LICENSE
data/Rakefile ADDED
@@ -0,0 +1,100 @@
1
+ # -*- ruby -*-
2
+ # Borrowed heavily from ferret's Rakefile.
3
+
4
+ $:.unshift('./lib')
5
+ require 'smugmug'
6
+
7
+ require 'rubygems'
8
+ require 'rake'
9
+ require 'rake/clean'
10
+ require 'rake/gempackagetask'
11
+ require 'rake/rdoctask'
12
+ require 'rake/testtask'
13
+
14
+ ## Globals ######################################
15
+
16
+ PKG_VERSION = SmugMug::VERSION
17
+ PKG_FILES = IO.read('MANIFEST').split
18
+
19
+ CLEAN.include(FileList['test/*.log'])
20
+
21
+ ## Tasks ########################################
22
+
23
+ task :default => :test
24
+
25
+ task :MANIFEST do
26
+ system "hg manifest | egrep -ve '^\.hg' > MANIFEST"
27
+ end
28
+
29
+ task :TODO do
30
+ FileList['**/*.rb'].egrep /#.*(FIXME|TODO|XXX)/
31
+ end
32
+
33
+ task :clean do
34
+ rm_rf 'pkg'
35
+ rm_rf 'doc/html'
36
+ end
37
+
38
+ task :tag do
39
+ release = "smugmug-#{PKG_VERSION}"
40
+ $stderr.puts("hg -m 'tagging #{release}' #{release}")
41
+ end
42
+
43
+ task :test do
44
+ system('cd test && ruby runner.rb')
45
+ end
46
+
47
+ rd = Rake::RDocTask.new(:docs) do |rdoc|
48
+ rdoc.rdoc_dir = 'doc/html'
49
+ rdoc.title = "SmugMug Library Documentation"
50
+ rdoc.options << '--line-numbers'
51
+ rdoc.options << '--inline-source'
52
+ rdoc.options << '--charset=utf-8'
53
+ rdoc.rdoc_files.include('README')
54
+ rdoc.rdoc_files.include('doc/API')
55
+ rdoc.rdoc_files.include('doc/TODO')
56
+ rdoc.rdoc_files.include('LICENSE')
57
+ rdoc.rdoc_files.include('bin/smugmug2sql')
58
+ rdoc.rdoc_files.include('lib/**/*.rb')
59
+ end
60
+
61
+ ## Gem ##########################################
62
+
63
+ spec = Gem::Specification.new do |s|
64
+ ## basic
65
+ s.name = 'smugmug'
66
+ s.version = PKG_VERSION
67
+ s.summary = 'SmugMug REST library'
68
+ s.description = <<-EOT
69
+ ruby-smugmug is a library to interface with smugmug.com using their
70
+ REST interface.
71
+ EOT
72
+
73
+ ## dependency/requirements
74
+ s.files = PKG_FILES
75
+ s.bindir = 'bin'
76
+ #s.executables = %w{smugmug2sql}
77
+
78
+ ## project details
79
+ s.author = 'Christopher Boumenot'
80
+ s.email = 'boumenot@gmail.com'
81
+ s.homepage = 'http://www.rubyforge.org/projects/smugmug'
82
+ s.rubyforge_project = 'smugmug'
83
+
84
+ ## rdoc
85
+ s.has_rdoc = true
86
+ s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a
87
+ s.rdoc_options << '--title' << 'SmugMug REST Library'
88
+ s.rdoc_options << '--main' << 'README'
89
+ s.rdoc_options << '--line-numbers'
90
+ s.rdoc_options << 'doc/TODO'
91
+ s.rdoc_options << 'doc/API'
92
+ end
93
+
94
+ package_task = Rake::GemPackageTask.new(spec) do |pkg|
95
+ unless RUBY_PLATFORM =~ /mswin/
96
+ pkg.need_tar = true
97
+ end
98
+ end
99
+
100
+ # vim: syntax=Ruby
data/bin/smcli ADDED
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env ruby
2
+ # $Hg: smcli,v f6ac7888e7aa 2007/07/17 14:23:26 boumenot $
3
+ #
4
+ # TODO: finish this, it does not work.
5
+ # SmugMug CLI
6
+
7
+ require 'forwardable'
8
+ require 'logger'
9
+ require 'net/http'
10
+ require 'readline'
11
+ require 'rexml/document'
12
+
13
+ require 'smugmug'
14
+
15
+ class Cache
16
+ def initialize
17
+ @cache = Hash.new
18
+ end
19
+
20
+ def cache(path, data)
21
+ @cache[path] ||= Hash.new
22
+ @cache[path]['valid'] = true
23
+ @cache[path]['data'] = data
24
+ end
25
+
26
+ def invalidate(path)
27
+ if @cache.has_key?(path)
28
+ @cache[path]['valid'] = false
29
+ end
30
+ end
31
+
32
+ def cached?(path)
33
+ if @cache.has_key?(path)
34
+ return @cache[path]['valid']
35
+ end
36
+ return false
37
+ end
38
+ end
39
+
40
+ class Context
41
+ OFFSET_ROOT = 0
42
+ OFFSET_ALBUM = 1
43
+ OFFSET_IMAGE = 2
44
+ OFFSET_INFO = 3
45
+ OFFSET_FILE = 4
46
+
47
+ def initialize
48
+ @path = Array.new
49
+ end
50
+
51
+ def leafs
52
+ return @path.size()
53
+ end
54
+
55
+ def [](i)
56
+ return @path[i]
57
+ end
58
+
59
+ def path
60
+ return @path.join('/')
61
+ end
62
+
63
+ def cd(path)
64
+ p = Array.new(@path)
65
+ q = path.split('/')
66
+
67
+ q.each do |d|
68
+ case d
69
+ when '.'
70
+ next
71
+ when '..'
72
+ if p.any?
73
+ p.shift
74
+ else
75
+ raise ContextError, "invalid cd"
76
+ end
77
+ else
78
+ p.push(d)
79
+ unless valid?(p)
80
+ raise ContextError, "invalid cd"
81
+ end
82
+ end
83
+ end
84
+ @path = p
85
+ end
86
+
87
+ def root_p() return self[OFFSET_ROOT] ; end
88
+ def album_p() return self[OFFSET_ALBUM] ; end
89
+ def image_p() return self[OFFSET_IMAGE] ; end
90
+ def info_p() return self[OFFSET_INFO] ; end
91
+
92
+ # XXX: ... must stop self from getting to fancy ...
93
+ # XXX: ... must write code that is readable ...
94
+ def root
95
+ return $cache.cache(root_p())
96
+ end
97
+ def album
98
+ return $cache.cache(album_p())
99
+ end
100
+ def image
101
+ return $cache.cache(album_p() + '/' + image_p())
102
+ end
103
+ def info
104
+ return $cache.cache(album_p() + '/' + image_p() + '/' + info_p())
105
+ end
106
+ def current
107
+ case leafs()
108
+ when 1 : return root()
109
+ when 2 : return album()
110
+ when 3 : return image()
111
+ when 4 : return info()
112
+ else
113
+ raise ContextError, "invalid current, path=#{@path}"
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def valid?(path)
120
+ if path.size() == 0 or path.size() >= OFFSET_FILE
121
+ return false
122
+ else
123
+ return $cache.cached?(path[OFFSET_ALBUM..path.size()].join('/'))
124
+ end
125
+ end
126
+ end
127
+
128
+ class Commands
129
+ def initialize
130
+ @context = Context.new()
131
+ end
132
+
133
+ # print a file to standard output
134
+ def cat(*args)
135
+ return if args.size != 2
136
+ end
137
+
138
+ # change directory
139
+ def cd(*args)
140
+ return unless args.any?
141
+ @context.cd(args.shift)
142
+ end
143
+
144
+ # print working directory
145
+ def pwd(*args)
146
+ return @context.path()
147
+ end
148
+
149
+ # return the date and time
150
+ def date(*args)
151
+ return Time.now()
152
+ end
153
+
154
+ # directory listing
155
+ def ls(*args)
156
+ if args.any?
157
+ args.each do |dir|
158
+ puts "#{dir}:"
159
+ temp = Context.new()
160
+ temp.cd(dir)
161
+ pp_listing(temp.current)
162
+ print "\n"
163
+ end
164
+ else
165
+ pp_listing(@context.current)
166
+ end
167
+ end
168
+
169
+ # def echo(*args) ; end
170
+ # def mv(*args) ; end
171
+ # def rm(*args) ; end
172
+
173
+ private
174
+
175
+ def pp_listing(ary)
176
+ ary.each {|x| puts x}
177
+ end
178
+ end
179
+
180
+ def main
181
+ username = ARGV.shift
182
+ password = ARGV.shift
183
+
184
+ $log = Logger.new('smcli.log')
185
+
186
+ $sm = SmugMug::SmugMug.new
187
+ $sm.login(username, password)
188
+
189
+ comm = Commands.new
190
+ diff = comm.methods = Object.methods
191
+ Readline.completion_proc = lamba do |s|
192
+ p.find_all { |e| e.match(s) }
193
+ end
194
+
195
+ loop do
196
+ line = Readline.readline("smcli> ", true)
197
+ next unless line.any?
198
+
199
+ if line.nil?
200
+ print "\n"
201
+ Kernel.exit
202
+ end
203
+
204
+ begin
205
+ args = line.split
206
+ command = args.shift
207
+
208
+ if comm.respond_to?(command)
209
+ comm.send(commands, *args)
210
+ else
211
+ puts "Do not understand the command #{command}"
212
+ end
213
+ rescue SmugMug::SmugMugError => e
214
+ puts "SmugMug: #{e}"
215
+ Kernel.exit
216
+ end
217
+ end
218
+ end
219
+
220
+ if __FILE__ == $0
221
+ main()
222
+ end
223
+
224
+ # vim:expandtab
225
+ # vim:autoindent
data/bin/smugmug2sql ADDED
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- Mode: ruby; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3
+ # $Hg$
4
+ #
5
+ # smugmug2sql converts SmugMug data into SQL, and inserts the data into a
6
+ # SQLite3 database. Data for albums, images, and exif are stored in the
7
+ # database. By moving the data to database it makes it much faster and
8
+ # easier to access the data. Because the data are in a database, one can
9
+ # build and execute complex queries against the data. For example, one could
10
+ # select all photos taken between 5/1/2007, and # 5/31/2007.
11
+
12
+ require 'dbi'
13
+ require 'forwardable'
14
+ require 'optparse'
15
+ require 'smugmug'
16
+
17
+ module SmugMug
18
+ class MethodTable
19
+ def sql_type(method)
20
+ case xtype(method)
21
+ when 'boolean': return 'INTEGER'
22
+ when 'float': return 'REAL'
23
+ when 'int': return 'INTEGER'
24
+ else return 'TEXT'
25
+ end
26
+ end
27
+
28
+ def sql_schema
29
+ return @schema unless @schema.nil?
30
+
31
+ @schema = "CREATE TABLE #{self.klass.sql_table_name} (\n"
32
+ each do |k,v|
33
+ @schema += "\t#{k} #{sql_type(k)},\n"
34
+ end
35
+ @schema[-2..-1] = ')'
36
+
37
+ $log.info("#{klass}#sql_schema") { @schema }
38
+ return @schema
39
+ end
40
+ end
41
+
42
+ class Base
43
+ def to_sql
44
+ stmt = "INSERT INTO #{sql_table_name} VALUES(\n"
45
+
46
+ self.class.table.each do |k,v|
47
+ val = self.send(k.to_sym)
48
+ stmt += (val.nil?) ? %Q{\tNULL,\n} : %Q{\t"#{val}",\n}
49
+ end
50
+ stmt[-2..-1] = ')'
51
+
52
+ $log.info("#{self.class}#to_sql") { stmt }
53
+ return stmt
54
+ end
55
+ end
56
+
57
+ module Album
58
+ class Info
59
+ def sql_table_name() return 'album' end
60
+ end
61
+ end
62
+
63
+ module Image
64
+ class Info
65
+ def sql_table_name() return 'image' end
66
+ end
67
+ class Exif
68
+ def sql_table_name() return 'exif' end
69
+ end
70
+ end
71
+ end
72
+
73
+
74
+ def do_SQL(dbh, klass)
75
+ return if $sql_replay.has_key?(klass.to_sql)
76
+
77
+ unless $init.has_key?(klass.sql_table_name)
78
+ $init[klass.sql_table_name] = 'true'
79
+ dbh.do(klass.class.table.sql_schema)
80
+ end
81
+
82
+ dbh.do(klass.to_sql)
83
+ $sql_replay[klass.to_sql] = true
84
+
85
+ sleep rand(5) + 1 # be kind...
86
+ end
87
+
88
+ def main()
89
+ options = {}
90
+ OptionParser.new do |opts|
91
+ opts.on("-p", "--password PASSWORD", "SmugMug password") do |v|
92
+ options[:password] = v
93
+ end
94
+ opts.on("-u", "--username USERNAME", "SmugMug email address") do |v|
95
+ options[:username] = v
96
+ end
97
+ opts.on("-d", "--database DATABASE", "name of the database file") do |v|
98
+ options[:database ] = v
99
+ end
100
+ opts.on("-l", "--logging FILENAME", "enable logging to FILENAME") do |v|
101
+ $log = Logger.new(v)
102
+ end
103
+ opts.on_tail("-h", "--help", "Show this message") do
104
+ puts opts
105
+ Kernel.exit
106
+ end
107
+ end.parse!
108
+
109
+ if options.has_key?(:username) ^ options.has_key?(:password)
110
+ puts "%Error: Must specify both --username and --password"
111
+ raise SystemExit
112
+ end
113
+
114
+ sm = if options.has_key?(:username)
115
+ SmugMug::SmugMug.new(options[:username], options[:password])
116
+ else
117
+ SmugMug::SmugMug.new
118
+ end
119
+
120
+ db_file = options[:database] || 'smugmug.db'
121
+
122
+ $init = {}
123
+ $sql_replay = {}
124
+
125
+ begin
126
+ dbh = DBI.connect("DBI:SQLite3:#{db_file}")
127
+
128
+ # Let me apologize up front to anyone reading this loop.
129
+ # SmugMug.com gets through a few albums and then starts
130
+ # timing out my connections. This is my attempt to deal
131
+ # with that issue.
132
+ sm.each do |album|
133
+ begin
134
+ do_SQL(dbh, album)
135
+ album.each do |image|
136
+ begin
137
+ do_SQL(dbh, image)
138
+ do_SQL(dbh, image.exif)
139
+ rescue Errno::ETIMEDOUT
140
+ sleep 30
141
+ redo
142
+ end
143
+ end
144
+ rescue Errno::ETIMEDOUT
145
+ sleep 30
146
+ redo
147
+ end
148
+ end
149
+ rescue DBI::DatabaseError => e
150
+ puts "%Error: #{e.err} -> #{e.errstr}"
151
+ ensure
152
+ dbh.disconnect if dbh
153
+ end
154
+ end
155
+
156
+ if $0 == __FILE__
157
+ main()
158
+ end