neofiles 1.1.4 → 1.2.1

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: 4a39d67d56f59a4bb677f560fb8aad30cdf9beb8
4
- data.tar.gz: c452ac1bb6de68fec36a1c3f69370768ee5ae8ac
3
+ metadata.gz: b3a3e40bfe2629b65d69615621cbcdc37ab95e20
4
+ data.tar.gz: 7f259aec163ff50fc5669735f4477e3c058cd942
5
5
  SHA512:
6
- metadata.gz: c66cf00f7b15ea2199a152ce86f04145aea3b90b1563279f6fb9dce9ef72c0b44d98c1c49a417cb571cedc36bee5b93e498c853fdf027533cb3d7cbc73dbd35e
7
- data.tar.gz: 1e3242347e8314812ce3749ff7c58c7948be423a57f2f1b9a2cf59cb298c9a1b80591f54f7ca9d8baa70c457b58aa6f96c03371a3eb9abc17345daae6b329374
6
+ metadata.gz: 3f6faca2b60cb0d6cb706ba969d729b2b25146a8eaeaad7dc937e72aa74a5fa2119ff424f27660b477d1eacca2a716b5e2c59bd9d0b36fc0c6c30fed7433cb47
7
+ data.tar.gz: 63bb20c3acf34a8e337bb9097454577c46d045de915f5948c810cbb9f38b45c225d08b027d945dbba73ec3dbde29ccff1216cf4769c604ac6e586e614570176d
@@ -0,0 +1,22 @@
1
+ module Neofiles::DataStore::Mongo::FileHelper
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ has_many :chunks, dependent: :destroy, order: [:n, :asc], class_name: 'Neofiles::FileChunk'
6
+ field :chunk_size, type: Integer, default: Neofiles::DataStore::Mongo::DEFAULT_CHUNK_SIZE
7
+ validates :chunk_size, presence: true
8
+
9
+ def self.copy_from_mongo_to_amazon_s3(ids)
10
+ ids.each do |id|
11
+ begin
12
+ mongo_object = Neofiles::DataStore::Mongo.find id
13
+ amazon_object = Neofiles::DataStore::AmazonS3.find(id) rescue nil
14
+ Neofiles::DataStore::AmazonS3.new(id).write(mongo_object.data) unless amazon_object
15
+ rescue Neofiles::DataStore::NotFoundException
16
+ next
17
+ end
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,2 @@
1
+ module Neofiles::DataStore
2
+ end
@@ -0,0 +1,95 @@
1
+ # Module for storing and reading files from Amazon S3
2
+ # If you want to work with amazon s3 you need set values for the following parameters in your config file
3
+ # amazon_s3_region - the AWS region to connect to. The region is used to construct the client endpoint.
4
+ # amazon_s3_api, amazon_s3_secret - used to set credentials statically
5
+ # bucket_name - storage name in amazon_s3. Bucket must have a name that conforms to the naming requirements for non-US Standard regions.
6
+ # http://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-s3-bucket-naming-requirements.html
7
+ # File will be named as id of the Neofiles::File object
8
+
9
+ require 'aws-sdk'
10
+
11
+ class Neofiles::DataStore::AmazonS3
12
+
13
+ def self.bucket_name
14
+ Rails.application.config.neofiles.amazon_s3_bucket
15
+ end
16
+
17
+ def self.find(id)
18
+ s3_object = new(id)
19
+ if s3_object.data
20
+ s3_object
21
+ else
22
+ raise Neofiles::DataStore::NotFoundException
23
+ end
24
+ end
25
+
26
+
27
+
28
+ attr_reader :id, :data, :length, :md5
29
+
30
+ def initialize(id)
31
+ @id = id
32
+ end
33
+
34
+ def data
35
+ @data ||= client.get_object(
36
+ bucket: bucket_name,
37
+ key: s3_key
38
+ ).body.read
39
+ rescue Aws::S3::Errors::ServiceError
40
+ nil
41
+ end
42
+
43
+ def length
44
+ @length ||= data.length
45
+ end
46
+
47
+ def md5
48
+ @md5 ||= begin
49
+ md5 = Digest::MD5.new
50
+ md5 << data
51
+ md5.hexdigest
52
+ end
53
+ end
54
+
55
+ def write(data)
56
+ if data.is_a? Tempfile
57
+ data.flush
58
+ data.rewind
59
+ data = data.read
60
+ end
61
+
62
+ client.put_object(
63
+ body: data,
64
+ bucket: bucket_name,
65
+ key: s3_key
66
+ )
67
+ @data = data
68
+ end
69
+
70
+
71
+
72
+ private
73
+
74
+ def s3_key
75
+ object_id = @id.to_s
76
+ object_id[0..1] + '/' + object_id[2..4] + '/' + object_id
77
+ end
78
+
79
+ def bucket_name
80
+ self.class.bucket_name
81
+ end
82
+
83
+ def client
84
+ @client ||= Aws::S3::Client.new(
85
+ region: Rails.application.config.neofiles.amazon_s3_region,
86
+ credentials: Aws::Credentials.new(
87
+ Rails.application.config.neofiles.amazon_s3_api,
88
+ Rails.application.config.neofiles.amazon_s3_secret
89
+ )
90
+ )
91
+ rescue Aws::S3::Errors::ServiceError
92
+ nil
93
+ end
94
+
95
+ end
@@ -0,0 +1,132 @@
1
+ class Neofiles::DataStore::Mongo
2
+
3
+ DEFAULT_CHUNK_SIZE = Rails.application.config.neofiles.mongo_default_chunk_size
4
+
5
+ def self.chunks(id)
6
+ Neofiles::FileChunk.where(file_id: id).order_by(n: :asc)
7
+ end
8
+
9
+ def self.find(id)
10
+ if chunks(id).any?
11
+ new(id)
12
+ else
13
+ raise Neofiles::DataStore::NotFoundException
14
+ end
15
+ end
16
+
17
+
18
+
19
+ attr_reader :id, :data, :length, :md5
20
+
21
+ def initialize(id)
22
+ @id = id
23
+ end
24
+
25
+ def data
26
+ @data ||= chunks.pluck(:data).map(&:data).join
27
+ end
28
+
29
+ def length
30
+ @length ||= data.length
31
+ end
32
+
33
+ def md5
34
+ @md5 ||= begin
35
+ md5 = Digest::MD5.new
36
+ md5 << data
37
+ md5.hexdigest
38
+ end
39
+ end
40
+
41
+ def write(data)
42
+ data_buf = []
43
+ md5 = Digest::MD5.new
44
+ length, n = 0, 0
45
+
46
+ reading(data) do |io|
47
+ chunking(io, DEFAULT_CHUNK_SIZE) do |buf|
48
+ md5 << buf
49
+ data_buf << buf
50
+ length += buf.size
51
+ chunk = chunks.build file_id: id
52
+ chunk.data = binary_for buf
53
+ chunk.n = n
54
+ n += 1
55
+ chunk.save!
56
+ end
57
+ end
58
+
59
+ @data = data_buf.join
60
+ @length = length
61
+ @md5 = md5.hexdigest
62
+ end
63
+
64
+
65
+
66
+ private
67
+
68
+ def chunks
69
+ self.class.chunks id
70
+ end
71
+
72
+ # Yield block with IO stream made from input arg, which can be file name or other IO readable object.
73
+ def reading(arg, &block)
74
+ if arg.respond_to?(:read)
75
+ rewind(arg) do |io|
76
+ block.call(io)
77
+ end
78
+ else
79
+ open(arg.to_s) do |io|
80
+ block.call(io)
81
+ end
82
+ end
83
+ end
84
+
85
+ # Split IO stream by chunks chunk_size bytes each and yield each chunk in block.
86
+ def chunking(io, chunk_size, &block)
87
+ if io.method(:read).arity == 0
88
+ data = io.read
89
+ i = 0
90
+ loop do
91
+ offset = i * chunk_size
92
+ length = i + chunk_size < data.size ? chunk_size : data.size - offset
93
+
94
+ break if offset >= data.size
95
+
96
+ buf = data[offset, length]
97
+ block.call(buf)
98
+ i += 1
99
+ end
100
+ else
101
+ while buf = io.read(chunk_size)
102
+ block.call(buf)
103
+ end
104
+ end
105
+ end
106
+
107
+ # Construct Mongoid binary object from string of bytes.
108
+ def binary_for(*buf)
109
+ BSON::Binary.new buf.join, :generic
110
+ end
111
+
112
+ # Yield IO-like argument to block rewinding it first, if possible.
113
+ def rewind(io, &block)
114
+ begin
115
+ pos = io.pos
116
+ io.flush
117
+ io.rewind
118
+ rescue
119
+ nil
120
+ end
121
+
122
+ begin
123
+ block.call(io)
124
+ ensure
125
+ begin
126
+ io.pos = pos
127
+ rescue
128
+ nil
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,2 @@
1
+ class Neofiles::DataStore::NotFoundException < StandardError
2
+ end
@@ -44,74 +44,36 @@ class Neofiles::File
44
44
 
45
45
  include Mongoid::Document
46
46
  include Mongoid::Timestamps
47
+ include Neofiles::DataStore::Mongo::FileHelper
47
48
 
48
49
  store_in collection: Rails.application.config.neofiles.mongo_files_collection, client: Rails.application.config.neofiles.mongo_client
49
50
 
50
- has_many :chunks, dependent: :destroy, order: [:n, :asc], class_name: 'Neofiles::FileChunk'
51
-
52
- DEFAULT_CHUNK_SIZE = Rails.application.config.neofiles.mongo_default_chunk_size
53
-
54
51
  field :filename, type: String
55
52
  field :content_type, type: String
56
53
  field :length, type: Integer, default: 0
57
- field :chunk_size, type: Integer, default: DEFAULT_CHUNK_SIZE
58
54
  field :md5, type: String, default: Digest::MD5.hexdigest('')
59
55
  field :description, type: String
60
56
  field :owner_type, type: String
61
57
  field :owner_id, type: String
62
58
  field :is_deleted, type: Mongoid::Boolean
63
59
 
64
- validates :filename, :length, :chunk_size, :md5, presence: true
65
-
66
60
  before_save :save_file
67
61
  after_save :nullify_unpersisted_file
68
62
 
69
-
70
-
71
- # Yield block for each chunk.
72
- def each(&block)
73
- chunks.all.order_by([:n, :asc]).each do |chunk|
74
- block.call(chunk.to_s)
75
- end
76
- end
77
-
78
- # Get a portion of chunks, either via Range of Fixnum (length).
79
- def slice(*args)
80
- case args.first
81
- when Range
82
- range = args.first
83
- first_chunk = (range.min / chunk_size).floor
84
- last_chunk = (range.max / chunk_size).ceil
85
- offset = range.min % chunk_size
86
- length = range.max - range.min + 1
87
- when Fixnum
88
- start = args.first
89
- start = self.length + start if start < 0
90
- length = args.size == 2 ? args.last : 1
91
- first_chunk = (start / chunk_size).floor
92
- last_chunk = ((start + length) / chunk_size).ceil
93
- offset = start % chunk_size
94
- end
95
-
96
- data = ''
97
-
98
- chunks.where(n: first_chunk..last_chunk).order_by(n: :asc).each do |chunk|
99
- data << chunk
100
- end
101
-
102
- data[offset, length]
103
- end
104
-
105
63
  # Chunks bytes concatenated, that is the whole file content.
106
64
  def data
107
- data = ''
108
- each { |chunk| data << chunk }
109
- data
65
+ self.class.read_data_stores.each do |store|
66
+ begin
67
+ return store.find(id).data
68
+ rescue Neofiles::DataStore::NotFoundException
69
+ next
70
+ end
71
+ end
110
72
  end
111
73
 
112
74
  # Encode bytes in base64.
113
75
  def base64
114
- Array(to_s).pack('m')
76
+ Array(data).pack('m')
115
77
  end
116
78
 
117
79
  # Encode bytes id data uri.
@@ -120,18 +82,6 @@ class Neofiles::File
120
82
  "data:#{content_type};base64,#{data}"
121
83
  end
122
84
 
123
- # Bytes as chunks array, if block is given — yield it.
124
- def bytes(&block)
125
- if block
126
- each { |data| block.call(data) }
127
- length
128
- else
129
- bytes = []
130
- each { |data| bytes.push(*data) }
131
- bytes
132
- end
133
- end
134
-
135
85
 
136
86
 
137
87
  attr_reader :file
@@ -159,26 +109,17 @@ class Neofiles::File
159
109
  # File length and md5 hash are computed automatically.
160
110
  def save_file
161
111
  if @file
162
- self.chunks.delete_all
163
-
164
- md5 = Digest::MD5.new
165
- length, n = 0, 0
166
-
167
- self.class.reading(@file) do |io|
168
- self.class.chunking(io, chunk_size) do |buf|
169
- md5 << buf
170
- length += buf.size
171
- chunk = self.chunks.build
172
- chunk.data = self.class.binary_for(buf)
173
- chunk.n = n
174
- n += 1
175
- chunk.save!
176
- self.chunks.push(chunk)
112
+ self.class.write_data_stores.each do |store|
113
+ begin
114
+ data_store_object = store.new id
115
+ data_store_object.write @file
116
+ self.length = data_store_object.length
117
+ self.md5 = data_store_object.md5
118
+ rescue => ex
119
+ notify_airbrake(ex) if defined? notify_airbrake
120
+ next
177
121
  end
178
122
  end
179
-
180
- self.length = length
181
- self.md5 = md5.hexdigest
182
123
  end
183
124
  end
184
125
 
@@ -193,46 +134,6 @@ class Neofiles::File
193
134
  template.neofiles_link self, nil, target: '_blank'
194
135
  end
195
136
 
196
- # Yield block with IO stream made from input arg, which can be file name or other IO readable object.
197
- def self.reading(arg, &block)
198
- if arg.respond_to?(:read)
199
- self.rewind(arg) do |io|
200
- block.call(io)
201
- end
202
- else
203
- open(arg.to_s) do |io|
204
- block.call(io)
205
- end
206
- end
207
- end
208
-
209
- # Split IO stream by chunks chunk_size bytes each and yield each chunk in block.
210
- def self.chunking(io, chunk_size, &block)
211
- if io.method(:read).arity == 0
212
- data = io.read
213
- i = 0
214
- loop do
215
- offset = i * chunk_size
216
- length = i + chunk_size < data.size ? chunk_size : data.size - offset
217
-
218
- break if offset >= data.size
219
-
220
- buf = data[offset, length]
221
- block.call(buf)
222
- i += 1
223
- end
224
- else
225
- while buf = io.read(chunk_size)
226
- block.call(buf)
227
- end
228
- end
229
- end
230
-
231
- # Construct Mongoid binary object from string of bytes.
232
- def self.binary_for(*buf)
233
- BSON::Binary.new(buf.join, :generic)
234
- end
235
-
236
137
  # Try different methods to extract file name or path from argument object.
237
138
  def self.extract_basename(object)
238
139
  filename = nil
@@ -300,24 +201,21 @@ class Neofiles::File
300
201
  class_by_file_name(extract_basename(file_object))
301
202
  end
302
203
 
303
- # Yield IO-like argument to block rewinding it first, if possible.
304
- def self.rewind(io, &block)
305
- begin
306
- pos = io.pos
307
- io.flush
308
- io.rewind
309
- rescue
310
- nil
311
- end
204
+ def self.read_data_stores
205
+ get_stores_class_name Rails.application.config.neofiles.read_data_stores
206
+ end
312
207
 
313
- begin
314
- block.call(io)
315
- ensure
316
- begin
317
- io.pos = pos
318
- rescue
319
- nil
320
- end
208
+ def self.write_data_stores
209
+ get_stores_class_name Rails.application.config.neofiles.write_data_stores
210
+ end
211
+
212
+ # return array with names for each store
213
+ def self.get_stores_class_name(stores)
214
+ if stores.is_a?(Array)
215
+ stores.map { |store| Neofiles::DataStore.const_get(store.camelize) }
216
+ else
217
+ get_stores_class_name [stores]
321
218
  end
322
219
  end
220
+
323
221
  end
data/lib/neofiles.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'neofiles/engine'
2
+ require 'aspect_ratio'
2
3
 
3
4
  module Neofiles
4
5
  # Attach Neofiles specific routes in your routes.rb file:
@@ -34,8 +35,6 @@ module Neofiles
34
35
  # width, height - max width and height after resize
35
36
  # resize_options - {crop: '1'/'0'}, @see Neofiles::ImagesController#show
36
37
  #
37
- # Can call ImageMagick.
38
- #
39
38
  def resized_image_dimensions(image_file, width, height, resize_options)
40
39
  # dimensions are equal to requested ones if cropping
41
40
  return width, height if crop_requested? resize_options
@@ -58,17 +57,7 @@ module Neofiles
58
57
  # image fits into requested dimensions, no resizing will occur
59
58
  return image_file_width, image_file_height if image_file_width <= width && image_file_height <= height
60
59
 
61
- # ... construct request ...
62
- command = MiniMagick::CommandBuilder.new(:convert) # convert input file...
63
- command.size([image_file_width, image_file_height].join 'x') # with the given dimensions...
64
- command.xc('white') # and filled with whites...
65
- command.resize([width, height].join 'x') # to fit in the given rectangle...
66
- command.push('info:-') # return info about the resulting file
67
-
68
- # ... and send it to ImageMagick
69
- # the result will be: xc:white XC 54x100 54x100+0+0 16-bit DirectClass 0.070u 0:00.119
70
- # extract dimensions and return them as array of integers
71
- MiniMagick::Image.new(nil, nil).run(command).match(/ (\d+)x(\d+) /).values_at(1, 2).map(&:to_i)
60
+ AspectRatio.resize(image_file_width, image_file_height, width, height).map(&:to_i)
72
61
 
73
62
  rescue
74
63
  nil
@@ -1,3 +1,3 @@
1
1
  module Neofiles
2
- VERSION = '1.1.4'
2
+ VERSION = '1.2.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neofiles
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.4
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konanykhin Ilya
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-04 00:00:00.000000000 Z
11
+ date: 2018-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - '='
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.2.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: aws-sdk
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: aspect_ratio
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description: 'Library for managing files: creating & storing, linking to file owners,
84
112
  serving files from MongoDB'
85
113
  email:
@@ -106,6 +134,11 @@ files:
106
134
  - app/controllers/neofiles/files_controller.rb
107
135
  - app/controllers/neofiles/images_controller.rb
108
136
  - app/helpers/neofiles/neofiles_helper.rb
137
+ - app/models/concerns/neofiles/data_store/mongo/file_helper.rb
138
+ - app/models/neofiles/data_store.rb
139
+ - app/models/neofiles/data_store/amazon_s3.rb
140
+ - app/models/neofiles/data_store/mongo.rb
141
+ - app/models/neofiles/data_store/not_found_exception.rb
109
142
  - app/models/neofiles/file.rb
110
143
  - app/models/neofiles/file_chunk.rb
111
144
  - app/models/neofiles/image.rb