Class: Bitferry::Volume

Inherits:
Object
  • Object
show all
Extended by:
Logging
Includes:
Logging
Defined in:
lib/bitferry.rb

Constant Summary collapse

STORAGE =
'.bitferry'
STORAGE_ =
'.bitferry~'
STORAGE_MASK =
'.bitferry*'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Logging

log, log

Constructor Details

#initialize(root, tag: Bitferry.tag, modified: DateTime.now, overwrite: false) ⇒ Volume

Returns a new instance of Volume.



301
302
303
304
305
306
307
308
# File 'lib/bitferry.rb', line 301

def initialize(root, tag: Bitferry.tag, modified: DateTime.now, overwrite: false)
  @tag = tag
  @generation = 0
  @vault = {}
  @modified = modified
  @overwrite = overwrite
  @root = Pathname.new(root).realdirpath
end

Instance Attribute Details

#generationObject (readonly)

Returns the value of attribute generation.



237
238
239
# File 'lib/bitferry.rb', line 237

def generation
  @generation
end

#rootObject (readonly)

Returns the value of attribute root.



240
241
242
# File 'lib/bitferry.rb', line 240

def root
  @root
end

#tagObject (readonly)

Returns the value of attribute tag.



234
235
236
# File 'lib/bitferry.rb', line 234

def tag
  @tag
end

#vaultObject (readonly)

Returns the value of attribute vault.



243
244
245
# File 'lib/bitferry.rb', line 243

def vault
  @vault
end

Class Method Details

.[](tag) ⇒ Object



246
247
248
249
# File 'lib/bitferry.rb', line 246

def self.[](tag)
  @@registry.each_value { |volume| return volume if volume.tag == tag }
  nil
end

.delete(*tags, wipe: false) ⇒ Object



286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/bitferry.rb', line 286

def self.delete(*tags, wipe: false)
  process = []
  tags.each do |tag|
    case (volumes = Volume.lookup(tag)).size
      when 0 then log.warn("no volumes matching (partial) tag #{tag}")
      when 1 then process += volumes
      else
        tags = volumes.collect { |v| v.tag }.join(', ')
        raise ArgumentError, "multiple volumes matching (partial) tag #{tag}: #{tags}"
    end
  end
  process.each { |volume| volume.delete(wipe: wipe) }
end

.endpoint(root) ⇒ Object

Raises:

  • (ArgumentError)


354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/bitferry.rb', line 354

def self.endpoint(root)
  path = Pathname.new(root).realdirpath
  intact.sort { |v1, v2| v2.root.to_s.size <=> v1.root.to_s.size }.each do |volume|
    begin
      stem = path.relative_path_from(volume.root).to_s #.chomp('/')
      case stem
        when '.' then return volume.endpoint
        when /^[^\.].*/ then return volume.endpoint(stem)
      end
    rescue ArgumentError
      # Catch different prefix error on Windows
    end
  end
  raise ArgumentError, "no intact volume encompasses path #{root}"
end

.intactObject



479
# File 'lib/bitferry.rb', line 479

def self.intact = registered.filter { |volume| volume.intact? }

.lookup(*tags) ⇒ Object

Return list of registered volumes whose tags match at least one specified partial



253
# File 'lib/bitferry.rb', line 253

def self.lookup(*tags) = match(tags, registered)

.match(tags, volumes) ⇒ Object



256
257
258
259
260
261
# File 'lib/bitferry.rb', line 256

def self.match(tags, volumes)
  rxs = tags.collect { |x| Regexp.new(x) }
  volumes.filter do |volume|
    rxs.any? { |rx| !(rx =~ volume.tag).nil? }
  end
end

.new(root, **opts) ⇒ Object



264
265
266
267
268
# File 'lib/bitferry.rb', line 264

def self.new(root, **opts)
  volume = allocate
  volume.send(:create, root, **opts)
  register(volume)
end

.register(volume) ⇒ Object



473
# File 'lib/bitferry.rb', line 473

def self.register(volume) = @@registry[volume.root] = volume

.registeredObject



476
# File 'lib/bitferry.rb', line 476

def self.registered = @@registry.values

.resetObject



470
# File 'lib/bitferry.rb', line 470

def self.reset = @@registry = {}

.restore(root) ⇒ Object



271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/bitferry.rb', line 271

def self.restore(root)
  begin
    volume = allocate
    volume.send(:restore, root)
    volume = register(volume)
    log.info("restored volume #{volume.tag} from #{root}")
    volume
  rescue => e
    log.error("failed to restore volume from #{root}")
    log.error(e.message) if $DEBUG
    raise
  end
end

Instance Method Details

#commitObject



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/bitferry.rb', line 333

def commit
  if modified?
    log.info("commit volume #{tag} (modified)")
    case @state
    when :pristine
      format
      store
    when :intact
      store
    when :removing
      remove
    else
      raise
    end
    committed
  else
    log.info("skipped committing volume #{tag} (unmodified)")
  end
end

#committedObject



395
396
397
398
399
# File 'lib/bitferry.rb', line 395

def committed
  x = tasks.collect { |t| t.generation }.min
  @generation = x ? x : 0
  @modified = false
end

#create(*args, **opts) ⇒ Object



311
312
313
314
315
# File 'lib/bitferry.rb', line 311

def create(*args, **opts)
  initialize(*args, **opts)
  @state = :pristine
  @modified = true
end

#delete(wipe: false) ⇒ Object



387
388
389
390
391
392
# File 'lib/bitferry.rb', line 387

def delete(wipe: false)
  touch
  @wipe = wipe
  @state = :removing
  log.info("marked volume #{tag} for deletion")
end

#endpoint(path = String.new) ⇒ Object



371
# File 'lib/bitferry.rb', line 371

def endpoint(path = String.new) = Endpoint::Bitferry.new(self, path)

#externalizeObject



448
449
450
451
452
453
454
455
456
457
458
# File 'lib/bitferry.rb', line 448

def externalize
  tasks = live_tasks
  v = vault.filter { |t| !Task[t].nil? && Task[t].live? } # Purge entries from non-existing (deleted) tasks
  {
    bitferry: "0",
    volume: tag,
    modified: (@modified = DateTime.now),
    tasks: tasks.empty? ? nil : tasks.collect(&:externalize),
    vault: v.empty? ? nil : v
  }.compact
end

#formatObject

Raises:

  • (IOError)


420
421
422
423
424
425
426
427
428
429
430
# File 'lib/bitferry.rb', line 420

def format
  raise IOError, "refuse to overwrite existing volume storage #{storage}" if !@overwrite && File.exist?(storage)
  if Bitferry.simulate?
    log.info("skipped storage formatting (simulation)")
  else
    FileUtils.mkdir_p(root)
    FileUtils.rm_f [storage, storage_]
    log.info("formatted volume #{tag} in #{root}")
  end
  @state = nil
end

#intact?Boolean

Returns:

  • (Boolean)


377
# File 'lib/bitferry.rb', line 377

def intact? = @state != :removing

#intact_tasksObject



467
# File 'lib/bitferry.rb', line 467

def intact_tasks = live_tasks.filter { |task| task.intact? }

#live_tasksObject



464
# File 'lib/bitferry.rb', line 464

def live_tasks = Task.live.filter { |task| task.refers?(self) }

#modified?Boolean

Returns:

  • (Boolean)


374
# File 'lib/bitferry.rb', line 374

def modified? = @modified || tasks.any? { |t| t.generation > generation }

#removeObject



433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/bitferry.rb', line 433

def remove
  unless Bitferry.simulate?
    if @wipe
      FileUtils.rm_rf(Dir[File.join(root, '*'), File.join(root, '.*')])
      log.info("wiped entire volume directory #{root}")
    else
      FileUtils.rm_f [storage, storage_]
      log.info("deleted volume #{tag} storage files #{File.join(root, STORAGE_MASK)}")
    end
  end
  @@registry.delete(root)
  @state = nil
end

#restore(root) ⇒ Object

Raises:

  • (IOError)


318
319
320
321
322
323
324
325
326
# File 'lib/bitferry.rb', line 318

def restore(root)
  hash = JSON.load_file(storage = Pathname(root).join(STORAGE), { symbolize_names: true })
  raise IOError, "bad volume storage #{storage}" unless hash.fetch(:bitferry) == "0"
  initialize(root, tag: hash.fetch(:volume), modified: DateTime.parse(hash.fetch(:modified)))
  hash.fetch(:tasks, []).each { |hash| Task::ROUTE.fetch(hash.fetch(:operation).intern).restore(hash) }
  @vault = hash.fetch(:vault, {}).transform_keys { |key| key.to_s }
  @state = :intact
  @modified = false
end

#storageObject



329
# File 'lib/bitferry.rb', line 329

def storage  = @storage  ||= root.join(STORAGE)

#storage_Object



330
# File 'lib/bitferry.rb', line 330

def storage_ = @storage_ ||= root.join(STORAGE_)

#storeObject



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/bitferry.rb', line 402

def store
  tasks.each(&:commit)
  hash = JSON.neat_generate(externalize, short: false, wrap: 200, afterColon: 1, afterComma: 1)
  if Bitferry.simulate?
    log.info("skipped volume #{tag} storage modification (simulation)")
  else
    begin
      File.write(storage_, hash)
      FileUtils.mv(storage_, storage)
      log.info("written volume #{tag} storage #{storage}")
    ensure
      FileUtils.rm_f(storage_)
    end
  end
  @state = :intact
end

#tasksObject



461
# File 'lib/bitferry.rb', line 461

def tasks = Task.registered.filter { |task| task.refers?(self) }

#touchObject



380
381
382
383
384
# File 'lib/bitferry.rb', line 380

def touch
  x = tasks.collect { |t| t.generation }.max
  @generation = x ? x + 1 : 0
  @modified = true
end