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/HISTORY
ADDED
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
|