albanpeignier-alsa-backup 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest CHANGED
@@ -1,3 +1,4 @@
1
1
  Autotest.add_hook :initialize do |autotest|
2
2
  autotest.add_exception(/tmp/)
3
+ autotest.add_exception(/log/)
3
4
  end
data/History.txt CHANGED
@@ -1,4 +1,13 @@
1
+ == 0.0.3
2
+
3
+ * Support --background to daemonize process
4
+ * Support --pidfile option
5
+ * Rename existing file
6
+
7
+ == 0.0.2 2009-05-19
8
+
9
+ * Improve capture by support partial reads and recovering errors
10
+
1
11
  == 0.0.1 2009-05-14
2
12
 
3
- * 1 major enhancement:
4
- * Initial release
13
+ * Initial release
data/Manifest.txt CHANGED
@@ -4,20 +4,26 @@ Manifest.txt
4
4
  PostInstall.txt
5
5
  README.rdoc
6
6
  Rakefile
7
+ TODO
7
8
  alsa-backup.gemspec
8
9
  bin/alsa.backup
9
10
  config.sample
10
11
  lib/alsa.rb
11
12
  lib/alsa_backup.rb
12
13
  lib/alsa_backup/cli.rb
14
+ lib/alsa_backup/core_ext.rb
15
+ lib/alsa_backup/length_controller.rb
13
16
  lib/alsa_backup/recorder.rb
17
+ lib/alsa_backup/writer.rb
14
18
  lib/sndfile.rb
15
19
  script/console
16
20
  script/destroy
17
21
  script/generate
18
22
  spec/alsa/pcm_spec.rb
19
23
  spec/alsa_backup/cli_spec.rb
24
+ spec/alsa_backup/core_ext_spec.rb
20
25
  spec/alsa_backup/recorder_spec.rb
26
+ spec/alsa_backup/writer_spec.rb
21
27
  spec/alsa_backup_spec.rb
22
28
  spec/fixtures/config_test.rb
23
29
  spec/sndfile/info_spec.rb
data/README.rdoc CHANGED
@@ -32,7 +32,43 @@ will load the specified configuration
32
32
 
33
33
  == CONFIGURATION:
34
34
 
35
- See config.sample for the moment.
35
+ The configuration file is a Ruby file. This piece of code can
36
+ configurate the AlsaBacup recorder :
37
+
38
+ AlsaBackup.config do |recorder|
39
+ # configure recorder here :
40
+ recorder.file = "record.wav"
41
+ end
42
+
43
+ === Recorder File
44
+
45
+ The recorder file can be specified as a simple string :
46
+
47
+ recorder.file = "record.wav"
48
+
49
+ The recorder file can be specified by a Proc which returns the string :
50
+
51
+ recorder.file = Proc.new {
52
+ Time.now.strftime("%Y/%m-%b/%d-%a/%Hh.wav")
53
+ }
54
+
55
+ will use the current time to create file names like these :
56
+
57
+ 2009/05-May/17-Sun/19h.wav
58
+ 2009/05-May/17-Sun/20h.wav
59
+
60
+ To use different files every 15 minutes :
61
+
62
+ recorder.file = Proc.new {
63
+ Time.now.floor(:min, 15).strftime("%Y/%m-%b/%d-%a/%Hh%M.wav")
64
+ }
65
+
66
+ will create files like these :
67
+
68
+ 2009/05-May/17-Sun/19h00.wav
69
+ 2009/05-May/17-Sun/19h15.wav
70
+
71
+ See config.sample.
36
72
 
37
73
  == REQUIREMENTS:
38
74
 
@@ -41,16 +77,14 @@ See config.sample for the moment.
41
77
 
42
78
  == INSTALL:
43
79
 
44
- * sudo apt-get install libasound2 libsndfile1
45
- * sudo gem install ruby-ffi
46
-
47
- * sudo gem install --source http://gems.github.com albanpeignier-alsa-backup
80
+ sudo apt-get install libasound2 libsndfile1
81
+ sudo gem install --source http://gems.github.com albanpeignier-alsa-backup
48
82
 
49
83
  == LICENSE:
50
84
 
51
85
  (The MIT License)
52
86
 
53
- Copyright (c) 2009 FIXME Alban Peignier
87
+ Copyright (c) 2009 Alban Peignier
54
88
 
55
89
  Permission is hereby granted, free of charge, to any person obtaining
56
90
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ $hoe = Hoe.new('alsa-backup', AlsaBackup::VERSION) do |p|
9
9
  p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
10
10
  p.rubyforge_name = p.name # TODO this is default value
11
11
  p.extra_deps = [
12
- ['ffi','>= 0.3.5'],
12
+ ['ffi','>= 0.3.5'], ['newgem', ">= #{::Newgem::VERSION}"]
13
13
  ]
14
14
  p.extra_dev_deps = [
15
15
  ['newgem', ">= #{::Newgem::VERSION}"]
@@ -25,3 +25,10 @@ require 'newgem/tasks' # load /tasks/*.rake
25
25
  Dir['tasks/**/*.rake'].each { |t| load t }
26
26
 
27
27
  task :default => :spec
28
+
29
+ namespace :gems do
30
+ task :install do
31
+ gems = %w{activesupport ffi rspec}
32
+ sh "sudo gem install #{gems.join(' ')}"
33
+ end
34
+ end
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ * configurable channels
2
+ * configurable sample rate
3
+ * configurable alsa device
4
+ * improve spec coverage
data/alsa-backup.gemspec CHANGED
@@ -2,17 +2,17 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{alsa-backup}
5
- s.version = "0.0.1"
5
+ s.version = "0.0.3"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Alban Peignier"]
9
- s.date = %q{2009-05-18}
9
+ s.date = %q{2009-05-21}
10
10
  s.default_executable = %q{alsa.backup}
11
11
  s.description = %q{ALSA client to perform continuous recording}
12
12
  s.email = ["alban.peignier@free.fr"]
13
13
  s.executables = ["alsa.backup"]
14
14
  s.extra_rdoc_files = ["History.txt", "Manifest.txt", "PostInstall.txt", "README.rdoc"]
15
- s.files = [".autotest", "History.txt", "Manifest.txt", "PostInstall.txt", "README.rdoc", "Rakefile", "alsa-backup.gemspec", "bin/alsa.backup", "config.sample", "lib/alsa.rb", "lib/alsa_backup.rb", "lib/alsa_backup/cli.rb", "lib/alsa_backup/recorder.rb", "lib/sndfile.rb", "script/console", "script/destroy", "script/generate", "spec/alsa/pcm_spec.rb", "spec/alsa_backup/cli_spec.rb", "spec/alsa_backup/recorder_spec.rb", "spec/alsa_backup_spec.rb", "spec/fixtures/config_test.rb", "spec/sndfile/info_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/rspec.rake"]
15
+ s.files = [".autotest", "History.txt", "Manifest.txt", "PostInstall.txt", "README.rdoc", "Rakefile", "TODO", "alsa-backup.gemspec", "bin/alsa.backup", "config.sample", "lib/alsa.rb", "lib/alsa_backup.rb", "lib/alsa_backup/cli.rb", "lib/alsa_backup/core_ext.rb", "lib/alsa_backup/length_controller.rb", "lib/alsa_backup/recorder.rb", "lib/alsa_backup/writer.rb", "lib/sndfile.rb", "script/console", "script/destroy", "script/generate", "spec/alsa/pcm_spec.rb", "spec/alsa_backup/cli_spec.rb", "spec/alsa_backup/core_ext_spec.rb", "spec/alsa_backup/recorder_spec.rb", "spec/alsa_backup/writer_spec.rb", "spec/alsa_backup_spec.rb", "spec/fixtures/config_test.rb", "spec/sndfile/info_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/rspec.rake"]
16
16
  s.has_rdoc = true
17
17
  s.homepage = %q{http://github.com/alban.peignier/alsa-backup}
18
18
  s.rdoc_options = ["--main", "README.rdoc"]
@@ -27,16 +27,19 @@ Gem::Specification.new do |s|
27
27
 
28
28
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
29
29
  s.add_runtime_dependency(%q<ffi>, [">= 0.3.5"])
30
+ s.add_runtime_dependency(%q<newgem>, [">= 1.4.1"])
30
31
  s.add_development_dependency(%q<newgem>, [">= 1.4.1"])
31
32
  s.add_development_dependency(%q<hoe>, [">= 1.8.0"])
32
33
  else
33
34
  s.add_dependency(%q<ffi>, [">= 0.3.5"])
34
35
  s.add_dependency(%q<newgem>, [">= 1.4.1"])
36
+ s.add_dependency(%q<newgem>, [">= 1.4.1"])
35
37
  s.add_dependency(%q<hoe>, [">= 1.8.0"])
36
38
  end
37
39
  else
38
40
  s.add_dependency(%q<ffi>, [">= 0.3.5"])
39
41
  s.add_dependency(%q<newgem>, [">= 1.4.1"])
42
+ s.add_dependency(%q<newgem>, [">= 1.4.1"])
40
43
  s.add_dependency(%q<hoe>, [">= 1.8.0"])
41
44
  end
42
45
  end
data/config.sample CHANGED
@@ -38,6 +38,25 @@ AlsaBackup.config do |recorder|
38
38
  # Define where the files are created
39
39
  #
40
40
  #recorder.directory = "/var/lib/alsa.backup"
41
+
42
+ #
43
+ # Customize error handler during record
44
+ #
45
+
46
+ # To sleep 30 seconds on error
47
+ #recorder.error_handler = Proc.new do |exception|
48
+ # 30
49
+ #end
50
+
51
+ # To fail on any error
52
+ #recorder.error_handler = Proc.new do |exception|
53
+ # false
54
+ #end
55
+
56
+ # To sleep the default time
57
+ #recorder.error_handler = Proc.new do |exception|
58
+ # true
59
+ #end
41
60
  end
42
61
 
43
62
 
data/lib/alsa.rb CHANGED
@@ -18,7 +18,7 @@ module ALSA
18
18
 
19
19
  def self.try_to(message, &block)
20
20
  logger.debug('alsa') { message }
21
- if (response = yield) < 0
21
+ if ALSA::Native::error_code?(response = yield)
22
22
  raise "cannot #{message} (#{ALSA::Native::strerror(response)})"
23
23
  else
24
24
  response
@@ -30,6 +30,10 @@ module ALSA
30
30
  ffi_lib "libasound.so"
31
31
 
32
32
  attach_function :strerror, :snd_strerror, [:int], :string
33
+
34
+ def self.error_code?(response)
35
+ response < 0
36
+ end
33
37
  end
34
38
 
35
39
  module PCM
@@ -77,6 +81,7 @@ module ALSA
77
81
  def hardware_parameters
78
82
  HwParameters.new(self).current_for_device
79
83
  end
84
+ alias_method :hw_params, :hardware_parameters
80
85
 
81
86
  def hardware_parameters=(attributes= {})
82
87
  attributes = {:access => :rw_interleaved}.update(attributes)
@@ -86,21 +91,33 @@ module ALSA
86
91
  end
87
92
 
88
93
  def read
89
- frame_count = hardware_parameters.sample_rate
90
- buffer = FFI::MemoryPointer.new(hardware_parameters.buffer_size_for(frame_count))
94
+ frame_count = hw_params.sample_rate / 2
95
+
96
+ FFI::MemoryPointer.new(:char, hw_params.buffer_size_for(frame_count)) do |buffer|
97
+ begin
98
+ read_buffer buffer, frame_count
99
+ end while yield buffer, frame_count
100
+ end
101
+ end
91
102
 
92
- continue = true
93
- while continue
94
- read_count = ALSA::try_to "read from audio interface" do
95
- ALSA::PCM::Native::readi(self.handle, buffer, frame_count)
103
+ def read_buffer(buffer, frame_count)
104
+ read_count = ALSA::try_to "read from audio interface" do
105
+ response = ALSA::PCM::Native::readi(self.handle, buffer, frame_count)
106
+ if ALSA::Native::error_code?(response)
107
+ ALSA.logger.debug('alsa') { "try to recover '#{ALSA::Native::strerror(response)}' on read"}
108
+ ALSA::PCM::Native::pcm_recover(self.handle, response, 1)
109
+ else
110
+ response
96
111
  end
97
-
98
- raise "can't read expected frame count (#{read_count}/#{frame_count})" unless read_count == frame_count
99
-
100
- continue = yield buffer, read_count
101
112
  end
102
113
 
103
- buffer.free
114
+ missing_frame_count = frame_count - read_count
115
+ if missing_frame_count > 0
116
+ ALSA.logger.debug('alsa') { "re-read missing frame count: #{missing_frame_count}"}
117
+ read_buffer_size = hw_params.buffer_size_for(read_count)
118
+ # buffer[read_buffer_size] doesn't return a MemoryPointer
119
+ read_buffer(buffer + read_buffer_size, missing_frame_count)
120
+ end
104
121
  end
105
122
 
106
123
  def close
@@ -242,6 +259,8 @@ module ALSA
242
259
 
243
260
  attach_function :readi, :snd_pcm_readi, [ :pointer, :pointer, :ulong ], :long
244
261
 
262
+ attach_function :pcm_recover, :snd_pcm_recover, [ :pointer, :int, :int ], :int
263
+
245
264
  attach_function :hw_params_malloc, :snd_pcm_hw_params_malloc, [:pointer], :int
246
265
  attach_function :hw_params_free, :snd_pcm_hw_params_free, [:pointer], :int
247
266
 
data/lib/alsa_backup.rb CHANGED
@@ -10,7 +10,7 @@ require 'activesupport'
10
10
  require 'logger'
11
11
 
12
12
  module AlsaBackup
13
- VERSION = '0.0.1'
13
+ VERSION = '0.0.3'
14
14
 
15
15
  def self.recorder
16
16
  @recorder ||= AlsaBackup::Recorder.new
@@ -33,15 +33,9 @@ module AlsaBackup
33
33
 
34
34
  end
35
35
 
36
- class Time
37
-
38
- def floor(attribute, modulo)
39
- actual = self.send(attribute)
40
- self.change(attribute => actual - actual%modulo)
41
- end
42
-
43
- end
44
-
36
+ require 'alsa_backup/core_ext'
37
+ require 'alsa_backup/length_controller'
38
+ require 'alsa_backup/writer'
45
39
  require 'alsa_backup/recorder'
46
40
 
47
41
 
@@ -1,4 +1,5 @@
1
1
  require 'optparse'
2
+ require 'daemons'
2
3
 
3
4
  module AlsaBackup
4
5
  class CLI
@@ -23,6 +24,10 @@ module AlsaBackup
23
24
  "Base directory") { |arg| options[:directory] = arg }
24
25
  opts.on("-c", "--config=CONFIG", String,
25
26
  "Configuration file") { |arg| options[:config] = arg }
27
+ opts.on("-p", "--pid=PID_FILE", String,
28
+ "File to write the process pid") { |arg| options[:pid] = arg }
29
+ opts.on("-b", "--background", nil,
30
+ "Daemonize the process") { |arg| options[:daemonize] = true }
26
31
  opts.on("-h", "--help",
27
32
  "Show this help message.") { stdout.puts opts; exit }
28
33
  opts.parse!(arguments)
@@ -36,9 +41,16 @@ module AlsaBackup
36
41
 
37
42
  AlsaBackup.recorder.file = options[:file] if options[:file]
38
43
  AlsaBackup.recorder.directory = options[:directory] if options[:directory]
44
+
45
+ pid_file = File.expand_path(options[:pid]) if options[:pid]
46
+
47
+ Daemonize.daemonize(nil, "alsa.backup") if options[:daemonize]
48
+ File.write(pid_file, $$) if pid_file
39
49
 
40
50
  length = options[:length].to_i if options[:length]
41
51
  AlsaBackup.recorder.start(length)
52
+ rescue Exception => e
53
+ AlsaBackup.logger.fatal(e)
42
54
  end
43
55
  end
44
56
  end
@@ -0,0 +1,42 @@
1
+ class Time
2
+
3
+ def floor(attribute, modulo)
4
+ actual = self.send(attribute)
5
+ self.change(attribute => actual - actual%modulo)
6
+ end
7
+
8
+ end
9
+
10
+ class File
11
+
12
+ def self.extension(file)
13
+ if file =~ /(\.[^.]*)$/
14
+ $1
15
+ else
16
+ ""
17
+ end
18
+ end
19
+
20
+ def self.suffix_basename(file, suffix)
21
+ dirname = File.dirname(file)
22
+
23
+ dirname =
24
+ case dirname
25
+ when "/": "/"
26
+ when ".": ""
27
+ else
28
+ dirname + "/"
29
+ end
30
+
31
+ extension = File.extension(file)
32
+ dirname +
33
+ File.basename(file, extension) +
34
+ suffix +
35
+ extension
36
+ end
37
+
38
+ def self.write(file, content)
39
+ File.open(file, "w") { |f| f.puts content }
40
+ end
41
+
42
+ end
@@ -0,0 +1,19 @@
1
+ module AlsaBackup
2
+ module LengthController
3
+ class Loop
4
+ def continue_after?(frame_count)
5
+ true
6
+ end
7
+ end
8
+
9
+ class FrameCount
10
+ def initialize(frame_count)
11
+ @frame_count = frame_count
12
+ end
13
+
14
+ def continue_after?(frame_count)
15
+ (@frame_count -= frame_count) > 0
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,71 +1,65 @@
1
1
  require 'alsa'
2
- require 'sndfile'
3
-
4
- require 'fileutils'
5
2
 
6
3
  module AlsaBackup
7
4
  class Recorder
8
5
 
9
6
  def initialize(file = "record.wav")
10
- @file = file
11
- @directory = "."
7
+ @file = File.basename(file)
8
+ @directory = File.dirname(file)
9
+
10
+ @error_handler = Proc.new { |e| true }
12
11
  end
13
12
 
14
- attr_accessor :file, :directory
13
+ attr_accessor :file, :directory, :error_handler
15
14
 
16
15
  def start(seconds_to_record = nil)
17
- frames_to_record = format[:sample_rate] * seconds_to_record if seconds_to_record
18
-
19
- # prepare sndfile
20
- self.sndfile
16
+ length_controller = self.length_controller(seconds_to_record)
21
17
 
22
- ALSA::PCM::Capture.open("hw:0", self.format(:sample_format => :s16_le)) do |capture|
23
- capture.read do |buffer, frame_count|
24
- self.sndfile.write buffer, frame_count
25
- if frames_to_record
26
- (frames_to_record -= frame_count) > 0
27
- else
28
- true
18
+ Writer.open(directory, file, format(:format => "wav pcm_16")) do |writer|
19
+ ALSA::PCM::Capture.open("hw:0", self.format(:sample_format => :s16_le)) do |capture|
20
+ capture.read do |buffer, frame_count|
21
+ writer.write buffer, frame_count
22
+ length_controller.continue_after? frame_count
29
23
  end
30
24
  end
31
25
  end
26
+ rescue Interrupt
27
+ AlsaBackup.logger.debug('recorder interrupted')
32
28
  rescue Exception => e
33
29
  AlsaBackup.logger.error(e)
34
- raise e
35
- ensure
36
- @sndfile.close if @sndfile
37
- end
30
+ AlsaBackup.logger.debug { e.backtrace.join("\n") }
38
31
 
39
- def file
40
- case @file
41
- when Proc
42
- @file.call
32
+ if seconds_to_record.nil? and continue_on_error?(e)
33
+ retry
43
34
  else
44
- @file
35
+ raise e
45
36
  end
46
37
  end
47
38
 
48
- def target_file
49
- File.join self.directory, self.file
39
+ def continue_on_error?(e)
40
+ error_handler_response = @error_handler.call(e) if @error_handler
41
+
42
+ if error_handler_response
43
+ sleep_time = Numeric === error_handler_response ? error_handler_response : 5
44
+ AlsaBackup.logger.warn("sleep #{sleep_time}s before retrying")
45
+ sleep sleep_time
46
+ end
47
+
48
+ error_handler_response
50
49
  end
51
50
 
52
51
  def format(additional_parameters = {})
53
52
  {:sample_rate => 44100, :channels => 2}.merge(additional_parameters)
54
53
  end
55
54
 
56
- def sndfile
57
- target_file = self.target_file
58
- raise "no recording file" unless target_file
59
-
60
- unless @sndfile and @sndfile.path == target_file
61
- @sndfile.close if @sndfile
62
- AlsaBackup.logger.info "new file #{target_file}"
63
-
64
- FileUtils.mkdir_p File.dirname(target_file)
65
- @sndfile = Sndfile::File.new(target_file, "w", self.format(:format => "wav pcm_16"))
55
+ def length_controller(seconds_to_record)
56
+ if seconds_to_record
57
+ AlsaBackup::LengthController::FrameCount.new format[:sample_rate] * seconds_to_record
58
+ else
59
+ AlsaBackup::LengthController::Loop.new
66
60
  end
67
- @sndfile
68
61
  end
69
-
62
+
70
63
  end
64
+
71
65
  end
@@ -0,0 +1,92 @@
1
+ require 'sndfile'
2
+ require 'fileutils'
3
+
4
+ module AlsaBackup
5
+ class Writer
6
+
7
+ attr_accessor :directory, :file, :format
8
+
9
+ def self.default_format
10
+ {:sample_rate => 44100, :channels => 2, :format => "wav pcm_16"}
11
+ end
12
+
13
+ def initialize(directory, file, format = Writer.default_format)
14
+ @directory = directory
15
+ @file = file
16
+ @format = format
17
+ end
18
+
19
+ def self.open(directory, file, format, &block)
20
+ writer = Writer.new(directory, file, format).prepare
21
+
22
+ begin
23
+ yield writer
24
+ ensure
25
+ writer.close
26
+ end
27
+ end
28
+
29
+ def prepare
30
+ # prepare sndfile
31
+ self.sndfile
32
+ self
33
+ end
34
+
35
+ def write(*arguments)
36
+ self.sndfile.write *arguments
37
+ end
38
+
39
+ def close
40
+ if @sndfile
41
+ AlsaBackup.logger.info('close current file')
42
+ @sndfile.close
43
+ end
44
+ @sndfile = nil
45
+ end
46
+
47
+ def file
48
+ case @file
49
+ when Proc
50
+ @file.call
51
+ else
52
+ @file
53
+ end
54
+ end
55
+
56
+ def target_file
57
+ File.join self.directory, self.file
58
+ end
59
+
60
+ def sndfile
61
+ target_file = self.target_file
62
+ raise "no recording file" unless target_file
63
+
64
+ unless @sndfile and @sndfile.path == target_file
65
+ @sndfile.close if @sndfile
66
+
67
+ Writer.rename_existing_file(target_file)
68
+ AlsaBackup.logger.info{"new file #{File.expand_path target_file}"}
69
+
70
+ FileUtils.mkdir_p File.dirname(target_file)
71
+ @sndfile = Sndfile::File.new(target_file, "w", self.format)
72
+ end
73
+ @sndfile
74
+ end
75
+
76
+ def self.rename_existing_file(file)
77
+ if File.exists?(file)
78
+ index = 1
79
+
80
+ while File.exists?(new_file = File.suffix_basename(file, "-#{index}"))
81
+ index += 1
82
+
83
+ raise "can't find a free file for #{file}" if index > 1000
84
+ end
85
+
86
+ AlsaBackup.logger.warn "rename existing file #{File.basename(file)} into #{new_file}"
87
+ File.rename(file, new_file)
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -15,7 +15,12 @@ describe AlsaBackup::CLI, "execute" do
15
15
  def execute_cli(options = {})
16
16
  options = { :file => @file, :length => 2 }.update(options)
17
17
  arguments = options.collect do |key,value|
18
- "--#{key}=#{value}" if value
18
+ case value
19
+ when true: "--#{key}"
20
+ when nil: nil
21
+ else
22
+ "--#{key}=#{value}"
23
+ end
19
24
  end.compact
20
25
 
21
26
  AlsaBackup::CLI.execute(@stdout_io, *arguments)
@@ -59,4 +64,16 @@ describe AlsaBackup::CLI, "execute" do
59
64
  AlsaBackup.recorder.file.should == argument_file
60
65
  end
61
66
 
67
+ it "should write pid in specified file" do
68
+ pid_file = test_file("pid")
69
+ execute_cli :pid => pid_file
70
+
71
+ IO.read(pid_file).strip.should == $$.to_s
72
+ end
73
+
74
+ it "should write pid in specified file" do
75
+ Daemonize.should_receive(:daemonize)
76
+ execute_cli :background => true
77
+ end
78
+
62
79
  end
@@ -0,0 +1,58 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe Time do
4
+
5
+ describe "floor" do
6
+
7
+ it "should change xh41 on xh49 for min with a modulo of 10" do
8
+ time = Time.now.change(:min => 49)
9
+ time.floor(:min, 10).should == time.change(:min => 40)
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+
16
+ describe File do
17
+
18
+ describe "extension" do
19
+
20
+ it "should be .rb for test.rb" do
21
+ File.extension('test.rb').should == '.rb'
22
+ end
23
+
24
+ it "should be '.' for 'test.'" do
25
+ File.extension('test.').should == '.'
26
+ end
27
+
28
+ it "should be blank for 'test'" do
29
+ File.extension('test').should be_blank
30
+ end
31
+
32
+ end
33
+
34
+ describe "suffix_basename" do
35
+
36
+ def take_this_form(expected_file)
37
+ simple_matcher("have this #{expected_file}") do |actual|
38
+ File.suffix_basename(actual,'<prefix>') == expected_file
39
+ end
40
+ end
41
+
42
+ it { 'test.rb'.should take_this_form('test<prefix>.rb') }
43
+
44
+ it "should return test<prefix> for test" do
45
+ File.suffix_basename('test','<prefix>').should == 'test<prefix>'
46
+ end
47
+
48
+ it "should return /path/test<prefix>.rb for /patest" do
49
+ File.suffix_basename('/path/test.rb','<prefix>').should == '/path/test<prefix>.rb'
50
+ end
51
+
52
+ it "should return /test<prefix>.rb for /test.rb" do
53
+ File.suffix_basename('/path/test.rb','<prefix>').should == '/path/test<prefix>.rb'
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -13,19 +13,72 @@ describe AlsaBackup::Recorder do
13
13
  lambda { @recorder.start(2) }.should_not raise_error
14
14
  end
15
15
 
16
- describe "file" do
16
+ describe "error handler" do
17
+
18
+ class TestErrorHandler
19
+
20
+ def initialize(proc)
21
+ proc = Proc.new { |e| proc } unless Proc == proc
22
+ @proc = proc
23
+ end
24
+
25
+ def call(e)
26
+ if @proc
27
+ response = @proc.call(e)
28
+ @proc = nil
29
+ response
30
+ end
31
+ end
32
+
33
+ end
17
34
 
18
- it "should accept file as string" do
19
- @recorder.file = file_name = "dummy"
20
- @recorder.file.should == file_name
35
+ before(:each) do
36
+ AlsaBackup::Writer.stub!(:open).and_raise("dummy")
37
+ @recorder.stub!(:sleep)
21
38
  end
22
39
 
23
- it "should accept file as Proc" do
24
- file_name = "dummy"
25
- @recorder.file = Proc.new { file_name }
26
- @recorder.file.should == file_name
40
+ it "should raise error when error handler is nil" do
41
+ @recorder.error_handler = nil
42
+ lambda { @recorder.start }.should raise_error
27
43
  end
28
-
44
+
45
+ it "should raise error when error handler returns nil or false" do
46
+ @recorder.error_handler = TestErrorHandler.new(nil)
47
+ lambda { @recorder.start }.should raise_error
48
+ end
49
+
50
+ def start_recorder(limit = nil)
51
+ @recorder.start(limit)
52
+ rescue RuntimeError
53
+
54
+ end
55
+
56
+ it "should retry when error handler returns something (not false or nil)" do
57
+ @recorder.error_handler = TestErrorHandler.new(true)
58
+ AlsaBackup::Writer.should_receive(:open).twice().and_raise("dummy")
59
+
60
+ start_recorder
61
+ end
62
+
63
+ it "should use the error handler response as sleep time if numerical" do
64
+ @recorder.error_handler = TestErrorHandler.new(error_handler_response = 5)
65
+ @recorder.should_receive(:sleep).with(error_handler_response)
66
+ start_recorder
67
+ end
68
+
69
+ it "should sleep 5 seconds when the error handler response is a number" do
70
+ @recorder.error_handler = TestErrorHandler.new(true)
71
+ @recorder.should_receive(:sleep).with(5)
72
+ start_recorder
73
+ end
74
+
75
+ it "should not use error handler when recorder is started with a time length" do
76
+ @recorder.error_handler = mock("error_handler")
77
+ @recorder.error_handler.should_not_receive(:call)
78
+
79
+ start_recorder(2)
80
+ end
81
+
29
82
  end
30
83
 
31
84
  end
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe AlsaBackup::Writer do
4
+
5
+ before(:each) do
6
+ @file = "test.wav"
7
+ @directory = test_directory
8
+ @recorder = AlsaBackup::Writer.new(@file, @directory)
9
+ end
10
+
11
+ describe "file" do
12
+
13
+ it "should accept file as string" do
14
+ @recorder.file = file_name = "dummy"
15
+ @recorder.file.should == file_name
16
+ end
17
+
18
+ it "should accept file as Proc" do
19
+ file_name = "dummy"
20
+ @recorder.file = Proc.new { file_name }
21
+ @recorder.file.should == file_name
22
+ end
23
+
24
+ end
25
+
26
+ describe "rename_existing_file" do
27
+
28
+ it "should keep file if not exists" do
29
+ File.should_receive(:exists?).with(@file).and_return(false)
30
+ File.should_not_receive(:rename)
31
+ AlsaBackup::Writer.rename_existing_file(@file)
32
+ end
33
+
34
+ it "should try to suffix with '-n' to find a free name" do
35
+ File.stub!(:exists?).and_return(true)
36
+
37
+ free_file = File.suffix_basename(@file, "-99")
38
+ File.should_receive(:exists?).with(free_file).and_return(false)
39
+
40
+ File.should_receive(:rename).with(@file, free_file)
41
+ AlsaBackup::Writer.rename_existing_file(@file)
42
+ end
43
+
44
+ it "should raise an error when no free file is found" do
45
+ File.stub!(:exists?).and_return(true)
46
+ lambda do
47
+ AlsaBackup::Writer.rename_existing_file(@file)
48
+ end.should raise_error
49
+ end
50
+
51
+ end
52
+
53
+ end
data/spec/spec_helper.rb CHANGED
@@ -9,13 +9,20 @@ end
9
9
  $:.unshift(File.dirname(__FILE__) + '/../lib')
10
10
  require 'alsa_backup'
11
11
 
12
+ def test_directory
13
+ File.dirname(__FILE__) + '/../tmp'
14
+ end
15
+
12
16
  def test_file(name = 'test.wav')
13
- File.dirname(__FILE__) + '/../tmp/' + name
17
+ File.join(test_directory, name)
14
18
  end
15
19
 
16
20
  def fixture_file(name)
17
21
  File.dirname(__FILE__) + '/fixtures/' + name
18
22
  end
19
23
 
24
+ log_dir = File.dirname(__FILE__) + '/../log/'
25
+ Dir.mkdir(log_dir) unless File.exists?(log_dir)
26
+
20
27
  ALSA::logger = AlsaBackup.logger =
21
- Logger.new(File.dirname(__FILE__) + '/../log/test.log')
28
+ Logger.new(File.join(log_dir,'test.log'))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: albanpeignier-alsa-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alban Peignier
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-18 00:00:00 -07:00
12
+ date: 2009-05-21 00:00:00 -07:00
13
13
  default_executable: alsa.backup
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,6 +22,16 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: 0.3.5
24
24
  version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: newgem
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.4.1
34
+ version:
25
35
  - !ruby/object:Gem::Dependency
26
36
  name: newgem
27
37
  type: :development
@@ -61,20 +71,26 @@ files:
61
71
  - PostInstall.txt
62
72
  - README.rdoc
63
73
  - Rakefile
74
+ - TODO
64
75
  - alsa-backup.gemspec
65
76
  - bin/alsa.backup
66
77
  - config.sample
67
78
  - lib/alsa.rb
68
79
  - lib/alsa_backup.rb
69
80
  - lib/alsa_backup/cli.rb
81
+ - lib/alsa_backup/core_ext.rb
82
+ - lib/alsa_backup/length_controller.rb
70
83
  - lib/alsa_backup/recorder.rb
84
+ - lib/alsa_backup/writer.rb
71
85
  - lib/sndfile.rb
72
86
  - script/console
73
87
  - script/destroy
74
88
  - script/generate
75
89
  - spec/alsa/pcm_spec.rb
76
90
  - spec/alsa_backup/cli_spec.rb
91
+ - spec/alsa_backup/core_ext_spec.rb
77
92
  - spec/alsa_backup/recorder_spec.rb
93
+ - spec/alsa_backup/writer_spec.rb
78
94
  - spec/alsa_backup_spec.rb
79
95
  - spec/fixtures/config_test.rb
80
96
  - spec/sndfile/info_spec.rb