asset_id 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/asset_id/asset.rb +154 -0
- data/lib/asset_id/backend/s3.rb +73 -0
- data/lib/asset_id/cache.rb +33 -0
- metadata +4 -1
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'mime/types'
|
3
|
+
require 'time'
|
4
|
+
require 'zlib'
|
5
|
+
|
6
|
+
module AssetID
|
7
|
+
class Asset
|
8
|
+
|
9
|
+
DEFAULT_ASSET_PATHS = ['favicon.ico', 'images', 'javascripts', 'stylesheets']
|
10
|
+
@@asset_paths = DEFAULT_ASSET_PATHS
|
11
|
+
|
12
|
+
DEFAULT_GZIP_TYPES = ['text/css', 'application/javascript']
|
13
|
+
@@gzip_types = DEFAULT_GZIP_TYPES
|
14
|
+
|
15
|
+
@@debug = false
|
16
|
+
@@nocache = false
|
17
|
+
|
18
|
+
def self.init(options)
|
19
|
+
@@debug = options[:debug] if options[:debug]
|
20
|
+
@@nocache = options[:nocache] if options[:nocache]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.asset_paths
|
24
|
+
@@asset_paths
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.gzip_types
|
28
|
+
@@gzip_types
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.asset_paths=(paths)
|
32
|
+
@@asset_paths = paths
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.gzip_types=(types)
|
36
|
+
@@gzip_types = types
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.find(paths=Asset.asset_paths)
|
40
|
+
paths.inject([]) {|assets, path|
|
41
|
+
path = File.join Rails.root, 'public', path
|
42
|
+
a = Asset.new(path)
|
43
|
+
assets << a if a.is_file? and !a.cache_hit?
|
44
|
+
assets += Dir.glob(path+'/**/*').inject([]) {|m, file|
|
45
|
+
a = Asset.new(file); m << a if a.is_file? and !a.cache_hit?; m
|
46
|
+
}
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.fingerprint(path)
|
51
|
+
Asset.new(path).fingerprint
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :path
|
55
|
+
|
56
|
+
def initialize(path)
|
57
|
+
@path = path
|
58
|
+
@path = absolute_path
|
59
|
+
end
|
60
|
+
|
61
|
+
def path_prefix
|
62
|
+
File.join Rails.root, 'public'
|
63
|
+
end
|
64
|
+
|
65
|
+
def absolute_path
|
66
|
+
path =~ /#{path_prefix}/ ? path : File.join(path_prefix, path)
|
67
|
+
end
|
68
|
+
|
69
|
+
def relative_path
|
70
|
+
path.gsub(path_prefix, '')
|
71
|
+
end
|
72
|
+
|
73
|
+
def gzip_type?
|
74
|
+
Asset.gzip_types.include? mime_type
|
75
|
+
end
|
76
|
+
|
77
|
+
def data
|
78
|
+
@data ||= File.read(path)
|
79
|
+
end
|
80
|
+
|
81
|
+
def md5
|
82
|
+
@digest ||= Digest::MD5.hexdigest(data)
|
83
|
+
end
|
84
|
+
|
85
|
+
def fingerprint
|
86
|
+
p = relative_path
|
87
|
+
File.join File.dirname(p), "#{File.basename(p, File.extname(p))}-id-#{md5}#{File.extname(p)}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def mime_type
|
91
|
+
MIME::Types.of(path).first.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
def css?
|
95
|
+
mime_type == 'text/css'
|
96
|
+
end
|
97
|
+
|
98
|
+
def replace_css_images!(options={})
|
99
|
+
options[:prefix] ||= ''
|
100
|
+
# adapted from https://github.com/blakink/asset_id
|
101
|
+
data.gsub! /url\((?:"([^"]*)"|'([^']*)'|([^)]*))\)/mi do |match|
|
102
|
+
begin
|
103
|
+
# $1 is the double quoted string, $2 is single quoted, $3 is no quotes
|
104
|
+
uri = ($1 || $2 || $3).to_s.strip
|
105
|
+
uri.gsub!(/^\.\.\//, '/')
|
106
|
+
|
107
|
+
# if the uri appears to begin with a protocol then the asset isn't on the local filesystem
|
108
|
+
if uri =~ /[a-z]+:\/\//i
|
109
|
+
"url(#{uri})"
|
110
|
+
else
|
111
|
+
asset = Asset.new(uri)
|
112
|
+
puts " - Changing CSS URI #{uri} to #{options[:prefix]}#{asset.fingerprint}" if @@debug
|
113
|
+
"url(#{options[:prefix]}#{asset.fingerprint})"
|
114
|
+
end
|
115
|
+
rescue Errno::ENOENT => e
|
116
|
+
puts " - Warning: #{uri} not found" if @@debug
|
117
|
+
"url(#{uri})"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def gzip!
|
123
|
+
# adapted from https://github.com/blakink/asset_id
|
124
|
+
@data = returning StringIO.open('', 'w') do |gz_data|
|
125
|
+
gz = Zlib::GzipWriter.new(gz_data, nil, nil)
|
126
|
+
gz.write(data)
|
127
|
+
gz.close
|
128
|
+
end.string
|
129
|
+
end
|
130
|
+
|
131
|
+
def expiry_date
|
132
|
+
@expiry_date ||= (Time.now + (60*60*24*365)).httpdate
|
133
|
+
end
|
134
|
+
|
135
|
+
def cache_headers
|
136
|
+
{'Expires' => expiry_date, 'Cache-Control' => 'public'} # 1 year expiry
|
137
|
+
end
|
138
|
+
|
139
|
+
def gzip_headers
|
140
|
+
{'Content-Encoding' => 'gzip', 'Vary' => 'Accept-Encoding'}
|
141
|
+
end
|
142
|
+
|
143
|
+
def is_file?
|
144
|
+
File.exists? absolute_path and !File.directory? absolute_path
|
145
|
+
end
|
146
|
+
|
147
|
+
def cache_hit?
|
148
|
+
return false if @@nocache or Cache.miss? self
|
149
|
+
puts "AssetID: #{relative_path} - Cache Hit"
|
150
|
+
return true
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'aws/s3'
|
2
|
+
|
3
|
+
module AssetID
|
4
|
+
class S3
|
5
|
+
|
6
|
+
def self.s3_config
|
7
|
+
@@config ||= YAML.load_file(File.join(Rails.root, "config/asset_id.yml"))[Rails.env] rescue nil || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.connect_to_s3
|
11
|
+
AWS::S3::Base.establish_connection!(
|
12
|
+
:access_key_id => s3_config['access_key_id'],
|
13
|
+
:secret_access_key => s3_config['secret_access_key']
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.s3_permissions
|
18
|
+
:public_read
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.s3_bucket
|
22
|
+
s3_config['bucket']
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.s3_prefix
|
26
|
+
s3_config['prefix']
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.upload(options={})
|
30
|
+
Asset.init(:debug => options[:debug])
|
31
|
+
|
32
|
+
assets = Asset.find
|
33
|
+
return if assets.empty?
|
34
|
+
|
35
|
+
connect_to_s3
|
36
|
+
|
37
|
+
assets.each do |asset|
|
38
|
+
|
39
|
+
puts "AssetID: #{asset.relative_path}" if options[:debug]
|
40
|
+
|
41
|
+
headers = {
|
42
|
+
:content_type => asset.mime_type,
|
43
|
+
:access => s3_permissions,
|
44
|
+
}.merge(asset.cache_headers)
|
45
|
+
|
46
|
+
asset.replace_css_images!(:prefix => s3_prefix) if asset.css?
|
47
|
+
|
48
|
+
if asset.gzip_type?
|
49
|
+
headers.merge!(asset.gzip_headers)
|
50
|
+
asset.gzip!
|
51
|
+
end
|
52
|
+
|
53
|
+
if options[:debug]
|
54
|
+
puts " - Uploading: #{asset.fingerprint} [#{asset.data.size} bytes]"
|
55
|
+
puts " - Headers: #{headers.inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
unless options[:dry_run]
|
59
|
+
res = AWS::S3::S3Object.store(
|
60
|
+
asset.fingerprint,
|
61
|
+
asset.data,
|
62
|
+
s3_bucket,
|
63
|
+
headers
|
64
|
+
)
|
65
|
+
puts " - Response: #{res.inspect}" if options[:debug]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
Cache.save! unless options[:dry_run]
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module AssetID
|
4
|
+
class Cache
|
5
|
+
|
6
|
+
def self.empty
|
7
|
+
@cache = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.cache
|
11
|
+
@cache ||= YAML.load_file(cache_path) rescue {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.cache_path
|
15
|
+
File.join(Rails.root, 'log', 'asset_id_cache.yml')
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.hit?(asset)
|
19
|
+
return true if cache[asset.relative_path] and cache[asset.relative_path][:fingerprint] == asset.fingerprint
|
20
|
+
cache[asset.relative_path] = {:expires => asset.expiry_date.to_s, :fingerprint => asset.fingerprint}
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.miss?(asset)
|
25
|
+
!hit?(asset)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.save!
|
29
|
+
File.open(cache_path, 'w') {|f| f.write(YAML.dump(cache))}
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: asset_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.2.
|
5
|
+
version: 0.2.1
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Richard Taylor
|
@@ -47,6 +47,9 @@ files:
|
|
47
47
|
- LICENSE
|
48
48
|
- README.textile
|
49
49
|
- lib/asset_id.rb
|
50
|
+
- lib/asset_id/asset.rb
|
51
|
+
- lib/asset_id/cache.rb
|
52
|
+
- lib/asset_id/backend/s3.rb
|
50
53
|
has_rdoc: true
|
51
54
|
homepage: http://github.com/moomerman/asset_id
|
52
55
|
licenses: []
|