lib/backup/model.rb in backup-3.0.19 vs lib/backup/model.rb in backup-3.0.20

- old
+ new

@@ -1,10 +1,10 @@ # encoding: utf-8 module Backup class Model - include Backup::CLI + include Backup::CLI::Helpers ## # The trigger is used as an identifier for # initializing the backup process attr_accessor :trigger @@ -40,10 +40,14 @@ ## # The syncers attribute holds an array of syncer objects attr_accessor :syncers ## + # The chunk_size attribute holds the size of the chunks in megabytes + attr_accessor :chunk_size + + ## # The time when the backup initiated (in format: 2011.02.20.03.29.59) attr_accessor :time class << self ## @@ -62,17 +66,27 @@ # Contains the currently-in-use model. This attribute should get set by Backup::Finder. # Use Backup::Model.current to retrieve the actual data of the model attr_accessor :current ## + # Contains an array of chunk suffixes for a given file + attr_accessor :chunk_suffixes + + ## # Returns the full path to the current file (including the current extension). # To just return the filename and extension without the path, use File.basename(Backup::Model.file) def file File.join(TMP_PATH, "#{ TIME }.#{ TRIGGER }.#{ Backup::Model.extension }") end ## + # Returns the @chunk_suffixes variable, sets it to an emtpy array if nil + def chunk_suffixes + @chunk_suffixes ||= Array.new + end + + ## # Returns the temporary trigger path of the current model # e.g. /Users/Michael/tmp/backup/my_trigger def tmp_path File.join(TMP_PATH, TRIGGER) end @@ -97,17 +111,13 @@ def initialize(trigger, label, &block) @trigger = trigger @label = label @time = TIME - @databases = Array.new - @archives = Array.new - @encryptors = Array.new - @compressors = Array.new - @storages = Array.new - @notifiers = Array.new - @syncers = Array.new + procedure_instance_variables.each do |variable| + instance_variable_set(variable, Array.new) + end instance_eval(&block) Backup::Model.all << self end @@ -155,14 +165,14 @@ end ## # Adds a storage method to the array of storage # methods to use during the backup process - def store_with(storage, &block) + def store_with(storage, storage_id = nil, &block) @storages << Backup::Storage.const_get( last_constant(storage) - ).new(&block) + ).new(storage_id, &block) end ## # Adds a syncer method to the array of syncer # methods to use during the backup process @@ -171,29 +181,46 @@ last_constant(syncer) ).new(&block) end ## + # Adds a method that allows the user to set the @chunk_size. + # The chunk_size (in megabytes) will later determine in how many chunks the + # backup needs to be split + def split_into_chunks_of(chunk_size = nil) + @chunk_size = chunk_size + end + + ## + # Returns the path to the current file (including proper extension) + def file + Backup::Model.file + end + + ## # Performs the backup process ## # [Databases] # Runs all (if any) database objects to dump the databases ## # [Archives] # Runs all (if any) archive objects to package all their # paths in to a single tar file and places it in the backup folder ## - # [Package] + # [Packaging] # After all the database dumps and archives are placed inside # the folder, it'll make a single .tar package (archive) out of it ## # [Encryption] # Optionally encrypts the packaged file with one or more encryptors ## # [Compression] # Optionally compresses the packaged file with one or more compressors ## + # [Splitting] + # Optionally splits the backup file in to multiple smaller chunks before transferring them + ## # [Storages] # Runs all (if any) storage objects to store the backups to remote locations # and (if configured) it'll cycle the files on the remote location to limit the # amount of backups stored on each individual location ## @@ -210,65 +237,97 @@ # After the whole backup process finishes, it'll go ahead and remove any temporary # file that it produced. If an exception(error) is raised during this process which # breaks the process, it'll always ensure it removes the temporary files regardless # to avoid mass consumption of storage space on the machine def perform! - begin - if databases.any? or archives.any? - databases.each { |d| d.perform! } - archives.each { |a| a.perform! } - package! - compressors.each { |c| c.perform! } - encryptors.each { |e| e.perform! } - storages.each { |s| s.perform! } - clean! + if databases.any? or archives.any? + procedures.each do |procedure| + (procedure.call; next) if procedure.is_a?(Proc) + procedure.each(&:perform!) end + end - syncers.each { |s| s.perform! } - notifiers.each { |n| n.perform!(self) } - rescue => exception - clean! - notifiers.each { |n| n.perform!(self, exception) } - show_exception!(exception) + syncers.each(&:perform!) + notifiers.each { |n| n.perform!(self) } + + rescue Exception => err + fatal = !err.is_a?(StandardError) + + Logger.error Backup::Errors::ModelError.wrap(err, <<-EOS) + Backup for #{label} (#{trigger}) Failed! + An Error occured which has caused this Backup to abort before completion. + Please review the Log for this Backup to determine if steps need to be taken + to clean up, based on the point at which the failure occured. + EOS + Logger.error "\nBacktrace:\n" + err.backtrace.join("\n\s\s") + "\n\n" + + if fatal + Logger.error Backup::Errors::ModelError.new(<<-EOS) + This Error was Fatal and Backup will now exit. + If you have other Backup jobs (triggers) configured to run, + they will not be processed. + EOS + else + Logger.message Backup::Errors::ModelError.new(<<-EOS) + If you have other Backup jobs (triggers) configured to run, + Backup will now attempt to continue... + EOS end + + notifiers.each do |n| + begin + n.perform!(self, err) + rescue Exception; end + end + + exit(1) if fatal + ensure + clean! end private ## # After all the databases and archives have been dumped and sorted, # these files will be bundled in to a .tar archive (uncompressed) so it # becomes a single (transferrable) packaged file. def package! - Logger.message "Backup started packaging everything to a single archive file." - run(%|#{ utility(:tar) } -c -f '#{ File.join(TMP_PATH, "#{TIME}.#{TRIGGER}.tar") }' -C '#{ TMP_PATH }' '#{ TRIGGER }'|) + Backup::Packager.new(self).package! end ## + # Create a new instance of Backup::Splitter, + # passing it the current model instance and runs it. + def split! + Backup::Splitter.new(self).split! + end + + ## # Cleans up the temporary files that were created after the backup process finishes def clean! - Logger.message "Backup started cleaning up the temporary files." - run("#{ utility(:rm) } -rf '#{ File.join(TMP_PATH, TRIGGER) }' '#{ File.join(TMP_PATH, "#{TIME}.#{TRIGGER}.#{Backup::Model.extension}") }'") + Backup::Cleaner.new(self).clean! end ## - # Returns the string representation of the last value of a nested constant - # example: - # Backup::Model::MySQL - # becomes and returns: - # "MySQL" - def last_constant(constant) - constant.to_s.split("::").last + # Returns an array of procedures + def procedures + Array.new([ + databases, archives, lambda { package! }, compressors, + encryptors, lambda { split! }, storages + ]) end ## - # Formats an exception - def show_exception!(exception) - Logger.normal "=" * 75 + "\nException that got raised:\n#{exception.class} - #{exception} \n" + "=" * 75 + "\n" + exception.backtrace.join("\n") - Logger.normal "=" * 75 + "\n\nYou are running Backup version \"#{Backup::Version.current}\" and Ruby version \"#{RUBY_VERSION} (patchlevel #{RUBY_PATCHLEVEL})\" on platform \"#{RUBY_PLATFORM}\".\n" - Logger.normal "If you've setup a \"Notification\" in your configuration file, the above error will have been sent." - #Notifies the shell an exception occured. - exit 1 + # Returns an Array of the names (String) of the procedure instance variables + def procedure_instance_variables + [:@databases, :@archives, :@encryptors, :@compressors, :@storages, :@notifiers, :@syncers] + end + + ## + # Returns the string representation of the last value of a nested constant + # example: last_constant(Backup::Model::MySQL) becomes and returns "MySQL" + def last_constant(constant) + constant.to_s.split("::").last end end end