Class: Bitferry::Volume
- Inherits:
-
Object
- Object
- Bitferry::Volume
- Extended by:
- Logging
- Includes:
- Logging
- Defined in:
- lib/bitferry.rb
Constant Summary collapse
- STORAGE =
'.bitferry'
- STORAGE_ =
'.bitferry~'
- STORAGE_MASK =
'.bitferry*'
- ENCOMPASSING_PATH_PREFIX =
not .. by itself and not starting with ../
/^(?!(\.\.$|\.\.\/))/
Instance Attribute Summary collapse
-
#generation ⇒ Object
readonly
Returns the value of attribute generation.
-
#root ⇒ Object
readonly
Returns the value of attribute root.
-
#tag ⇒ Object
readonly
Returns the value of attribute tag.
-
#vault ⇒ Object
readonly
Returns the value of attribute vault.
Class Method Summary collapse
- .[](tag) ⇒ Object
- .delete(*tags, wipe: false) ⇒ Object
- .endpoint(root) ⇒ Object
- .intact ⇒ Object
-
.lookup(*tags) ⇒ Object
Return list of registered volumes whose tags match at least one specified partial.
- .match(tags, volumes) ⇒ Object
- .new(root, **opts) ⇒ Object
- .register(volume) ⇒ Object
- .registered ⇒ Object
- .reset ⇒ Object
- .restore(root) ⇒ Object
Instance Method Summary collapse
- #commit ⇒ Object
- #committed ⇒ Object
- #create(*args, **opts) ⇒ Object
- #delete(wipe: false) ⇒ Object
- #endpoint(path = String.new) ⇒ Object
- #externalize ⇒ Object
- #format ⇒ Object
-
#initialize(root, tag: Bitferry.tag, modified: nil, overwrite: false) ⇒ Volume
constructor
A new instance of Volume.
- #intact? ⇒ Boolean
- #intact_tasks ⇒ Object
- #live_tasks ⇒ Object
- #modified? ⇒ Boolean
- #remove ⇒ Object
- #restore(root) ⇒ Object
- #storage ⇒ Object
- #storage_ ⇒ Object
- #store ⇒ Object
- #tasks ⇒ Object
- #touch ⇒ Object
Methods included from Logging
Constructor Details
#initialize(root, tag: Bitferry.tag, modified: nil, overwrite: false) ⇒ Volume
Returns a new instance of Volume.
308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/bitferry.rb', line 308 def initialize(root, tag: Bitferry.tag, modified: nil, overwrite: false) @tag = tag @generation = 0 @vault = {} @modified = case modified when nil then DateTime.now when DateTime then modified else DateTime.parse(modified) end @overwrite = overwrite @root = Pathname.new(root).realdirpath end |
Instance Attribute Details
#generation ⇒ Object (readonly)
Returns the value of attribute generation.
244 245 246 |
# File 'lib/bitferry.rb', line 244 def generation @generation end |
#root ⇒ Object (readonly)
Returns the value of attribute root.
247 248 249 |
# File 'lib/bitferry.rb', line 247 def root @root end |
#tag ⇒ Object (readonly)
Returns the value of attribute tag.
241 242 243 |
# File 'lib/bitferry.rb', line 241 def tag @tag end |
#vault ⇒ Object (readonly)
Returns the value of attribute vault.
250 251 252 |
# File 'lib/bitferry.rb', line 250 def vault @vault end |
Class Method Details
.[](tag) ⇒ Object
253 254 255 256 |
# File 'lib/bitferry.rb', line 253 def self.[](tag) @@registry.each_value { |volume| return volume if volume.tag == tag } nil end |
.delete(*tags, wipe: false) ⇒ Object
293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/bitferry.rb', line 293 def self.delete(*, wipe: false) process = [] .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 = volumes.collect { |v| v.tag }.join(', ') raise ArgumentError, "multiple volumes matching (partial) tag #{tag}: #{}" end end process.each { |volume| volume.delete(wipe: wipe) } end |
.endpoint(root) ⇒ Object
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'lib/bitferry.rb', line 376 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 ENCOMPASSING_PATH_PREFIX 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 |
.intact ⇒ Object
502 |
# File 'lib/bitferry.rb', line 502 def self.intact = registered.filter { |volume| volume.intact? } |
.lookup(*tags) ⇒ Object
Return list of registered volumes whose tags match at least one specified partial
260 |
# File 'lib/bitferry.rb', line 260 def self.lookup(*) = match(, registered) |
.match(tags, volumes) ⇒ Object
263 264 265 266 267 268 |
# File 'lib/bitferry.rb', line 263 def self.match(, volumes) rxs = .collect { |x| Regexp.new(x) } volumes.filter do |volume| rxs.any? { |rx| !(rx =~ volume.tag).nil? } end end |
.new(root, **opts) ⇒ Object
271 272 273 274 275 |
# File 'lib/bitferry.rb', line 271 def self.new(root, **opts) volume = allocate volume.send(:create, root, **opts) register(volume) end |
.register(volume) ⇒ Object
496 |
# File 'lib/bitferry.rb', line 496 def self.register(volume) = @@registry[volume.root] = volume |
.registered ⇒ Object
499 |
# File 'lib/bitferry.rb', line 499 def self.registered = @@registry.values |
.reset ⇒ Object
493 |
# File 'lib/bitferry.rb', line 493 def self.reset = @@registry = {} |
.restore(root) ⇒ Object
278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/bitferry.rb', line 278 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.) if $DEBUG raise end end |
Instance Method Details
#commit ⇒ Object
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/bitferry.rb', line 352 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 |
#committed ⇒ Object
417 418 419 420 421 |
# File 'lib/bitferry.rb', line 417 def committed x = tasks.collect { |t| t.generation }.min @generation = x ? x : 0 @modified = false end |
#create(*args, **opts) ⇒ Object
322 323 324 325 326 |
# File 'lib/bitferry.rb', line 322 def create(*args, **opts) initialize(*args, **opts) @state = :pristine @modified = true end |
#delete(wipe: false) ⇒ Object
409 410 411 412 413 414 |
# File 'lib/bitferry.rb', line 409 def delete(wipe: false) touch @wipe = wipe @state = :removing log.info("marked volume #{tag} for deletion") end |
#endpoint(path = String.new) ⇒ Object
393 |
# File 'lib/bitferry.rb', line 393 def endpoint(path = String.new) = Endpoint::Bitferry.new(self, path) |
#externalize ⇒ Object
471 472 473 474 475 476 477 478 479 480 481 |
# File 'lib/bitferry.rb', line 471 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 |
#format ⇒ Object
443 444 445 446 447 448 449 450 451 452 453 |
# File 'lib/bitferry.rb', line 443 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
399 |
# File 'lib/bitferry.rb', line 399 def intact? = @state != :removing |
#intact_tasks ⇒ Object
490 |
# File 'lib/bitferry.rb', line 490 def intact_tasks = live_tasks.filter { |task| task.intact? } |
#live_tasks ⇒ Object
487 |
# File 'lib/bitferry.rb', line 487 def live_tasks = Task.live.filter { |task| task.refers?(self) } |
#modified? ⇒ Boolean
396 |
# File 'lib/bitferry.rb', line 396 def modified? = @modified || tasks.any? { |t| t.generation > generation } |
#remove ⇒ Object
456 457 458 459 460 461 462 463 464 465 466 467 468 |
# File 'lib/bitferry.rb', line 456 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
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/bitferry.rb', line 329 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" volume = hash.fetch(:volume) begin modified = DateTime.parse(hash.fetch(:modified)) @modified = false rescue modified = nil @modified = true log.warn("modified key missing - flagging volume #{volume} out of date") end initialize(root, tag: volume, modified: 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 end |
#storage ⇒ Object
348 |
# File 'lib/bitferry.rb', line 348 def storage = @storage ||= root.join(STORAGE) |
#storage_ ⇒ Object
349 |
# File 'lib/bitferry.rb', line 349 def storage_ = @storage_ ||= root.join(STORAGE_) |
#store ⇒ Object
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 |
# File 'lib/bitferry.rb', line 424 def store require 'neatjson' 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 |
#tasks ⇒ Object
484 |
# File 'lib/bitferry.rb', line 484 def tasks = Task.registered.filter { |task| task.refers?(self) } |
#touch ⇒ Object
402 403 404 405 406 |
# File 'lib/bitferry.rb', line 402 def touch x = tasks.collect { |t| t.generation }.max @generation = x ? x + 1 : 0 @modified = true end |