neofiles 1.1.4 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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