smugmug 0.0.1

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.
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