static_sync 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +8 -3
- data/lib/static_sync/config.rb +4 -4
- data/lib/static_sync/processor.rb +70 -0
- data/lib/static_sync/storage.rb +22 -46
- data/lib/static_sync/uploadable.rb +96 -0
- data/lib/static_sync/version.rb +1 -1
- data/lib/static_sync.rb +2 -2
- data/spec/processor_spec.rb +40 -0
- data/spec/uploadable_spec.rb +131 -0
- data/static_sync.gemspec +1 -0
- metadata +24 -13
- data/lib/static_sync/meta/caching.rb +0 -27
- data/lib/static_sync/meta/compression.rb +0 -38
- data/lib/static_sync/meta.rb +0 -43
- data/spec/meta/caching_spec.rb +0 -79
- data/spec/meta/compression_spec.rb +0 -50
- data/spec/meta_spec.rb +0 -50
- data/spec/storage_spec.rb +0 -84
data/README.md
CHANGED
@@ -32,9 +32,6 @@ In your project directory create a `.static` file:
|
|
32
32
|
local:
|
33
33
|
directory: build
|
34
34
|
|
35
|
-
# What not to upload (ruby regular expression).
|
36
|
-
ignored: (psd,gz)$
|
37
|
-
|
38
35
|
# Where to upload
|
39
36
|
remote:
|
40
37
|
provider: AWS
|
@@ -43,12 +40,20 @@ remote:
|
|
43
40
|
password: my-aws-secret
|
44
41
|
directory: my-aws-bucket
|
45
42
|
|
43
|
+
# Everything below this line is optional.
|
44
|
+
|
45
|
+
# What not to upload (ruby regular expression).
|
46
|
+
ignored: (psd,gz)$
|
47
|
+
|
46
48
|
# Number of seconds to cache each content type, defaults to no cache.
|
47
49
|
cache:
|
48
50
|
html: 31536000
|
49
51
|
javascript: 31536000
|
50
52
|
css: 31536000
|
51
53
|
image: 31536000
|
54
|
+
|
55
|
+
# If you wish to strictly prevent modification of existing files.
|
56
|
+
immutable_mode: false
|
52
57
|
```
|
53
58
|
|
54
59
|
And simply run the following command any time you want to upload.
|
data/lib/static_sync/config.rb
CHANGED
@@ -7,6 +7,10 @@ module StaticSync
|
|
7
7
|
self.fetch('log', true)
|
8
8
|
end
|
9
9
|
|
10
|
+
def immutable_mode
|
11
|
+
self.fetch('immutable_mode', false)
|
12
|
+
end
|
13
|
+
|
10
14
|
def cache
|
11
15
|
self.fetch('cache', {})
|
12
16
|
end
|
@@ -33,10 +37,6 @@ module StaticSync
|
|
33
37
|
})
|
34
38
|
end
|
35
39
|
|
36
|
-
def gzip
|
37
|
-
self.fetch('gzip', true)
|
38
|
-
end
|
39
|
-
|
40
40
|
def ignored
|
41
41
|
self['ignored']
|
42
42
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "fog"
|
2
|
+
require "logger"
|
3
|
+
|
4
|
+
require_relative "storage"
|
5
|
+
require_relative "uploadable"
|
6
|
+
|
7
|
+
module StaticSync
|
8
|
+
class Processor
|
9
|
+
|
10
|
+
class ImmutableError < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(config, storage = nil)
|
14
|
+
@config = config
|
15
|
+
@storage = storage || StaticSync::Storage.new(config)
|
16
|
+
end
|
17
|
+
|
18
|
+
def sync
|
19
|
+
log.info("Synching #{@config.source} to #{@config.target}.") if @config.log
|
20
|
+
Dir.chdir(@config.source) do
|
21
|
+
local_filtered_files.each do |file|
|
22
|
+
current_file = Uploadable.new(file, @config)
|
23
|
+
|
24
|
+
unless @storage.has_version?(current_file.version)
|
25
|
+
if @storage.has_file?(current_file.version)
|
26
|
+
log.info("Overwriting #{file}") if @config.log
|
27
|
+
if @config.immutable_mode
|
28
|
+
raise ImmutableError, "immutable_mode does not allow modifications to existing files."
|
29
|
+
end
|
30
|
+
else
|
31
|
+
log.info("Uploading #{file}") if @config.log
|
32
|
+
end
|
33
|
+
begin
|
34
|
+
@storage.create(current_file.headers)
|
35
|
+
rescue => error
|
36
|
+
log.error("Failed to upload #{file}")
|
37
|
+
raise error
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
log.info("Synching done.") if @config.log
|
43
|
+
end
|
44
|
+
|
45
|
+
def local_filtered_files
|
46
|
+
local_files.reject do |file|
|
47
|
+
file =~ Regexp.new(@config.ignored) if @config.ignored
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def local_files
|
54
|
+
Dir.glob("**/*.*").reject do |file|
|
55
|
+
File.directory?(file)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def log
|
60
|
+
@log ||= begin
|
61
|
+
logger = Logger.new(STDOUT)
|
62
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
63
|
+
"#{datetime}: #{severity} -- #{msg}\n"
|
64
|
+
end
|
65
|
+
logger
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
data/lib/static_sync/storage.rb
CHANGED
@@ -1,73 +1,49 @@
|
|
1
1
|
require "fog"
|
2
2
|
require "logger"
|
3
3
|
|
4
|
-
require_relative "meta"
|
5
|
-
|
6
4
|
module StaticSync
|
7
5
|
class Storage
|
8
6
|
|
7
|
+
class Version < Struct.new(:path, :etag)
|
8
|
+
end
|
9
|
+
|
9
10
|
def initialize(config)
|
10
11
|
@config = config
|
11
|
-
@meta = Meta.new(config)
|
12
|
-
end
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
verify_remote_directory
|
17
|
-
remote_keys = []
|
18
|
-
remote_directory.files.each do |file|
|
19
|
-
remote_keys << [file.key, file.etag]
|
20
|
-
end
|
21
|
-
Dir.chdir(@config.source) do
|
22
|
-
local_filtered_files.each do |file|
|
23
|
-
current_file = @meta.for(file)
|
24
|
-
current_file_key = [current_file[:key], current_file[:etag]]
|
13
|
+
validate_credentials!
|
14
|
+
end
|
25
15
|
|
26
|
-
|
27
|
-
|
28
|
-
begin
|
29
|
-
remote_directory.files.create(current_file)
|
30
|
-
rescue => error
|
31
|
-
log.error("Failed to upload #{file}")
|
32
|
-
raise error
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
log.info("Synching done.") if @config.log
|
16
|
+
def has_file?(version)
|
17
|
+
file_versions.map(&:path).include?(version.path)
|
38
18
|
end
|
39
19
|
|
40
|
-
|
20
|
+
def has_version?(version)
|
21
|
+
file_versions.include?(version)
|
22
|
+
end
|
41
23
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
24
|
+
def file_versions
|
25
|
+
@file_versions ||= begin
|
26
|
+
result = []
|
27
|
+
remote_directory.files.each do |file|
|
28
|
+
result << Version.new(file.key, file.etag)
|
29
|
+
end
|
30
|
+
result
|
45
31
|
end
|
46
32
|
end
|
47
33
|
|
48
|
-
def
|
49
|
-
|
50
|
-
File.directory?(file)
|
51
|
-
end
|
34
|
+
def create(headers)
|
35
|
+
remote_directory.files.create(headers)
|
52
36
|
end
|
53
37
|
|
38
|
+
private
|
39
|
+
|
54
40
|
def remote_directory
|
55
41
|
@config.storage.directories.new(:key => @config.target)
|
56
42
|
end
|
57
43
|
|
58
|
-
def
|
44
|
+
def validate_credentials!
|
59
45
|
@config.storage.get_bucket(@config.target)
|
60
46
|
end
|
61
47
|
|
62
|
-
def log
|
63
|
-
@log ||= begin
|
64
|
-
logger = Logger.new(STDOUT)
|
65
|
-
logger.formatter = proc do |severity, datetime, progname, msg|
|
66
|
-
"#{datetime}: #{severity} -- #{msg}\n"
|
67
|
-
end
|
68
|
-
logger
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
48
|
end
|
73
49
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "digest/md5"
|
3
|
+
require "mime/types"
|
4
|
+
require "tempfile"
|
5
|
+
require "zlib"
|
6
|
+
|
7
|
+
require_relative "storage"
|
8
|
+
|
9
|
+
module StaticSync
|
10
|
+
class Uploadable < Struct.new(:path, :config)
|
11
|
+
|
12
|
+
def version
|
13
|
+
Storage::Version.new(path, etag)
|
14
|
+
end
|
15
|
+
|
16
|
+
def mime
|
17
|
+
MIME::Types::of(path).first
|
18
|
+
end
|
19
|
+
|
20
|
+
def content_type
|
21
|
+
if mime
|
22
|
+
MIME::Type.simplified(mime)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def gzipped?
|
27
|
+
mime && mime.ascii?
|
28
|
+
end
|
29
|
+
|
30
|
+
def content_encoding
|
31
|
+
if gzipped?
|
32
|
+
'gzip'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def cache_time
|
37
|
+
if mime
|
38
|
+
type = mime.sub_type
|
39
|
+
type = mime.media_type if mime.media_type == "image"
|
40
|
+
if config.cache.has_key?(type)
|
41
|
+
return config.cache[type].to_i
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def content
|
47
|
+
@content ||= begin
|
48
|
+
result = File.open(path, 'rb') { |f| f.read }
|
49
|
+
if gzipped?
|
50
|
+
result = File.open(gzip(result), 'rb') { |f| f.read }
|
51
|
+
end
|
52
|
+
result
|
53
|
+
rescue
|
54
|
+
""
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def etag
|
59
|
+
@etag ||= Digest::MD5.hexdigest(content)
|
60
|
+
end
|
61
|
+
|
62
|
+
def md5
|
63
|
+
@md5 ||= Digest::MD5.base64digest(content)
|
64
|
+
end
|
65
|
+
|
66
|
+
def headers
|
67
|
+
base_headers = {
|
68
|
+
:key => path,
|
69
|
+
:body => content,
|
70
|
+
:etag => etag,
|
71
|
+
:content_md5 => md5,
|
72
|
+
:storage_class => 'REDUCED_REDUNDANCY',
|
73
|
+
:public => true
|
74
|
+
}
|
75
|
+
base_headers.merge!(:cache_control => "public, max-age=#{cache_time}") if cache_time
|
76
|
+
base_headers.merge!(:content_type => content_type) if content_type
|
77
|
+
base_headers.merge!(:content_encoding => content_encoding) if content_encoding
|
78
|
+
base_headers.merge!(:expires => CGI.rfc1123_date(Time.now + cache_time)) if cache_time
|
79
|
+
base_headers
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def gzip(content)
|
85
|
+
zipped = Tempfile.new("static_sync")
|
86
|
+
Zlib::GzipWriter.open(zipped) do |archive|
|
87
|
+
# Gzip by default incorporates file creation time into the content.
|
88
|
+
# For the purpose of repeatable etag comparison we want avoid this.
|
89
|
+
archive.mtime = 1
|
90
|
+
archive.write content
|
91
|
+
end
|
92
|
+
zipped
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
data/lib/static_sync/version.rb
CHANGED
data/lib/static_sync.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require_relative "static_sync/version"
|
2
2
|
require_relative "static_sync/config"
|
3
|
-
require_relative "static_sync/
|
3
|
+
require_relative "static_sync/processor"
|
4
4
|
|
5
5
|
module StaticSync
|
6
6
|
def self.upload(config = {})
|
7
|
-
|
7
|
+
Processor.new(Config.new.load.merge(config)).sync
|
8
8
|
end
|
9
9
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "./lib/static_sync/config"
|
2
|
+
require "./lib/static_sync/processor"
|
3
|
+
|
4
|
+
describe StaticSync::Processor do
|
5
|
+
|
6
|
+
let(:config) do
|
7
|
+
StaticSync::Config.new.merge({
|
8
|
+
'log' => false,
|
9
|
+
'local' => {
|
10
|
+
'directory' => 'spec/fixtures/site'
|
11
|
+
}
|
12
|
+
})
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:storage) { stub }
|
16
|
+
|
17
|
+
subject do
|
18
|
+
StaticSync::Processor.new(config, storage)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#local_filtered_files" do
|
22
|
+
|
23
|
+
before do
|
24
|
+
config.merge!(
|
25
|
+
'ignored' => '(css|gif)$'
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "does not include files matching the ignore regex" do
|
30
|
+
Dir.chdir(config.source) do
|
31
|
+
subject.local_filtered_files.should == [
|
32
|
+
"assets/javascripts/jquery.min.js",
|
33
|
+
"cat.com/index.html",
|
34
|
+
"index.html"
|
35
|
+
]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require "timecop"
|
2
|
+
|
3
|
+
require "./lib/static_sync/config"
|
4
|
+
require "./lib/static_sync/uploadable"
|
5
|
+
|
6
|
+
describe StaticSync::Uploadable do
|
7
|
+
|
8
|
+
let(:config) { StaticSync::Config.new }
|
9
|
+
|
10
|
+
subject(:html_file) { StaticSync::Uploadable.new("index.html", config) }
|
11
|
+
subject(:text_file) { StaticSync::Uploadable.new("data.txt", config) }
|
12
|
+
subject(:css_file) { StaticSync::Uploadable.new("screen.css", config) }
|
13
|
+
subject(:gif_file) { StaticSync::Uploadable.new("spinner.gif", config) }
|
14
|
+
subject(:png_file) { StaticSync::Uploadable.new("background.png", config) }
|
15
|
+
|
16
|
+
describe "#headers" do
|
17
|
+
|
18
|
+
it "should make all files viewable by everyone" do
|
19
|
+
html_file.headers[:public].should be_true
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should reduce storage costs for all files" do
|
23
|
+
html_file.headers[:storage_class].should == "REDUCED_REDUNDANCY"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should set the content type header for html files" do
|
27
|
+
html_file.headers[:content_type].should == "text/html"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should set the content type header for png files" do
|
31
|
+
png_file.headers[:content_type].should == "image/png"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should set the content encoding header for html files" do
|
35
|
+
html_file.headers[:content_encoding].should == "gzip"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should not set the content encoding header for png files" do
|
39
|
+
png_file.headers[:content_encoding].should be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should set a content md5 header for html files" do
|
43
|
+
html_file.headers[:content_md5].should == "1B2M2Y8AsgTpgAmY7PhCfg=="
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should set an etag header for html files" do
|
47
|
+
html_file.headers[:etag].should == "d41d8cd98f00b204e9800998ecf8427e"
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when caching is enabled for html" do
|
51
|
+
|
52
|
+
before do
|
53
|
+
config['cache'] = { 'html' => 60 * 60 * 24 * 365 }
|
54
|
+
end
|
55
|
+
|
56
|
+
before do
|
57
|
+
Timecop.freeze(Time.local(2010))
|
58
|
+
end
|
59
|
+
|
60
|
+
it "sets the cache control header for html files" do
|
61
|
+
html_file.headers[:cache_control].should == "public, max-age=31536000"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "sets the expires header for html files " do
|
65
|
+
html_file.headers[:expires].should == "Fri, 31 Dec 2010 13:00:00 GMT"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#cache_time" do
|
72
|
+
|
73
|
+
context "when caching is disabled" do
|
74
|
+
|
75
|
+
before do
|
76
|
+
config['cache'] = { }
|
77
|
+
end
|
78
|
+
|
79
|
+
it "is nil for html files" do
|
80
|
+
html_file.cache_time.should be_nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "when caching is enabled for html" do
|
85
|
+
|
86
|
+
before do
|
87
|
+
config['cache'] = { 'html' => '86400' }
|
88
|
+
end
|
89
|
+
|
90
|
+
it "is the configured value for html files" do
|
91
|
+
html_file.cache_time.should == 86400
|
92
|
+
end
|
93
|
+
|
94
|
+
it "is the configured value for text files" do
|
95
|
+
text_file.cache_time.should be_nil
|
96
|
+
end
|
97
|
+
|
98
|
+
it "is nil for css files" do
|
99
|
+
css_file.cache_time.should be_nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "when caching is enabled for images" do
|
104
|
+
|
105
|
+
before do
|
106
|
+
config['cache'] = { 'image' => '86400' }
|
107
|
+
end
|
108
|
+
|
109
|
+
it "is the configured value for gif files" do
|
110
|
+
gif_file.cache_time.should == 86400
|
111
|
+
end
|
112
|
+
|
113
|
+
it "is the configured value for png files" do
|
114
|
+
png_file.cache_time.should == 86400
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "#gzipped?" do
|
121
|
+
|
122
|
+
it "is true for html files" do
|
123
|
+
html_file.gzipped?.should be_true
|
124
|
+
end
|
125
|
+
|
126
|
+
it "is false for png files" do
|
127
|
+
png_file.gzipped?.should be_false
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
data/static_sync.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: static_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fog
|
@@ -43,6 +43,22 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: timecop
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
46
62
|
description:
|
47
63
|
email:
|
48
64
|
- shanonmcquay@gmail.com
|
@@ -59,10 +75,9 @@ files:
|
|
59
75
|
- bin/static_sync
|
60
76
|
- lib/static_sync.rb
|
61
77
|
- lib/static_sync/config.rb
|
62
|
-
- lib/static_sync/
|
63
|
-
- lib/static_sync/meta/caching.rb
|
64
|
-
- lib/static_sync/meta/compression.rb
|
78
|
+
- lib/static_sync/processor.rb
|
65
79
|
- lib/static_sync/storage.rb
|
80
|
+
- lib/static_sync/uploadable.rb
|
66
81
|
- lib/static_sync/version.rb
|
67
82
|
- spec/fixtures/site/assets/images/spinner.gif
|
68
83
|
- spec/fixtures/site/assets/javascripts/jquery.min.js
|
@@ -70,10 +85,8 @@ files:
|
|
70
85
|
- spec/fixtures/site/assets/stylesheets/screen.scss
|
71
86
|
- spec/fixtures/site/cat.com/index.html
|
72
87
|
- spec/fixtures/site/index.html
|
73
|
-
- spec/
|
74
|
-
- spec/
|
75
|
-
- spec/meta_spec.rb
|
76
|
-
- spec/storage_spec.rb
|
88
|
+
- spec/processor_spec.rb
|
89
|
+
- spec/uploadable_spec.rb
|
77
90
|
- static_sync.gemspec
|
78
91
|
homepage:
|
79
92
|
licenses: []
|
@@ -100,7 +113,5 @@ signing_key:
|
|
100
113
|
specification_version: 3
|
101
114
|
summary: Command line tool for uploading static websites to amazon/rackspace.
|
102
115
|
test_files:
|
103
|
-
- spec/
|
104
|
-
- spec/
|
105
|
-
- spec/meta_spec.rb
|
106
|
-
- spec/storage_spec.rb
|
116
|
+
- spec/processor_spec.rb
|
117
|
+
- spec/uploadable_spec.rb
|
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'cgi'
|
2
|
-
|
3
|
-
module StaticSync
|
4
|
-
class Meta
|
5
|
-
class Caching
|
6
|
-
|
7
|
-
def initialize(config)
|
8
|
-
@config = config
|
9
|
-
end
|
10
|
-
|
11
|
-
def for(file, mime)
|
12
|
-
meta = {}
|
13
|
-
type = mime.sub_type
|
14
|
-
type = mime.media_type if mime.media_type == "image"
|
15
|
-
if @config.cache.has_key?(type)
|
16
|
-
expiry = @config.cache[type].to_i
|
17
|
-
meta.merge!(
|
18
|
-
:cache_control => "public, max-age=#{expiry}",
|
19
|
-
:expires => CGI.rfc1123_date(Time.now + expiry)
|
20
|
-
)
|
21
|
-
end
|
22
|
-
meta
|
23
|
-
end
|
24
|
-
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
require "tempfile"
|
2
|
-
require "zlib"
|
3
|
-
|
4
|
-
module StaticSync
|
5
|
-
class Meta
|
6
|
-
class Compression
|
7
|
-
|
8
|
-
def initialize(config)
|
9
|
-
@config = config
|
10
|
-
end
|
11
|
-
|
12
|
-
def for(file, mime)
|
13
|
-
meta = {}
|
14
|
-
if @config.gzip && !mime.binary?
|
15
|
-
meta.merge!(
|
16
|
-
:body => gzip(file),
|
17
|
-
:content_encoding => 'gzip'
|
18
|
-
)
|
19
|
-
end
|
20
|
-
meta
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def gzip(file)
|
26
|
-
zipped = Tempfile.new("static_sync")
|
27
|
-
Zlib::GzipWriter.open(zipped) do |archive|
|
28
|
-
# Gzip by default incorporates file creation time into the content.
|
29
|
-
# For the purpose of repeatable etag comparison we want avoid this.
|
30
|
-
archive.mtime = 1
|
31
|
-
archive.write File.read(file)
|
32
|
-
end
|
33
|
-
zipped
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
data/lib/static_sync/meta.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
require "digest/md5"
|
2
|
-
require "mime/types"
|
3
|
-
|
4
|
-
require_relative "meta/caching"
|
5
|
-
require_relative "meta/compression"
|
6
|
-
|
7
|
-
module StaticSync
|
8
|
-
class Meta
|
9
|
-
|
10
|
-
def initialize(config)
|
11
|
-
@config = config
|
12
|
-
@compression = Compression.new(@config)
|
13
|
-
@caching = Caching.new(@config)
|
14
|
-
end
|
15
|
-
|
16
|
-
def for(file)
|
17
|
-
meta = {
|
18
|
-
:key => file,
|
19
|
-
:body => File.new(file),
|
20
|
-
:public => true,
|
21
|
-
:storage_class => 'REDUCED_REDUNDANCY'
|
22
|
-
}
|
23
|
-
|
24
|
-
mime = MIME::Types::of(file).first
|
25
|
-
|
26
|
-
if mime
|
27
|
-
meta.merge!(
|
28
|
-
:content_type => MIME::Type.simplified(mime)
|
29
|
-
)
|
30
|
-
meta.merge!(@compression.for(file, mime))
|
31
|
-
meta.merge!(@caching.for(file, mime))
|
32
|
-
end
|
33
|
-
|
34
|
-
body = meta[:body].read
|
35
|
-
|
36
|
-
meta.merge!(
|
37
|
-
:etag => Digest::MD5.hexdigest(body), # A local version of the etag expected after upload.
|
38
|
-
:content_md5 => Digest::MD5.base64digest(body) # Enable checksum validation during uploads.
|
39
|
-
)
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
end
|
data/spec/meta/caching_spec.rb
DELETED
@@ -1,79 +0,0 @@
|
|
1
|
-
require "mime/types"
|
2
|
-
require "./lib/static_sync/config"
|
3
|
-
require "./lib/static_sync/meta/caching"
|
4
|
-
|
5
|
-
describe StaticSync::Meta::Caching do
|
6
|
-
|
7
|
-
let(:config) { StaticSync::Config.new }
|
8
|
-
|
9
|
-
let(:html) { MIME::Type.new('text/html') }
|
10
|
-
let(:plain) { MIME::Type.new('text/plain') }
|
11
|
-
let(:css) { MIME::Type.new('text/css') }
|
12
|
-
let(:gif) { MIME::Type.new('image/gif') }
|
13
|
-
let(:jpg) { MIME::Type.new('image/jpg') }
|
14
|
-
let(:png) { MIME::Type.new('image/png') }
|
15
|
-
|
16
|
-
subject do
|
17
|
-
StaticSync::Meta::Caching.new(config)
|
18
|
-
end
|
19
|
-
|
20
|
-
context "when caching is completely disabled" do
|
21
|
-
|
22
|
-
before do
|
23
|
-
config['cache'] = { }
|
24
|
-
end
|
25
|
-
|
26
|
-
describe "#for" do
|
27
|
-
it "does not set cache headers for html files" do
|
28
|
-
subject.for("index.html", html).should_not include(:cache_control)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
context "when caching is enabled for html only" do
|
34
|
-
|
35
|
-
before do
|
36
|
-
config['cache'] = { 'html' => '86400' }
|
37
|
-
end
|
38
|
-
|
39
|
-
describe "#for" do
|
40
|
-
it "sets cache headers for html files" do
|
41
|
-
subject.for("index.html", html).should include(:cache_control)
|
42
|
-
end
|
43
|
-
|
44
|
-
it "sets expires headers for html files" do
|
45
|
-
subject.for("index.html", html).should include(:expires)
|
46
|
-
end
|
47
|
-
|
48
|
-
it "does not set cache headers for text files" do
|
49
|
-
subject.for("data.txt", plain).should_not include(:cache_control)
|
50
|
-
end
|
51
|
-
|
52
|
-
it "does not set cache headers for css files" do
|
53
|
-
subject.for("screen.css", css).should_not include(:cache_control)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
context "when caching is enabled for images" do
|
59
|
-
|
60
|
-
before do
|
61
|
-
config['cache'] = { 'image' => '86400' }
|
62
|
-
end
|
63
|
-
|
64
|
-
describe "#for" do
|
65
|
-
it "sets cache headers for gif files" do
|
66
|
-
subject.for("spinner.gif", gif).should include(:cache_control)
|
67
|
-
end
|
68
|
-
|
69
|
-
it "sets cache headers for jpg files" do
|
70
|
-
subject.for("kitten.jpg", jpg).should include(:cache_control)
|
71
|
-
end
|
72
|
-
|
73
|
-
it "sets cache headers for png files" do
|
74
|
-
subject.for("cat.png", png).should include(:cache_control)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
require "mime/types"
|
2
|
-
require "./lib/static_sync/config"
|
3
|
-
require "./lib/static_sync/meta/compression"
|
4
|
-
|
5
|
-
describe StaticSync::Meta::Compression do
|
6
|
-
|
7
|
-
let(:config) { StaticSync::Config.new }
|
8
|
-
|
9
|
-
let(:html) { MIME::Type.new('text/html') }
|
10
|
-
let(:image) { MIME::Type.new('image/gif') }
|
11
|
-
|
12
|
-
subject do
|
13
|
-
StaticSync::Meta::Compression.new(config)
|
14
|
-
end
|
15
|
-
|
16
|
-
before do
|
17
|
-
File.stub(:read).and_return("")
|
18
|
-
end
|
19
|
-
|
20
|
-
context "when compression is disabled" do
|
21
|
-
|
22
|
-
before do
|
23
|
-
config['gzip'] = false
|
24
|
-
end
|
25
|
-
|
26
|
-
describe "#for" do
|
27
|
-
it "does not set compression headers for html files" do
|
28
|
-
subject.for("index.html", html).should_not include(:content_encoding => "gzip")
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
context "when compression is enabled" do
|
34
|
-
|
35
|
-
before do
|
36
|
-
config['gzip'] = true
|
37
|
-
end
|
38
|
-
|
39
|
-
describe "#for" do
|
40
|
-
it "sets compression headers for html files" do
|
41
|
-
subject.for("index.html", html).should include(:content_encoding => "gzip")
|
42
|
-
end
|
43
|
-
|
44
|
-
it "does not set compression headers for binary files" do
|
45
|
-
subject.for("assets/images/spinner.gif", image).should_not include(:content_encoding => "gzip")
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
end
|
data/spec/meta_spec.rb
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
require "./lib/static_sync/config"
|
2
|
-
require "./lib/static_sync/meta"
|
3
|
-
|
4
|
-
describe StaticSync::Meta do
|
5
|
-
|
6
|
-
let(:config) { StaticSync::Config.new }
|
7
|
-
|
8
|
-
subject do
|
9
|
-
StaticSync::Meta.new(config)
|
10
|
-
end
|
11
|
-
|
12
|
-
it "sets files as public" do
|
13
|
-
Dir.chdir("spec/fixtures/site") do
|
14
|
-
subject.for("index.html").should include(:public => true)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
it "sets appropriate content types for a range of common file types" do
|
19
|
-
Dir.chdir("spec/fixtures/site") do
|
20
|
-
subject.for("index.html").should include(:content_type => "text/html")
|
21
|
-
subject.for("assets/images/spinner.gif").should include(:content_type => "image/gif")
|
22
|
-
subject.for("assets/stylesheets/screen.css").should include(:content_type => "text/css")
|
23
|
-
subject.for("assets/javascripts/jquery.min.js").should include(:content_type => "application/javascript")
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
describe "all files" do
|
28
|
-
|
29
|
-
it "should be public" do
|
30
|
-
Dir.chdir("spec/fixtures/site") do
|
31
|
-
subject.for("index.html").should include(
|
32
|
-
:key => "index.html",
|
33
|
-
:content_type => "text/html",
|
34
|
-
:public => true
|
35
|
-
)
|
36
|
-
subject.for("assets/stylesheets/screen.css").should include(
|
37
|
-
:key => "assets/stylesheets/screen.css",
|
38
|
-
:content_type => "text/css",
|
39
|
-
:public => true
|
40
|
-
)
|
41
|
-
subject.for("assets/javascripts/jquery.min.js").should include(
|
42
|
-
:key => "assets/javascripts/jquery.min.js",
|
43
|
-
:content_type => "application/javascript",
|
44
|
-
:public => true
|
45
|
-
)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
end
|
data/spec/storage_spec.rb
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
require "./lib/static_sync/config"
|
2
|
-
require "./lib/static_sync/storage"
|
3
|
-
|
4
|
-
describe StaticSync::Storage do
|
5
|
-
|
6
|
-
let(:config) do
|
7
|
-
StaticSync::Config.new.merge({
|
8
|
-
'log' => false,
|
9
|
-
'local' => {
|
10
|
-
'directory' => 'spec/fixtures/site'
|
11
|
-
},
|
12
|
-
'remote' => {
|
13
|
-
'provider' => 'AWS',
|
14
|
-
'username' => 'lol',
|
15
|
-
'password' => 'cat',
|
16
|
-
'directory' => 'bucket'
|
17
|
-
}
|
18
|
-
})
|
19
|
-
end
|
20
|
-
|
21
|
-
subject do
|
22
|
-
StaticSync::Storage.new(config)
|
23
|
-
end
|
24
|
-
|
25
|
-
before do
|
26
|
-
Fog.mock!
|
27
|
-
end
|
28
|
-
|
29
|
-
after do
|
30
|
-
Fog::Mock.reset
|
31
|
-
end
|
32
|
-
|
33
|
-
context "with a remote storage directory" do
|
34
|
-
|
35
|
-
before do
|
36
|
-
config.storage.directories.create(
|
37
|
-
:key => config.storage_directory,
|
38
|
-
:public => true
|
39
|
-
)
|
40
|
-
end
|
41
|
-
|
42
|
-
context "syncing a new site" do
|
43
|
-
|
44
|
-
describe "#sync" do
|
45
|
-
|
46
|
-
it "sets a unique key for each uploaded file" do
|
47
|
-
subject.sync
|
48
|
-
|
49
|
-
config.storage.directories.get(config.storage_directory).files.map(&:key).should == [
|
50
|
-
"assets/images/spinner.gif",
|
51
|
-
"assets/javascripts/jquery.min.js",
|
52
|
-
"assets/stylesheets/screen.css",
|
53
|
-
"assets/stylesheets/screen.scss",
|
54
|
-
"cat.com/index.html",
|
55
|
-
"index.html"
|
56
|
-
]
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
context "synching ignored files" do
|
62
|
-
|
63
|
-
describe "#sync" do
|
64
|
-
|
65
|
-
before do
|
66
|
-
config.merge!(
|
67
|
-
'ignored' => '(css|gif)$'
|
68
|
-
)
|
69
|
-
end
|
70
|
-
|
71
|
-
it "does not upload them" do
|
72
|
-
subject.sync
|
73
|
-
|
74
|
-
config.storage.directories.get(config.storage_directory).files.map(&:key).should == [
|
75
|
-
"assets/javascripts/jquery.min.js",
|
76
|
-
"cat.com/index.html",
|
77
|
-
"index.html"
|
78
|
-
]
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|