asset_id 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.0
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: []