paparazzi 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +6 -6
- data/lib/paparazzi/camera.rb +43 -37
- data/lib/paparazzi/version.rb +1 -1
- data/test/unit/camera_test.rb +15 -2
- metadata +3 -3
data/README.md
CHANGED
@@ -39,14 +39,14 @@ Create a ruby script that you'll run hourly from a cron.
|
|
39
39
|
Available Settings
|
40
40
|
------------------
|
41
41
|
|
42
|
-
* `:source`
|
42
|
+
* `:source` **required** The source folder to be backed up. Trailing '/' recommended. See rsync manpage
|
43
43
|
for explanation of trailing '/'
|
44
|
-
* `:destination`
|
44
|
+
* `:destination` **required** The destination folder for backups to be written to, preferably on a different
|
45
45
|
physical drive.
|
46
|
-
* `:
|
47
|
-
default: {:hourly => 24, :daily => 7, :weekly => 5, :monthly => 12, :yearly => 9999}
|
48
|
-
* `:rsync_flags`
|
49
|
-
whatever you add. The author suggests considering
|
46
|
+
* `:intervals` A hash of snapshot intervals and number of snapshots of each to keep before purging.
|
47
|
+
default: `{:hourly => 24, :daily => 7, :weekly => 5, :monthly => 12, :yearly => 9999}`
|
48
|
+
* `:rsync_flags` Additional flags to pass to rsync. Paparazzi uses `-aq`, `--delete`, & `--link_dest`, plus
|
49
|
+
whatever you add. The author suggests considering `-L` and `--exclude`.
|
50
50
|
|
51
51
|
|
52
52
|
Supported Operating Systems
|
data/lib/paparazzi/camera.rb
CHANGED
@@ -8,7 +8,7 @@ module Paparazzi
|
|
8
8
|
REQUIRED_SETTINGS = [:source,:destination]
|
9
9
|
|
10
10
|
class << self
|
11
|
-
attr_accessor :source, :destination, :rsync_flags, :
|
11
|
+
attr_accessor :source, :destination, :rsync_flags, :intervals
|
12
12
|
|
13
13
|
def trigger(settings = {})
|
14
14
|
validate_and_cache_settings(settings)
|
@@ -25,7 +25,7 @@ module Paparazzi
|
|
25
25
|
#######
|
26
26
|
|
27
27
|
def validate_and_cache_settings(settings)
|
28
|
-
[:source,:destination,:rsync_flags,:reserves].each do |setting_name|
|
28
|
+
[:source,:destination,:rsync_flags,:intervals,:reserves].each do |setting_name|
|
29
29
|
if REQUIRED_SETTINGS.include?(setting_name) and settings[setting_name].nil?
|
30
30
|
raise MissingSettingError, "#{setting_name} is required"
|
31
31
|
else
|
@@ -42,21 +42,21 @@ module Paparazzi
|
|
42
42
|
|
43
43
|
def setup
|
44
44
|
@previous_snapshot_name = {}
|
45
|
-
|
46
|
-
Dir.mkdir(destination(
|
45
|
+
interval_names.each do |interval_name|
|
46
|
+
Dir.mkdir(destination(interval_name)) unless File.exists?(destination(interval_name)) && File.directory?(destination(interval_name))
|
47
47
|
|
48
|
-
full_path = Dir[destination(
|
49
|
-
@previous_snapshot_name[
|
48
|
+
full_path = Dir[destination(interval_name)+'/*'].sort{|a,b| File.ctime(b) <=> File.ctime(a) }.first
|
49
|
+
@previous_snapshot_name[interval_name] = full_path ? File.basename(full_path) : ''
|
50
50
|
end
|
51
51
|
|
52
|
-
if @previous_snapshot_name[
|
53
|
-
File.rename(previous_snapshot(
|
54
|
-
@previous_snapshot_name[
|
52
|
+
if @previous_snapshot_name[interval_names.first] != last_successful_snapshot and !last_successful_snapshot.nil? and File.exists?(destination(interval_names.first) + '/' + last_successful_snapshot)
|
53
|
+
File.rename(previous_snapshot(interval_names.first), current_snapshot(interval_names.first))
|
54
|
+
@previous_snapshot_name[interval_names.first] = last_successful_snapshot
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
def destination(
|
59
|
-
|
58
|
+
def destination(interval_name = nil)
|
59
|
+
interval_name.nil? ? @destination : "#{@destination}/#{interval_name}"
|
60
60
|
end
|
61
61
|
|
62
62
|
def last_successful_snapshot
|
@@ -70,35 +70,34 @@ module Paparazzi
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def purge_old_snapshots
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
full_path = Dir[destination(frequency)+'/*'].sort{|a,b| File.ctime(a) <=> File.ctime(b) }.first
|
73
|
+
interval_names.each do |interval_name|
|
74
|
+
while Dir[destination(interval_name)+'/*'].size > (intervals[interval_name]-1)
|
75
|
+
full_path = Dir[destination(interval_name)+'/*'].sort{|a,b| File.ctime(a) <=> File.ctime(b) }.first
|
77
76
|
FileUtils.rm_rf(full_path)
|
78
77
|
end
|
79
78
|
end
|
80
79
|
end
|
81
80
|
|
82
81
|
def make_snapshots
|
83
|
-
|
84
|
-
Dir.mkdir(current_snapshot(
|
85
|
-
if
|
86
|
-
system 'rsync', *(['-aq', '--delete'] + rsync_flags + [source, current_snapshot(
|
87
|
-
self.last_successful_snapshot = current_snapshot_name(
|
88
|
-
elsif previous_snapshot_name(
|
89
|
-
system 'rsync', *(['-aq', '--delete', "--link-dest=#{link_destination(
|
90
|
-
self.last_successful_snapshot = current_snapshot_name(
|
82
|
+
interval_names.each do |interval_name|
|
83
|
+
Dir.mkdir(current_snapshot(interval_name)) unless File.exists?(current_snapshot(interval_name))
|
84
|
+
if interval_name == interval_names.first and previous_snapshot_name(interval_name) == ''
|
85
|
+
system 'rsync', *(['-aq', '--delete'] + rsync_flags + [source, current_snapshot(interval_name)])
|
86
|
+
self.last_successful_snapshot = current_snapshot_name(interval_name)
|
87
|
+
elsif previous_snapshot_name(interval_name) != current_snapshot_name(interval_name)
|
88
|
+
system 'rsync', *(['-aq', '--delete', "--link-dest=#{link_destination(interval_name)}"] + rsync_flags + [source, current_snapshot(interval_name)])
|
89
|
+
self.last_successful_snapshot = current_snapshot_name(interval_names.first)
|
91
90
|
end
|
92
91
|
end
|
93
92
|
end
|
94
93
|
|
95
|
-
def current_snapshot(
|
96
|
-
destination(
|
94
|
+
def current_snapshot(interval_name)
|
95
|
+
destination(interval_name) + '/' + current_snapshot_name(interval_name)
|
97
96
|
end
|
98
97
|
|
99
|
-
def current_snapshot_name(
|
98
|
+
def current_snapshot_name(interval_name)
|
100
99
|
@start_time ||= Time.now #lock in time so that all results stay consistent over long runs
|
101
|
-
case
|
100
|
+
case interval_name
|
102
101
|
when :hourly then @start_time.strftime('%Y-%m-%d.%H')
|
103
102
|
when :daily then @start_time.strftime('%Y-%m-%d')
|
104
103
|
when :weekly then sprintf("%04d-%02d-week-%02d", @start_time.year, @start_time.month, (@start_time.day/7))
|
@@ -107,28 +106,35 @@ module Paparazzi
|
|
107
106
|
end
|
108
107
|
end
|
109
108
|
|
110
|
-
def previous_snapshot(
|
111
|
-
destination(
|
109
|
+
def previous_snapshot(interval_name)
|
110
|
+
destination(interval_name) + '/' + previous_snapshot_name(interval_name)
|
112
111
|
end
|
113
112
|
|
114
|
-
def previous_snapshot_name(
|
115
|
-
@previous_snapshot_name[
|
113
|
+
def previous_snapshot_name(interval_name)
|
114
|
+
@previous_snapshot_name[interval_name]
|
116
115
|
end
|
117
116
|
|
118
|
-
def link_destination(
|
119
|
-
|
117
|
+
def link_destination(interval_name)
|
118
|
+
interval_name == interval_names.first ? "../#{previous_snapshot_name(interval_names.first)}" : "../../#{interval_names.first}/#{current_snapshot_name(interval_names.first)}"
|
120
119
|
end
|
121
120
|
|
122
|
-
def
|
123
|
-
@
|
121
|
+
def intervals
|
122
|
+
@intervals ||= {:hourly => 24, :daily => 7, :weekly => 5, :monthly => 12, :yearly => 9999}
|
124
123
|
end
|
125
124
|
|
126
|
-
def
|
127
|
-
|
125
|
+
def interval_names
|
126
|
+
intervals.keys.select{ |key| intervals[key] > 0 }.sort{ |a,b|
|
128
127
|
[:hourly,:daily,:weekly,:monthly,:yearly].find_index(a) <=> [:hourly,:daily,:weekly,:monthly,:yearly].find_index(b)
|
129
128
|
}
|
130
129
|
end
|
131
130
|
|
131
|
+
# provide backwards compatability with silly setting name
|
132
|
+
def reserves=(x)
|
133
|
+
if x.is_a?(Hash)
|
134
|
+
$stderr.puts ':reserves is deprecated. Please use :intervals instead.'
|
135
|
+
self.intervals=x
|
136
|
+
end
|
137
|
+
end
|
132
138
|
end
|
133
139
|
end
|
134
140
|
|
data/lib/paparazzi/version.rb
CHANGED
data/test/unit/camera_test.rb
CHANGED
@@ -107,11 +107,24 @@ class CameraTest < Test::Unit::TestCase
|
|
107
107
|
|
108
108
|
def test_should_not_make_un_requested_frequency_snapshots
|
109
109
|
my_settings = default_test_settings
|
110
|
-
my_settings[:
|
110
|
+
my_settings[:intervals][:hourly] = 0
|
111
111
|
Paparazzi::Camera.trigger(my_settings)
|
112
112
|
assert(!File.exists?("#{destination}/hourly"))
|
113
113
|
end
|
114
114
|
|
115
|
+
def test_should_be_backwards_compatible_with_deprecated_config_option
|
116
|
+
original_stderr, $stderr = $stderr, StringIO.new
|
117
|
+
my_settings = default_test_settings
|
118
|
+
my_settings[:reserves] = my_settings[:intervals]
|
119
|
+
my_settings[:reserves][:hourly] = 1
|
120
|
+
my_settings.delete(:intervals)
|
121
|
+
Paparazzi::Camera.trigger(my_settings)
|
122
|
+
assert_equal(my_settings[:reserves],Paparazzi::Camera.instance_variable_get('@intervals'))
|
123
|
+
assert_equal(':reserves is deprecated. Please use :intervals instead.',$stderr.string.chomp)
|
124
|
+
ensure
|
125
|
+
$stderr = original_stderr
|
126
|
+
end
|
127
|
+
|
115
128
|
#######
|
116
129
|
private
|
117
130
|
#######
|
@@ -124,7 +137,7 @@ class CameraTest < Test::Unit::TestCase
|
|
124
137
|
{
|
125
138
|
:source => "#{File.expand_path('../../source', __FILE__)}/",
|
126
139
|
:destination => destination,
|
127
|
-
:
|
140
|
+
:intervals => {:hourly => 24, :daily => 7, :weekly => 5, :monthly => 12, :yearly => 9999},
|
128
141
|
:rsync_flags => '-L --exclude test.exclude'
|
129
142
|
|
130
143
|
}
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
8
|
+
- 1
|
9
|
+
version: 0.2.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Jonathan S. Garvin
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-05-
|
17
|
+
date: 2011-05-28 00:00:00 -06:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|