@@ -19,23 +19,29 @@ module Cache
1919 # Example usage:
2020 # config.cache_store = :source_control_cache_store, cache_path: "tmp/cache"
2121 class SourceControlCacheStore < Store
22- attr_reader :cache_path
22+ attr_reader :cache_path , :subdirectory_delimiter
2323
2424 # Initialize a new SourceControlCacheStore
2525 #
2626 # @param cache_path [String] The directory where cache files will be stored
27+ # @param subdirectory_delimiter [String, nil] Optional delimiter to split keys into subdirectories
2728 # @param options [Hash] Additional options (currently unused)
28- def initialize ( cache_path :, **options )
29+ def initialize ( cache_path :, subdirectory_delimiter : nil , **options )
2930 super ( options )
3031 @cache_path = cache_path
32+ @subdirectory_delimiter = subdirectory_delimiter
3133 FileUtils . mkdir_p ( @cache_path )
3234 end
3335
3436 # Clear all cache entries
3537 def clear ( options = nil )
3638 if File . directory? ( @cache_path )
37- Dir . glob ( File . join ( @cache_path , "*" ) ) . each do |file |
38- File . delete ( file ) if File . file? ( file )
39+ Dir . glob ( File . join ( @cache_path , "*" ) ) . each do |path |
40+ if File . file? ( path )
41+ File . delete ( path )
42+ elsif File . directory? ( path )
43+ FileUtils . rm_rf ( path )
44+ end
3945 end
4046 end
4147 true
@@ -49,8 +55,7 @@ def clear(options = nil)
4955 # @param options [Hash] Options (unused)
5056 # @return [Object, nil] The cached value or nil if not found
5157 def read_entry ( key , **options )
52- hash = hash_key ( key )
53- value_file = value_path ( hash )
58+ value_file = value_path_for_key ( key )
5459
5560 return nil unless File . exist? ( value_file )
5661
@@ -74,6 +79,18 @@ def read_entry(key, **options)
7479 # @param options [Hash] Options (expiration is ignored)
7580 # @return [Boolean] Returns true on success, false on failure
7681 def write_entry ( key , entry , **options )
82+ if @subdirectory_delimiter
83+ write_entry_with_subdirectories ( key , entry , **options )
84+ else
85+ write_entry_simple ( key , entry , **options )
86+ end
87+ rescue StandardError
88+ # Return false if write fails (permissions, disk space, etc.)
89+ false
90+ end
91+
92+ # Write entry using simple hash-based file structure
93+ def write_entry_simple ( key , entry , **options )
7794 hash = hash_key ( key )
7895
7996 # Write the key file
@@ -83,9 +100,27 @@ def write_entry(key, entry, **options)
83100 File . write ( value_path ( hash ) , serialize_entry ( entry , **options ) )
84101
85102 true
86- rescue StandardError
87- # Return false if write fails (permissions, disk space, etc.)
88- false
103+ end
104+
105+ # Write entry using subdirectory structure
106+ def write_entry_with_subdirectories ( key , entry , **options )
107+ chunks = key . to_s . split ( @subdirectory_delimiter )
108+ current_dir = @cache_path
109+
110+ # Create subdirectories for each chunk
111+ chunks . each_with_index do |chunk , index |
112+ chunk_hash = hash_chunk ( chunk )
113+ current_dir = File . join ( current_dir , chunk_hash )
114+ FileUtils . mkdir_p ( current_dir )
115+
116+ # Write _key_chunk file
117+ File . write ( File . join ( current_dir , "_key_chunk" ) , chunk )
118+ end
119+
120+ # Write the value file in the final directory
121+ File . write ( File . join ( current_dir , "value" ) , serialize_entry ( entry , **options ) )
122+
123+ true
89124 end
90125
91126 # Delete an entry from the cache
@@ -94,6 +129,15 @@ def write_entry(key, entry, **options)
94129 # @param options [Hash] Options (unused)
95130 # @return [Boolean] Returns true if any file was deleted
96131 def delete_entry ( key , **options )
132+ if @subdirectory_delimiter
133+ delete_entry_with_subdirectories ( key , **options )
134+ else
135+ delete_entry_simple ( key , **options )
136+ end
137+ end
138+
139+ # Delete entry using simple hash-based file structure
140+ def delete_entry_simple ( key , **options )
97141 hash = hash_key ( key )
98142 key_file = key_path ( hash )
99143 value_file = value_path ( hash )
@@ -115,6 +159,25 @@ def delete_entry(key, **options)
115159 deleted
116160 end
117161
162+ # Delete entry using subdirectory structure
163+ def delete_entry_with_subdirectories ( key , **options )
164+ value_file = value_path_for_key ( key )
165+
166+ return false unless File . exist? ( value_file )
167+
168+ # Delete the entire directory tree for this key
169+ chunks = key . to_s . split ( @subdirectory_delimiter )
170+ first_chunk_hash = hash_chunk ( chunks [ 0 ] )
171+ dir_to_delete = File . join ( @cache_path , first_chunk_hash )
172+
173+ begin
174+ FileUtils . rm_rf ( dir_to_delete ) if File . exist? ( dir_to_delete )
175+ true
176+ rescue StandardError
177+ false
178+ end
179+ end
180+
118181 # Generate a hash for the given key
119182 #
120183 # @param key [String] The cache key
@@ -123,6 +186,14 @@ def hash_key(key)
123186 ::Digest ::SHA256 . hexdigest ( key . to_s )
124187 end
125188
189+ # Generate a hash for a key chunk
190+ #
191+ # @param chunk [String] A chunk of the cache key
192+ # @return [String] The SHA256 hash of the chunk
193+ def hash_chunk ( chunk )
194+ ::Digest ::SHA256 . hexdigest ( chunk . to_s )
195+ end
196+
126197 # Get the path for the key file
127198 #
128199 # @param hash [String] The hash of the key
@@ -138,6 +209,26 @@ def key_path(hash)
138209 def value_path ( hash )
139210 File . join ( @cache_path , "#{ hash } .value" )
140211 end
212+
213+ # Get the value file path for a given key
214+ #
215+ # @param key [String] The cache key
216+ # @return [String] The full path to the value file
217+ def value_path_for_key ( key )
218+ if @subdirectory_delimiter
219+ chunks = key . to_s . split ( @subdirectory_delimiter )
220+ current_dir = @cache_path
221+
222+ chunks . each do |chunk |
223+ chunk_hash = hash_chunk ( chunk )
224+ current_dir = File . join ( current_dir , chunk_hash )
225+ end
226+
227+ File . join ( current_dir , "value" )
228+ else
229+ value_path ( hash_key ( key ) )
230+ end
231+ end
141232 end
142233 end
143234end
0 commit comments