backup_organizer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rvmrc +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/Guardfile +8 -0
- data/LICENSE +22 -0
- data/README.md +73 -0
- data/Rakefile +2 -0
- data/backup_organizer.gemspec +21 -0
- data/lib/backup_organizer.rb +22 -0
- data/lib/backup_organizer/configuration.rb +25 -0
- data/lib/backup_organizer/error/base_error.rb +6 -0
- data/lib/backup_organizer/error/configuration_error.rb +8 -0
- data/lib/backup_organizer/error/error_with_custom_message.rb +11 -0
- data/lib/backup_organizer/error/invalid_path_error.rb +6 -0
- data/lib/backup_organizer/error/setup_error.rb +8 -0
- data/lib/backup_organizer/extensions/file_extensions.rb +66 -0
- data/lib/backup_organizer/file_age.rb +31 -0
- data/lib/backup_organizer/file_mover.rb +18 -0
- data/lib/backup_organizer/file_utils.rb +27 -0
- data/lib/backup_organizer/pattern.rb +21 -0
- data/lib/backup_organizer/rule.rb +20 -0
- data/lib/backup_organizer/setup.rb +16 -0
- data/lib/backup_organizer/version.rb +3 -0
- data/spec/configuration_spec.rb +26 -0
- data/spec/extensions/file_extensions_spec.rb +123 -0
- data/spec/file_age_spec.rb +11 -0
- data/spec/file_mover_spec.rb +60 -0
- data/spec/file_utils_spec.rb +12 -0
- data/spec/integration_spec.rb +96 -0
- data/spec/pattern_spec.rb +15 -0
- data/spec/rule_spec.rb +25 -0
- data/spec/setup_spec.rb +59 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/helpers.rb +43 -0
- data/spec/support/temp_dir.rb +12 -0
- metadata +153 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.9.3
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2, :cli => "--color --format doc" do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Thorben Schröder
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# BackupOrganizer [![Build Status](https://secure.travis-ci.org/walski/backup_organizer.png)](http://travis-ci.org/walski/backup_organizer)
|
2
|
+
|
3
|
+
This gem helps you to keep your backups organized and to let the density of kept files to fade out over time.
|
4
|
+
|
5
|
+
The rules of how you want your backups organized are described in a Pattern like this:
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'backup_organizer'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install backup_organizer
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Scenario
|
24
|
+
* You are doing daily backups.
|
25
|
+
* The goal is to have a script that you can run daily and that ensures, that
|
26
|
+
* all the backups of the last 30 days are kept
|
27
|
+
* one file per month for the last year is kept
|
28
|
+
* one file per year is kept forever
|
29
|
+
* all other files are deleted
|
30
|
+
|
31
|
+
### Solution
|
32
|
+
|
33
|
+
#### How it looks like
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
# Load ActiveSupport to make date wrangling a bit more convenient
|
37
|
+
require 'rubygems'
|
38
|
+
require 'active_support/core_ext'
|
39
|
+
|
40
|
+
# Define the pattern
|
41
|
+
BackupOrganizer.organize('/basepath/to/your/backups') do |files|
|
42
|
+
files.stored_in('daily').if {|file| file.age < 30.days}
|
43
|
+
|
44
|
+
files.stored_in('monthly').if do |file|
|
45
|
+
file.age < 1.year &&
|
46
|
+
file.most_recent_in_its_month?
|
47
|
+
end
|
48
|
+
|
49
|
+
files.stored_in('yearly').if {|file| file.most_recent_in_its_year?}
|
50
|
+
end
|
51
|
+
````
|
52
|
+
|
53
|
+
#### What it does
|
54
|
+
|
55
|
+
Given that pattern you should drop all your backups in ``/basepath/to/your/backups/daily``. The organizer then does the following:
|
56
|
+
|
57
|
+
1. Move all files in ``/basepath/to/your/backups/daily`` which
|
58
|
+
* are not created in the current month
|
59
|
+
|
60
|
+
to ``/basepath/to/your/backups/monthly``
|
61
|
+
|
62
|
+
2. Move all files in ``/basepath/to/your/backups/monthly`` which
|
63
|
+
* are not created in the current year
|
64
|
+
* are among all the files created in the same month **not** most recent file
|
65
|
+
|
66
|
+
to ``/basepath/to/your/backups/yearly``
|
67
|
+
|
68
|
+
3. **Delete** (as this is the last rule) all files in ``/basepath/to/your/backups/monthly`` which
|
69
|
+
* are among all the files created in the same year **not** the most recent file
|
70
|
+
|
71
|
+
#### What else?
|
72
|
+
|
73
|
+
If you run the script make sure at least the ``/basepath/to/your/backups`` directory exists, the BackupOrganizer will take care of getting everything else in place.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/backup_organizer/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Thorben Schröder"]
|
6
|
+
gem.email = ["stillepost@gmail.com"]
|
7
|
+
gem.description = %q{Organizes backup files in folders in patterns that can be defined by the user.}
|
8
|
+
gem.summary = %q{If backups should only be kept around for a certain time and be stored sparsely (weekly, monthly, yearly, whatever) after that backup_organizer can do that for you.}
|
9
|
+
gem.homepage = "http://github.com/walski/backup_organizer"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "backup_organizer"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = BackupOrganizer::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rspec', '>= 2'
|
19
|
+
gem.add_development_dependency 'guard-rspec'
|
20
|
+
gem.add_development_dependency 'ruby_gntp'
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "backup_organizer/version"
|
2
|
+
require "backup_organizer/file_age"
|
3
|
+
require "backup_organizer/extensions/file_extensions"
|
4
|
+
require "backup_organizer/error/base_error"
|
5
|
+
Dir[File.expand_path('../backup_organizer/error/**/*.rb', __FILE__)].each {|f| require f}
|
6
|
+
require "backup_organizer/file_utils"
|
7
|
+
require "backup_organizer/rule"
|
8
|
+
require "backup_organizer/pattern"
|
9
|
+
require "backup_organizer/configuration"
|
10
|
+
require "backup_organizer/setup"
|
11
|
+
require "backup_organizer/file_mover"
|
12
|
+
|
13
|
+
module BackupOrganizer
|
14
|
+
def self.organize(path)
|
15
|
+
pattern = Pattern.new do |files|
|
16
|
+
yield files
|
17
|
+
end
|
18
|
+
configuration = Configuration.new(:path => path, :pattern => pattern)
|
19
|
+
Setup.create_structure(configuration)
|
20
|
+
configuration.move_all_files
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "backup_organizer/file_mover"
|
2
|
+
|
3
|
+
module BackupOrganizer
|
4
|
+
class Configuration
|
5
|
+
attr_reader :path, :pattern
|
6
|
+
def initialize(options)
|
7
|
+
@path, @pattern = options[:path], options[:pattern]
|
8
|
+
|
9
|
+
raise Error::ConfigurationError.new('No path set') unless @path
|
10
|
+
raise Error::ConfigurationError.new('No pattern set') unless @path
|
11
|
+
end
|
12
|
+
|
13
|
+
def directories
|
14
|
+
pattern.directories.map {|directory| File.expand_path("./#{directory}", @path)}
|
15
|
+
end
|
16
|
+
|
17
|
+
def move_all_files
|
18
|
+
sources = directories
|
19
|
+
destinations = (directories << :delete)[1..-1]
|
20
|
+
sources.each_with_index do |source, i|
|
21
|
+
FileMover.move_files(source, @pattern.rules[i], destinations[i])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "backup_organizer/file_age"
|
2
|
+
require 'backup_organizer/file_utils'
|
3
|
+
|
4
|
+
module BackupOrganizer
|
5
|
+
module Extensions
|
6
|
+
module FileExtensions
|
7
|
+
def age
|
8
|
+
FileAge.new(BackupOrganizer::FileUtils.age_of(self))
|
9
|
+
end
|
10
|
+
|
11
|
+
def most_recent_in_its_hour?
|
12
|
+
mtime = BackupOrganizer::FileUtils.mtime(self)
|
13
|
+
hour = Time.local(mtime.year, mtime.month, mtime.day, mtime.hour)
|
14
|
+
one_hour = 60 * 60
|
15
|
+
|
16
|
+
most_recent_in?(hour...(hour + one_hour))
|
17
|
+
end
|
18
|
+
|
19
|
+
def most_recent_in_its_day?
|
20
|
+
mtime = BackupOrganizer::FileUtils.mtime(self)
|
21
|
+
midnight = Time.local(mtime.year, mtime.month, mtime.day)
|
22
|
+
one_day = 24 * 60 * 60
|
23
|
+
|
24
|
+
most_recent_in?(midnight...(midnight + one_day))
|
25
|
+
end
|
26
|
+
|
27
|
+
def most_recent_in_its_week?
|
28
|
+
mtime = BackupOrganizer::FileUtils.mtime(self)
|
29
|
+
midnight = Time.local(mtime.year, mtime.month, mtime.day)
|
30
|
+
one_day = 24 * 60 * 60
|
31
|
+
monday = midnight - ((midnight.wday - 1) * one_day)
|
32
|
+
monday = monday - (7 * one_day) if midnight.wday == 0
|
33
|
+
|
34
|
+
most_recent_in?(monday...(monday + (7 * one_day)))
|
35
|
+
end
|
36
|
+
|
37
|
+
def most_recent_in_its_month?
|
38
|
+
mtime = BackupOrganizer::FileUtils.mtime(self)
|
39
|
+
this_month = Time.local(mtime.year, mtime.month, 1)
|
40
|
+
mtime = this_month + 32.days
|
41
|
+
next_month = Time.local(mtime.year, mtime.month, 1)
|
42
|
+
|
43
|
+
most_recent_in?(this_month...next_month)
|
44
|
+
end
|
45
|
+
|
46
|
+
def most_recent_in_its_year?
|
47
|
+
mtime = BackupOrganizer::FileUtils.mtime(self)
|
48
|
+
this_year = Time.local(mtime.year, 1, 1)
|
49
|
+
next_year = Time.local(this_year.year + 1, 1, 1)
|
50
|
+
|
51
|
+
most_recent_in?(this_year...next_year)
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
def most_recent_in?(period)
|
56
|
+
dir = File.dirname(self)
|
57
|
+
|
58
|
+
files_in_dir = Dir[File.expand_path('./*', dir)]
|
59
|
+
method = Range.instance_methods.include?(:cover?) ? :cover? : :include?
|
60
|
+
files_this_month = files_in_dir.select {|f| period.send(method, FileUtils.mtime(f))}
|
61
|
+
|
62
|
+
self == files_this_month.sort {|a,b| File.mtime(a) <=> File.mtime(b)}.last
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module BackupOrganizer
|
2
|
+
class BasicObject #:nodoc:
|
3
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval/ }
|
4
|
+
end unless defined?(BasicObject)
|
5
|
+
|
6
|
+
class FileAge < BasicObject
|
7
|
+
include ::Comparable
|
8
|
+
|
9
|
+
def initialize(age)
|
10
|
+
@age = age
|
11
|
+
end
|
12
|
+
|
13
|
+
def <=>(b)
|
14
|
+
b = b.to_f if b.respond_to?(:to_f)
|
15
|
+
@age.<=>(b)
|
16
|
+
end
|
17
|
+
|
18
|
+
def respond_to?(method)
|
19
|
+
@age.respond_to?(method)
|
20
|
+
end
|
21
|
+
|
22
|
+
def is_a?(klass)
|
23
|
+
FileAge == klass || @age.is_a?(klass)
|
24
|
+
end
|
25
|
+
alias :kind_of? :is_a?
|
26
|
+
|
27
|
+
def method_missing(name, *attrs)
|
28
|
+
@age.send(name, *attrs)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'backup_organizer/file_utils'
|
2
|
+
|
3
|
+
module BackupOrganizer
|
4
|
+
class FileMover
|
5
|
+
def self.move_files(source, rule, destination)
|
6
|
+
Dir[File.expand_path('./*', source)].each do |file|
|
7
|
+
move_file(file, rule, destination)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.move_file(file, rule, destination)
|
12
|
+
return if rule.applies_for?(file)
|
13
|
+
return FileUtils.rm_rf(file) if destination == :delete
|
14
|
+
|
15
|
+
FileUtils.mv(file, destination)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module BackupOrganizer
|
2
|
+
class FileUtils
|
3
|
+
def self.mv(from, to)
|
4
|
+
::FileUtils.mv(from, to)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.rm_rf(path)
|
8
|
+
::FileUtils.rm_rf(path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.touch(path)
|
12
|
+
::FileUtils.touch(path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.mkdir(path)
|
16
|
+
Dir.mkdir(path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.age_of(path)
|
20
|
+
Time.now - mtime(path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.mtime(path)
|
24
|
+
File.mtime(path)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module BackupOrganizer
|
2
|
+
class Pattern
|
3
|
+
def initialize
|
4
|
+
yield self
|
5
|
+
end
|
6
|
+
|
7
|
+
def stored_in(directory)
|
8
|
+
rule = Rule.new(directory)
|
9
|
+
rules << rule
|
10
|
+
rule
|
11
|
+
end
|
12
|
+
|
13
|
+
def directories
|
14
|
+
rules.map &:directory
|
15
|
+
end
|
16
|
+
|
17
|
+
def rules
|
18
|
+
@rules ||= []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module BackupOrganizer
|
2
|
+
class Rule
|
3
|
+
def initialize(directory)
|
4
|
+
@directory = directory
|
5
|
+
end
|
6
|
+
|
7
|
+
def if(&rule)
|
8
|
+
@rule = rule
|
9
|
+
end
|
10
|
+
|
11
|
+
def applies_for?(file)
|
12
|
+
file.extend(Extensions::FileExtensions)
|
13
|
+
@rule.call(file)
|
14
|
+
end
|
15
|
+
|
16
|
+
def directory
|
17
|
+
@directory
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'backup_organizer/file_utils'
|
2
|
+
|
3
|
+
module BackupOrganizer
|
4
|
+
class Setup
|
5
|
+
def self.create_structure(configuration)
|
6
|
+
@configuration = configuration
|
7
|
+
raise Error::InvalidPathError unless File.exist?(@configuration.path) && File.directory?(@configuration.path)
|
8
|
+
|
9
|
+
@configuration.directories.each do |necessary_directory|
|
10
|
+
next if File.directory?(necessary_directory)
|
11
|
+
raise Error::SetupError.new('`#{necessary_directory}` should be a directory but is a file.') if File.exist?(necessary_directory)
|
12
|
+
BackupOrganizer::FileUtils.mkdir(necessary_directory)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe BackupOrganizer::Configuration do
|
5
|
+
it "returns the full path to all the directories of it's pattern" do
|
6
|
+
path = Tempdir.new
|
7
|
+
pattern = mock(BackupOrganizer::Pattern, :directories => %w{daily weekly yearly})
|
8
|
+
configuration = BackupOrganizer::Configuration.new(:path => path, :pattern => pattern)
|
9
|
+
configuration.directories.should eq pattern.directories.map {|d| File.expand_path("./#{d}", path)}
|
10
|
+
end
|
11
|
+
|
12
|
+
it "runs the file mover for all rules/destinations" do
|
13
|
+
path = Tempdir.new
|
14
|
+
rule1 = mock(BackupOrganizer::Rule, :path => 'daily')
|
15
|
+
rule2 = mock(BackupOrganizer::Rule, :path => 'weekly')
|
16
|
+
rule3 = mock(BackupOrganizer::Rule, :path => 'yearly')
|
17
|
+
pattern = mock(BackupOrganizer::Pattern, :directories => %w{daily weekly yearly}, :rules => [rule1, rule2, rule3])
|
18
|
+
configuration = BackupOrganizer::Configuration.new(:path => path, :pattern => pattern)
|
19
|
+
|
20
|
+
BackupOrganizer::FileMover.should_receive(:move_files).with(File.expand_path("./#{rule1.path}", path), rule1, File.expand_path("./#{rule2.path}", path)).ordered
|
21
|
+
BackupOrganizer::FileMover.should_receive(:move_files).with(File.expand_path("./#{rule2.path}", path), rule2, File.expand_path("./#{rule3.path}", path)).ordered
|
22
|
+
BackupOrganizer::FileMover.should_receive(:move_files).with(File.expand_path("./#{rule3.path}", path), rule3, :delete).ordered
|
23
|
+
|
24
|
+
configuration.move_all_files
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
def check_expectations(expectations, method)
|
4
|
+
dir = Tempdir.new
|
5
|
+
files = []
|
6
|
+
|
7
|
+
# Create all the files first
|
8
|
+
expectations.each do |date, expectation|
|
9
|
+
path = temp_file_at(date, dir).extend(BackupOrganizer::Extensions::FileExtensions)
|
10
|
+
files << [date, path, expectation]
|
11
|
+
end
|
12
|
+
|
13
|
+
# Check for the expectations
|
14
|
+
files.each do |date, path, expectation|
|
15
|
+
path.send(method).should be(expectation), "File with mtime of #{date} should return #{expectation} to #{method} but didn't"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe BackupOrganizer::Extensions::FileExtensions do
|
20
|
+
it "can tell a file's age" do
|
21
|
+
file = '/tmp/some/path'
|
22
|
+
file.extend BackupOrganizer::Extensions::FileExtensions
|
23
|
+
BackupOrganizer::FileUtils.should_receive(:age_of).with(file).and_return(1.234)
|
24
|
+
age = file.age
|
25
|
+
age.kind_of?(BackupOrganizer::FileAge).should be true
|
26
|
+
age.should eq 1.234
|
27
|
+
end
|
28
|
+
|
29
|
+
it "can tell if a file is the most recent one in the same hour of it's last modification (mtime)" do
|
30
|
+
time = Time.now
|
31
|
+
|
32
|
+
first_of_this_hour = Time.local(time.year, time.month, time.day, time.hour)
|
33
|
+
last_of_last_hours = first_of_this_hour - 1
|
34
|
+
mid_of_this_hour = first_of_this_hour + 30.minutes
|
35
|
+
first_of_next_hour = first_of_this_hour + 1.hour
|
36
|
+
|
37
|
+
expectations = {
|
38
|
+
first_of_this_hour => false,
|
39
|
+
last_of_last_hours => true,
|
40
|
+
mid_of_this_hour => true,
|
41
|
+
first_of_next_hour => true
|
42
|
+
}
|
43
|
+
|
44
|
+
check_expectations(expectations, :most_recent_in_its_hour?)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "can tell if a file is the most recent one in the same day of it's last modification (mtime)" do
|
48
|
+
time = Time.now
|
49
|
+
|
50
|
+
midnight = Time.local(time.year, time.month, time.day)
|
51
|
+
|
52
|
+
first_of_this_day = midnight
|
53
|
+
last_of_last_day = first_of_this_day - 1
|
54
|
+
mid_of_this_day = first_of_this_day + 12.hours
|
55
|
+
first_of_next_day = first_of_this_day + 24.hours
|
56
|
+
|
57
|
+
expectations = {
|
58
|
+
first_of_this_day => false,
|
59
|
+
last_of_last_day => true,
|
60
|
+
mid_of_this_day => true,
|
61
|
+
first_of_next_day => true
|
62
|
+
}
|
63
|
+
|
64
|
+
check_expectations(expectations, :most_recent_in_its_day?)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "can tell if a file is the most recent one in the same week of it's last modification (mtime)" do
|
68
|
+
time = Time.now
|
69
|
+
|
70
|
+
midnight = Time.local(time.year, time.month, time.day)
|
71
|
+
first_of_this_week = midnight - (midnight.wday - 1).days
|
72
|
+
first_of_this_week -= 7.days if midnight.wday == 0
|
73
|
+
last_of_last_week = first_of_this_week - 1
|
74
|
+
mid_of_this_week = first_of_this_week + 3.days
|
75
|
+
first_of_next_week = first_of_this_week + 7.days
|
76
|
+
|
77
|
+
expectations = {
|
78
|
+
first_of_this_week => false,
|
79
|
+
last_of_last_week => true,
|
80
|
+
mid_of_this_week => true,
|
81
|
+
first_of_next_week => true
|
82
|
+
}
|
83
|
+
|
84
|
+
check_expectations(expectations, :most_recent_in_its_week?)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "can tell if a file is the most recent one in the same month of it's last modification (mtime)" do
|
88
|
+
time = Time.now
|
89
|
+
|
90
|
+
first_of_this_month = Time.local(time.year, time.month, 1)
|
91
|
+
last_of_last_month = first_of_this_month - 1
|
92
|
+
mid_of_this_month = first_of_this_month + 15.days
|
93
|
+
next_month = (first_of_this_month + 32.days)
|
94
|
+
first_of_next_month = Time.local(next_month.year, next_month.month, 1)
|
95
|
+
|
96
|
+
expectations = {
|
97
|
+
first_of_this_month => false,
|
98
|
+
last_of_last_month => true,
|
99
|
+
mid_of_this_month => true,
|
100
|
+
first_of_next_month => true
|
101
|
+
}
|
102
|
+
|
103
|
+
check_expectations(expectations, :most_recent_in_its_month?)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "can tell if a file is the most recent one in the same year of it's last modification (mtime)" do
|
107
|
+
time = Time.now
|
108
|
+
|
109
|
+
first_of_this_year = Time.local(time.year, 1, 1)
|
110
|
+
last_of_last_year = first_of_this_year - 1
|
111
|
+
mid_of_this_year = first_of_this_year + 180.days
|
112
|
+
first_of_next_year = Time.local(first_of_this_year.year + 1, 1, 1)
|
113
|
+
|
114
|
+
expectations = {
|
115
|
+
first_of_this_year => false,
|
116
|
+
last_of_last_year => true,
|
117
|
+
mid_of_this_year => true,
|
118
|
+
first_of_next_year => true
|
119
|
+
}
|
120
|
+
|
121
|
+
check_expectations(expectations, :most_recent_in_its_year?)
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BackupOrganizer::FileAge do
|
4
|
+
it "can be compared with Time" do
|
5
|
+
age = BackupOrganizer::FileAge.new(Time.now - 2.days)
|
6
|
+
age.should < (Time.now - 1.day)
|
7
|
+
age.should_not > (Time.now - 1.day)
|
8
|
+
age.should > (Time.now - 3.days)
|
9
|
+
age.should_not < (Time.now - 3.days)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BackupOrganizer::FileMover do
|
4
|
+
describe "moving a single file to a destination" do
|
5
|
+
before do
|
6
|
+
@destination = Tempdir.new
|
7
|
+
@path = Tempfile.new('backup_organizer_file').path
|
8
|
+
@expected_path = File.expand_path("./#{File.basename(@path)}", @destination)
|
9
|
+
File.exist?(@path).should be true
|
10
|
+
File.exist?(@expected_path).should be false
|
11
|
+
end
|
12
|
+
|
13
|
+
it "is not carried out when the rule applies" do
|
14
|
+
rule = mock(BackupOrganizer::Rule, :applies_for? => true)
|
15
|
+
BackupOrganizer::FileMover.move_file(@path, rule, @destination)
|
16
|
+
File.exist?(@path).should be true
|
17
|
+
File.exist?(@expected_path).should be false
|
18
|
+
end
|
19
|
+
|
20
|
+
it "is carried out when the does not rule applies" do
|
21
|
+
rule = mock(BackupOrganizer::Rule, :applies_for? => false)
|
22
|
+
BackupOrganizer::FileMover.move_file(@path, rule, @destination)
|
23
|
+
File.exist?(@path).should be false
|
24
|
+
File.exist?(@expected_path).should be true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "deletes the file if the destination is :delete" do
|
28
|
+
rule = mock(BackupOrganizer::Rule, :applies_for? => false)
|
29
|
+
BackupOrganizer::FileMover.move_file(@path, rule, :delete)
|
30
|
+
File.exist?(@path).should be false
|
31
|
+
File.exist?(@expected_path).should be false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "moves all files in a directory that match a rule" do
|
36
|
+
directory = Tempdir.new
|
37
|
+
%w{some test-1 files with different-1 looks}.each {|f| BackupOrganizer::FileUtils.touch(File.expand_path("./#{f}", directory))}
|
38
|
+
rule = mock(BackupOrganizer::Rule)
|
39
|
+
def rule.applies_for?(file)
|
40
|
+
file !~ /-1$/
|
41
|
+
end
|
42
|
+
destination = Tempdir.new
|
43
|
+
|
44
|
+
BackupOrganizer::FileMover.move_files(directory, rule, destination)
|
45
|
+
|
46
|
+
File.exist?(File.expand_path("./some", directory) ).should be true
|
47
|
+
File.exist?(File.expand_path("./files", directory) ).should be true
|
48
|
+
File.exist?(File.expand_path("./with", directory) ).should be true
|
49
|
+
File.exist?(File.expand_path("./looks", directory) ).should be true
|
50
|
+
File.exist?(File.expand_path("./test-1", directory) ).should be false
|
51
|
+
File.exist?(File.expand_path("./different-1", directory)).should be false
|
52
|
+
|
53
|
+
File.exist?(File.expand_path("./some", destination) ).should be false
|
54
|
+
File.exist?(File.expand_path("./files", destination) ).should be false
|
55
|
+
File.exist?(File.expand_path("./with", destination) ).should be false
|
56
|
+
File.exist?(File.expand_path("./looks", destination) ).should be false
|
57
|
+
File.exist?(File.expand_path("./test-1", destination) ).should be true
|
58
|
+
File.exist?(File.expand_path("./different-1", destination)).should be true
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BackupOrganizer::FileUtils do
|
4
|
+
it "knows the age of a file" do
|
5
|
+
path = temp_file_at(Time.now - 3.days)
|
6
|
+
BackupOrganizer::FileUtils.age_of(path).should be_within(1).of(3.days)
|
7
|
+
path = temp_file_at(Time.now - 99.days)
|
8
|
+
BackupOrganizer::FileUtils.age_of(path).should be_within(1).of(99.days)
|
9
|
+
path = temp_file_at(Time.now - 17.years)
|
10
|
+
BackupOrganizer::FileUtils.age_of(path).should be_within(1).of(17.years)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'find'
|
3
|
+
|
4
|
+
def include_files(dir_name, actual, expected, expectation)
|
5
|
+
expected.each do |file|
|
6
|
+
basename = File.basename(file)
|
7
|
+
actual.include?(basename).should be(expectation), "#{dir_name} should#{expectation ? '' : ' not'} include #{basename} but does #{expectation ? 'not' : ''}."
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "integration of the whole system" do
|
12
|
+
before do
|
13
|
+
@now = Time.local(Time.now.year, 12, 30)
|
14
|
+
Time.stub(:now).and_return(@now)
|
15
|
+
|
16
|
+
@base_path = Tempdir.new
|
17
|
+
@daily_destination = File.expand_path("./daily", @base_path)
|
18
|
+
@monthly_destination = File.expand_path("./monthly", @base_path)
|
19
|
+
@yearly_destination = File.expand_path("./yearly", @base_path)
|
20
|
+
BackupOrganizer::FileUtils.mkdir(@daily_destination)
|
21
|
+
BackupOrganizer::FileUtils.mkdir(@yearly_destination)
|
22
|
+
|
23
|
+
@file_01_day_ago = temp_file_at(Time.now - 1.day + 2, @daily_destination)
|
24
|
+
@file_04_days_ago = temp_file_at(Time.now - 10.days + 2, @yearly_destination)
|
25
|
+
@file_10_days_ago = temp_file_at(Time.now - 10.days + 2, @daily_destination)
|
26
|
+
@file_30_days_ago = temp_file_at(Time.now - 30.days + 2, @daily_destination)
|
27
|
+
@file_31_days_ago = temp_file_at(Time.now - 31.days + 2, @daily_destination)
|
28
|
+
@file_02_month_ago = temp_file_at(Time.now - 60.days + 2, @daily_destination)
|
29
|
+
@file_02_month_and_a_bit_ago = temp_file_at(Time.now - 65.days + 2, @daily_destination)
|
30
|
+
@file_06_month_ago = temp_file_at(Time.now - 180.days + 2, @daily_destination)
|
31
|
+
@file_07_month_ago = temp_file_at(Time.now - 240.days + 2, @yearly_destination)
|
32
|
+
@file_365_days_ago = temp_file_at(Time.now - 365.days + 2, @daily_destination)
|
33
|
+
@file_366_days_ago = temp_file_at(Time.now - 366.days + 2, @daily_destination)
|
34
|
+
time_366_days_ago = Time.now - 366.days
|
35
|
+
@file_a_year_and_a_bit_ago = temp_file_at(Time.local(time_366_days_ago.year, 1, 1) + 2, @daily_destination)
|
36
|
+
@file_three_years_ago = temp_file_at(Time.now - 3.years + 2, @yearly_destination)
|
37
|
+
@file_ten_years_ago = temp_file_at(Time.now - 10.years + 2, @yearly_destination)
|
38
|
+
end
|
39
|
+
|
40
|
+
after do
|
41
|
+
Time.unstub(:now)
|
42
|
+
BackupOrganizer::FileUtils.rm_rf @base_path
|
43
|
+
end
|
44
|
+
|
45
|
+
it "works fine" do
|
46
|
+
BackupOrganizer.organize(@base_path) do |files|
|
47
|
+
files.stored_in('daily').if {|file| file.age < 30.days}
|
48
|
+
|
49
|
+
files.stored_in('monthly').if do |file|
|
50
|
+
file.age < 1.year &&
|
51
|
+
file.most_recent_in_its_month?
|
52
|
+
end
|
53
|
+
|
54
|
+
files.stored_in('yearly').if {|file| file.most_recent_in_its_year?}
|
55
|
+
end
|
56
|
+
|
57
|
+
expected_in_daily = [
|
58
|
+
@file_01_day_ago,
|
59
|
+
@file_10_days_ago,
|
60
|
+
@file_30_days_ago
|
61
|
+
]
|
62
|
+
|
63
|
+
expected_in_monthly = [
|
64
|
+
@file_31_days_ago,
|
65
|
+
@file_02_month_ago,
|
66
|
+
@file_06_month_ago,
|
67
|
+
@file_365_days_ago
|
68
|
+
]
|
69
|
+
|
70
|
+
expected_in_yearly = [
|
71
|
+
@file_04_days_ago,
|
72
|
+
@file_366_days_ago,
|
73
|
+
@file_three_years_ago,
|
74
|
+
@file_ten_years_ago
|
75
|
+
]
|
76
|
+
|
77
|
+
expected_deleted = [
|
78
|
+
@file_07_month_ago,
|
79
|
+
@file_a_year_and_a_bit_ago,
|
80
|
+
@file_02_month_and_a_bit_ago
|
81
|
+
]
|
82
|
+
|
83
|
+
dailies = files_in(File.expand_path('./daily', @base_path))
|
84
|
+
monthlies = files_in(File.expand_path('./monthly', @base_path))
|
85
|
+
yearlies = files_in(File.expand_path('./yearly', @base_path))
|
86
|
+
|
87
|
+
include_files(:dailies, dailies, expected_in_daily, true)
|
88
|
+
include_files(:dailies, dailies, expected_in_monthly + expected_in_yearly + expected_deleted, false)
|
89
|
+
|
90
|
+
include_files(:monthlies, monthlies, expected_in_monthly, true)
|
91
|
+
include_files(:monthlies, monthlies, expected_in_daily + expected_in_yearly + expected_deleted, false)
|
92
|
+
|
93
|
+
include_files(:yearlies, yearlies, expected_in_yearly, true)
|
94
|
+
include_files(:yearlies, yearlies, expected_in_daily + expected_in_monthly + expected_deleted, false)
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BackupOrganizer::Pattern do
|
4
|
+
subject {
|
5
|
+
BackupOrganizer::Pattern.new do |files|
|
6
|
+
files.stored_in('daily' ).if {|f| true}
|
7
|
+
files.stored_in('weekly').if {|f| true}
|
8
|
+
files.stored_in('yearly').if {|f| true}
|
9
|
+
end
|
10
|
+
}
|
11
|
+
|
12
|
+
it "can list all directories it needs" do
|
13
|
+
subject.directories.should eq %w{daily weekly yearly}
|
14
|
+
end
|
15
|
+
end
|
data/spec/rule_spec.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BackupOrganizer::Rule do
|
4
|
+
subject {BackupOrganizer::Rule.new('/tmp/dir')}
|
5
|
+
|
6
|
+
it "knows it's directory" do
|
7
|
+
subject.directory.should eq '/tmp/dir'
|
8
|
+
end
|
9
|
+
|
10
|
+
it "can tell if the rule apply to a certain file" do
|
11
|
+
subject.if {|f| f.applies}
|
12
|
+
|
13
|
+
file = mock(File, :applies => false)
|
14
|
+
subject.applies_for?(file).should be false
|
15
|
+
|
16
|
+
file = mock(File, :applies => true)
|
17
|
+
subject.applies_for?(file).should be true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "mixes the FileExtensions in to each yielded path" do
|
21
|
+
subject.if{|f| (class << f; self; end).included_modules.include?(BackupOrganizer::Extensions::FileExtensions).should be true}
|
22
|
+
file = mock(File, :applies => true)
|
23
|
+
subject.applies_for?(file)
|
24
|
+
end
|
25
|
+
end
|
data/spec/setup_spec.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe BackupOrganizer::Setup do
|
5
|
+
before do
|
6
|
+
path = Tempdir.new
|
7
|
+
@configuration = mock(BackupOrganizer::Configuration, :path => path, :directories => %w{daily weekly yearly}.map {|e| File.expand_path("./#{e}", path)})
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "creating the structure" do
|
11
|
+
describe "raises an error when" do
|
12
|
+
it "the path is not a directory" do
|
13
|
+
file = Tempfile.new('backup_organizer_fake_dir')
|
14
|
+
path = file.path
|
15
|
+
@configuration.stub(:path).and_return(path)
|
16
|
+
file.unlink
|
17
|
+
lambda {
|
18
|
+
BackupOrganizer::Setup.create_structure(@configuration)
|
19
|
+
}.should raise_error(BackupOrganizer::Error::InvalidPathError)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "the path is a file" do
|
23
|
+
file = Tempfile.new('backup_organizer_fake_dir')
|
24
|
+
path = file.path
|
25
|
+
@configuration.stub(:path).and_return(path)
|
26
|
+
lambda {
|
27
|
+
BackupOrganizer::Setup.create_structure(@configuration)
|
28
|
+
}.should raise_error(BackupOrganizer::Error::InvalidPathError)
|
29
|
+
file.unlink
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "results in having all the needed directories present" do
|
34
|
+
BackupOrganizer::Setup.create_structure(@configuration)
|
35
|
+
@configuration.directories.each do |necessary_directory|
|
36
|
+
File.directory?(necessary_directory).should be true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "leaves an existing structure intact" do
|
41
|
+
BackupOrganizer::Setup.create_structure(@configuration)
|
42
|
+
files = %w{some test files}
|
43
|
+
require 'backup_organizer/file_utils'
|
44
|
+
@configuration.directories.each do |directory|
|
45
|
+
files.each do |file|
|
46
|
+
BackupOrganizer::FileUtils.touch(File.expand_path("./#{file}", directory))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
BackupOrganizer::Setup.create_structure(@configuration)
|
51
|
+
|
52
|
+
@configuration.directories.each do |directory|
|
53
|
+
files.each do |file|
|
54
|
+
File.exist?(File.expand_path("./#{file}", directory)).should be true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
def temp_file_at(time, destination = nil)
|
2
|
+
path = Tempfile.new('backup_organizer_integration_spec').path
|
3
|
+
File.utime(time, time, path)
|
4
|
+
if destination
|
5
|
+
BackupOrganizer::FileUtils.mv path, destination
|
6
|
+
File.expand_path("./#{File.basename(path)}", destination)
|
7
|
+
else
|
8
|
+
path
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def files_in(path)
|
13
|
+
path = "#{File.expand_path('./', path)}/"
|
14
|
+
files = []
|
15
|
+
Find.find(path) {|file| files << file.gsub(/^#{Regexp.escape(path)}/, '')}
|
16
|
+
files.select {|e| !e.empty?}
|
17
|
+
end
|
18
|
+
|
19
|
+
class Fixnum
|
20
|
+
def minutes
|
21
|
+
self * 60
|
22
|
+
end
|
23
|
+
alias minute minutes
|
24
|
+
|
25
|
+
def hours
|
26
|
+
self * 60.minutes
|
27
|
+
end
|
28
|
+
alias hour hours
|
29
|
+
|
30
|
+
def days
|
31
|
+
self * 24.hours
|
32
|
+
end
|
33
|
+
alias day days
|
34
|
+
|
35
|
+
def years
|
36
|
+
self * 365.days
|
37
|
+
end
|
38
|
+
alias year years
|
39
|
+
|
40
|
+
def ago
|
41
|
+
Time.now - self
|
42
|
+
end
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: backup_organizer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- "Thorben Schr\xC3\xB6der"
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-01-24 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 7
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
version: "2"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: guard-rspec
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: ruby_gntp
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
description: Organizes backup files in folders in patterns that can be defined by the user.
|
63
|
+
email:
|
64
|
+
- stillepost@gmail.com
|
65
|
+
executables: []
|
66
|
+
|
67
|
+
extensions: []
|
68
|
+
|
69
|
+
extra_rdoc_files: []
|
70
|
+
|
71
|
+
files:
|
72
|
+
- .gitignore
|
73
|
+
- .rvmrc
|
74
|
+
- .travis.yml
|
75
|
+
- Gemfile
|
76
|
+
- Guardfile
|
77
|
+
- LICENSE
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- backup_organizer.gemspec
|
81
|
+
- lib/backup_organizer.rb
|
82
|
+
- lib/backup_organizer/configuration.rb
|
83
|
+
- lib/backup_organizer/error/base_error.rb
|
84
|
+
- lib/backup_organizer/error/configuration_error.rb
|
85
|
+
- lib/backup_organizer/error/error_with_custom_message.rb
|
86
|
+
- lib/backup_organizer/error/invalid_path_error.rb
|
87
|
+
- lib/backup_organizer/error/setup_error.rb
|
88
|
+
- lib/backup_organizer/extensions/file_extensions.rb
|
89
|
+
- lib/backup_organizer/file_age.rb
|
90
|
+
- lib/backup_organizer/file_mover.rb
|
91
|
+
- lib/backup_organizer/file_utils.rb
|
92
|
+
- lib/backup_organizer/pattern.rb
|
93
|
+
- lib/backup_organizer/rule.rb
|
94
|
+
- lib/backup_organizer/setup.rb
|
95
|
+
- lib/backup_organizer/version.rb
|
96
|
+
- spec/configuration_spec.rb
|
97
|
+
- spec/extensions/file_extensions_spec.rb
|
98
|
+
- spec/file_age_spec.rb
|
99
|
+
- spec/file_mover_spec.rb
|
100
|
+
- spec/file_utils_spec.rb
|
101
|
+
- spec/integration_spec.rb
|
102
|
+
- spec/pattern_spec.rb
|
103
|
+
- spec/rule_spec.rb
|
104
|
+
- spec/setup_spec.rb
|
105
|
+
- spec/spec_helper.rb
|
106
|
+
- spec/support/helpers.rb
|
107
|
+
- spec/support/temp_dir.rb
|
108
|
+
homepage: http://github.com/walski/backup_organizer
|
109
|
+
licenses: []
|
110
|
+
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
hash: 3
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
version: "0"
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
hash: 3
|
131
|
+
segments:
|
132
|
+
- 0
|
133
|
+
version: "0"
|
134
|
+
requirements: []
|
135
|
+
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 1.8.10
|
138
|
+
signing_key:
|
139
|
+
specification_version: 3
|
140
|
+
summary: If backups should only be kept around for a certain time and be stored sparsely (weekly, monthly, yearly, whatever) after that backup_organizer can do that for you.
|
141
|
+
test_files:
|
142
|
+
- spec/configuration_spec.rb
|
143
|
+
- spec/extensions/file_extensions_spec.rb
|
144
|
+
- spec/file_age_spec.rb
|
145
|
+
- spec/file_mover_spec.rb
|
146
|
+
- spec/file_utils_spec.rb
|
147
|
+
- spec/integration_spec.rb
|
148
|
+
- spec/pattern_spec.rb
|
149
|
+
- spec/rule_spec.rb
|
150
|
+
- spec/setup_spec.rb
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
- spec/support/helpers.rb
|
153
|
+
- spec/support/temp_dir.rb
|