rb.rotate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +20 -0
- data/README.md +81 -0
- data/Rakefile +41 -0
- data/VERSION +1 -0
- data/bin/rb.rotate +12 -0
- data/lib/rb.rotate.rb +30 -0
- data/lib/rb.rotate/configuration.rb +312 -0
- data/lib/rb.rotate/directory.rb +201 -0
- data/lib/rb.rotate/dispatcher.rb +112 -0
- data/lib/rb.rotate/file.rb +174 -0
- data/lib/rb.rotate/hook.rb +135 -0
- data/lib/rb.rotate/install/defaults.yaml.initial +25 -0
- data/lib/rb.rotate/install/rotate.yaml.initial +349 -0
- data/lib/rb.rotate/log.rb +80 -0
- data/lib/rb.rotate/mail.rb +40 -0
- data/lib/rb.rotate/reader.rb +94 -0
- data/lib/rb.rotate/state.rb +211 -0
- data/lib/rb.rotate/state/archive.rb +109 -0
- data/lib/rb.rotate/state/file.rb +139 -0
- data/lib/rb.rotate/storage.rb +208 -0
- data/lib/rb.rotate/storage/entry.rb +120 -0
- data/lib/rb.rotate/storage/item.rb +415 -0
- data/rb.rotate.gemspec +85 -0
- metadata +153 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "rb.rotate/state"
|
3
|
+
|
4
|
+
module RbRotate
|
5
|
+
module StateModule
|
6
|
+
|
7
|
+
##
|
8
|
+
# Represents file state record.
|
9
|
+
#
|
10
|
+
|
11
|
+
class File
|
12
|
+
|
13
|
+
##
|
14
|
+
# Holds the file data.
|
15
|
+
#
|
16
|
+
|
17
|
+
@data
|
18
|
+
|
19
|
+
##
|
20
|
+
# Holds path of the appropriate file.
|
21
|
+
#
|
22
|
+
|
23
|
+
@path
|
24
|
+
|
25
|
+
##
|
26
|
+
# Constructor.
|
27
|
+
#
|
28
|
+
|
29
|
+
def initialize(path, data)
|
30
|
+
@path = path
|
31
|
+
@data = data
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Indicates tate record for file exists.
|
36
|
+
#
|
37
|
+
|
38
|
+
def exists?
|
39
|
+
not @data.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Returns last archival date.
|
44
|
+
#
|
45
|
+
|
46
|
+
def date
|
47
|
+
@data[:date]
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Touches date to current date.
|
52
|
+
#
|
53
|
+
|
54
|
+
def touch!
|
55
|
+
@data[:date] = Time::now
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Returns extension.
|
60
|
+
#
|
61
|
+
|
62
|
+
def extension
|
63
|
+
@data[:filename][:extension]
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Returns basename.
|
68
|
+
#
|
69
|
+
|
70
|
+
def name
|
71
|
+
@data[:filename][:name]
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Returns items list.
|
76
|
+
#
|
77
|
+
|
78
|
+
def items
|
79
|
+
@data[:items]
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Returns directory specification.
|
84
|
+
#
|
85
|
+
|
86
|
+
def directory
|
87
|
+
if @data.has_key? :directory
|
88
|
+
@data[:directory].to_sym
|
89
|
+
else
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Sets items list.
|
96
|
+
#
|
97
|
+
|
98
|
+
def items=(value)
|
99
|
+
@data[:items].replace(value)
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Creates the state record.
|
104
|
+
#
|
105
|
+
|
106
|
+
def create(file)
|
107
|
+
extension = ::File.extname(file.path)[1..-1]
|
108
|
+
if extension.nil?
|
109
|
+
extension = nil
|
110
|
+
cut = 0..-1
|
111
|
+
else
|
112
|
+
cut = 0..-2
|
113
|
+
end
|
114
|
+
|
115
|
+
new = {
|
116
|
+
:date => Time::now,
|
117
|
+
:items => { },
|
118
|
+
:directory => file.directory.identifier,
|
119
|
+
:filename => {
|
120
|
+
:name => ::File.basename(file.path, extension.to_s)[cut],
|
121
|
+
:extension => extension
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
@data.replace(new)
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Destroys the state record.
|
130
|
+
#
|
131
|
+
|
132
|
+
def destroy!
|
133
|
+
State::files.delete(@path.to_sym)
|
134
|
+
@data = nil
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "rb.rotate/storage/entry"
|
4
|
+
require "rb.rotate/file"
|
5
|
+
require "rb.rotate/configuration"
|
6
|
+
|
7
|
+
module RbRotate
|
8
|
+
|
9
|
+
##
|
10
|
+
# Represents storage for archived files.
|
11
|
+
#
|
12
|
+
|
13
|
+
class Storage
|
14
|
+
|
15
|
+
##
|
16
|
+
# Directory for which storage is aimed.
|
17
|
+
#
|
18
|
+
|
19
|
+
@directory
|
20
|
+
attr_reader :directory
|
21
|
+
|
22
|
+
##
|
23
|
+
# Constructor.
|
24
|
+
#
|
25
|
+
|
26
|
+
def initialize(directory)
|
27
|
+
@directory = directory
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Alias for new.
|
32
|
+
#
|
33
|
+
|
34
|
+
def self.get(directory)
|
35
|
+
self::new(directory)
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Creates storage according to file and puts it to it.
|
40
|
+
#
|
41
|
+
|
42
|
+
def self.put(file)
|
43
|
+
self::get(file.directory).put(file)
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Puts file to storage.
|
48
|
+
#
|
49
|
+
|
50
|
+
def put(file)
|
51
|
+
self.do_actions! file
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Indicates, storage is compressed.
|
56
|
+
#
|
57
|
+
|
58
|
+
def compressed?
|
59
|
+
self.directory.compressable?
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Runs file actions.
|
64
|
+
#
|
65
|
+
|
66
|
+
def do_actions!(file)
|
67
|
+
entry = StorageModule::Entry::new(self, file)
|
68
|
+
|
69
|
+
# Loads them
|
70
|
+
actions = @directory.configuration[:action].split("+")
|
71
|
+
actions.map! do |i|
|
72
|
+
i.strip!
|
73
|
+
k, v = i.split(":", 2)
|
74
|
+
[k.to_sym, v]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Does them
|
78
|
+
variables = { }
|
79
|
+
|
80
|
+
actions.each do |action, arguments|
|
81
|
+
case action
|
82
|
+
when :move, :copy, :append
|
83
|
+
variables[:filepath] = entry.put! action
|
84
|
+
when :remove
|
85
|
+
variables[:filepath] = file.remove!
|
86
|
+
when :create, :truncate
|
87
|
+
variables[:filepath] = file.create!
|
88
|
+
when :mail
|
89
|
+
variables[:filepath] = entry.mail! arguments
|
90
|
+
when :hook
|
91
|
+
name, arguments = arguments.split(":", 2)
|
92
|
+
variables.merge! Hook::new(name.to_sym, arguments, variables).run!
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Cleanups expired items from the storage.
|
99
|
+
#
|
100
|
+
|
101
|
+
def cleanup!
|
102
|
+
self.each_entry do |entry|
|
103
|
+
entry.cleanup!
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Returns item identifier of the storage.
|
109
|
+
#
|
110
|
+
|
111
|
+
def item_identifier
|
112
|
+
self.directory.configuration[:identifier]
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Indicates numeric identifier.
|
117
|
+
#
|
118
|
+
|
119
|
+
def numeric_identifier?
|
120
|
+
self.item_identifier.to_sym == :numeric
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Removes orphans.
|
125
|
+
#
|
126
|
+
|
127
|
+
def self.remove_orphans!
|
128
|
+
self::each_entry do |entry|
|
129
|
+
items_count = 0
|
130
|
+
|
131
|
+
entry.each_item do |item|
|
132
|
+
if item.expired?
|
133
|
+
item.remove!
|
134
|
+
elsif not item.exists?
|
135
|
+
item.unregister!
|
136
|
+
else
|
137
|
+
items_count += 1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
file = entry.file
|
142
|
+
if (not file.exists?) and (items_count <= 0)
|
143
|
+
file.state.destroy!
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
##
|
149
|
+
# Traverses through each item in current storage.
|
150
|
+
#
|
151
|
+
|
152
|
+
def each_item(&block)
|
153
|
+
self.each_entry do |entry|
|
154
|
+
entry.each_item(&block)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# Traverses through all entries of this directory storage.
|
160
|
+
#
|
161
|
+
|
162
|
+
def each_entry
|
163
|
+
State::each_file do |path, state|
|
164
|
+
if state.directory == self.directory.identifier
|
165
|
+
file = File::new(path)
|
166
|
+
entry = StorageModule::Entry::new(self, file)
|
167
|
+
|
168
|
+
yield entry
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# Traverses through all items in global storage.
|
175
|
+
#
|
176
|
+
|
177
|
+
def self.each_item(&block)
|
178
|
+
self.each_entry do |entry|
|
179
|
+
entry.each_item(&block)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
##
|
184
|
+
# Traverses through all entries.
|
185
|
+
#
|
186
|
+
|
187
|
+
def self.each_entry
|
188
|
+
State::each_file do |path, state|
|
189
|
+
file = File::new(path)
|
190
|
+
storage = self::new(file.directory)
|
191
|
+
entry = StorageModule::Entry::new(storage, file)
|
192
|
+
|
193
|
+
yield entry
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
##
|
198
|
+
# Traverses through all directories in storage.
|
199
|
+
#
|
200
|
+
|
201
|
+
def self.each_directory
|
202
|
+
Configuration::each_directory do |directory|
|
203
|
+
yield self::get(directory)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "rb.rotate/storage/item"
|
4
|
+
require "rb.rotate/mail"
|
5
|
+
|
6
|
+
module RbRotate
|
7
|
+
module StorageModule
|
8
|
+
|
9
|
+
##
|
10
|
+
# Represents an entry of the storage.
|
11
|
+
# Entry is archived items of one file.
|
12
|
+
#
|
13
|
+
|
14
|
+
class Entry
|
15
|
+
|
16
|
+
##
|
17
|
+
# Holds file of the entry.
|
18
|
+
#
|
19
|
+
|
20
|
+
@file
|
21
|
+
attr_reader :file
|
22
|
+
|
23
|
+
##
|
24
|
+
# Holds parent storage.
|
25
|
+
#
|
26
|
+
|
27
|
+
@storage
|
28
|
+
attr_reader :storage
|
29
|
+
|
30
|
+
##
|
31
|
+
# Constructor.
|
32
|
+
#
|
33
|
+
|
34
|
+
def initialize(storage, file)
|
35
|
+
@storage = storage
|
36
|
+
@file = file
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Puts current version of the file to items.
|
41
|
+
#
|
42
|
+
|
43
|
+
def put!(method)
|
44
|
+
|
45
|
+
# If necessary, creates the state record
|
46
|
+
if not self.file.state.exists?
|
47
|
+
self.file.state.create(@file)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Rotates other items
|
51
|
+
new_list = { }
|
52
|
+
self.each_item do |item|
|
53
|
+
if item.exists?
|
54
|
+
item.rotate!
|
55
|
+
new_list[item.identifier] = item.path
|
56
|
+
else
|
57
|
+
item.unregister!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Puts new item
|
62
|
+
item = Item::new(self).allocate(method)
|
63
|
+
new_list[item.identifier] = item.path
|
64
|
+
|
65
|
+
self.file.state.touch!
|
66
|
+
self.file.state.items = new_list
|
67
|
+
|
68
|
+
return self.file.path
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Cleanups the items.
|
73
|
+
#
|
74
|
+
|
75
|
+
def cleanup!
|
76
|
+
self.each_item do |item|
|
77
|
+
if item.expired?
|
78
|
+
item.remove!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Mail file to specified address.
|
85
|
+
#
|
86
|
+
|
87
|
+
def mail!(to)
|
88
|
+
if to.nil?
|
89
|
+
to = @storage.directory.configuration[:mail]
|
90
|
+
end
|
91
|
+
if to.nil?
|
92
|
+
raise Exception("No e-mail address specified for sending log to.")
|
93
|
+
end
|
94
|
+
|
95
|
+
require "etc"
|
96
|
+
require "socket"
|
97
|
+
|
98
|
+
Mail::send(
|
99
|
+
:from => Etc.getlogin.dup << "@" << Socket.gethostname,
|
100
|
+
:to => to,
|
101
|
+
:subject => Socket.gethostname.dup << " : log : " << self.file.path,
|
102
|
+
:body => ::File.read(self.file.path)
|
103
|
+
)
|
104
|
+
|
105
|
+
return self.file.path
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Traverses through all items.
|
110
|
+
#
|
111
|
+
|
112
|
+
def each_item
|
113
|
+
self.file.state.items.each_pair do |identifier, path|
|
114
|
+
yield Item::new(self, identifier, path)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|