Class: S3DB::FileBackend

Inherits:
Backend show all
Defined in:
lib/s3db/file_backend.rb

Constant Summary collapse

PATH_BLACKLIST =
[
  'bin',
  'boot',
  'cdrom',
  'data',
  'dev',
  'docker',
  'etc',
  'home',
  'lib',
  'lib64',
  'media',
  'mnt',
  'opt',
  'proc',
  'root',
  'run',
  'sbin',
  'srv',
  'sys',
  'usr',
  'var',
  'badpath', #this is just to be VERY sure we don't trash a real dir in tests
]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ FileBackend

Create a new FileBackend.

path - String path to use as the base storage location.

returns a new FileBackend.



94
95
96
97
98
# File 'lib/s3db/file_backend.rb', line 94

def initialize(path)
  @errors = []

  @path = path.strip
end

Instance Attribute Details

#errorsObject (readonly)

Returns the value of attribute errors.



5
6
7
# File 'lib/s3db/file_backend.rb', line 5

def errors
  @errors
end

#pathObject (readonly)

Returns the value of attribute path.



5
6
7
# File 'lib/s3db/file_backend.rb', line 5

def path
  @path
end

Class Method Details

.create(path) ⇒ Object

Create a new base path for a file backend storage location. This method will create the basepath if it doesn’t exist, but also use an existing path if it does exest.

path - String base path. Required.

returns a new FileBackend.



40
41
42
43
44
45
# File 'lib/s3db/file_backend.rb', line 40

def create(path)
  be = new(path)
  be.save

  be
end

.create!(path) ⇒ Object

Create a new base path for a file backend storage location. This method will throw an error if the path already exists. This is safer.

path - String base path. Required.

returns a new FileBackend.



53
54
55
56
57
58
# File 'lib/s3db/file_backend.rb', line 53

def create!(path)
  be = new(path)
  be.save!

  be
end

.delete(path) ⇒ Object

Destroy a base path, whether or not it’s empty. This is dangerous, and should be used with great care.

path - String path to delete. Required.

returns itself on success, and raises an error on failure.



78
79
80
81
82
83
84
85
86
# File 'lib/s3db/file_backend.rb', line 78

def delete(path)
  be = new(path)

  if be.valid!
    FileUtils.rm_rf(path)
  end

  self
end

.destroy(path) ⇒ Object

Destroy a base path for data storage. This method will raise an error if the directory is not empty.

path - String base path. Required.

returns the String path that was removed.



66
67
68
69
70
# File 'lib/s3db/file_backend.rb', line 66

def destroy(path)
  be = new(path).destroy

  be
end

Instance Method Details

#collection_path(db_name, collection_name) ⇒ Object

Build a full path to a collection from the base path, database name and collection name.

db_name - String name of database. Required. collection_name - String name of collection. Required.

returns a String path.



219
220
221
# File 'lib/s3db/file_backend.rb', line 219

def collection_path(db_name, collection_name)
  File.join(@path, db_name, collection_name)
end

#data_path(db_name, collection_name) ⇒ Object

Build a full path to the data dir from the base path, database name and collection name.

db_name - String name of database. Required. collection_name - String name of collection. Required.

returns a String path.



241
242
243
# File 'lib/s3db/file_backend.rb', line 241

def data_path(db_name, collection_name)
  File.join(@path, db_name, collection_name, 'data')
end

#db_exist?(db_name) ⇒ Boolean

Returns:

  • (Boolean)


488
489
490
# File 'lib/s3db/file_backend.rb', line 488

def db_exist?(db_name)
  Dir.exist?(db_path(db_name))
end

#db_path(db_name) ⇒ Object

Build a full path from the base path and database name.

db_name - String name of database. Required.

returns a String path.



208
209
210
# File 'lib/s3db/file_backend.rb', line 208

def db_path(db_name)
  File.join(@path, db_name)
end

#delete_collection(db_name, collection_name) ⇒ Object

Delete a collection directory from disk. Will only succed if the collection is empty.

db_name - String name of database to remove. Required. collection_name - String name of collection to remove. Required.

returns an Array of the databases deleted, or empty if the database did not exist.



420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/s3db/file_backend.rb', line 420

def delete_collection(db_name, collection_name)
  path = collection_path(db_name, collection_name)

  if File.exist?(schema_path(db_name, collection_name))
    File.delete(schema_path(db_name, collection_name))
  end

  if Dir.exist?(data_path(db_name, collection_name))
    begin
      Dir.rmdir(data_path(db_name, collection_name))
    rescue Errno::ENOTEMPTY
      raise ArgumentError, 'collection/data is not empty!'
    end
  end

  if Dir.exist?(path)
    begin
      Dir.rmdir(path)
    rescue Errno::ENOTEMPTY
      raise ArgumentError, 'collection is not empty!'
    end

    return [collection_name]
  end

  []
end

#delete_db(db_name) ⇒ Object

Delete a database directory from disk. Will only succed if the database is empty.

db_name - String name of database to remove. Required.

returns an Array of the databases deleted, or empty if the database did not exist.



396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/s3db/file_backend.rb', line 396

def delete_db(db_name)
  path = db_path(db_name)

  if Dir.exist?(path)
    begin
      Dir.rmdir(path)
    rescue Errno::ENOTEMPTY
      raise ArgumentError, 'database is not empty!'
    end

    return [db_name]
  end

  []
end

#delete_record(db_name, collection_name, filename) ⇒ Object

Delete a collection directory from disk. Will only succed if the collection is empty.

db_name - String name of database to remove. Required. collection_name - String name of collection to remove. Required.

returns an Array of the databases deleted, or empty if the database did not exist.



456
457
458
459
460
461
462
463
464
465
466
# File 'lib/s3db/file_backend.rb', line 456

def delete_record(db_name, collection_name, filename)
  path = record_path(db_name, collection_name, filename)

  begin
    File.delete(path)
  rescue Errno::ENOENT
    return ''
  end

  filename
end

#delete_record!(db_name, collection_name, filename) ⇒ Object

Delete a collection directory from disk. Will only succed if the collection is empty.

db_name - String name of database to remove. Required. collection_name - String name of collection to remove. Required.

returns an Array of the databases deleted, or empty if the database did not exist.



476
477
478
479
480
481
482
483
484
485
486
# File 'lib/s3db/file_backend.rb', line 476

def delete_record!(db_name, collection_name, filename)
  path = record_path(db_name, collection_name, filename)

  begin
    File.delete(path)
  rescue Errno::ENOENT
    raise ArgumentError, 'record does not exist!'
  end

  filename
end

#destroyObject

Destroy a FileBackend basepath. The directory must be empty (which means you must destroy all collections/dbs in the basepath first).

returns itself on success, false on failure.



173
174
175
176
177
178
179
180
181
182
183
# File 'lib/s3db/file_backend.rb', line 173

def destroy
  return false unless valid?

  begin
    Dir.rmdir(@path)
  rescue Errno::ENOTEMPTY, Errno::ENOENT
    return false
  end

  self
end

#destroy!Object

Destroy a FileBackend basepath. The directory must be empty (which means you must destroy all collections/dbs in the basepath first).

returns itself on success, raises an error on failure.



189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/s3db/file_backend.rb', line 189

def destroy!
  valid!

  begin
    Dir.rmdir(@path)
  rescue Errno::ENOENT
    raise ArgumentError, 'basepath does not exist!'
  rescue Errno::ENOTEMPTY
    raise ArgumentError, 'basepath not empty!'
  end

  self
end

#list_collections(db_name) ⇒ Object

List all available collections in a database.

db_name - String name of database to look in. Required.

returns an Array of String collection names.



330
331
332
333
334
# File 'lib/s3db/file_backend.rb', line 330

def list_collections(db_name)
  Dir.entries(db_path(db_name)).select do |dir|
    !File.directory?(dir)
  end
end

#list_records(db_name, coll_name) ⇒ Object

List all available records for a database/collection.

db_name - String name of database to look in. Required. collection_name - String name of collection to look in. Required.

returns an Array of String record file names.



342
343
344
345
346
347
348
349
350
351
352
# File 'lib/s3db/file_backend.rb', line 342

def list_records(db_name, coll_name)
  output = []

  Dir.open(data_path(db_name, coll_name)) do |dir|
    dir.each do |file|
      output << file unless File.directory?(file)
    end
  end

  output
end

#read_record(db_name, coll_name, filename) ⇒ Object

Read the contents of a record in a database/collection

db_name - String name of database to look in. Required. collection_name - String name of collection to look in. Required. filename - String name of the file to read w/extension. Required.

returns the String contents of the record file. Raises an error if the file is missing.



379
380
381
382
383
384
385
386
387
# File 'lib/s3db/file_backend.rb', line 379

def read_record(db_name, coll_name, filename)
  file = record_path(db_name, coll_name, filename)

  begin
    File.read(file)
  rescue Errno::ENOENT
    raise ArgumentError, 'record does not exist!'
  end
end

#read_schema(db_name, collection_name) ⇒ Object

Read the contents of the schema file.

db_name - String name of database to look in. Required. collection_name - String name of collection to look in. Required.

returns the String contents of the schema file. Raises an error if the file is missing.



361
362
363
364
365
366
367
368
369
# File 'lib/s3db/file_backend.rb', line 361

def read_schema(db_name, collection_name)
  file = schema_path(db_name, collection_name)

  begin
    File.read(file)
  rescue Errno::ENOENT
    raise ArgumentError, 'schema does not exist!'
  end
end

#record_path(db_name, collection_name, filename) ⇒ Object

Build a full path to a record from the base path, database name, collection name, and filename.

db_name - String name of database. Required. collection_name - String name of collection. Required.

returns a String path.



252
253
254
# File 'lib/s3db/file_backend.rb', line 252

def record_path(db_name, collection_name, filename)
  File.join(@path, db_name, collection_name, 'data', filename)
end

#saveObject

Save a FileBackend, which means writing its root path directory to the filesystem.

returns itself on success, returns false on failure.



144
145
146
147
148
149
150
# File 'lib/s3db/file_backend.rb', line 144

def save
  return false unless valid?

  FileUtils.mkdir_p(@path)

  self
end

#save!Object

Save a FileBackend, which means writing its root path directory to the filesystem.

returns itself on success, raises an error on failure.



156
157
158
159
160
161
162
163
164
165
166
# File 'lib/s3db/file_backend.rb', line 156

def save!
  valid!

  begin
    Dir.mkdir(@path)
  rescue Errno::EEXIST
    raise ArgumentError, 'base path exists!'
  end

  self
end

#schema_path(db_name, collection_name) ⇒ Object

Build a full path to a scema from the base path, database name and collection name.

db_name - String name of database. Required. collection_name - String name of collection. Required.

returns a String path.



230
231
232
# File 'lib/s3db/file_backend.rb', line 230

def schema_path(db_name, collection_name)
  File.join(@path, db_name, collection_name, 'schema.json')
end

#valid!Object

Confirm that the backend is in a consistent state. Raises an error on failure.

returns true on success, raises an error on failure.



132
133
134
135
136
137
138
# File 'lib/s3db/file_backend.rb', line 132

def valid!
  if !valid?
    raise ArgumentError, @errors.join(', ')
  end

  true
end

#valid?Boolean

Check to see whether the backend is in a valid state.

returns Bool.

Returns:

  • (Boolean)


120
121
122
123
124
125
126
# File 'lib/s3db/file_backend.rb', line 120

def valid?
  @errors = []

  validate_path

  !@errors.any?
end

#validate_pathObject

Check a path to ensure it does not violate basic sanity rules, such as being a linux system path, or having weird characters in the name.

path - String path to check. Required.

returns true if it checks out, false otherwise. It will raise an error for dangerous exceptions.



107
108
109
110
111
112
113
114
115
# File 'lib/s3db/file_backend.rb', line 107

def validate_path
  PATH_BLACKLIST.each do |p|
    @errors << "`#{p}` is insane to use as a base path!" if @path =~ /#{p}/i
  end

  if @path !~ /^(\w|\/)+$/
    @errors << "path does not match /^(\w|\/)+$/"
  end
end

#write_collection(db_name, collection_name) ⇒ Object

Write a directory to disk for the name of the collection. Will ignore the write if the directory exists.

db_name - String name of database. Required. collection_name - String name of collection. Required.

returns collection_name on success; raises an error on failure.



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/s3db/file_backend.rb', line 275

def write_collection(db_name, collection_name)
  dir = collection_path(db_name, collection_name)

  unless Dir.exist?(dir)
    Dir.mkdir(dir)
  end

  dir = data_path(db_name, collection_name)

  unless Dir.exist?(dir)
    Dir.mkdir(dir)
  end

  collection_name
end

#write_db(db_name) ⇒ Object

Write a directory to disk for the name of the database. Will fail if the database already exists.

db_name - String name of database. Required.

returns db_name on success; raises an error on failure.



262
263
264
265
266
# File 'lib/s3db/file_backend.rb', line 262

def write_db(db_name)
  FileUtils.mkdir_p(db_path(db_name))

  db_name
end

#write_record(db_name, coll_name, filename, data) ⇒ Object

Write a record to a database/collection. The record can be in any format. No parsing of the file is performed. Data must be sent as a string.

db_name - String name of database. Required. collection_name - String name of collection. Required. filename - String name of file to write, w/extension. Required. data - String data to write to the file. Required.

returns the data written.

Raises:

  • (ArgumentError)


315
316
317
318
319
320
321
322
323
# File 'lib/s3db/file_backend.rb', line 315

def write_record(db_name, coll_name, filename, data)
  raise ArgumentError, 'data must be a string!' unless data.is_a?(String)

  File.open(record_path(db_name, coll_name, filename), 'w') do |f|
    f.puts data
  end

  data
end

#write_schema(db_name, collection_name, schema) ⇒ Object

Write a file to disk for the schema for a database/collection.

db_name - String name of database. Required. collection_name - String name of collection. Required. schema - String schema contents. Required.

returns the schema on success; raises an error on failure.



298
299
300
301
302
303
304
# File 'lib/s3db/file_backend.rb', line 298

def write_schema(db_name, collection_name, schema)
  File.open(schema_path(db_name, collection_name), 'w') do |f|
    f.puts schema
  end

  schema
end