shrine-content_addressable 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 163dc9d16d93c7e7caa3716dc691a493f34530ce
4
- data.tar.gz: 315dc4e44269b2250694dc4fb0656679fb012625
3
+ metadata.gz: 0a2e3d00b3be4739f5fcc1484c838bdedebef812
4
+ data.tar.gz: eab756297c29f2e9b8f2232d944d4948a19479a9
5
5
  SHA512:
6
- metadata.gz: e6fe17ba932227d32277f64f431e96ae21e334274798056b6906cb4153880352fc33fdb0619885bcfc112ac58ca5b33ecb90eef4d589cab508fdb2987587008f
7
- data.tar.gz: e988eef64178c3d9f79a644b822e3636a7d2251bf9d8470daf8e0a45ac057487b95eeddd194c128d87582749c0749fba7dbe2e8ec64f6979c326abd585b8db51
6
+ metadata.gz: 6df1452483522aebedae8598595518af88f48f8f7ddbdb0ae53d5b4b3c32cd0105ba5c4e5fb12e0be838c84acc64bd7ba8d2b4987b878d1899d80b2124d97c40
7
+ data.tar.gz: 0df1cb9d5688ac5d8385df5ed586c08567be451556d69e2f402f9aa0de2d21e59eddcae84184eecebd98f3cff90221c91920d2170a73e2cad7dacfa4eb002ddb
data/.gitignore CHANGED
@@ -1,11 +1,11 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- .idea/
11
- Gemfile.lock
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ .idea/
11
+ Gemfile.lock
data/.travis.yml CHANGED
@@ -1,20 +1,20 @@
1
- sudo: false
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.3.0
6
- - 2.4
7
- - 2.5
8
- - 2.6
9
- - rbx-3
10
- - ruby-head
11
- matrix:
12
- allow_failures:
13
- - rvm: ruby-head
14
- - rvm: rbx-3
15
- - rvm: 2.6
16
- before_install:
17
- - gem update --system
18
- - gem --version
19
- install:
20
- - bundle install --with development --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.3.0
6
+ - 2.4
7
+ - 2.5
8
+ - 2.6
9
+ - rbx-3
10
+ - ruby-head
11
+ matrix:
12
+ allow_failures:
13
+ - rvm: ruby-head
14
+ - rvm: rbx-3
15
+ - rvm: 2.6
16
+ before_install:
17
+ - gem update --system
18
+ - gem --version
19
+ install:
20
+ - bundle install --with development --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}
data/CHANGELOG.md CHANGED
@@ -1,4 +1,7 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+ Add `ContentAddressableFile` for easy lookup
5
+
3
6
  ## 0.1.0
4
7
  Initial release
data/CODE_OF_CONDUCT.md CHANGED
@@ -1,74 +1,74 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- In the interest of fostering an open and welcoming environment, we as
6
- contributors and maintainers pledge to making participation in our project and
7
- our community a harassment-free experience for everyone, regardless of age, body
8
- size, disability, ethnicity, gender identity and expression, level of experience,
9
- nationality, personal appearance, race, religion, or sexual identity and
10
- orientation.
11
-
12
- ## Our Standards
13
-
14
- Examples of behavior that contributes to creating a positive environment
15
- include:
16
-
17
- * Using welcoming and inclusive language
18
- * Being respectful of differing viewpoints and experiences
19
- * Gracefully accepting constructive criticism
20
- * Focusing on what is best for the community
21
- * Showing empathy towards other community members
22
-
23
- Examples of unacceptable behavior by participants include:
24
-
25
- * The use of sexualized language or imagery and unwelcome sexual attention or
26
- advances
27
- * Trolling, insulting/derogatory comments, and personal or political attacks
28
- * Public or private harassment
29
- * Publishing others' private information, such as a physical or electronic
30
- address, without explicit permission
31
- * Other conduct which could reasonably be considered inappropriate in a
32
- professional setting
33
-
34
- ## Our Responsibilities
35
-
36
- Project maintainers are responsible for clarifying the standards of acceptable
37
- behavior and are expected to take appropriate and fair corrective action in
38
- response to any instances of unacceptable behavior.
39
-
40
- Project maintainers have the right and responsibility to remove, edit, or
41
- reject comments, commits, code, wiki edits, issues, and other contributions
42
- that are not aligned to this Code of Conduct, or to ban temporarily or
43
- permanently any contributor for other behaviors that they deem inappropriate,
44
- threatening, offensive, or harmful.
45
-
46
- ## Scope
47
-
48
- This Code of Conduct applies both within project spaces and in public spaces
49
- when an individual is representing the project or its community. Examples of
50
- representing a project or community include using an official project e-mail
51
- address, posting via an official social media account, or acting as an appointed
52
- representative at an online or offline event. Representation of a project may be
53
- further defined and clarified by project maintainers.
54
-
55
- ## Enforcement
56
-
57
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at derk-jan+github@karrenbeld.info. All
59
- complaints will be reviewed and investigated and will result in a response that
60
- is deemed necessary and appropriate to the circumstances. The project team is
61
- obligated to maintain confidentiality with regard to the reporter of an incident.
62
- Further details of specific enforcement policies may be posted separately.
63
-
64
- Project maintainers who do not follow or enforce the Code of Conduct in good
65
- faith may face temporary or permanent repercussions as determined by other
66
- members of the project's leadership.
67
-
68
- ## Attribution
69
-
70
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [http://contributor-covenant.org/version/1/4][version]
72
-
73
- [homepage]: http://contributor-covenant.org
74
- [version]: http://contributor-covenant.org/version/1/4/
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at derk-jan+github@karrenbeld.info. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
- source 'https://rubygems.org'
2
-
3
- git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
-
5
- # Specify your gem's dependencies in shrine-content_addressable.gemspec
6
- gemspec
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in shrine-content_addressable.gemspec
6
+ gemspec
data/LICENSE.txt CHANGED
@@ -1,21 +1,21 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2018 Derk-Jan Karrenbeld
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Derk-Jan Karrenbeld
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,69 +1,107 @@
1
- # shrine-content_addressable
2
- [![Build Status](https://travis-ci.com/SleeplessByte/shrine-content_addressable.svg?branch=master)](https://travis-ci.com/SleeplessByte/content_addressable)
3
- [![Gem Version](https://badge.fury.io/rb/shrine-content_addressable.svg)](https://badge.fury.io/rb/content_addressable)
4
- [![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)
5
-
6
- Generate content addressable locations for shrine uploads.
7
-
8
- ## Installation
9
-
10
- Add this line to your application's Gemfile:
11
-
12
- ```ruby
13
- gem 'shrine-content_addressable'
14
- ```
15
-
16
- And then execute:
17
-
18
- $ bundle
19
-
20
- Or install it yourself as:
21
-
22
- $ gem install shrine-content_addressable
23
-
24
- ## Usage
25
-
26
- This plugin depends on the `signature` plugin:
27
-
28
- ```Ruby
29
- require 'shrine/plugins/content_addressable'
30
- require 'shrine/plugins/signature'
31
-
32
- class MyUploader < Shrine
33
- plugin :signature
34
- plugin :content_addressable, hash: :sha256, prefix: '/'
35
- end
36
- ```
37
-
38
- Currently you can enter `:md5`, `:sha`, `:sha256` and `:sha512` for `hash:`. If you have a different algorithm that is
39
- correctly supported by the `signature` plugin, and has a `multihash` code, add the multihash code mapping:
40
-
41
- ```Ruby
42
- # Let's say the signature plugin starts supporting blake 2b as :blake2_b,
43
- # the multihash code is 'blake2b'
44
- plugin :content_addressable, hash: :blake2_b, multihash: 'blake2b'
45
- ```
46
-
47
- ## Development
48
-
49
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can
50
- also run `bin/console` for an interactive prompt that will allow you to experiment.
51
-
52
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
53
- version number in `shrine-configurable_storage.gemspec.rb`, and then run `bundle exec rake release`, which will create
54
- a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
55
-
56
- ## Contributing
57
-
58
- Bug reports and pull requests are welcome on GitHub at https://github.com/SleeplessByte/shrine-configurable_storage.
59
- This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to
60
- the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
61
-
62
- ## License
63
-
64
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
65
-
66
- ## Code of Conduct
67
-
68
- Everyone interacting in the Shrine::ConfigurableStorage project’s codebases, issue trackers, chat rooms and mailing
69
- lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/shrine-configurable_storage/blob/master/CODE_OF_CONDUCT.md).
1
+ # shrine-content_addressable
2
+ [![Build Status](https://travis-ci.com/SleeplessByte/shrine-content_addressable.svg?branch=master)](https://travis-ci.com/SleeplessByte/content_addressable)
3
+ [![Gem Version](https://badge.fury.io/rb/shrine-content_addressable.svg)](https://badge.fury.io/rb/content_addressable)
4
+ [![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)
5
+
6
+ Generate content addressable locations for shrine uploads.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'shrine-content_addressable'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install shrine-content_addressable
23
+
24
+ ## Usage
25
+
26
+ This plugin depends on the `signature` plugin:
27
+
28
+ ```Ruby
29
+ require 'shrine/plugins/content_addressable'
30
+ require 'shrine/plugins/signature'
31
+
32
+ class MyUploader < Shrine
33
+ plugin :signature
34
+ plugin :content_addressable, hash: :sha256, prefix: '/'
35
+ end
36
+ ```
37
+
38
+ Currently you can enter `:md5`, `:sha`, `:sha256` and `:sha512` for `hash:`. If you have a different algorithm that is
39
+ correctly supported by the `signature` plugin, and has a `multihash` code, add the multihash code mapping:
40
+
41
+ ```Ruby
42
+ # Let's say the signature plugin starts supporting blake 2b as :blake2_b,
43
+ # the multihash code is 'blake2b'
44
+ plugin :content_addressable, hash: :blake2_b, multihash: 'blake2b'
45
+ ```
46
+
47
+ ### ContentAddressable IO
48
+ Since a content-addressable stored file is the same across whichever storage, it *MUST* not matter what storage the file
49
+ is accessed from when it comes to reading. A wrapper is provided so files can be looked up by their content-addressable
50
+ id / hash, instead of a data hash (default for Shrine).
51
+
52
+ ```Ruby
53
+ require 'content_addressable_file'
54
+
55
+ # You currently need to register the storages
56
+ ContentAddressableFile.register_storage(lookup, lookup, lookup)
57
+
58
+ file = ContentAddressableFile.new(content_addressable_hash)
59
+
60
+ # => file methods like open, rewind, read, close and eof? are available
61
+ # => file.url gives the first url that exists
62
+ # => file.exists? is true if it exists in any storage
63
+ # => file.delete attempts to delete it from ALL storages
64
+ ```
65
+
66
+ To reset known storages use:
67
+ ```Ruby
68
+ ContentAddressableFile.reset
69
+ ```
70
+
71
+ In a later version registration might be automatic. A lookup storage needs to respond to:
72
+ ```Ruby
73
+ lookup = Shrine::Storage::Memory.new
74
+ id = content_addressable_hash
75
+
76
+ lookup.open(id) # IO.open
77
+ lookup.exists?(id) # true if storage has it (and can open)
78
+
79
+ # optional
80
+ lookup.url(id) # url to the io (only if storage has it)
81
+ lookup.delete(id) # delete from storage
82
+ lookup.download(id) # download the io
83
+ ```
84
+
85
+ ## Development
86
+
87
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can
88
+ also run `bin/console` for an interactive prompt that will allow you to experiment.
89
+
90
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
91
+ version number in `shrine-configurable_storage.gemspec.rb`, and then run `bundle exec rake release`, which will create
92
+ a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
93
+
94
+ ## Contributing
95
+
96
+ Bug reports and pull requests are welcome on GitHub at [SleeplessByte/shrine-configurable_storage](https://github.com/SleeplessByte/shrine-configurable_storage).
97
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to
98
+ the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
99
+
100
+ ## License
101
+
102
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
103
+
104
+ ## Code of Conduct
105
+
106
+ Everyone interacting in the Shrine::ConfigurableStorage project’s codebases, issue trackers, chat rooms and mailing
107
+ lists is expected to follow the [code of conduct](https://github.com/SleeplessByte/shrine-configurable_storage/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -1,12 +1,12 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rake/testtask'
5
-
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << 'test'
8
- t.libs << 'lib'
9
- t.test_files = FileList['test/**/*_test.rb']
10
- end
11
-
12
- task default: :test
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ end
11
+
12
+ task default: :test
data/bin/console CHANGED
@@ -1,14 +1,14 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'bundler/setup'
4
- require 'shrine/plugins/content_addressable'
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require 'irb'
14
- IRB.start(__FILE__)
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'shrine/plugins/content_addressable'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
data/bin/setup CHANGED
@@ -1,8 +1,8 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
5
+ class ContentAddressableFile
6
+
7
+ class << self
8
+ attr_accessor :storages
9
+
10
+ def register_storage(*storage)
11
+ self.storages = Array(storages).push(*storage)
12
+ self
13
+ end
14
+
15
+ def reset
16
+ self.storages = []
17
+ self
18
+ end
19
+ end
20
+
21
+ attr_reader :id
22
+
23
+ def initialize(id)
24
+ self.id = id
25
+ end
26
+
27
+ # Calls `#open` on the storages to open the uploaded file for reading.
28
+ # Most storages will return a lazy IO object which dynamically
29
+ # retrieves file content from the storage as the object is being read.
30
+ #
31
+ # If a block is given, the opened IO object is yielded to the block,
32
+ # and at the end of the block it's automatically closed. In this case
33
+ # the return value of the method is the block return value.
34
+ #
35
+ # If no block is given, the opened IO object is returned.
36
+ #
37
+ # content_addressable.open #=> IO object returned by the storage
38
+ # content_addressable.read #=> "..."
39
+ # content_addressable.close
40
+ #
41
+ # # or
42
+ #
43
+ # content_addressable.open { |io| io.read }
44
+ # #=> "..."
45
+ def open(*args)
46
+ return to_io unless block_given?
47
+
48
+ begin
49
+ @io = pin_storage(:open, id, *args)
50
+ yield @io
51
+ ensure
52
+ @io&.close
53
+ @io = nil
54
+ end
55
+ end
56
+
57
+ alias safe_open open
58
+
59
+ # Calls `#download` on the storages if the storage that has the file
60
+ # implements it, otherwise streams content into a newly created Tempfile.
61
+ #
62
+ # If the file exists in multiple storages, any that allows download will
63
+ # be pinned.
64
+ #
65
+ # If a block is given, the opened Tempfile object is yielded to the
66
+ # block, and at the end of the block it's automatically closed and
67
+ # deleted. In this case the return value of the method is the block
68
+ # return value.
69
+ #
70
+ # If no block is given, the opened Tempfile is returned.
71
+ #
72
+ # content_addressable.download
73
+ # #=> #<File:/var/folders/.../20180302-33119-1h1vjbq.jpg>
74
+ #
75
+ # # or
76
+ #
77
+ # content_addressable.download { |tempfile| tempfile.read }
78
+ # # tempfile is deleted
79
+ # #=> "..."
80
+ def download(*args)
81
+ if any_storage(:respond_to?, :download)
82
+ tempfile = pin_storage(:download, id, *args)
83
+ else
84
+ tempfile = Tempfile.new(['content-addressable', id], binmode: true)
85
+ stream(tempfile, *args)
86
+ tempfile.open
87
+ end
88
+
89
+ block_given? ? yield(tempfile) : tempfile
90
+ ensure
91
+ tempfile.close! if ($ERROR_INFO || block_given?) && tempfile
92
+ end
93
+
94
+ # Streams uploaded file content into the specified destination. The
95
+ # destination object is given directly to `IO.copy_stream`, so it can
96
+ # be either a path on disk or an object that responds to `#write`.
97
+ #
98
+ # If the uploaded file is already opened, it will be simply rewinded
99
+ # after streaming finishes. Otherwise the uploaded file is opened and
100
+ # then closed after streaming.
101
+ #
102
+ # content_addressable.stream(StringIO.new)
103
+ # # or
104
+ # content_addressable.stream("/path/to/destination")
105
+ def stream(destination, *args)
106
+ if @io
107
+ IO.copy_stream(io, destination)
108
+ io.rewind
109
+ else
110
+ safe_open(*args) { |io| IO.copy_stream(io, destination) }
111
+ end
112
+ end
113
+
114
+ # Part of complying to the IO interface. It delegates to the internally
115
+ # opened IO object.
116
+ def read(*args)
117
+ io.read(*args)
118
+ end
119
+
120
+ # Part of complying to the IO interface. It delegates to the internally
121
+ # opened IO object.
122
+ def eof?
123
+ io.eof?
124
+ end
125
+
126
+ # Part of complying to the IO interface. It delegates to the internally
127
+ # opened IO object.
128
+ def rewind
129
+ io.rewind
130
+ end
131
+
132
+ # Part of complying to the IO interface. It delegates to the internally
133
+ # opened IO object.
134
+ def close
135
+ io.close if @io
136
+ end
137
+
138
+ # Calls `#url` on the storage where the file is first found, forwarding any
139
+ # given URL options.
140
+ def url(**options)
141
+ pin_storage(:url, id, **options)
142
+ end
143
+
144
+ # Calls `#exists?` on the storages, which checks whether the file exists
145
+ # on any of the storages.
146
+ def exists?
147
+ pin_storage(:exists?, id)
148
+ end
149
+
150
+ # Calls `#delete` on the storages, which deletes the file from the
151
+ # storage.
152
+ def delete
153
+ all_storages(:delete, id)
154
+ end
155
+
156
+ # Returns an opened IO object for the uploaded file.
157
+ def to_io
158
+ io
159
+ end
160
+
161
+ # Returns true if the other File has the same id
162
+ def ==(other)
163
+ other.is_a?(self.class) && id == other.id
164
+ end
165
+ alias eql? ==
166
+
167
+ # Enables using File objects as hash keys.
168
+ def hash
169
+ [id].hash
170
+ end
171
+
172
+ # Returns the storage that this file was uploaded to.
173
+ def storage
174
+ pin_storage(:exists?, id) && @pin_storage
175
+ end
176
+
177
+ private
178
+
179
+ attr_writer :id
180
+
181
+ # Returns an opened IO object for the uploaded file by calling `#open`
182
+ # on the storage.
183
+ def io
184
+ @io ||= safe_open(id)
185
+ end
186
+
187
+ # rubocop:disable Style/RescueModifier
188
+ def all_storages(method, *args)
189
+ self.class.storages.map do |storage|
190
+ storage.send(method, *args) rescue next
191
+ end
192
+ end
193
+
194
+ def any_storage(method, *args)
195
+ self.class.storages.each do |storage|
196
+ result = storage.send(method, *args) rescue next
197
+ break result if result
198
+ end
199
+ end
200
+
201
+ def pin_storage(method, *args)
202
+ @pin_storage = self.class.storages.find do |storage|
203
+ storage.send(:exists?, id) rescue next
204
+ end
205
+
206
+ @pin_storage&.send(method, *args)
207
+ end
208
+ # rubocop:enable Style/RescueModifier
209
+
210
+ end
@@ -1,85 +1,85 @@
1
- # frozen_string_literal: true
2
-
3
- require 'shrine'
4
- require 'shrine/plugins/signature'
5
-
6
- require 'multihashes'
7
- require 'digest'
8
-
9
- class Shrine
10
- module Plugins
11
- ##
12
- # plugin :signature
13
- # plugin :content_addressable
14
- #
15
- # This plugin uses the {Signature} plugin to turn the content into a content
16
- # addressable, which simply means the digest of the content is now
17
- # the location, and location the file by digest becomes possible.
18
- #
19
- # The results are wrapped in a multihash, so you may change your hashing
20
- # method in production, whilst not losing access to the old hashes. This
21
- # works by adding metadata to the hash of what function was used.
22
- # https://github.com/multiformats/multihash
23
- #
24
- # @example Setup the plugin using sha256
25
- #
26
- # plugin :content_addressable, hash: :sha256
27
- # # /1220142711d38ca7a33c5218416f8ffcc64648ca4616a625b5e0a0ab3da1911d5d7a
28
- #
29
- # @example You can also prefix the final location, for example to make the
30
- # location ready for IPFS
31
- # https://ipfs.io/
32
- #
33
- # plugin :content_addressable, hash: :sha256, prefix: 'ipfs'
34
- # # /ipfs/1220142711d38ca7a33c5218416f8ffcc64648ca4616a625b5e0a0ab3da1911d5d7a
35
- #
36
- # @example Reading out the hash data
37
- #
38
- # location = '/ipfs/1220142711d38ca7a33c5218416f8ffcc64648ca4616a625b5e0a0ab3da1911d5d7a'
39
- # hash = location.rpartition('/').last
40
- # out = Multihashes.decode [hash].pack('H*')
41
- # # => {:code=>18, :hash_function=>"sha2-256", :length=>32, :digest=>"\x14'\x11\xD3\x8C\xA7\xA3<R\x18Ao\x8F\xFC\xC6FH\xCAF\x16\xA6%\xB5\xE0\xA0\xAB=\xA1\x 91\x1D]z"}
42
- #
43
- module ContentAddressable
44
-
45
- MULTIHASH_LOOKUP = {
46
- md5: 'md5',
47
- sha1: 'sha1',
48
- sha256: 'sha2-256',
49
- sha512: 'sha2-512'
50
- }.freeze
51
-
52
- def self.configure(uploader, opts = {})
53
- uploader.opts[:content_addressable_hash] = opts.fetch(:hash, uploader.opts[:content_addressable_hash])
54
- uploader.opts[:content_addressable_multihash] = opts.fetch(:multihash, uploader.opts[:content_addressable_multihash])
55
- uploader.opts[:content_addressable_prefix] = opts.fetch(:prefix, uploader.opts[:content_addressable_prefix])
56
- end
57
-
58
- module InstanceMethods
59
- def content_addressable_hash
60
- (opts[:content_addressable_hash] || 'sha256').to_sym
61
- end
62
-
63
- def content_addressable_multihash
64
- String(
65
- opts[:content_addressable_multihash] ||
66
- MULTIHASH_LOOKUP.fetch(content_addressable_hash)
67
- )
68
- end
69
-
70
- def content_addressable_hex(io)
71
- digest = calculate_signature(io, content_addressable_hash, format: :none)
72
- Multihashes.encode(digest, content_addressable_multihash)
73
- .unpack('H*')
74
- .first
75
- end
76
-
77
- def generate_location(io, _)
78
- [opts[:content_addressable_prefix], content_addressable_hex(io)].compact.join('/')
79
- end
80
- end
81
- end
82
-
83
- register_plugin(:content_addressable, ContentAddressable)
84
- end
85
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'shrine'
4
+ require 'shrine/plugins/signature'
5
+
6
+ require 'multihashes'
7
+ require 'digest'
8
+
9
+ class Shrine
10
+ module Plugins
11
+ ##
12
+ # plugin :signature
13
+ # plugin :content_addressable
14
+ #
15
+ # This plugin uses the {Signature} plugin to turn the content into a content
16
+ # addressable, which simply means the digest of the content is now
17
+ # the location, and location the file by digest becomes possible.
18
+ #
19
+ # The results are wrapped in a multihash, so you may change your hashing
20
+ # method in production, whilst not losing access to the old hashes. This
21
+ # works by adding metadata to the hash of what function was used.
22
+ # https://github.com/multiformats/multihash
23
+ #
24
+ # @example Setup the plugin using sha256
25
+ #
26
+ # plugin :content_addressable, hash: :sha256
27
+ # # /1220142711d38ca7a33c5218416f8ffcc64648ca4616a625b5e0a0ab3da1911d5d7a
28
+ #
29
+ # @example You can also prefix the final location, for example to make the
30
+ # location ready for IPFS
31
+ # https://ipfs.io/
32
+ #
33
+ # plugin :content_addressable, hash: :sha256, prefix: 'ipfs'
34
+ # # /ipfs/1220142711d38ca7a33c5218416f8ffcc64648ca4616a625b5e0a0ab3da1911d5d7a
35
+ #
36
+ # @example Reading out the hash data
37
+ #
38
+ # location = '/ipfs/1220142711d38ca7a33c5218416f8ffcc64648ca4616a625b5e0a0ab3da1911d5d7a'
39
+ # hash = location.rpartition('/').last
40
+ # out = Multihashes.decode [hash].pack('H*')
41
+ # # => {:code=>18, :hash_function=>"sha2-256", :length=>32, :digest=>"\x14'\x11\xD3\x8C\xA7\xA3<R\x18Ao\x8F\xFC\xC6FH\xCAF\x16\xA6%\xB5\xE0\xA0\xAB=\xA1\x 91\x1D]z"}
42
+ #
43
+ module ContentAddressable
44
+
45
+ MULTIHASH_LOOKUP = {
46
+ md5: 'md5',
47
+ sha1: 'sha1',
48
+ sha256: 'sha2-256',
49
+ sha512: 'sha2-512'
50
+ }.freeze
51
+
52
+ def self.configure(uploader, opts = {})
53
+ uploader.opts[:content_addressable_hash] = opts.fetch(:hash, uploader.opts[:content_addressable_hash])
54
+ uploader.opts[:content_addressable_multihash] = opts.fetch(:multihash, uploader.opts[:content_addressable_multihash])
55
+ uploader.opts[:content_addressable_prefix] = opts.fetch(:prefix, uploader.opts[:content_addressable_prefix])
56
+ end
57
+
58
+ module InstanceMethods
59
+ def content_addressable_hash
60
+ (opts[:content_addressable_hash] || 'sha256').to_sym
61
+ end
62
+
63
+ def content_addressable_multihash
64
+ String(
65
+ opts[:content_addressable_multihash] ||
66
+ MULTIHASH_LOOKUP.fetch(content_addressable_hash)
67
+ )
68
+ end
69
+
70
+ def content_addressable_hex(io)
71
+ digest = calculate_signature(io, content_addressable_hash, format: :none)
72
+ Multihashes.encode(digest, content_addressable_multihash)
73
+ .unpack('H*')
74
+ .first
75
+ end
76
+
77
+ def generate_location(io, _)
78
+ [opts[:content_addressable_prefix], content_addressable_hex(io)].compact.join('/')
79
+ end
80
+ end
81
+ end
82
+
83
+ register_plugin(:content_addressable, ContentAddressable)
84
+ end
85
+ end
@@ -1,48 +1,48 @@
1
- # frozen_string_literal: true
2
-
3
- lib = File.expand_path('lib', __dir__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = 'shrine-content_addressable'
8
- spec.version = '0.1.0'
9
- spec.authors = ['Derk-Jan Karrenbeld']
10
- spec.email = ['derk-jan+github@karrenbeld.info']
11
-
12
- spec.license = 'MIT'
13
- spec.summary = 'Generate a content addressable location for shrine uploads'
14
-
15
- spec.metadata = {
16
- 'bug_tracker_uri' => 'https://github.com/SleeplessByte/shrine-content_addressable/issues',
17
- 'changelog_uri' => 'https://github.com/SleeplessByte/shrine-content_addressable/CHANGELOG.md',
18
- 'homepage_uri' => 'https://github.com/SleeplessByte/shrine-content_addressable',
19
- 'source_code_uri' => 'https://github.com/SleeplessByte/shrine-content_addressable'
20
- }
21
-
22
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
23
- # to allow pushing to a single host or delete this section to allow pushing to any host.
24
- if spec.respond_to?(:metadata)
25
- # spec.metadata['allowed_push_host'] = 'https://gems.sleeplessbyte.technology'
26
- else
27
- raise 'RubyGems 2.0 or newer is required to protect against ' \
28
- 'public gem pushes.'
29
- end
30
-
31
- # Specify which files should be added to the gem when it is released.
32
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
33
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
34
- f.match(%r{^(test|spec|features)/})
35
- end
36
- spec.bindir = 'exe'
37
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
38
- spec.require_paths = ['lib']
39
-
40
- spec.add_dependency 'multihashes', '~> 0.1.3'
41
- spec.add_dependency 'shrine', '>= 2.0.0', '< 3.0.0'
42
-
43
- spec.add_development_dependency 'bundler', '~> 1.16'
44
- spec.add_development_dependency 'minitest', '~> 5.0'
45
- spec.add_development_dependency 'rake', '~> 10.0'
46
- spec.add_development_dependency 'shrine-configurable_storage', '< 1.0.0'
47
- spec.add_development_dependency 'shrine-memory'
48
- end
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'shrine-content_addressable'
8
+ spec.version = '0.2.0'
9
+ spec.authors = ['Derk-Jan Karrenbeld']
10
+ spec.email = ['derk-jan+github@karrenbeld.info']
11
+
12
+ spec.license = 'MIT'
13
+ spec.summary = 'Generate a content addressable location for shrine uploads'
14
+
15
+ spec.metadata = {
16
+ 'bug_tracker_uri' => 'https://github.com/SleeplessByte/shrine-content_addressable/issues',
17
+ 'changelog_uri' => 'https://github.com/SleeplessByte/shrine-content_addressable/CHANGELOG.md',
18
+ 'homepage_uri' => 'https://github.com/SleeplessByte/shrine-content_addressable',
19
+ 'source_code_uri' => 'https://github.com/SleeplessByte/shrine-content_addressable'
20
+ }
21
+
22
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
23
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
24
+ if spec.respond_to?(:metadata)
25
+ # spec.metadata['allowed_push_host'] = 'https://gems.sleeplessbyte.technology'
26
+ else
27
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
28
+ 'public gem pushes.'
29
+ end
30
+
31
+ # Specify which files should be added to the gem when it is released.
32
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
33
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
34
+ f.match(%r{^(test|spec|features)/})
35
+ end
36
+ spec.bindir = 'exe'
37
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
38
+ spec.require_paths = ['lib']
39
+
40
+ spec.add_dependency 'multihashes', '~> 0.1.3'
41
+ spec.add_dependency 'shrine', '>= 2.0.0', '< 3.0.0'
42
+
43
+ spec.add_development_dependency 'bundler', '~> 1.16'
44
+ spec.add_development_dependency 'minitest', '~> 5.0'
45
+ spec.add_development_dependency 'rake', '~> 10.0'
46
+ spec.add_development_dependency 'shrine-configurable_storage', '< 1.0.0'
47
+ spec.add_development_dependency 'shrine-memory'
48
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shrine-content_addressable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derk-Jan Karrenbeld
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-06-09 00:00:00.000000000 Z
11
+ date: 2018-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multihashes
@@ -132,6 +132,7 @@ files:
132
132
  - Rakefile
133
133
  - bin/console
134
134
  - bin/setup
135
+ - lib/content_addressable_file.rb
135
136
  - lib/shrine/plugins/content_addressable.rb
136
137
  - shrine-content_addressable.gemspec
137
138
  homepage: