static_sync 0.1.6 → 0.1.7
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.
- 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
|