file_pool 0.5.2 → 0.6.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.
Files changed (3) hide show
  1. data/lib/file_pool/version.rb +1 -1
  2. data/lib/file_pool.rb +168 -61
  3. metadata +5 -5
@@ -1,3 +1,3 @@
1
1
  module FilePool
2
- VERSION = "0.5.2"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/file_pool.rb CHANGED
@@ -27,20 +27,20 @@ module FilePool
27
27
  # encryption/decryption in bytes. Larger blocks need more memory and less time (less IO).
28
28
  # Defaults to 1'048'576 (1 MiB).
29
29
  # * :copy_source (true,false)
30
- # if +false+ files added to the pool are hard-linked with the source if source and file pool
30
+ # if +false+ files added to the pool are hard-linked with the source if source and file pool
31
31
  # are on the same file system (default). If set to +true+ files are always copied into the pool.
32
32
  # * :mode (Integer)
33
33
  # File mode to set on all files added to the pool. E.g. +mode:+ +0640+ for +rw-r-----+ or symbolic "u=wrx,go=rx"
34
34
  # (see Ruby stdlib FileUtils#chmod).
35
- # Note that the desired mode is not set if the file is hard-linked with the source.
35
+ # Note that the desired mode is not set if the file is hard-linked with the source.
36
36
  # Use +copy_source:true+ when to ensure.
37
37
  # * :owner
38
- # Owner of the files added to the pool.
39
- # Note that the desired owner is not set if the file is hard-linked with the source.
38
+ # Owner of the files added to the pool.
39
+ # Note that the desired owner is not set if the file is hard-linked with the source.
40
40
  # Use +copy_source:true+ when to ensure.
41
41
  # * :group
42
- # Group of the files added to the pool.
43
- # Note that the desired group is not set if the file is hard-linked with the source.
42
+ # Group of the files added to the pool.
43
+ # Note that the desired group is not set if the file is hard-linked with the source.
44
44
  # Use +copy_source:true+ when to ensure.
45
45
  def self.setup root, options={}
46
46
  unless(unknown = options.keys - [:encryption_block_size, :secrets_file, :copy_source, :mode, :owner, :group]).empty?
@@ -70,32 +70,48 @@ module FilePool
70
70
  #
71
71
  # path (String)::
72
72
  # path of the file to add.
73
+ # options (Hash)::
74
+ # :background (true,false) adding large files can take long (esp. with encryption), +true+ won't block, default is +false+
73
75
  #
74
76
  # === Return Value:
75
77
  #
76
78
  # :: *String* containing a new unique ID for the file added.
77
- def self.add! orig_path
79
+ def self.add! orig_path, options = {}
78
80
  newid = uuid
79
- target = path newid
80
81
 
81
- if @@crypted_mode
82
- FileUtils.mkpath(id2dir_secured newid)
83
- path = crypt(orig_path)
84
- else
85
- path = orig_path
86
- FileUtils.mkpath(id2dir newid)
87
- end
82
+ raise Errno::ENOENT unless File.exist?(orig_path)
88
83
 
89
- if !@@copy_source and (File.stat(path).dev == File.stat(File.dirname(target)).dev)
90
- FileUtils.link(path, target)
91
- else
92
- FileUtils.copy(path, target)
84
+ child = fork do
85
+ target = path newid
86
+
87
+ if @@crypted_mode
88
+ FileUtils.mkpath(id2dir_secured newid)
89
+ path = encrypt_to_tempfile(orig_path)
90
+ else
91
+ path = orig_path
92
+ FileUtils.mkpath(id2dir newid)
93
+ end
94
+
95
+ if !@@copy_source and (File.stat(path).dev == File.stat(File.dirname(target)).dev)
96
+ FileUtils.link(path, target)
97
+ else
98
+ FileUtils.copy(path, target)
99
+ end
100
+
101
+ # don't chmod if orginal file is same as target (hard-linked)
102
+ if File.stat(orig_path).ino != File.stat(File.dirname(target)).ino
103
+ FileUtils.chmod(@@mode, target) if @@mode
104
+ FileUtils.chown(@@owner, @@group, target)
105
+ end
93
106
  end
94
107
 
95
- # don't chmod if orginal file is same as target (hard-linked)
96
- if File.stat(orig_path).ino != File.stat(File.dirname(target)).ino
97
- FileUtils.chmod(@@mode, target) if @@mode
98
- FileUtils.chown(@@owner, @@group, target)
108
+
109
+ if options[:background]
110
+ # don't wait, avoid zombies
111
+ Process.detach(child)
112
+ else
113
+ # block until done
114
+ Process.waitpid(child)
99
115
  end
100
116
 
101
117
  newid
@@ -110,18 +126,45 @@ module FilePool
110
126
  #
111
127
  # source (String)::
112
128
  # path of the file to add.
129
+ # options (Hash)::
130
+ # :background (true,false) adding large files can take long (esp. with encryption), +true+ won't block, default is +false+
113
131
  #
114
132
  # === Return Value:
115
133
  #
116
134
  # :: *String* containing a new unique ID for the file added.
117
135
  # :: +false+ when the file could not be stored.
118
- def self.add path
119
- self.add!(path)
136
+ def self.add path, options = {}
137
+ self.add!(path, options)
120
138
 
121
139
  rescue Exception
122
140
  return false
123
141
  end
124
142
 
143
+ #
144
+ # Add a new file from a stream to the file pool
145
+ #
146
+ def self.add_stream in_stream
147
+ newid = uuid
148
+
149
+ # ensure target path exists
150
+ if @@crypted_mode
151
+ FileUtils.mkpath(id2dir_secured newid)
152
+ else
153
+ FileUtils.mkpath(id2dir newid)
154
+ end
155
+
156
+ child = fork do
157
+ # create the target file to write
158
+ open(path(newid),'w') do |out_stream|
159
+ encrypt in_stream, out_stream
160
+ end
161
+ end
162
+
163
+ Process.detach(child)
164
+
165
+ newid
166
+ end
167
+
125
168
  #
126
169
  # Return the file's path corresponding to the passed file ID, no matter if it
127
170
  # exists or not. In encrypting mode the file is first decrypted and the
@@ -141,7 +184,7 @@ module FilePool
141
184
  #
142
185
  # :: *String*, absolute path of the file in the pool or to temporary location if it was decrypted.
143
186
  def self.path fid, options={}
144
- options[:decrypt] = true unless options[:decrypt] == false
187
+ options[:decrypt] = options.fetch(:decrypt, true)
145
188
 
146
189
  raise InvalidFileId unless valid?(fid)
147
190
 
@@ -151,7 +194,7 @@ module FilePool
151
194
  if @@crypted_mode
152
195
  if options[:decrypt]
153
196
  # return path of decrypted file (tmp path)
154
- decrypt id2dir_secured(fid) + "/#{fid}"
197
+ decrypt_to_tempfile id2dir_secured(fid) + "/#{fid}"
155
198
  else
156
199
  id2dir_secured(fid) + "/#{fid}"
157
200
  end
@@ -171,6 +214,33 @@ module FilePool
171
214
  end
172
215
  end
173
216
 
217
+ # Returns an IO object providing an (unencrypted) stream of data of the given file ID
218
+ # To get the stream of the encrypted data pass :decrypt => false, as an option.
219
+ #
220
+ # === Parameters:
221
+ #
222
+ # fid (String)::
223
+ # File ID which was generated by a previous #add operation.
224
+ #
225
+ # options (Hash)::
226
+ # :decrypt (true,false) In encryption mode don't decrypt, but prioved the encrypted data. Defaults to +true+.
227
+ #
228
+ # === Return Value:
229
+ #
230
+ # :: *IO*, IO stream open for reading
231
+ #
232
+ def self.stream fid, options={}
233
+ options[:decrypt] = options.fetch(:decrypt, true)
234
+
235
+ if path = path(fid, :decrypt => false)
236
+ if @@crypted_mode and options[:decrypt]
237
+ decrypt_to_stream path
238
+ else
239
+ open(path)
240
+ end
241
+ end
242
+ end
243
+
174
244
  #
175
245
  # Remove a previously added file by its ID. Same as FilePool.remove,
176
246
  # but throws exceptions on failure.
@@ -271,7 +341,7 @@ module FilePool
271
341
  end
272
342
 
273
343
  #
274
- # Crypt a file and store the result in the temp.
344
+ # Crypt a file and store the result a Tempfile
275
345
  #
276
346
  # Returns the path to the crypted file.
277
347
  #
@@ -282,30 +352,19 @@ module FilePool
282
352
  #
283
353
  # === Return Value:
284
354
  #
285
- # :: *String*Path and name of the crypted file.
286
- def self.crypt path
355
+ # :: *String* Path and name of the crypted file.
356
+ def self.encrypt_to_tempfile path
287
357
  # Crypt the file in the temp folder and copy after
288
- cipher = create_cipher
289
- result = Tempfile.new 'FilePool-encrypt'
358
+ tempfile = Tempfile.new 'FilePool-encrypt'
359
+ tempfile.sync = true
290
360
 
291
- result.set_encoding Encoding::BINARY,Encoding::BINARY
292
- buf = ''.encode(Encoding::BINARY)
361
+ encrypt open(path), tempfile
293
362
 
294
- File.open(path) do |inf|
295
- while inf.read(@@block_size, buf)
296
- result << cipher.update(buf)
297
- result.flush
298
- result.fsync
299
- end
300
- result << cipher.final
301
- end
302
-
303
- result.close
304
- result.path
363
+ tempfile.path
305
364
  end
306
365
 
307
366
  #
308
- # Decrypt a file and give a path to it.
367
+ # Decrypt file to a file in /tmp
309
368
  #
310
369
  # Returns the path to the decrypted file.
311
370
  #
@@ -316,28 +375,76 @@ module FilePool
316
375
  #
317
376
  # === Return Value:
318
377
  #
319
- # :: *String*Path and name of the crypted file.
320
- def self.decrypt path
321
- decipher = create_decipher
322
- # Now decrypt the data:
323
- output = Tempfile.new 'FilePool-decrypt'
378
+ # :: *String* Path and name of the decrypted file.
379
+ def self.decrypt_to_tempfile path
380
+ tmpfile = Tempfile.new 'FilePool-decrypt'
381
+ tmpfile.sync = true
324
382
 
325
- output.set_encoding Encoding::BINARY,Encoding::BINARY
326
- buf = ''.encode(Encoding::BINARY)
383
+ File.open(path) do |encrypted_file|
384
+ decrypt encrypted_file, tmpfile
385
+ end
386
+
387
+ tmpfile.path
388
+ end
389
+
390
+ #
391
+ # Decrypt file to a stream (IO) non-blocking by forking a child process.
392
+ #
393
+ # === Parameters:
394
+ #
395
+ # path (String)::
396
+ # path of the file to decrypt.
397
+ #
398
+ # === Return Value:
399
+ #
400
+ # :: *IO* decrypted data as stream
401
+ def self.decrypt_to_stream path
402
+
403
+ pipe_read, pipe_write = IO.pipe
404
+
405
+ child = fork do
406
+ # in child process: decrypting
407
+ pipe_read.close
327
408
 
328
- File.open(path) do |inf|
329
- while inf.read(@@block_size, buf)
330
- output << decipher.update(buf)
331
- output.flush
332
- output.fsync
409
+ # open encrypted file
410
+ File.open(path) do |encrypted_file|
411
+ decrypt encrypted_file, pipe_write
333
412
  end
334
- output << decipher.final
413
+
414
+ pipe_write.close
335
415
  end
336
416
 
337
- output.open # re-open for reading, prevents early deletion of tempfile
338
- output.path
417
+ # in parent
418
+ Process.detach(child) # not waiting for it
419
+ pipe_write.close
420
+ pipe_read
339
421
  end
340
422
 
423
+
424
+ # decrypts data stream +in_stream+ (IO: readable) to +out+ (IO: writeable), blocking
425
+ def self.decrypt in_stream, out_stream
426
+ crypt(in_stream, out_stream, :decrypt)
427
+ end
428
+
429
+ # encrypts data stream +in+ (IO: readable) to +out+ (IO: writeable), blocking
430
+ def self.encrypt in_stream, out_stream
431
+ crypt(in_stream, out_stream, :encrypt)
432
+ end
433
+
434
+ def self.crypt in_stream, out_stream, direction
435
+ cipher = (direction == :encrypt) ? create_cipher : create_decipher
436
+
437
+ buf = ''.encode(Encoding::BINARY)
438
+ out_stream.set_encoding Encoding::BINARY,Encoding::BINARY
439
+ in_stream.set_encoding Encoding::BINARY,Encoding::BINARY
440
+
441
+ while in_stream.read(@@block_size, buf)
442
+ out_stream << cipher.update(buf)
443
+ end
444
+ out_stream << cipher.final
445
+
446
+ nil
447
+ end
341
448
  #
342
449
  # Creates a cipher to encrypt data.
343
450
  #
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: file_pool
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 7
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 5
9
- - 2
10
- version: 0.5.2
8
+ - 6
9
+ - 0
10
+ version: 0.6.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - "robokopp (Robert Anni\xC3\xA9s)"
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2018-10-19 00:00:00 +02:00
18
+ date: 2019-02-12 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency