thorero-cache 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,203 @@
1
+ class Merb::Cache
2
+ cattr_accessor :cached_pages
3
+ self.cached_pages = {}
4
+ end
5
+
6
+ module Merb::Cache::ControllerClassMethods
7
+ # Mixed in Merb::Controller. Provides class methods related to page caching
8
+ # Page caching is mostly action caching with file backend using its own output directory of .html files
9
+
10
+ # Register the action for page caching
11
+ #
12
+ # ==== Parameters
13
+ # action<Symbol>:: The name of the action to register
14
+ # from_now<~minutes>::
15
+ # The number of minutes (from now) the cache should persist
16
+ #
17
+ # ==== Examples
18
+ # cache_page :mostly_static
19
+ # cache_page :barely_dynamic, 10
20
+ def cache_page(action, from_now = nil)
21
+ cache_pages([action, from_now])
22
+ end
23
+
24
+ # Register actions for page caching (before and after filters)
25
+ #
26
+ # ==== Parameter
27
+ # pages<Symbol,Array[Symbol,~minutes]>:: See #cache_page
28
+ #
29
+ # ==== Example
30
+ # cache_pages :mostly_static, [:barely_dynamic, 10]
31
+ def cache_pages(*pages)
32
+ if pages.any? && !Merb::Cache.cached_pages.key?(controller_name)
33
+ before(:cache_page_before)
34
+ after(:cache_page_after)
35
+ end
36
+ pages.each do |action, from_now|
37
+ _pages = Merb::Cache.cached_pages[controller_name] ||= {}
38
+ _pages[action] = [from_now, 0]
39
+ end
40
+ true
41
+ end
42
+ end
43
+
44
+ module Merb::Cache::ControllerInstanceMethods
45
+ # Mixed in Merb::Controller. Provides methods related to page caching
46
+
47
+ DEFAULT_PAGE_EXTENSION = 'html'
48
+
49
+ # Checks whether a cache entry exists
50
+ #
51
+ # ==== Parameter
52
+ # options<String,Hash>:: The options that will be passed to #key_for
53
+ #
54
+ # ==== Returns
55
+ # true if the cache entry exists, false otherwise
56
+ #
57
+ # ==== Example
58
+ # cached_page?(:action => 'show', :params => [params[:page]])
59
+ # cached_page?(:action => 'show', :extension => 'js')
60
+ def cached_page?(options)
61
+ key = Merb::Controller._cache.key_for(options, controller_name, true)
62
+ extension = options[:extension] || DEFAULT_PAGE_EXTENSION
63
+ File.file?(Merb::Controller._cache.config[:cache_html_directory] / "#{key}.#{extension}")
64
+ end
65
+
66
+ # Expires the page identified by the key computed after the parameters
67
+ #
68
+ # ==== Parameter
69
+ # options<String,Hash>:: The options that will be passed to #expire_key_for
70
+ #
71
+ # ==== Examples (See Merb::Cache#expire_key_for for more options)
72
+ # # will expire path/to/page/cache/news/show/1.html
73
+ # expire_page(:key => url(:news,News.find(1)))
74
+ #
75
+ # # will expire path/to/page/cache/news/show.html
76
+ # expire_page(:action => 'show', :controller => 'news')
77
+ #
78
+ # # will expire path/to/page/cache/news/show*
79
+ # expire_page(:action => 'show', :match => true)
80
+ #
81
+ # # will expire path/to/page/cache/news/show.js
82
+ # expire_page(:action => 'show', :extension => 'js')
83
+ def expire_page(options)
84
+ config_dir = Merb::Controller._cache.config[:cache_html_directory]
85
+ Merb::Controller._cache.expire_key_for(options, controller_name, true) do |key, match|
86
+ if match
87
+ files = Dir.glob(config_dir / "#{key}*")
88
+ else
89
+ extension = options[:extension] || DEFAULT_PAGE_EXTENSION
90
+ files = config_dir / "#{key}.#{extension}"
91
+ end
92
+ FileUtils.rm_rf(files)
93
+ end
94
+ true
95
+ end
96
+
97
+ # Expires all the pages stored in config[:cache_html_directory]
98
+ def expire_all_pages
99
+ FileUtils.rm_rf(Dir.glob(Merb::Controller._cache.config[:cache_html_directory] / "*"))
100
+ end
101
+
102
+ # You can call this method if you need to prevent caching the page
103
+ # after it has been rendered.
104
+ def abort_cache_page
105
+ @capture_page = false
106
+ end
107
+
108
+ private
109
+
110
+ # Called by the before and after filters. Stores or recalls a cache entry.
111
+ # The name used for the cache file is based on request.path
112
+ # If the name ends with "/" then it is removed
113
+ # If the name is "/" then it will be replaced by "index"
114
+ #
115
+ # ==== Parameters
116
+ # data<String>:: the data to put in cache
117
+ #
118
+ # ==== Examples
119
+ # All the file are written to config[:cache_html_directory]
120
+ # If request.path is "/", the name will be "/index.html"
121
+ # If request.path is "/news/show/1", the name will be "/news/show/1.html"
122
+ # If request.path is "/news/show/", the name will be "/news/show.html"
123
+ # If request.path is "/news/styles.css", the name will be "/news/styles.css"
124
+ def _cache_page(data = nil)
125
+ return if Merb::Controller._cache.config[:disable_page_caching]
126
+ controller = controller_name
127
+ action = action_name.to_sym
128
+ pages = Merb::Controller._cache.cached_pages[controller]
129
+ return unless pages && pages.key?(action)
130
+ path = request.path.chomp("/")
131
+ path = "index" if path.empty?
132
+ no_format = params[:format].nil? || params[:format].empty?
133
+ ext = "." + (no_format ? DEFAULT_PAGE_EXTENSION : params[:format])
134
+ ext = nil if File.extname(path) == ext
135
+ cache_file = Merb::Controller._cache.config[:cache_html_directory] / "#{path}#{ext}"
136
+ if data
137
+ cache_directory = File.dirname(cache_file)
138
+ FileUtils.mkdir_p(cache_directory)
139
+ _expire_in = pages[action][0]
140
+ pages[action][1] = _expire_in.minutes.from_now unless _expire_in.nil?
141
+ cache_write_page(cache_file, data)
142
+ Merb.logger.info("cache: set (#{path})")
143
+ else
144
+ @capture_page = false
145
+ if File.file?(cache_file)
146
+ _data = cache_read_page(cache_file)
147
+ _expire_in, _expire_at = pages[action]
148
+ if _expire_in.nil? || Time.now.to_i < _expire_at.to_i
149
+ Merb.logger.info("cache: hit (#{path})")
150
+ throw(:halt, _data)
151
+ end
152
+ FileUtils.rm_f(cache_file)
153
+ end
154
+ @capture_page = true
155
+ end
156
+ true
157
+ end
158
+
159
+ # Read data from a file using exclusive lock
160
+ #
161
+ # ==== Parameters
162
+ # cache_file<String>:: the full path to the file
163
+ #
164
+ # ==== Returns
165
+ # data<String>:: the data that has been read from the file
166
+ def cache_read_page(cache_file)
167
+ _data = nil
168
+ File.open(cache_file, "r") do |cache_data|
169
+ cache_data.flock(File::LOCK_EX)
170
+ _data = cache_data.read
171
+ cache_data.flock(File::LOCK_UN)
172
+ end
173
+ _data
174
+ end
175
+
176
+ # Write data to a file using exclusive lock
177
+ #
178
+ # ==== Parameters
179
+ # cache_file<String>:: the full path to the file
180
+ # data<String>:: the data that will be written to the file
181
+ def cache_write_page(cache_file, data)
182
+ File.open(cache_file, "w+") do |cache_data|
183
+ cache_data.flock(File::LOCK_EX)
184
+ cache_data.write(data)
185
+ cache_data.flock(File::LOCK_UN)
186
+ end
187
+ true
188
+ end
189
+
190
+ # before filter
191
+ def cache_page_before
192
+ # recalls a cached entry or set @capture_page to true in order
193
+ # to grab the response in the after filter
194
+ _cache_page
195
+ end
196
+
197
+ # after filter
198
+ def cache_page_after
199
+ # takes the body of the response
200
+ # if the cache entry expired, if it doesn't exist or status is 200
201
+ _cache_page(body) if @capture_page && status == 200
202
+ end
203
+ end
@@ -0,0 +1,88 @@
1
+ module Merb::Cache::DatabaseStore::ActiveRecord
2
+ # Module that provides ActiveRecord support for the database backend
3
+
4
+ # The cache model migration
5
+ class CacheMigration < ActiveRecord::Migration
6
+ def self.up
7
+ create_table (Merb::Controller._cache.config[:table_name]), :primary_key => :ckey do |t|
8
+ t.column :ckey, :string
9
+ t.column :data, :text
10
+ t.datetime :expire, :default => nil
11
+ end
12
+ end
13
+
14
+ def self.down
15
+ drop_table Merb::Controller._cache.config[:table_name]
16
+ end
17
+ end
18
+
19
+ # The cache model
20
+ class CacheModel < ActiveRecord::Base
21
+ set_table_name Merb::Controller._cache.config[:table_name]
22
+
23
+ # Fetch data from the database using the specified key
24
+ # The entry is deleted if it has expired
25
+ #
26
+ # ==== Parameter
27
+ # key<Sting>:: The key identifying the cache entry
28
+ #
29
+ # ==== Returns
30
+ # data<String, NilClass>::
31
+ # nil is returned whether the entry expired or was not found
32
+ def self.cache_get(key)
33
+ if entry = self.find(:first, :conditions => ["ckey=?", key])
34
+ return entry.data if entry.expire.nil? || Time.now < entry.expire
35
+ self.expire(key)
36
+ end
37
+ nil
38
+ end
39
+
40
+ # Store data to the database using the specified key
41
+ #
42
+ # ==== Parameters
43
+ # key<Sting>:: The key identifying the cache entry
44
+ # data<String>:: The data to be put in cache
45
+ # expire<~minutes>::
46
+ # The number of minutes (from now) the cache should persist
47
+ # get<Boolean>::
48
+ # used internally to behave like this
49
+ # - when set to true, perform update_or_create on the cache entry
50
+ # - when set to false, force creation of the cache entry
51
+ def self.cache_set(key, data, expire = nil, get = true)
52
+ attributes = {:ckey => key, :data => data, :expire => expire }
53
+ if get
54
+ entry = self.find(:first, :conditions => ["ckey=?",key])
55
+ entry.nil? ? self.create(attributes) : entry.update_attributes(attributes)
56
+ else
57
+ self.create(attributes)
58
+ end
59
+ true
60
+ end
61
+
62
+ # Expire the cache entry identified by the given key
63
+ #
64
+ # ==== Parameter
65
+ # key<Sting>:: The key identifying the cache entry
66
+ def self.expire(key)
67
+ self.delete_all(["ckey=?", key])
68
+ end
69
+
70
+ # Expire the cache entries matching the given key
71
+ #
72
+ # ==== Parameter
73
+ # key<Sting>:: The key matching the cache entries
74
+ def self.expire_match(key)
75
+ self.delete_all(["ckey like ?", key + "%"])
76
+ end
77
+
78
+ # Expire all the cache entries
79
+ def self.expire_all
80
+ self.delete_all
81
+ end
82
+
83
+ # Perform auto-migration in case the table is unknown in the database
84
+ def self.check_table
85
+ CacheMigration.up unless self.table_exists?
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,79 @@
1
+ module Merb::Cache::DatabaseStore::DataMapper
2
+ # Module that provides DataMapper support for the database backend
3
+
4
+ # The cache model
5
+ class CacheModel < DataMapper::Base
6
+ set_table_name Merb::Controller._cache.config[:table_name]
7
+ property :ckey, :string, :length => 255, :lazy => false, :key => true
8
+ property :data, :text, :lazy => false
9
+ property :expire, :datetime, :default => nil
10
+
11
+ # Fetch data from the database using the specified key
12
+ # The entry is deleted if it has expired
13
+ #
14
+ # ==== Parameter
15
+ # key<Sting>:: The key identifying the cache entry
16
+ #
17
+ # ==== Returns
18
+ # data<String, NilClass>::
19
+ # nil is returned whether the entry expired or was not found
20
+ def self.cache_get(key)
21
+ if entry = self.first(key)
22
+ return entry.data if entry.expire.nil? || DateTime.now < entry.expire
23
+ entry.destroy!
24
+ end
25
+ nil
26
+ end
27
+
28
+ # Store data to the database using the specified key
29
+ #
30
+ # ==== Parameters
31
+ # key<Sting>:: The key identifying the cache entry
32
+ # data<String>:: The data to be put in cache
33
+ # expire<~minutes>::
34
+ # The number of minutes (from now) the cache should persist
35
+ # get<Boolean>::
36
+ # used internally to behave like this:
37
+ # - when set to true, perform update_or_create on the cache entry
38
+ # - when set to false, force creation of the cache entry
39
+ def self.cache_set(key, data, expire = nil, get = true)
40
+ attributes = {:ckey => key, :data => data,
41
+ :expire => expire.nil? ? nil : expire.to_s_db }
42
+ if get
43
+ entry = self.first(key)
44
+ entry.nil? ? self.create(attributes) : entry.update_attributes(attributes)
45
+ else
46
+ self.create(attributes)
47
+ end
48
+ true
49
+ end
50
+
51
+ # Expire the cache entry identified by the given key
52
+ #
53
+ # ==== Parameter
54
+ # key<Sting>:: The key identifying the cache entry
55
+ def self.expire(key)
56
+ entry = self.first(key)
57
+ entry.destroy! unless entry.nil?
58
+ end
59
+
60
+ # Expire the cache entries matching the given key
61
+ #
62
+ # ==== Parameter
63
+ # key<Sting>:: The key matching the cache entries
64
+ def self.expire_match(key)
65
+ #FIXME
66
+ database.execute("DELETE FROM #{self.table.name} WHERE ckey LIKE '#{key}%'")
67
+ end
68
+
69
+ # Expire all the cache entries
70
+ def self.expire_all
71
+ self.truncate!
72
+ end
73
+
74
+ # Perform auto-migration in case the table is unknown in the database
75
+ def self.check_table
76
+ self.auto_migrate! unless self.table.exists?
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,78 @@
1
+ module Merb::Cache::DatabaseStore::Sequel
2
+ # Module that provides Sequel support for the database backend
3
+
4
+ # The cache model
5
+ class CacheModel < Sequel::Model(Merb::Controller._cache.config[:table_name].to_sym)
6
+ set_schema do
7
+ primary_key :id
8
+ varchar :ckey, :index => true
9
+ varchar :data
10
+ timestamp :expire, :default => nil
11
+ end
12
+
13
+ # Fetch data from the database using the specified key
14
+ # The entry is deleted if it has expired
15
+ #
16
+ # ==== Parameter
17
+ # key<Sting>:: The key identifying the cache entry
18
+ #
19
+ # ==== Returns
20
+ # data<String, NilClass>::
21
+ # nil is returned whether the entry expired or was not found
22
+ def self.cache_get(key)
23
+ if entry = self.filter(:ckey => key).single_record(:limit => 1)
24
+ return entry.data if entry.expire.nil? || Time.now < entry.expire
25
+ self.expire(key)
26
+ end
27
+ nil
28
+ end
29
+
30
+ # Store data to the database using the specified key
31
+ #
32
+ # ==== Parameters
33
+ # key<Sting>:: The key identifying the cache entry
34
+ # data<String>:: The data to be put in cache
35
+ # expire<~minutes>::
36
+ # The number of minutes (from now) the cache should persist
37
+ # get<Boolean>::
38
+ # used internally to behave like this
39
+ # - when set to true, perform update_or_create on the cache entry
40
+ # - when set to false, force creation of the cache entry
41
+ def self.cache_set(key, data, expire = nil, get = true)
42
+ attributes = {:ckey => key, :data => data, :expire => expire }
43
+ if get
44
+ entry = self.filter(:ckey => key).single_record(:limit => 1)
45
+ entry.nil? ? self.create(attributes) : entry.set(attributes)
46
+ else
47
+ self.create(attributes)
48
+ end
49
+ true
50
+ end
51
+
52
+ # Expire the cache entry identified by the given key
53
+ #
54
+ # ==== Parameter
55
+ # key<Sting>:: The key identifying the cache entry
56
+ def self.expire(key)
57
+ self.filter(:ckey => key).delete
58
+ end
59
+
60
+ # Expire the cache entries matching the given key
61
+ #
62
+ # ==== Parameter
63
+ # key<Sting>:: The key matching the cache entries
64
+ def self.expire_match(key)
65
+ self.filter{:ckey.like key + "%"}.delete
66
+ end
67
+
68
+ # Expire all the cache entries
69
+ def self.expire_all
70
+ self.delete_all
71
+ end
72
+
73
+ # Perform auto-migration in case the table is unknown in the database
74
+ def self.check_table
75
+ self.create_table unless self.table_exists?
76
+ end
77
+ end
78
+ end