Class: Btape::Recorder

Inherits:
Object
  • Object
show all
Defined in:
lib/btape/recorder.rb

Overview

Captures the frames a GIF is built from.

In :interval mode it screenshots the page on a background thread until stopped. In :manual mode it captures only when the script asks with a Screenshot command, which is what a tape wants when it needs one frame per page rather than a few hundred near-identical ones.

Constant Summary collapse

FRAME_NAME =

A name becomes part of a filename, so it may not carry anything that would put the frame somewhere other than the frames directory. Parser checks it too, to report a bad Screenshot line with its line number; this is the check for everyone else, since capture is public API.

/\A[\w.-]+\z/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(page, directory, interval: 0.1, mode: :interval, on_frame: nil, max_frames: nil, lock: Monitor.new) ⇒ Recorder

Returns a new instance of Recorder.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/btape/recorder.rb', line 19

def initialize(page, directory, interval: 0.1, mode: :interval, on_frame: nil, max_frames: nil,
               lock: Monitor.new)
  @page = page
  @directory = directory
  @interval = interval
  @mode = mode.to_sym
  @on_frame = on_frame
  @max_frames = max_frames
  # Shared with the executor, so a screenshot and a command are never in
  # flight against the same page at once.
  @lock = lock
  @paths = []
  @named_paths = {}
end

Instance Attribute Details

#named_pathsObject (readonly)

Returns the value of attribute named_paths.



34
35
36
# File 'lib/btape/recorder.rb', line 34

def named_paths
  @named_paths
end

#pathsObject (readonly)

Returns the value of attribute paths.



34
35
36
# File 'lib/btape/recorder.rb', line 34

def paths
  @paths
end

Instance Method Details

#capture(name: nil) ⇒ Object

Captures one frame now. A name puts it at a predictable path, so a caller can pick a particular frame out of the run.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/btape/recorder.rb', line 63

def capture(name: nil)
  validate_name!(name)
  path, index = @lock.synchronize do
    exhausted! if @max_frames && @paths.length >= @max_frames

    path = File.join(@directory, filename(name))
    screenshot(path)
    index = @paths.length
    @paths << path
    @named_paths[name] = path if name
    [path, index]
  end
  # Outside the lock: the callback is the caller's code and may take as
  # long as it likes without holding up the next capture.
  @on_frame&.call(path, index)
  path
end

#startObject



36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/btape/recorder.rb', line 36

def start
  return if manual?

  capture
  @running = true
  @thread = Thread.new do
    loop do
      sleep @interval
      capture
    end
  rescue StandardError => e
    @error = e
  end
end

#stopObject

Raises:

  • (@error)


51
52
53
54
55
56
57
58
59
# File 'lib/btape/recorder.rb', line 51

def stop
  return unless @running

  @running = false
  @thread&.kill
  @thread&.join
  capture
  raise @error if @error
end