disk_store 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 93990471281e2c963748cbebdfc8856ae2959ade
4
+ data.tar.gz: ca3fd4505c2c65f150b55a8fcbe5d0356f11a4a0
5
+ SHA512:
6
+ metadata.gz: 67b15e61229c63c442e53495e4ea82a4ac8034c457b6d5513896c8bf0c9319836871b3bb618316dc5adace98787cfe1f02f311875f300b0c5d27d34c009473a7
7
+ data.tar.gz: b361398d2d4f22802061480be065f8c1002ceb43e7dbafaa1cbc9946f55724336945ecb1052867bef378f0af6390ee8c1c3593bd6558ba84391d3eee6b60de9f
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,38 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ disk_store (0.0.1)
5
+ rake
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.3.5)
11
+ crack (0.4.1)
12
+ safe_yaml (~> 0.9.0)
13
+ diff-lcs (1.2.5)
14
+ multi_json (1.8.4)
15
+ rake (10.1.1)
16
+ rspec (2.14.1)
17
+ rspec-core (~> 2.14.0)
18
+ rspec-expectations (~> 2.14.0)
19
+ rspec-mocks (~> 2.14.0)
20
+ rspec-core (2.14.7)
21
+ rspec-expectations (2.14.5)
22
+ diff-lcs (>= 1.1.3, < 2.0)
23
+ rspec-mocks (2.14.5)
24
+ safe_yaml (0.9.7)
25
+ vcr (2.8.0)
26
+ webmock (1.13.0)
27
+ addressable (>= 2.2.7)
28
+ crack (>= 0.3.2)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ disk_store!
35
+ multi_json
36
+ rspec
37
+ vcr
38
+ webmock
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # DiskStore
2
+
3
+ DiskStore is a way of caching large files to disk in Ruby. Unlike ActiveSupport::Cache::Store,
4
+ which is designed primarily for storing values of strings, DiskStore is meant for
5
+ caching files to disk.
6
+
7
+ DiskStore stores the files on disk in a very similar way to Rails' [ActiveSupport::Cache::FileStore](http://api.rubyonrails.org/classes/ActiveSupport/Cache/FileStore.html).
8
+
9
+ ## Installation
10
+
11
+ ```ruby
12
+ gem 'disk_store'
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ You should use this in a similar way that you would use most Ruby caching libraries. Instead
18
+ of passing around string values to cache, you should pass around IO objects. Here are a few
19
+ examples.
20
+
21
+ ### Setup
22
+
23
+ DiskStore requires a directory to to store the files.
24
+
25
+ It takes a single parameter, which is the directory at which to store the cached files.
26
+ If no parameter is specified, it uses the current directory.
27
+
28
+ ```ruby
29
+ cache = DiskStore.new("path/to/my/cache/directory")
30
+ ```
31
+
32
+ ### Reading
33
+
34
+ ```ruby
35
+ cache = DiskStore.new
36
+ cached_file = cache.read("my_cache_key") #=> File.open('somewhere_on_disk')
37
+ ```
38
+
39
+ ### Writing
40
+
41
+ ```ruby
42
+ cache = DiskStore.new
43
+ cache.write("my_cache_key", File.open('file.psd', 'rb'))
44
+ ```
45
+
46
+ ### Fetching
47
+
48
+ ```ruby
49
+ cache = DiskStore.new
50
+ cache.fetch("my_cache_key") do
51
+ File.open('file.psd', 'rb')
52
+ end
53
+ ```
54
+
55
+ This is where it gets cool. You can also feed it other IO classes.
56
+ Here we cache the result of a file download onto disk.
57
+
58
+ ```ruby
59
+ cache = DiskStore.new
60
+ cache.fetch("my_other_cache_key") do
61
+ open("https://layervault.com/cats.gif")
62
+ end
63
+ ```
64
+
65
+ ### Deleting
66
+
67
+ ```ruby
68
+ cache = DiskStore.new
69
+ cache.delete("my_cache_key") #=> true
70
+ ```
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ task :console do
2
+ require 'irb'
3
+ require 'irb/completion'
4
+ require 'disk_store'
5
+ ARGV.clear
6
+ IRB.start
7
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'disk_store/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "disk_store"
8
+ gem.version = DiskStore::VERSION
9
+ gem.authors = ["Kelly Sutton"]
10
+ gem.email = ["kelly@layervault.com"]
11
+ gem.description = %q{Cache files the smart way.}
12
+ gem.summary = %q{Cache fiels the smart way.}
13
+ gem.homepage = "http://cosmos.layervault.com/cache.html"
14
+ gem.license = 'MIT'
15
+
16
+ gem.files = `git ls-files`.split($/).delete_if { |f| f.include?('examples/') }
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ gem.add_dependency "rake"
22
+
23
+ gem.test_files = Dir.glob("spec/**/*")
24
+ gem.add_development_dependency "multi_json"
25
+ gem.add_development_dependency "rspec"
26
+ gem.add_development_dependency "vcr"
27
+ gem.add_development_dependency "webmock"
28
+ end
@@ -0,0 +1,3 @@
1
+ class DiskStore
2
+ VERSION = "0.1.0"
3
+ end
data/lib/disk_store.rb ADDED
@@ -0,0 +1,84 @@
1
+ require 'open-uri'
2
+
3
+ class DiskStore
4
+ DIR_FORMATTER = "%03X"
5
+ FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
6
+ EXCLUDED_DIRS = ['.', '..'].freeze
7
+
8
+ def initialize(path=nil)
9
+ path ||= "."
10
+ @root_path = File.expand_path path
11
+ end
12
+
13
+ def read(key)
14
+ File.open(key_file_path(key), 'rb')
15
+ end
16
+
17
+ def write(key, io)
18
+ file_path = key_file_path(key)
19
+ ensure_cache_path(File.dirname(file_path))
20
+ IO::copy_stream(io, File.open(file_path, 'wb'))
21
+ end
22
+
23
+ def exist?(key)
24
+ File.exist?(key_file_path(key))
25
+ end
26
+
27
+ def delete(key)
28
+ file_path = key_file_path(key)
29
+ if exist?(key)
30
+ File.delete(file_path)
31
+ delete_empty_directories(File.dirname(file_path))
32
+ end
33
+ end
34
+
35
+ def fetch(key)
36
+ if block_given?
37
+ if exist?(key)
38
+ read(key)
39
+ else
40
+ io = yield
41
+ write(key, io)
42
+ read(key)
43
+ end
44
+ else
45
+ read(key)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # These methods were borrowed mostly from ActiveSupport::Cache::FileStore
52
+
53
+ # Translate a key into a file path.
54
+ def key_file_path(key)
55
+ fname = URI.encode_www_form_component(key)
56
+ hash = Zlib.adler32(fname)
57
+ hash, dir_1 = hash.divmod(0x1000)
58
+ dir_2 = hash.modulo(0x1000)
59
+ fname_paths = []
60
+
61
+ # Make sure file name doesn't exceed file system limits.
62
+ begin
63
+ fname_paths << fname[0, FILENAME_MAX_SIZE]
64
+ fname = fname[FILENAME_MAX_SIZE..-1]
65
+ end until fname.nil? || fname == ""
66
+
67
+ File.join(@root_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
68
+ end
69
+
70
+ # Make sure a file path's directories exist.
71
+ def ensure_cache_path(path)
72
+ FileUtils.makedirs(path) unless File.exist?(path)
73
+ end
74
+
75
+ # Delete empty directories in the cache.
76
+ def delete_empty_directories(dir)
77
+ return if File.realpath(dir) == File.realpath(@root_path)
78
+ if Dir.entries(dir).reject{ |f| EXCLUDED_DIRS.include?(f) }.empty?
79
+ Dir.delete(dir) rescue nil
80
+ delete_empty_directories(File.dirname(dir))
81
+ end
82
+ end
83
+
84
+ end