Class: Rsyncbackup

Inherits:
Object
  • Object
show all
Includes:
Methadone::CLILogging
Defined in:
lib/rsyncbackup.rb,
lib/rsyncbackup/version.rb,
lib/rsyncbackup/utilities.rb

Constant Summary collapse

VERSION =
"2.1.2"
DEFAULT_EXCLUSIONS =
File.expand_path('.rsyncbackup.exclusions', ENV['HOME'])
DEFAULT_INCOMPLETE_DIR_NAME =
'.incomplete'
DEFAULT_LAST_FULL_DIR_NAME =
'.lastfull'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source, target, opts = {}) ⇒ Rsyncbackup

Returns a new instance of Rsyncbackup.



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/rsyncbackup.rb', line 11

def initialize(source, target, opts={})
  logger.level = Logger::WARN
  logger.level = Logger::INFO if opts[:verbose]
  logger.error_level = Logger::DEBUG if opts[:debug]

  raise "Unknown target: #{target}" unless File.exist?(target) or opts[:dry_run]

  @source     = strip_trailing_separator_if_any(source,true)
  @target     = strip_trailing_separator_if_any(target)

  @options = {
    :dry_run         => false,
    :exclusions      => DEFAULT_EXCLUSIONS,
    :archive         => true,
    :one_file_system => true,
    :hard_links      => true,
    :human_readable  => true,
    :inplace         => true,
    :numeric_ids     => true,
    :delete          => true,
    :link_dest       => last_full_backup,
    :rsync_cmd       => rsync_executable
  }.merge(opts)
  
  @incomplete = File.join(target,DEFAULT_INCOMPLETE_DIR_NAME)
  @complete   = File.join(target,backup_dir_name)
  
  
  debug "#{caller(0,1).first} @source: #{@source}, @target: #{@target}, @options: #{@options.inspect}"

end

Instance Attribute Details

#errorObject

Returns the value of attribute error.



9
10
11
# File 'lib/rsyncbackup.rb', line 9

def error
  @error
end

#optionsObject

Returns the value of attribute options.



9
10
11
# File 'lib/rsyncbackup.rb', line 9

def options
  @options
end

#outputObject

Returns the value of attribute output.



9
10
11
# File 'lib/rsyncbackup.rb', line 9

def output
  @output
end

#sourceObject

Returns the value of attribute source.



9
10
11
# File 'lib/rsyncbackup.rb', line 9

def source
  @source
end

#statusObject

Returns the value of attribute status.



9
10
11
# File 'lib/rsyncbackup.rb', line 9

def status
  @status
end

#targetObject

Returns the value of attribute target.



9
10
11
# File 'lib/rsyncbackup.rb', line 9

def target
  @target
end

Instance Method Details

#_run_the_command(cmd) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
# File 'lib/rsyncbackup/utilities.rb', line 16

def _run_the_command(cmd)
  Open3.popen3(cmd) do |stdin, stdout, stderr, t|
    pid = t.pid
    stdin.close
    err_thr = Thread.new { copy_lines(stderr, $stderr) }
    debug "#{caller(0,1).first} Reading STDOUT"
    copy_lines(stdout, $stdout)
    err_thr.join
    t.value
  end
end

#backup_dir_nameObject

returns the directory name for the current backup directory name consists of a time format: YYYY-MM-DDTHH-MM-SS



86
87
88
# File 'lib/rsyncbackup/utilities.rb', line 86

def backup_dir_name
  @backup_dir_name ||= Time.now.strftime("%FT%H-%M-%S")
end

#build_commandObject

returns the command string to execute with all parameters set



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/rsyncbackup/utilities.rb', line 41

def build_command
  
  cmd = []
  cmd << options[:rsync_cmd]
  cmd << '--dry-run'             if options[:dry_run]
  cmd << '--verbose --progress --itemize-changes' if (options[:verbose] || options[:debug])
  cmd << '--archive'             if options[:archive]
  cmd << '--one-file-system'     if options[:one_file_system]
  cmd << '--hard-links'          if options[:hard_links]
  cmd << '--human-readable'      if options[:human_readable]
  cmd << '--inplace'             if options[:inplace]
  cmd << '--numeric-ids'         if options[:numeric_ids]
  cmd << '--delete'              if options[:delete]
  cmd << "--exclude-file #{options[:exclusions]}" if File.exist?(options[:exclusions])
  cmd << "--link-dest '#{options[:link_dest]}'" if options[:link_dest]
  cmd << ?" + @source + ?"
  cmd << ?" + temp_target_path + ?"
  
  cmd.join(' ')
  
end

#copy_lines(str_in, str_out) ⇒ Object



28
29
30
# File 'lib/rsyncbackup/utilities.rb', line 28

def copy_lines(str_in, str_out)
  str_in.each_line {|line| str_out.puts line}
end

#finalizeObject



65
66
67
68
69
# File 'lib/rsyncbackup.rb', line 65

def finalize
  File.rename(@incomplete, @complete) if File.exist?(@incomplete)
  File.write(File.join(@target,DEFAULT_LAST_FULL_DIR_NAME), backup_dir_name)
  info "Backup saved in #{@complete}"
end

#full_target_pathObject

returns the full target path, including backup directory name



91
92
93
# File 'lib/rsyncbackup/utilities.rb', line 91

def full_target_path
  @full_target_path ||= File.join(@target, backup_dir_name)
end

#last_full_backupObject

returns the directory name of the last full backup returns nil otherwise



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/rsyncbackup/utilities.rb', line 65

def last_full_backup
  
  unless @last_full_backup
    lastfull = File.join(@target,DEFAULT_LAST_FULL_DIR_NAME)
    @last_full_backup = unless File.exist?(lastfull)
                          nil
                        else
                          last_full_directory = IO.readlines(lastfull).first.chomp
                       unless File.exist?(File.join(@target,last_full_directory))
                            nil
                          else
                            last_full_directory
                          end
                        end
  end
  @last_full_backup

end

#rsync_executableObject

returns the path to the rsync executable If none found, raises an Exception



104
105
106
107
108
# File 'lib/rsyncbackup/utilities.rb', line 104

def rsync_executable
  rsync = `which rsync`.chomp
  raise "No rsync executable. Are you sure it\'s installed?" if rsync.empty?
  rsync
end

#runObject



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/rsyncbackup.rb', line 44

def run
  @cmd = build_command
  
  info "Backing up #{@source} to #{@target} on #{Time.now}"
  info "Rsync command: #{@cmd}"
  info "Dry run only" if options[:dry_run]
    
  if File.exist? temp_target_path
    warn "Preexisting temporary target. Moving it aside."
    File.rename temp_target_path, "#{temp_target_path}-#{Time.now.to_i}"
  end

  # the dry run option will be passed through to the rsync command,
  # so we still do want to run it.
  self.status = _run_the_command(@cmd)
  debug "#{caller(0,1).first} self.status #{self.status.inspect}"
  raise "Rsync Error: exit status: #{self.status.exitstatus}" unless (self.status.success? || self.status.exitstatus == 23) # don't abort if some files could not be transferred
  finalize
  self
end

#strip_trailing_separator_if_any(s, keep_if_symlink = false) ⇒ Object

Strip the trailing directory separator from the rsync source or target.

s

string to strip



114
115
116
117
118
119
120
121
122
# File 'lib/rsyncbackup/utilities.rb', line 114

def strip_trailing_separator_if_any(s,keep_if_symlink=false)
  s = s.to_s
  s_s = s.sub(%r{#{File::SEPARATOR}+$},'')
  unless keep_if_symlink
    s_s
  else
    File.symlink?(s_s) ? s : s_s
  end
end

#success?Boolean

returns true if the rsync command was successful

Returns:

  • (Boolean)


35
36
37
# File 'lib/rsyncbackup/utilities.rb', line 35

def success?
  (@status.nil?) ? nil : @status.success?
end

#temp_target_pathObject

returns the temporary target path



96
97
98
# File 'lib/rsyncbackup/utilities.rb', line 96

def temp_target_path
  @temp_target_path ||= File.join(@target, DEFAULT_INCOMPLETE_DIR_NAME)
end