dragonfly-cache 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
+ SHA256:
3
+ metadata.gz: 4b1e061f17b26657fa84f4c209065c21252cf93506e360f5163bd11275265ead
4
+ data.tar.gz: ac29268ea5f9f80b0c001756824dc7bec2cba028bef6c11c11a26123b3c76068
5
+ SHA512:
6
+ metadata.gz: 5770d3c394b80041de1a096948d883f4170d9a69f91c4ab3757b020db2878291f53760bc63ac97a906d52e29841119846770e41224d68d5dc39c3fd0ca34ba30
7
+ data.tar.gz: bf0aa35be633102f0b1957c4e37849c2c507539c0a656838be955332f7a94a2dfc42270f24e2235175b63e5dfca1ef0f15fc83783cf35e124550a7643291257d
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in dragonfly-cache.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dragonfly-cache (0.1.0)
5
+ dragonfly (~> 1.1.5)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.5.2)
11
+ public_suffix (>= 2.0.2, < 4.0)
12
+ ast (2.4.0)
13
+ diff-lcs (1.3)
14
+ dragonfly (1.1.5)
15
+ addressable (~> 2.3)
16
+ multi_json (~> 1.0)
17
+ rack (>= 1.3)
18
+ jaro_winkler (1.5.1)
19
+ multi_json (1.13.1)
20
+ parallel (1.12.1)
21
+ parser (2.5.1.2)
22
+ ast (~> 2.4.0)
23
+ powerpack (0.1.2)
24
+ public_suffix (3.0.3)
25
+ rack (2.0.5)
26
+ rainbow (3.0.0)
27
+ rake (10.5.0)
28
+ rspec (3.8.0)
29
+ rspec-core (~> 3.8.0)
30
+ rspec-expectations (~> 3.8.0)
31
+ rspec-mocks (~> 3.8.0)
32
+ rspec-core (3.8.0)
33
+ rspec-support (~> 3.8.0)
34
+ rspec-expectations (3.8.1)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.8.0)
37
+ rspec-mocks (3.8.0)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.8.0)
40
+ rspec-support (3.8.0)
41
+ rubocop (0.58.2)
42
+ jaro_winkler (~> 1.5.1)
43
+ parallel (~> 1.10)
44
+ parser (>= 2.5, != 2.5.1.1)
45
+ powerpack (~> 0.1)
46
+ rainbow (>= 2.2.2, < 4.0)
47
+ ruby-progressbar (~> 1.7)
48
+ unicode-display_width (~> 1.0, >= 1.0.1)
49
+ ruby-progressbar (1.10.0)
50
+ unicode-display_width (1.4.0)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ bundler (~> 1.16)
57
+ dragonfly-cache!
58
+ rake (~> 10.0)
59
+ rspec (~> 3.8.0)
60
+ rubocop
61
+
62
+ BUNDLED WITH
63
+ 1.16.2
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2018 notus.sh
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # dragonfly-cache
2
+
3
+ `dragonfly-cache` is a cache adapter for [Dragonfly](http://markevans.github.io/dragonfly/). It allows you to store Dragonfly's jobs results without running them again and again on each call.
4
+
5
+ **For now**, `dragonfly-cache` supports only local caching of local files. It will be extended in a near future to support remote cache and file storages.
6
+
7
+ ## Installation
8
+
9
+ Add `dragonfly-cache` to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'dragonfly-cache'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```shell
18
+ bundle install
19
+ ```
20
+
21
+ Or install it yourself as:
22
+
23
+ ```shell
24
+ gem install dragonfly-cache
25
+ ```
26
+
27
+ ## Configuration
28
+
29
+ Once installed, you should configure `dragonfly-cache` to set your application's root (`/public` by default, if the directory exists) and your cached url format (`/dragonfly-cache/:sha/:name` by default).
30
+
31
+ ```ruby
32
+ Dragonfly.app.configure do
33
+ plugin :dragonfly_cache,
34
+ server_root: Rails.application.root.join('public'),
35
+ url_format: '/media-cache/:sha/:name'
36
+ end
37
+ ```
38
+
39
+ Configured as this, cached files will be stored in `/public/media-cache/:sha/:name`, where `:name` is [the Dragonfly `:name` attribute of the attached file](http://markevans.github.io/dragonfly/models#name-and-extension) and `:sha` is a contraction of the job SHA params.
40
+
41
+ `dragonfly-cache` will try to do its best to shorten the `:sha` part of the url and prevent cache conflicts.
42
+
43
+ ## How does it work?
44
+
45
+ `dragonfly-cache` use a method similar to [the one described in the Dragonfly documentation to process files on-the-fly and serve them remotely](http://markevans.github.io/dragonfly/cache#processing-on-the-fly-and-serving-remotely) but use only `define_url`.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ require 'rubocop/rake_task'
6
+ RuboCop::RakeTask.new do |task|
7
+ task.options = ['--config', 'config/linters/ruby.yml']
8
+ end
9
+
10
+ require 'rspec/core/rake_task'
11
+ RSpec::Core::RakeTask.new
12
+
13
+ task default: :spec
@@ -0,0 +1,11 @@
1
+ inherit_mode:
2
+ merge:
3
+ - Exclude
4
+
5
+ Metrics/LineLength:
6
+ Max: 150
7
+
8
+ # DSLs produce long blocks.
9
+ Metrics/BlockLength:
10
+ Exclude:
11
+ - 'spec/**/*_spec.rb'
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'dragonfly/cache/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'dragonfly-cache'
9
+ spec.version = Dragonfly::Cache::VERSION
10
+ spec.licenses = ['Apache-2.0']
11
+ spec.authors = ['Gaël-Ian Havard']
12
+ spec.email = ['gael-ian@notus.sh']
13
+
14
+ spec.summary = 'Cache adapter for Dragonfly'
15
+ spec.description = 'Allow Dragonfly to keep a cache of jobs results'
16
+ spec.homepage = 'https://github.com/notus-sh/dragonfly-cache'
17
+
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
20
+ else
21
+ raise 'RubyGems 2.0 or newer is required.'
22
+ end
23
+
24
+ spec.require_paths = ['lib']
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ end
28
+
29
+ spec.add_dependency 'dragonfly', '~> 1.1.5'
30
+
31
+ spec.add_development_dependency 'bundler', '~> 1.16'
32
+ spec.add_development_dependency 'rake', '~> 10.0'
33
+ spec.add_development_dependency 'rspec', '~> 3.8.0'
34
+ spec.add_development_dependency 'rubocop'
35
+
36
+ spec.post_install_message = <<~POST_INSTALL_MESSAGE
37
+ Don't forget to configure Dragonfly::Cache:
38
+
39
+ Dragonfly.app.configure do
40
+ plugin :dragonfly_cache,
41
+ server_root: Rails.application.root.join('public'),
42
+ url_format: '/media-cache/:sha/:name'
43
+ end
44
+
45
+ POST_INSTALL_MESSAGE
46
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'dragonfly'
5
+
6
+ module Dragonfly
7
+ module Cache
8
+ class Error < ::StandardError; end
9
+ end
10
+ end
11
+
12
+ require 'dragonfly/cache/plugin'
13
+ Dragonfly::App.register_plugin(:dragonfly_cache) { Dragonfly::Cache::Plugin.new }
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dragonfly
4
+ module Cache
5
+ class Config
6
+ attr_accessor :servers_options
7
+
8
+ def initialize(servers_options = {})
9
+ self.servers_options = {
10
+ url_format: '/dragonfly-cache/:sha/:name',
11
+ server_root: File.join(Dir.pwd, 'public')
12
+ }.merge(servers_options)
13
+
14
+ validate!
15
+ rewrite_url_format!
16
+ end
17
+
18
+ protected
19
+
20
+ def validate!
21
+ if servers_options[:server_root].nil? || !File.exist?(servers_options[:server_root])
22
+ raise Dragonfly::Cache::Error, ':server_root option is missing or directory does not exist'
23
+ end
24
+
25
+ raise Dragonfly::Cache::Error, ':url_format option must include `:sha`' if (servers_options[:url_format] =~ %r{/:sha/}).nil?
26
+ end
27
+
28
+ def rewrite_url_format!
29
+ servers_options[:url_format] = servers_options[:url_format].gsub(%r{/:sha/}, '/:shaish/')
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dragonfly/cache/storage'
4
+
5
+ module Dragonfly
6
+ module Cache
7
+ class Manager
8
+ extend Forwardable
9
+
10
+ MIN_SHA_SIZE = 2
11
+ MAX_SHA_SIZE = 16 # Length of SHA identifier generated by Dragonfly
12
+
13
+ attr_reader :plugin, :storage, :sha_size
14
+
15
+ delegate %i[servers_options] => :plugin
16
+ delegate %i[write writen? current_max_sha_size base_dir] => :storage
17
+
18
+ def initialize(plugin)
19
+ @plugin = plugin
20
+ @storage = Dragonfly::Cache::Storage.new(self)
21
+ initialize!
22
+ end
23
+
24
+ def valid_uri?(job, uri)
25
+ return true unless wrong_key?(job, uri) || wrong_value?(job, uri)
26
+ increase_sha_size!
27
+ false
28
+ end
29
+
30
+ def store(job, uri)
31
+ write(job, uri) unless writen?(job, uri)
32
+ map(job, uri) unless mapped?(job, uri)
33
+ rescue Dragonfly::Cache::Error => e
34
+ Dragonfly.warn(e.message)
35
+ end
36
+
37
+ protected
38
+
39
+ def wrong_key?(job, uri)
40
+ @cache_map.value?(uri.path) && @cache_map.key(uri.path) != job.sha
41
+ end
42
+
43
+ def wrong_value?(job, uri)
44
+ @cache_map.key?(job.sha) && @cache_map[job.sha] != uri.path
45
+ end
46
+
47
+ def initialize!
48
+ detect_sha_size
49
+ load_map
50
+ rescue ::StandardError => e
51
+ raise Dragonfly::Cache::Error, e.message
52
+ end
53
+
54
+ def detect_sha_size
55
+ @sha_size = [current_max_sha_size, MIN_SHA_SIZE].compact.max
56
+ @sha_size = [@sha_size, MAX_SHA_SIZE].min
57
+ end
58
+
59
+ def increase_sha_size!
60
+ raise Error, "Can't build longer :sha identifier" if @sha_size == MAX_SHA_SIZE
61
+ @sha_size += 1
62
+
63
+ @cache_map.clear
64
+ save_map
65
+ end
66
+
67
+ def map_path
68
+ @map_path ||= File.join(base_dir, 'map.yml')
69
+ end
70
+
71
+ def load_map
72
+ @cache_map = File.size?(map_path) ? YAML.load_file(map_path) : {}
73
+ end
74
+
75
+ def save_map
76
+ File.open(map_path, 'wb') { |f| YAML.dump(@cache_map, f) }
77
+ end
78
+
79
+ def map(job, uri)
80
+ @cache_map[job.sha] = uri.path
81
+ save_map
82
+ end
83
+
84
+ def mapped?(job, _uri)
85
+ @cache_map.key?(job.sha)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'dragonfly/cache/config'
5
+ require 'dragonfly/cache/manager'
6
+
7
+ module Dragonfly
8
+ module Cache
9
+ class Plugin
10
+ extend Forwardable
11
+
12
+ @@servers = {}
13
+
14
+ attr_reader :config, :manager
15
+
16
+ delegate %i[servers_options] => :config
17
+ delegate %i[sha_size valid_uri? store] => :manager
18
+
19
+ def call(app, cache_servers_options = {})
20
+ @config = Dragonfly::Cache::Config.new(cache_servers_options)
21
+ @manager = Dragonfly::Cache::Manager.new(self)
22
+
23
+ app.define_url do |app, job, opts|
24
+ url_for(app, job, opts)
25
+ rescue Dragonfly::Cache::Error => e
26
+ Dragonfly.warn(e.message)
27
+ app.server.url_for(job, opts) # Fallback to default Dragonfly::App url building
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ def url_for(app, job, opts)
34
+ uri = find_valid_url_for(app, job, opts)
35
+ # File are stored on url building instead of in a before_serve block to allow use of assets host
36
+ store(job, uri)
37
+ uri.to_s
38
+ end
39
+
40
+ def find_valid_url_for(app, job, opts)
41
+ loop do
42
+ url = server_for(app).url_for(job, options_for(job).merge(opts))
43
+ uri = URI.parse(url)
44
+ uri.query = nil
45
+ uri.fragment = nil
46
+ return uri if valid_uri?(job, uri)
47
+ end
48
+ end
49
+
50
+ def options_for(job)
51
+ {
52
+ shaish: job.sha[0..(sha_size - 1)]
53
+ }
54
+ end
55
+
56
+ def server_for(app)
57
+ @@servers[app.name] ||= begin
58
+ server = app.server.dup
59
+ servers_options.each do |name, value|
60
+ server.send("#{name}=", value) if server.respond_to?("#{name}=")
61
+ end
62
+ server
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dragonfly
4
+ module Cache
5
+ class Storage
6
+ extend Forwardable
7
+
8
+ attr_reader :manager
9
+
10
+ delegate %i[servers_options] => :manager
11
+
12
+ def initialize(manager)
13
+ @manager = manager
14
+ check_directory!(base_dir)
15
+ end
16
+
17
+ def current_max_sha_size
18
+ longest = Dir["#{base_dir}/*"].select { |p| File.directory?(p) }.max { |a, b| a <=> b }
19
+ (longest ? File.basename(longest).size : nil)
20
+ end
21
+
22
+ def write(job, uri)
23
+ path = cache_path(uri)
24
+ check_directory!(File.dirname(path))
25
+ with_umask(0o022) do
26
+ job.to_file(path, mode: 0o644, mkdirs: false)
27
+ end
28
+ end
29
+
30
+ def writen?(_job, uri)
31
+ File.exist?(cache_path(uri))
32
+ end
33
+
34
+ def base_dir
35
+ @base_dir ||= begin
36
+ path_format = File.join(servers_options[:server_root], servers_options[:url_format])
37
+ path_format.split('/').take_while { |p| p != ':shaish' }.join('/')
38
+ end
39
+ end
40
+
41
+ protected
42
+
43
+ def check_directory!(directory)
44
+ return if File.exist?(directory) && File.directory?(directory) && File.writable?(directory)
45
+ with_umask(0o022) do
46
+ File.unlink(directory) if File.exist?(directory) && !File.directory?(directory)
47
+ FileUtils.mkdir_p(directory, mode: 0o755) unless File.exist?(directory)
48
+ File.chmod(0o755, directory) unless File.writable?(directory)
49
+ end
50
+ rescue ::StandardError => e
51
+ raise Dragonfly::Cache::Error, e.message
52
+ end
53
+
54
+ def with_umask(umask)
55
+ original_umask = File.umask(umask)
56
+ begin
57
+ yield
58
+ ensure
59
+ File.umask(original_umask)
60
+ end
61
+ end
62
+
63
+ def cache_path(uri)
64
+ File.join(servers_options[:server_root], uri.path)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dragonfly
4
+ module Cache
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dragonfly-cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gaël-Ian Havard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-09-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dragonfly
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.1.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.1.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.8.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.8.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Allow Dragonfly to keep a cache of jobs results
84
+ email:
85
+ - gael-ian@notus.sh
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - Gemfile
93
+ - Gemfile.lock
94
+ - LICENSE
95
+ - README.md
96
+ - Rakefile
97
+ - config/linters/ruby.yml
98
+ - dragonfly-cache.gemspec
99
+ - lib/dragonfly/cache.rb
100
+ - lib/dragonfly/cache/config.rb
101
+ - lib/dragonfly/cache/manager.rb
102
+ - lib/dragonfly/cache/plugin.rb
103
+ - lib/dragonfly/cache/storage.rb
104
+ - lib/dragonfly/cache/version.rb
105
+ homepage: https://github.com/notus-sh/dragonfly-cache
106
+ licenses:
107
+ - Apache-2.0
108
+ metadata:
109
+ allowed_push_host: https://rubygems.org
110
+ post_install_message: |+
111
+ Don't forget to configure Dragonfly::Cache:
112
+
113
+ Dragonfly.app.configure do
114
+ plugin :dragonfly_cache,
115
+ server_root: Rails.application.root.join('public'),
116
+ url_format: '/media-cache/:sha/:name'
117
+ end
118
+
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.7.6
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Cache adapter for Dragonfly
138
+ test_files: []