smugsync 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/AUTHORS +2 -0
- data/COPYING +340 -0
- data/README.md +35 -0
- data/Rakefile +13 -0
- data/bin/smug +5 -0
- data/lib/smugsync/albums.rb +26 -0
- data/lib/smugsync/command.rb +67 -0
- data/lib/smugsync/config.rb +66 -0
- data/lib/smugsync/console.rb +45 -0
- data/lib/smugsync/download.rb +68 -0
- data/lib/smugsync/fetch.rb +95 -0
- data/lib/smugsync/init.rb +40 -0
- data/lib/smugsync/status.rb +123 -0
- data/lib/smugsync/upload.rb +117 -0
- data/lib/smugsync/utils.rb +22 -0
- data/lib/smugsync.rb +11 -0
- data/smugsync.gemspec +27 -0
- data/smugsync.kdev4 +3 -0
- data/test/local/.gitignore +1 -0
- data/test/mock/command.rb +15 -0
- data/test/mock/oauth/tokens/request_token.rb +11 -0
- data/test/mock/smug_server.rb +196 -0
- data/test/server/.gitignore +1 -0
- data/test/smug_test_case.rb +88 -0
- data/test/status_command_test.rb +55 -0
- metadata +190 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
module Smug
|
3
|
+
|
4
|
+
# TODO: maybe rename
|
5
|
+
class FetchCommand < Command
|
6
|
+
|
7
|
+
def exec
|
8
|
+
optparser = Trollop::Parser.new do
|
9
|
+
banner <<-END
|
10
|
+
Usage: smug fetch [<options>]
|
11
|
+
Show the status of local SmugMug folder.
|
12
|
+
|
13
|
+
Options:
|
14
|
+
END
|
15
|
+
opt :force,
|
16
|
+
"Force full refresh of albums and images list",
|
17
|
+
:short => :f
|
18
|
+
end
|
19
|
+
|
20
|
+
opts = Trollop::with_standard_exception_handling(optparser) do
|
21
|
+
optparser.parse(ARGV)
|
22
|
+
end
|
23
|
+
|
24
|
+
authenticate
|
25
|
+
|
26
|
+
status_message "Refreshing albums cache"
|
27
|
+
refresh_cache(:all_albums, opts) { |album, cache_status| status_message "." }
|
28
|
+
status_message " done\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
# block is executed for each album with two argumens: album and cache_status
|
32
|
+
# cache_status is:
|
33
|
+
# - :refreshed album cache was refreshed
|
34
|
+
# - :fresh album in cache is the same as in server
|
35
|
+
# options can be:
|
36
|
+
# - :force => true list of album's images is always refreshed
|
37
|
+
# this is necessary for refreshing cache after uploads and here's why:
|
38
|
+
# - smugmug stores LastUpdated data for albums with 1 second precision
|
39
|
+
# - upload can be fast enough to create album and add image to the album
|
40
|
+
# in one second
|
41
|
+
def refresh_cache(albums_to_refresh, options = {}, &block)
|
42
|
+
authenticate
|
43
|
+
|
44
|
+
old_cache = if File.exist?(Config::config_file_name("cache"))
|
45
|
+
JSON::parse(Config::config_file("cache", "r").read)
|
46
|
+
else
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
50
|
+
# - to refresh all albums we need to reconstruct the cache from scratch
|
51
|
+
# to make sure that deleted albums are not left in the cache
|
52
|
+
# - to refresh selected albums we start from existing cache
|
53
|
+
# and replace only refreshed albums in that cache
|
54
|
+
albums_on_server = smugmug_albums_get(:Heavy => true)["Albums"]
|
55
|
+
if albums_to_refresh == :all_albums
|
56
|
+
albums_to_refresh = albums_on_server
|
57
|
+
new_cache = []
|
58
|
+
else
|
59
|
+
albums_to_refresh_ids = albums_to_refresh.map { |a| a["id"] }
|
60
|
+
# refetch albums metadata
|
61
|
+
albums_to_refresh = albums_on_server.select { |a| albums_to_refresh_ids.include?(a["id"]) }
|
62
|
+
# leave albums that should not be refreshed in the cache
|
63
|
+
new_cache = old_cache.reject { |a| albums_to_refresh_ids.include?(a["id"]) }
|
64
|
+
end
|
65
|
+
|
66
|
+
albums_to_refresh.each do |album|
|
67
|
+
cached_album = old_cache.find { |a| a["id"] == album["id"] }
|
68
|
+
|
69
|
+
if !options[:force] and cached_album and cached_album["LastUpdated"] == album["LastUpdated"]
|
70
|
+
# album is in cache and is not changed: copy images from old cache
|
71
|
+
# because it can take a long time to get the list of images
|
72
|
+
# from server for large albums
|
73
|
+
album["Images"] = cached_album["Images"]
|
74
|
+
yield(album, :skipped) if block_given?
|
75
|
+
else
|
76
|
+
# album was changed, get the list of images
|
77
|
+
album["Images"] = smugmug_images_get(
|
78
|
+
:AlbumID => album["id"],
|
79
|
+
:AlbumKey => album["Key"],
|
80
|
+
:Heavy => true
|
81
|
+
)["Album"]["Images"]
|
82
|
+
yield(album, :refreshed) if block_given?
|
83
|
+
end
|
84
|
+
|
85
|
+
new_cache << album
|
86
|
+
end
|
87
|
+
|
88
|
+
cache_file = Config::config_file("cache", "w+")
|
89
|
+
cache_file.puts JSON::pretty_generate(new_cache)
|
90
|
+
cache_file.close
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'oauth'
|
2
|
+
|
3
|
+
module Smug
|
4
|
+
|
5
|
+
class InitCommand < Command
|
6
|
+
|
7
|
+
def exec
|
8
|
+
# need to request access token from the user
|
9
|
+
request_token = oauth_consumer.get_request_token
|
10
|
+
# TODO: explain what happens better
|
11
|
+
puts <<-EOS
|
12
|
+
Authorize app at:
|
13
|
+
#{request_token.authorize_url}&Access=Full&Permissions=Modify
|
14
|
+
Press Enter when finished
|
15
|
+
EOS
|
16
|
+
gets
|
17
|
+
access_token = nil
|
18
|
+
begin
|
19
|
+
access_token = request_token.get_access_token
|
20
|
+
rescue OAuth::Unauthorized
|
21
|
+
$stderr.puts "Fatal: Could not authorize with SmugMug. Run 'smug init' again."
|
22
|
+
exit(-1)
|
23
|
+
end
|
24
|
+
|
25
|
+
config = {
|
26
|
+
:access_token => {
|
27
|
+
:secret => access_token.secret,
|
28
|
+
:token => access_token.token
|
29
|
+
}
|
30
|
+
}
|
31
|
+
Smug::Config::create_config_dir
|
32
|
+
File.open(Smug::Config::config_file_name(Smug::Config::ACCESS_TOKEN_CONFIG_FILE), "w+") do |f|
|
33
|
+
f.puts JSON.pretty_generate(config)
|
34
|
+
end
|
35
|
+
puts "Initialized SmugMug folder and authorized with SmugMug"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
|
2
|
+
module Smug
|
3
|
+
|
4
|
+
class StatusCommand < Command
|
5
|
+
|
6
|
+
include Utils
|
7
|
+
|
8
|
+
ALBUM_STATUS_FORMAT = "%20s\t%s/(%s files)"
|
9
|
+
IMAGE_STATUS_FORMAT = "%20s\t%s/%s"
|
10
|
+
|
11
|
+
def exec
|
12
|
+
authenticate
|
13
|
+
|
14
|
+
# TODO: support categories and nested categories
|
15
|
+
# for now comparison supports only flat list of albums
|
16
|
+
status = current_dir_status
|
17
|
+
status.each do |album_status|
|
18
|
+
if album_status[:status] != :not_modified
|
19
|
+
puts sprintf(ALBUM_STATUS_FORMAT, album_status[:status], album_status[:album], album_status[:images].length)
|
20
|
+
else
|
21
|
+
album_status[:images].each do |image_status|
|
22
|
+
puts sprintf(IMAGE_STATUS_FORMAT, image_status[:status], album_status[:album], image_status[:image])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def current_dir_status
|
29
|
+
current_dir = Config::relative_to_root(Dir.pwd).to_s
|
30
|
+
if current_dir == '.'
|
31
|
+
albums_status
|
32
|
+
else
|
33
|
+
Dir::chdir('..')
|
34
|
+
status = [album_status(
|
35
|
+
:local => current_dir,
|
36
|
+
:remote => albums.find { |a| a["Title"] == current_dir }
|
37
|
+
)]
|
38
|
+
Dir::chdir(current_dir)
|
39
|
+
status
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def albums_status
|
44
|
+
status = []
|
45
|
+
|
46
|
+
local_albums = Pathname::pwd.children(false).find_all { |a| a.directory? and a.basename.to_s != ".smug" }.map { |a| a.basename.to_s }
|
47
|
+
remote_albums = albums.map { |a| a["Title"] }
|
48
|
+
|
49
|
+
status += albums.map do |album|
|
50
|
+
album_status(
|
51
|
+
:remote => album,
|
52
|
+
:local => local_albums.find { |a| a == album["Title"] }
|
53
|
+
)
|
54
|
+
end
|
55
|
+
status += (local_albums - remote_albums).map do |local_album|
|
56
|
+
album_status(:remote => nil, :local => local_album)
|
57
|
+
end
|
58
|
+
|
59
|
+
status
|
60
|
+
end
|
61
|
+
|
62
|
+
def album_status(options = { :local => nil, :remote => nil })
|
63
|
+
local = options[:local]
|
64
|
+
remote = options[:remote]
|
65
|
+
|
66
|
+
if !local and !remote
|
67
|
+
raise "album_status: requires either local or remote album"
|
68
|
+
elsif local and !remote
|
69
|
+
{
|
70
|
+
:album => local,
|
71
|
+
:status => :not_uploaded,
|
72
|
+
:images => local_images(local).map { |img| { :image => img, :status => :not_uploaded} }
|
73
|
+
}
|
74
|
+
elsif !local and remote
|
75
|
+
{
|
76
|
+
:album => remote["Title"],
|
77
|
+
:status => :not_downloaded,
|
78
|
+
:images => remote_images(remote).map { |img| { :image => img, :status => :not_downloaded} }
|
79
|
+
}
|
80
|
+
else
|
81
|
+
# TODO: assure that local and remote titles are the same
|
82
|
+
# TODO: document case insensitivity
|
83
|
+
not_uploaded = local_images(local) - remote_images(remote)
|
84
|
+
not_downloaded = remote_images(remote) - local_images(local)
|
85
|
+
|
86
|
+
{
|
87
|
+
:album => remote["Title"],
|
88
|
+
:status => :not_modified,
|
89
|
+
:images => not_uploaded.map { |img| { :image => img, :status => :not_uploaded} } + not_downloaded.map { |img| { :image => img, :status => :not_downloaded} }
|
90
|
+
}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def albums
|
97
|
+
# TODO: status doesn't work without cache
|
98
|
+
@albums ||= JSON::parse Config::config_file("cache", "r").read
|
99
|
+
end
|
100
|
+
|
101
|
+
def local_images(album_path)
|
102
|
+
ignore_list = []
|
103
|
+
|
104
|
+
ignore_file_name = Pathname.new(album_path).join(".smugignore")
|
105
|
+
if File.exists?(ignore_file_name)
|
106
|
+
ignore_list = File.read(ignore_file_name).split($/)
|
107
|
+
end
|
108
|
+
|
109
|
+
Pathname.new(album_path).children(false).reject do |p|
|
110
|
+
filename = p.basename.to_s
|
111
|
+
filename == '.smugignore' or ignore_list.include? filename
|
112
|
+
end.map do |p|
|
113
|
+
p.basename.to_s.gsub(/^\d\d\d\./, '')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def remote_images(album)
|
118
|
+
album["Images"].map {|p| p["FileName"] }
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'system_timer'
|
2
|
+
require 'md5'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Smug
|
6
|
+
|
7
|
+
class UploadCommand < Command
|
8
|
+
|
9
|
+
# current upload algorithm:
|
10
|
+
# 1. get the list not uploaded stuff starting from current dir and below
|
11
|
+
# 2. chdir to root (for simplicity)
|
12
|
+
# 3. for each album
|
13
|
+
# 3.1. create it if it doesn't exist
|
14
|
+
# 3.2. upload files
|
15
|
+
def exec
|
16
|
+
optparser = Trollop::Parser.new do
|
17
|
+
banner <<-END
|
18
|
+
Usage: smug upload [<options>]
|
19
|
+
Upload files to SmugMug.
|
20
|
+
|
21
|
+
Options:
|
22
|
+
END
|
23
|
+
opt :timeout,
|
24
|
+
"Timeout to upload one file in seconds",
|
25
|
+
:short => :t,
|
26
|
+
:default => 24*3600
|
27
|
+
end
|
28
|
+
|
29
|
+
opts = Trollop::with_standard_exception_handling(optparser) do
|
30
|
+
optparser.parse(ARGV)
|
31
|
+
end
|
32
|
+
|
33
|
+
authenticate
|
34
|
+
|
35
|
+
status = StatusCommand.new.current_dir_status
|
36
|
+
Dir::chdir(Config::root_dir)
|
37
|
+
|
38
|
+
modified_albums = Set.new
|
39
|
+
|
40
|
+
status.each_with_index do |album_status, i|
|
41
|
+
if album_status[:status] == :not_uploaded
|
42
|
+
# create album
|
43
|
+
puts "Creating album #{album_status[:album]}"
|
44
|
+
result = smugmug_albums_create(
|
45
|
+
"Title" => album_status[:album]
|
46
|
+
)
|
47
|
+
raise "Cannot create album" unless result["stat"] == "ok"
|
48
|
+
|
49
|
+
# refresh cache for the newly created album
|
50
|
+
album = smugmug_albums_getInfo(
|
51
|
+
:AlbumID => result["Album"]["id"],
|
52
|
+
:AlbumKey => result["Album"]["Key"]
|
53
|
+
)["Album"]
|
54
|
+
|
55
|
+
FetchCommand.new.refresh_cache([album])
|
56
|
+
@albums = nil # TODO: hack to force reparsing of cache file
|
57
|
+
elsif album_status[:status] == :not_downloaded
|
58
|
+
next
|
59
|
+
end
|
60
|
+
|
61
|
+
album = albums.find { |a| a["Title"] == album_status[:album] }
|
62
|
+
|
63
|
+
files_to_upload = album_status[:images].find_all { |i| i[:status] == :not_uploaded }.map { |i| "#{album["Title"]}/#{i[:image]}" }
|
64
|
+
|
65
|
+
File.open("upload.log", "w+") { |f| f.puts "start" }
|
66
|
+
num_files = files_to_upload.length
|
67
|
+
files_to_upload.each_with_index do |filename, i|
|
68
|
+
modified_albums << album
|
69
|
+
|
70
|
+
begin
|
71
|
+
SystemTimer.timeout_after(opts[:timeout]) do
|
72
|
+
puts "Uploading #{filename} (#{Time.now.to_s}) (#{i}/#{num_files})"
|
73
|
+
|
74
|
+
data = File.open(filename, "rb") { |f| f.read }
|
75
|
+
|
76
|
+
req = {}
|
77
|
+
req['Content-Length'] = File.size(filename).to_s
|
78
|
+
req['Content-MD5'] = MD5.hexdigest(data)
|
79
|
+
req['X-Smug-AlbumID'] = album["id"].to_s
|
80
|
+
req['X-Smug-Version'] = '1.3.0'
|
81
|
+
req['X-Smug-FileName'] = filename
|
82
|
+
req['X-Smug-ResponseType'] = "JSON"
|
83
|
+
|
84
|
+
results = put("http://upload.smugmug.com/#{filename}", data, req)
|
85
|
+
puts " => #{results["stat"]} (#{Time.now.to_s})"
|
86
|
+
end
|
87
|
+
rescue Timeout::Error
|
88
|
+
puts " => timed out"
|
89
|
+
File.open("upload.log", "a") { |f| f.puts filename }
|
90
|
+
|
91
|
+
# TODO: delete image from server
|
92
|
+
rescue Exception => e
|
93
|
+
puts " => error #{e.message}"
|
94
|
+
puts e.backtrace.join("\n")
|
95
|
+
File.open("upload.log", "a") { |f| f.puts filename }
|
96
|
+
|
97
|
+
# TODO: delete image from server
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# refresh cache for modified albums only
|
103
|
+
puts "Refreshing albums cache"
|
104
|
+
FetchCommand.new.refresh_cache(modified_albums, :force => true)
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def albums
|
110
|
+
# TODO: upload doesn't work without cache
|
111
|
+
@albums ||= JSON::parse Config::config_file("cache", "r").read
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module Smug
|
3
|
+
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
def status_message(message)
|
7
|
+
$stdout.print message
|
8
|
+
$stdout.flush
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(method, *args, &block)
|
12
|
+
# if method looks like smugmug API call, call get()
|
13
|
+
if method.to_s =~ /^smugmug_/
|
14
|
+
get(method.to_s.gsub('_', '.'), *args, &block)
|
15
|
+
else
|
16
|
+
super(method, *args, &block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/lib/smugsync.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'trollop'
|
3
|
+
require 'smugsync/config'
|
4
|
+
require 'smugsync/utils'
|
5
|
+
require 'smugsync/command'
|
6
|
+
require 'smugsync/albums'
|
7
|
+
require 'smugsync/upload'
|
8
|
+
require 'smugsync/init'
|
9
|
+
require 'smugsync/status'
|
10
|
+
require 'smugsync/fetch'
|
11
|
+
require 'smugsync/download'
|
data/smugsync.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
SPEC = Gem::Specification.new do |s|
|
4
|
+
s.name = "smugsync"
|
5
|
+
s.version = "0.2"
|
6
|
+
s.author = "Alexander Dymo"
|
7
|
+
s.email = "adymo@kdevelop.org"
|
8
|
+
s.homepage = "http://github.com/adymo/smugsync"
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.summary = "."
|
11
|
+
|
12
|
+
s.add_development_dependency('bundler', '>= 1.0.0')
|
13
|
+
s.add_development_dependency('assert_same', '>= 0.1.0')
|
14
|
+
|
15
|
+
s.add_dependency('trollop', '>= 1.16.0')
|
16
|
+
s.add_dependency('json', '>= 1.6.0')
|
17
|
+
s.add_dependency('oauth', '>= 0.4.0')
|
18
|
+
s.add_dependency('system_timer', '>= 1.2.0')
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
|
24
|
+
s.require_path = "lib"
|
25
|
+
s.autorequire = "smugsync"
|
26
|
+
s.has_rdoc = false
|
27
|
+
end
|
data/smugsync.kdev4
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
class Smug::Command
|
3
|
+
|
4
|
+
alias_method :get_without_mock, :get
|
5
|
+
def get(method, params = {})
|
6
|
+
mocked_method = method.gsub(".", "_")
|
7
|
+
server = SmugServer.new
|
8
|
+
if server.respond_to?(mocked_method)
|
9
|
+
JSON.parse(server.send(mocked_method, params))
|
10
|
+
else
|
11
|
+
get_without_mock(method, params)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
|
2
|
+
class SmugServer
|
3
|
+
|
4
|
+
attr_reader :server_albums
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@server_albums = get_server_albums_info
|
8
|
+
end
|
9
|
+
|
10
|
+
def smugmug_albums_get(params = { :Heavy => false })
|
11
|
+
result = {
|
12
|
+
"stat" => "ok",
|
13
|
+
"method" => "smugmug.albums.get",
|
14
|
+
"Albums" => []
|
15
|
+
}
|
16
|
+
|
17
|
+
server_albums.each do |album|
|
18
|
+
if params[:Heavy]
|
19
|
+
result["Albums"] << album.delete_if { |key, value| key == 'Images' }
|
20
|
+
else
|
21
|
+
result["Albums"] << album.delete_if do |key, value|
|
22
|
+
not ["id", "Key", "Category", "SubCategory", "Title"].include?(key)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
result.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
def smugmug_images_get(params = { :AlbumID => nil, :AlbumKey => nil, :Heavy => false })
|
31
|
+
result = {
|
32
|
+
"stat" => "ok",
|
33
|
+
"method" => "smugmug.images.get",
|
34
|
+
}
|
35
|
+
|
36
|
+
album = server_albums.find { |album| album["id"] == params[:AlbumID] and album["Key"] == params[:AlbumKey] }
|
37
|
+
return result unless album
|
38
|
+
|
39
|
+
result["Album"] = {
|
40
|
+
"id" => params[:AlbumID],
|
41
|
+
"Key" => params[:AlbumKey],
|
42
|
+
"ImageCount" => 0,
|
43
|
+
"Images" => []
|
44
|
+
}
|
45
|
+
|
46
|
+
album["Images"].each do |image|
|
47
|
+
result["Album"]["ImageCount"] += 1
|
48
|
+
if params[:Heavy]
|
49
|
+
result["Album"]["Images"] << image
|
50
|
+
else
|
51
|
+
result["Album"]["Images"] << image.delete_if do |key, value|
|
52
|
+
not ["id", "Key"].include?(key)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
result.to_json
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def get_server_albums_info
|
63
|
+
albums = []
|
64
|
+
|
65
|
+
server_dir = Dir.new(SmugTestCase.server_dir)
|
66
|
+
album_id = 0
|
67
|
+
image_id = 0
|
68
|
+
server_dir.each do |album_name|
|
69
|
+
next if ['.', '..', '.gitignore'].include?(album_name)
|
70
|
+
|
71
|
+
album_full_path = File.join(SmugTestCase.server_dir, album_name)
|
72
|
+
raise "Server directory contains file #{album_name} outside of album" unless File.directory?(album_full_path)
|
73
|
+
|
74
|
+
# nice name in smugmug is the name users see in url
|
75
|
+
# currently we do nothing with that but let's keep it
|
76
|
+
# different from album name
|
77
|
+
album_nice_name = album_name.gsub(" ", "_")
|
78
|
+
album_id += 1
|
79
|
+
album_key = "key#{album_id.to_s(27).tr("0-9a-q", "A-Z")}"
|
80
|
+
|
81
|
+
album = {
|
82
|
+
# default settings for smugmug albums that we do not care about
|
83
|
+
"WordSearchable" => true,
|
84
|
+
"Theme" => {
|
85
|
+
"Name" => "default",
|
86
|
+
"id" => 1
|
87
|
+
},
|
88
|
+
"SortMethod" => "Position",
|
89
|
+
"ImageCount" => 1,
|
90
|
+
"Highlight" => {
|
91
|
+
"id" => 1725930094,
|
92
|
+
"Key" => "b9fk6m6",
|
93
|
+
"Type" => "Random"
|
94
|
+
},
|
95
|
+
"External" => true,
|
96
|
+
"Comments" => true,
|
97
|
+
"Clean" => false,
|
98
|
+
"X3Larges" => true,
|
99
|
+
"Keywords" => "",
|
100
|
+
"UnsharpAmount" => 0.2,
|
101
|
+
"Public" => true,
|
102
|
+
"Position" => 1,
|
103
|
+
"UnsharpRadius" => 1,
|
104
|
+
"SortDirection" => false,
|
105
|
+
"Filenames" => true,
|
106
|
+
"ColorCorrection" => 2,
|
107
|
+
"SquareThumbs" => true,
|
108
|
+
"Originals" => true,
|
109
|
+
"SmugSearchable" => true,
|
110
|
+
"Header" => false,
|
111
|
+
"X2Larges" => true,
|
112
|
+
"FamilyEdit" => false,
|
113
|
+
"EXIF" => true,
|
114
|
+
"UnsharpThreshold" => 0.05,
|
115
|
+
"Template" => {
|
116
|
+
"id" => 0
|
117
|
+
},
|
118
|
+
"HideOwner" => false,
|
119
|
+
"CanRank" => true,
|
120
|
+
"UnsharpSigma" => 1,
|
121
|
+
"Share" => true,
|
122
|
+
"Protected" => false,
|
123
|
+
"Printable" => true,
|
124
|
+
"UploadKey" => "",
|
125
|
+
"Geography" => true,
|
126
|
+
"FriendEdit" => false,
|
127
|
+
"Category" => {
|
128
|
+
"Name" => "TestCategory",
|
129
|
+
"id" => 1
|
130
|
+
},
|
131
|
+
"Passworded" => false,
|
132
|
+
"PasswordHint" => "",
|
133
|
+
"Password" => "",
|
134
|
+
"Description" => "",
|
135
|
+
|
136
|
+
# albums settings that we currently care about
|
137
|
+
"id" => album_id,
|
138
|
+
"Key" => album_key,
|
139
|
+
"NiceName" => album_nice_name,
|
140
|
+
"URL" => "http://user.smugmug.com/TestCategory/#{album_nice_name}/#{album_id}_#{album_key}",
|
141
|
+
"Title" => album_name,
|
142
|
+
"LastUpdated" => "2012-02-26 02:40:44",
|
143
|
+
"Images" => []
|
144
|
+
}
|
145
|
+
|
146
|
+
album_dir = Dir.new(album_full_path)
|
147
|
+
album_dir.each do |image_name|
|
148
|
+
next if ['.', '..'].include?(image_name)
|
149
|
+
|
150
|
+
image_id += 1
|
151
|
+
image_key = "key#{image_id.to_s(27).tr("0-9a-q", "A-Z")}"
|
152
|
+
|
153
|
+
album["Images"] << {
|
154
|
+
# image urls, for now not used/tested
|
155
|
+
"URL" => "http://url",
|
156
|
+
"OriginalURL" => "http://original_url",
|
157
|
+
"X3LargeURL" => "http://xl3_url",
|
158
|
+
"X2LargeURL" => "http://xl2_url",
|
159
|
+
"XLargeURL" => "http://xl_url",
|
160
|
+
"LargeURL" => "http://large_url",
|
161
|
+
"MediumURL" => "http://medium_url",
|
162
|
+
"SmallURL" => "http://small_url",
|
163
|
+
"TinyURL" => "http://tiny_url",
|
164
|
+
"ThumbURL" => "http://thumb_url",
|
165
|
+
"LightboxURL" => "http://lightbox_url",
|
166
|
+
|
167
|
+
# settings that we don't care about
|
168
|
+
"Status" => "Open",
|
169
|
+
"Keywords" => "",
|
170
|
+
"Hidden" => false,
|
171
|
+
"Serial" => 1,
|
172
|
+
"Position" => 1,
|
173
|
+
"Size" => 91144,
|
174
|
+
"Width" => 638,
|
175
|
+
"Height" => 638,
|
176
|
+
"Format" => "JPG",
|
177
|
+
"Date" => "2012-02-26 02:40:44",
|
178
|
+
"Watermark" => false,
|
179
|
+
"MD5Sum" => "d63af258e5ec78db084af9164a8f3581",
|
180
|
+
"Caption" => "",
|
181
|
+
"LastUpdated" => "2012-02-26 02:42:01",
|
182
|
+
|
183
|
+
# albums settings that we currently care about
|
184
|
+
"FileName" => image_name,
|
185
|
+
"id" => image_id,
|
186
|
+
"Key" => image_key,
|
187
|
+
"Type" => "Album",
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
albums << album
|
192
|
+
end
|
193
|
+
albums
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
*
|