module Doing::Util::Backup
Backup
utils
Public Instance Methods
Retrieve the most recent backup
@param filename The filename @return [String] filename
# File lib/doing/util_backup.rb, line 33 def last_backup(filename = nil, count: 1) filename ||= Doing.setting('doing_file') backup = get_backups(filename).slice(count - 1) backup.nil? ? nil : File.join(backup_dir, backup) end
Delete all but most recent 5 backups
@param limit Maximum number of backups to retain
# File lib/doing/util_backup.rb, line 16 def prune_backups(filename, limit = 10) backups = get_backups(filename) return unless backups.count > limit backups[limit..-1].each do |file| FileUtils.rm(File.join(backup_dir, file)) end clear_redo(filename) end
Undo last undo
@param filename The filename
# File lib/doing/util_backup.rb, line 67 def redo_backup(filename = nil, count: 1) filename ||= Doing.setting('doing_file') undones = Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).sort.reverse total = undones.count count = total if count > total skipped = undones.slice!(0, count) undone = skipped.pop raise HistoryLimitError, 'End of redo history' if undone.nil? redo_file = File.join(backup_dir, undone) move_backup(redo_file, filename) skipped.each do |f| FileUtils.mv(File.join(backup_dir, f), File.join(backup_dir, f.sub(/^undone/, ''))) end Doing.logger.warn('File update:', "restored undo step #{count}/#{total}") Doing.logger.debug('Backup:', "#{total - skipped.count - 1} redos remaining") end
Restore the most recent backup. If a filename is provided, only backups of that filename will be used.
@param filename The filename to restore, if different from default
# File lib/doing/util_backup.rb, line 47 def restore_last_backup(filename = nil, count: 1) Doing.logger.benchmark(:restore_backup, :start) filename ||= Doing.setting('doing_file') backup_file = last_backup(filename, count: count) raise HistoryLimitError, 'End of undo history' if backup_file.nil? save_undone(filename) move_backup(backup_file, filename) prune_backups_after(File.basename(backup_file)) Doing.logger.warn('File update:', "restored from #{backup_file}") Doing.logger.benchmark(:restore_backup, :finish) end
Select from recent backups. If a filename is provided, only backups of that filename will be used.
@param filename The filename to restore
# File lib/doing/util_backup.rb, line 135 def select_backup(filename = nil) filename ||= Doing.setting('doing_file') options = get_backups(filename).each_with_object([]) do |file, arr| d, _base = date_of_backup(file) next if d.nil? arr.push("#{d.time_ago}\t#{File.join(backup_dir, file)}") end raise MissingBackupFile, 'No backup files to load' if options.empty? backup_file = show_menu(options, filename) Util.write_to_file(File.join(backup_dir, "undone___#{File.basename(filename)}"), IO.read(filename), backup: false) move_backup(backup_file, filename) prune_backups_after(File.basename(backup_file)) Doing.logger.warn('File update:', "restored from #{backup_file}") end
Select from recent undos. If a filename is provided, only backups of that filename will be used.
@param filename The filename to restore
# File lib/doing/util_backup.rb, line 97 def select_redo(filename = nil) filename ||= Doing.setting('doing_file') undones = Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).sort raise HistoryLimitError, 'End of redo history' if undones.empty? total = undones.count options = undones.each_with_object([]) do |file, arr| d, _base = date_of_backup(file) next if d.nil? arr.push("#{d.time_ago}\t#{File.join(backup_dir, file)}") end raise MissingBackupFile, 'No backup files to load' if options.empty? backup_file = show_menu(options, filename) idx = undones.index(File.basename(backup_file)) skipped = undones.slice!(idx, undones.count - idx) undone = skipped.shift redo_file = File.join(backup_dir, undone) move_backup(redo_file, filename) skipped.each do |f| FileUtils.mv(File.join(backup_dir, f), File.join(backup_dir, f.sub(/^undone/, ''))) end Doing.logger.warn('File update:', "restored undo step #{idx}/#{total}") Doing.logger.debug('Backup:', "#{total - skipped.count - 1} redos remaining") end
Writes a copy of the content to a dated backup file in a hidden directory
@param filename [String] The filename
# File lib/doing/util_backup.rb, line 159 def write_backup(filename = nil) Doing.logger.benchmark(:_write_backup, :start) filename ||= Doing.setting('doing_file') unless File.exist?(filename) Doing.logger.debug('Backup:', "original file doesn't exist (#{filename})") return end backup_file = File.join(backup_dir, "#{timestamp_filename}___#{File.basename(filename)}") # compressed = Zlib::Deflate.deflate(content) # Zlib::GzipWriter.open(backup_file + '.gz') do |gz| # gz.write(IO.read(filename)) # end FileUtils.cp(filename, backup_file) prune_backups(filename, Doing.setting('history_size').to_i) clear_undone(filename) Doing.logger.benchmark(:_write_backup, :finish) end
Private Instance Methods
Return a location for storing backups, creating if needed
@return Path to backup directory
# File lib/doing/util_backup.rb, line 282 def backup_dir @backup_dir ||= create_backup_dir end
Delete all redo files
@param filename [String] The filename
# File lib/doing/util_backup.rb, line 241 def clear_redo(filename) filename ||= Doing.setting('doing_file') backups = Dir.glob("undone*___#{File.basename(filename)}", base: backup_dir).sort.reverse backups.each do |file| FileUtils.rm(File.join(backup_dir, file)) end end
# File lib/doing/util_backup.rb, line 228 def clear_undone(filename = nil) filename ||= Doing.setting('doing_file') # redo_file = File.join(backup_dir, "undone___#{File.basename(filename)}") Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).each do |f| FileUtils.rm(File.join(backup_dir, f)) end end
# File lib/doing/util_backup.rb, line 286 def create_backup_dir dir = File.expand_path(Doing.setting('backup_dir')) || File.join(user_home, '.doing_backup') if File.exist?(dir) && !File.directory?(dir) raise DoingNoTraceError.new("#{dir} is not a directory", topic: 'History:', exit_code: 27) end unless File.exist?(dir) FileUtils.mkdir_p(dir) Doing.logger.warn('Backup:', "backup directory created at #{dir}") end dir end
Retrieve date from backup filename
@param filename The filename
# File lib/doing/util_backup.rb, line 270 def date_of_backup(filename) m = filename.match(/^(?:undone)?(?<date>\d{4}-\d{2}-\d{2})_(?<time>\d{2}\.\d{2}\.\d{2})___(?<file>.*?)$/) return nil if m.nil? [Time.parse("#{m['date']} #{m['time'].gsub(/\./, ':')}"), m['file']] end
# File lib/doing/util_backup.rb, line 253 def get_backups(filename = nil, include_forward: false) filename ||= Doing.setting('doing_file') backups = Dir.glob("*___#{File.basename(filename)}", base: backup_dir).sort.reverse backups.delete_if { |f| f =~ /^undone/ } unless include_forward end
# File lib/doing/util_backup.rb, line 183 def move_backup(source, dest) Hooks.trigger :pre_write, WWID.new, dest FileUtils.mv(source, dest) Hooks.trigger :post_write, dest end
Delete backups newer than selected filename
@param filename The filename
# File lib/doing/util_backup.rb, line 306 def prune_backups_after(filename) target_date, base = date_of_backup(filename) return if target_date.nil? counter = 0 get_backups(base).each do |file| date, _base = date_of_backup(file) if date && target_date < date FileUtils.mv(File.join(backup_dir, file), File.join(backup_dir, "undone#{file}")) counter += 1 end end Doing.logger.debug('Backup:', "deleted #{counter} files newer than restored backup") end
# File lib/doing/util_backup.rb, line 259 def save_undone(filename = nil) filename ||= Doing.setting('doing_file') undone_file = File.join(backup_dir, "undone#{timestamp_filename}___#{File.basename(filename)}") FileUtils.cp(filename, undone_file) end
# File lib/doing/util_backup.rb, line 249 def timestamp_filename Time.now.strftime('%Y-%m-%d_%H.%M.%S') end