file_pool 0.5.2 → 0.6.0

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