qdumpfs 0.4.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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +23 -0
- data/LICENSE.txt +339 -0
- data/README.md +31 -0
- data/Rakefile +10 -0
- data/TODO.txt +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/build.cmd +14 -0
- data/build.sh +19 -0
- data/exe/qdumpfs +6 -0
- data/lib/qdumpfs.rb +540 -0
- data/lib/qdumpfs/option.rb +302 -0
- data/lib/qdumpfs/util.rb +283 -0
- data/lib/qdumpfs/version.rb +3 -0
- data/lib/qdumpfs/win32.rb +146 -0
- data/qdumpfs.gemspec +43 -0
- data/run2_qdumpfs.cmd +6 -0
- data/run_list.cmd +7 -0
- data/run_qdumpfs.cmd +5 -0
- data/run_qdumpfs.sh +4 -0
- data/run_sync.cmd +6 -0
- data/test_pdumpfs/data/bar +1 -0
- data/test_pdumpfs/data/baz/quux +1 -0
- data/test_pdumpfs/data/file +1 -0
- data/test_pdumpfs/data/foo +1 -0
- data/test_pdumpfs/data/secret/secret +1 -0
- data/test_pdumpfs/pdumpfs-test +86 -0
- metadata +120 -0
@@ -0,0 +1,302 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Qdumpfs
|
3
|
+
# 日毎のバックアップフォルダに対応
|
4
|
+
class BackupDir
|
5
|
+
def self.scan_backup_dirs(target_dir)
|
6
|
+
backup_dirs = []
|
7
|
+
Dir.glob("#{target_dir}/[0-9][0-9][0-9][0-9]/[0-1][0-9]/[0-3][0-9]").sort.each do |path|
|
8
|
+
if File.directory?(path) && path =~ /(\d\d\d\d)\/(\d\d)\/(\d\d)/
|
9
|
+
# puts "Backup dir: #{path}"
|
10
|
+
backup_dir = BackupDir.new
|
11
|
+
backup_dir.path = path
|
12
|
+
backup_dir.date = Date.new($1.to_i, $2.to_i, $3.to_i)
|
13
|
+
backup_dirs << backup_dir
|
14
|
+
end
|
15
|
+
end
|
16
|
+
backup_dirs
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.find(backup_dirs, from_date, to_date)
|
20
|
+
backup_dirs.select{|backup_dir| backup_dir.date >= from_date && backup_dir.date <= to_date}
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@keep = false
|
25
|
+
end
|
26
|
+
attr_accessor :path, :date, :keep
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
class NullLogger
|
31
|
+
def close
|
32
|
+
end
|
33
|
+
def print(msg)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class SimpleLogger
|
39
|
+
def initialize(filename)
|
40
|
+
@file = File.open(filename, "a")
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
@file.close
|
45
|
+
end
|
46
|
+
|
47
|
+
def print(msg)
|
48
|
+
@file.puts msg
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
class Error < StandardError
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
class NullMatcher
|
58
|
+
def initialize(options = {})
|
59
|
+
end
|
60
|
+
def exclude?(path)
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
class FileMatcher
|
67
|
+
def initialize(options = {})
|
68
|
+
@patterns = options[:patterns] || []
|
69
|
+
@globs = options[:globs] || []
|
70
|
+
@size = calc_size(options[:size])
|
71
|
+
end
|
72
|
+
|
73
|
+
def calc_size(size)
|
74
|
+
table = { "K" => 1, "M" => 2, "G" => 3, "T" => 4, "P" => 5 }
|
75
|
+
pattern = table.keys.join('')
|
76
|
+
case size
|
77
|
+
when nil
|
78
|
+
-1
|
79
|
+
when /^(\d+)([#{pattern}]?)$/i
|
80
|
+
num = Regexp.last_match[1].to_i
|
81
|
+
unit = Regexp.last_match[2]
|
82
|
+
num * 1024 ** (table[unit] or 0)
|
83
|
+
else
|
84
|
+
raise "Invalid size: #{size}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def exclude?(path)
|
89
|
+
stat = File.lstat(path)
|
90
|
+
if @size >= 0 and stat.file? and stat.size >= @size
|
91
|
+
return true
|
92
|
+
elsif @patterns.find {|pattern| pattern.match(path) }
|
93
|
+
return true
|
94
|
+
elsif stat.file? and
|
95
|
+
@globs.find {|glob| File.fnmatch(glob, File.basename(path)) }
|
96
|
+
return true
|
97
|
+
end
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
class Option
|
104
|
+
def initialize(opts, dirs)
|
105
|
+
@opts = opts
|
106
|
+
@dirs = dirs
|
107
|
+
@src = dirs[0] if dirs.size > 0
|
108
|
+
@dst = dirs[1] if dirs.size > 1
|
109
|
+
@cmd = @opts[:c] || 'backup'
|
110
|
+
|
111
|
+
# @logger = NullLogger.new
|
112
|
+
logfile = @opts[:logfile] || 'log.txt'
|
113
|
+
#ログディレクトリの作成
|
114
|
+
@logdir = File.expand_path('.')
|
115
|
+
Dir.mkdir(@logdir) unless FileTest.directory?(@logdir)
|
116
|
+
@logpath = File.join(@logdir, logfile)
|
117
|
+
@logger = SimpleLogger.new(@logpath)
|
118
|
+
|
119
|
+
verifyfile = 'verify.txt'
|
120
|
+
@verifypath = File.join(@logdir, verifyfile)
|
121
|
+
|
122
|
+
@matcher = NullMatcher.new
|
123
|
+
|
124
|
+
size = @opts[:es]
|
125
|
+
globs = @opts[:eg]
|
126
|
+
patterns = @opts[:ep]
|
127
|
+
if size || globs || patterns
|
128
|
+
@matcher = FileMatcher.new(:size => size, :globs => globs, :patterns => patterns)
|
129
|
+
end
|
130
|
+
|
131
|
+
# 同期用のオプションは日常使いのpdumpfs-cleanのオプションより期間短めに設定
|
132
|
+
@limit = @opts[:limit]
|
133
|
+
@keep_year = 100
|
134
|
+
@keep_month = 12
|
135
|
+
@keep_week = 12
|
136
|
+
@keep_day = 30
|
137
|
+
keep = @opts[:keep]
|
138
|
+
@keep_year = $1.to_i if keep =~ /(\d+)Y/
|
139
|
+
@keep_month = $1.to_i if keep =~ /(\d+)M/
|
140
|
+
@keep_week = $1.to_i if keep =~ /(\d+)W/
|
141
|
+
@keep_day = $1.to_i if keep =~ /(\d+)D/
|
142
|
+
@today = Date.today
|
143
|
+
end
|
144
|
+
attr_reader :dirs, :src, :dst, :cmd
|
145
|
+
attr_reader :keep_year, :keep_month, :keep_week, :keep_day
|
146
|
+
attr_reader :logdir, :logfile, :verifyfile
|
147
|
+
attr_reader :logger, :matcher, :reporter, :interval_proc
|
148
|
+
|
149
|
+
def report(type, filename)
|
150
|
+
if @opts[:v]
|
151
|
+
stat = File.stat(filename)
|
152
|
+
size = stat.size
|
153
|
+
format_size = convert_bytes(size)
|
154
|
+
msg = format_report_with_size(type, filename, size, format_size)
|
155
|
+
elsif @opts[:r]
|
156
|
+
if type =~ /^new_file/
|
157
|
+
stat = File.stat(filename)
|
158
|
+
size = stat.size
|
159
|
+
format_size = convert_bytes(size)
|
160
|
+
msg = format_report_with_size(type, filename, size, format_size)
|
161
|
+
end
|
162
|
+
else
|
163
|
+
# 何も指定されていない場合
|
164
|
+
if type == 'new_file'
|
165
|
+
msg = format_report(type, filename)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
log(msg)
|
169
|
+
end
|
170
|
+
|
171
|
+
def log(msg, console = true)
|
172
|
+
return if (msg.nil? || msg == '')
|
173
|
+
puts msg if console
|
174
|
+
@logger.print(msg)
|
175
|
+
end
|
176
|
+
|
177
|
+
def dry_run
|
178
|
+
@opts[:n]
|
179
|
+
end
|
180
|
+
|
181
|
+
def limit_sec
|
182
|
+
@limit.to_i * 3600
|
183
|
+
end
|
184
|
+
|
185
|
+
def validate_directory(dir)
|
186
|
+
if dir.nil? || !File.directory?(dir)
|
187
|
+
raise ArgumentError, "No such directory: #{dir}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def validate_directories(min_count)
|
192
|
+
@dirs.each do |dir|
|
193
|
+
validate_directory(dir)
|
194
|
+
end
|
195
|
+
if @dirs.size == 2 && windows?
|
196
|
+
# ディレクトリが2つだけ指定されている場合、コピー先はntfsである必要がある
|
197
|
+
unless ntfs?(dst)
|
198
|
+
fstype = get_filesystem_type(dst)
|
199
|
+
raise sprintf("only NTFS is supported but %s is %s.", dst, fstype)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
if @dirs.size < min_count
|
203
|
+
raise "#{min_count} directories required."
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def detect_keep_dirs(backup_dirs)
|
208
|
+
detect_year_keep_dirs(backup_dirs)
|
209
|
+
detect_month_keep_dirs(backup_dirs)
|
210
|
+
detect_week_keep_dirs(backup_dirs)
|
211
|
+
detect_day_keep_dirs(backup_dirs)
|
212
|
+
end
|
213
|
+
|
214
|
+
def open_verifyfile
|
215
|
+
if FileTest.file?(@verifypath)
|
216
|
+
File.unlink(@verifypath)
|
217
|
+
end
|
218
|
+
File.open(@verifypath, 'a')
|
219
|
+
end
|
220
|
+
|
221
|
+
def open_listfile
|
222
|
+
filename = File.join(@logdir, "list_" + @src.gsub(/[:\/]/, '_') + '.txt')
|
223
|
+
if FileTest.file?(filename)
|
224
|
+
File.unlink(filename)
|
225
|
+
end
|
226
|
+
File.open(filename, 'a')
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
def format_report(type, filename)
|
231
|
+
sprintf("%-12s %s\n", type, filename)
|
232
|
+
end
|
233
|
+
|
234
|
+
def format_report_with_size(type, filename, size, format_size)
|
235
|
+
sprintf("%s\t%s\t%d\t%s\n", type, filename, size, format_size)
|
236
|
+
end
|
237
|
+
|
238
|
+
def format_report_with_size_as_csv(type, filename, size, format_size)
|
239
|
+
sprintf("%s,%s,%d,%s\n", type, filename.encode('cp932', undef: :replace), size, format_size)
|
240
|
+
end
|
241
|
+
|
242
|
+
def format_report_as_csv(type, filename)
|
243
|
+
sprintf("%s,%s\n", type, filename.encode('cp932', undef: :replace))
|
244
|
+
end
|
245
|
+
|
246
|
+
def convert_bytes(bytes)
|
247
|
+
if bytes < 1024
|
248
|
+
sprintf("%dB", bytes)
|
249
|
+
elsif bytes < 1024 * 1000 # 1000kb
|
250
|
+
sprintf("%.1fKB", bytes.to_f / 1024)
|
251
|
+
elsif bytes < 1024 * 1024 * 1000 # 1000mb
|
252
|
+
sprintf("%.1fMB", bytes.to_f / 1024 / 1024)
|
253
|
+
else
|
254
|
+
sprintf("%.1fGB", bytes.to_f / 1024 / 1024 / 1024)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def keep_dirs(backup_dirs, num)
|
259
|
+
num.downto(0) do |i|
|
260
|
+
from_date, to_date = yield(i)
|
261
|
+
dirs = BackupDir.find(backup_dirs, from_date, to_date)
|
262
|
+
dirs[0].keep = true if dirs.size > 0
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def detect_year_keep_dirs(backup_dirs)
|
267
|
+
keep_dirs(backup_dirs, @keep_year) do |i|
|
268
|
+
from_date = Date.new(@today.year - i, 1, 1)
|
269
|
+
to_date = Date.new(@today.year - i, 12, 31)
|
270
|
+
[from_date, to_date]
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def detect_month_keep_dirs(backup_dirs)
|
275
|
+
keep_dirs(backup_dirs, @keep_month) do |i|
|
276
|
+
base_date = @today << i
|
277
|
+
from_date = Date.new(base_date.year, base_date.month, 1)
|
278
|
+
to_date = from_date >> 1
|
279
|
+
[from_date, to_date]
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def detect_week_keep_dirs(backup_dirs)
|
284
|
+
keep_dirs(backup_dirs, @keep_week) do |i|
|
285
|
+
base_date = @today - 7 * i
|
286
|
+
from_date = base_date - base_date.cwday # 1
|
287
|
+
to_date = from_date + 6
|
288
|
+
[from_date, to_date]
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def detect_day_keep_dirs(backup_dirs)
|
293
|
+
keep_dirs(backup_dirs, @keep_day) do |i|
|
294
|
+
base_date = @today - i
|
295
|
+
from_date = base_date
|
296
|
+
to_date = base_date
|
297
|
+
[from_date, to_date]
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
end
|
data/lib/qdumpfs/util.rb
ADDED
@@ -0,0 +1,283 @@
|
|
1
|
+
def wprintf(format, *args)
|
2
|
+
STDERR.printf("pdumpfs: " + format + "\n", *args)
|
3
|
+
end
|
4
|
+
|
5
|
+
#https://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby
|
6
|
+
# Cross-platform way of finding an executable in the $PATH.
|
7
|
+
#
|
8
|
+
# which('ruby') #=> /usr/bin/ruby
|
9
|
+
def which(cmd)
|
10
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
11
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
12
|
+
exts.each do |ext|
|
13
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
14
|
+
return exe if File.executable?(exe) && !File.directory?(exe)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
class File
|
21
|
+
def self.real_file?(path)
|
22
|
+
FileTest.file?(path) and not FileTest.symlink?(path)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.anything_exist?(path)
|
26
|
+
FileTest.exist?(path) or FileTest.symlink?(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.real_directory?(path)
|
30
|
+
FileTest.directory?(path) and not FileTest.symlink?(path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.force_symlink(src, dest)
|
34
|
+
begin
|
35
|
+
File.unlink(dest) if File.anything_exist?(dest)
|
36
|
+
File.symlink(src, dest)
|
37
|
+
rescue
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.force_link(src, dest)
|
42
|
+
File.unlink(dest) if File.anything_exist?(dest)
|
43
|
+
File.link(src, dest)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.readable_file?(path)
|
47
|
+
FileTest.file?(path) and FileTest.readable?(path)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.split_all(path)
|
51
|
+
parts = []
|
52
|
+
while true
|
53
|
+
dirname, basename = File.split(path)
|
54
|
+
break if path == dirname
|
55
|
+
parts.unshift(basename) unless basename == "."
|
56
|
+
path = dirname
|
57
|
+
end
|
58
|
+
return parts
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
module QdumpfsFind
|
64
|
+
def find(logger, *paths)
|
65
|
+
block_given? or return enum_for(__method__, *paths)
|
66
|
+
paths.collect!{|d|
|
67
|
+
raise Errno::ENOENT unless File.exist?(d);
|
68
|
+
d.dup
|
69
|
+
}
|
70
|
+
while file = paths.shift
|
71
|
+
catch(:prune) do
|
72
|
+
yield file.dup.taint
|
73
|
+
begin
|
74
|
+
s = File.lstat(file)
|
75
|
+
rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG => e
|
76
|
+
logger.print("File.lstat path=#{file} error=#{e.message}")
|
77
|
+
next
|
78
|
+
end
|
79
|
+
if s.directory? then
|
80
|
+
begin
|
81
|
+
fs = Dir.entries(file, :encoding=>'UTF-8')
|
82
|
+
rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG => e
|
83
|
+
logger.print("Dir.entries path=#{file} error=#{e.message}")
|
84
|
+
next
|
85
|
+
end
|
86
|
+
fs.sort!
|
87
|
+
fs.reverse_each {|f|
|
88
|
+
next if f == "." or f == ".."
|
89
|
+
f = File.join(file, f)
|
90
|
+
paths.unshift f.untaint
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def prune
|
98
|
+
throw :prune
|
99
|
+
end
|
100
|
+
|
101
|
+
module_function :find, :prune
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
module QdumpfsUtils
|
106
|
+
|
107
|
+
# We don't use File.copy for calling @interval_proc.
|
108
|
+
def copy_file(src, dest)
|
109
|
+
File.open(src, 'rb') {|r|
|
110
|
+
File.open(dest, 'wb') {|w|
|
111
|
+
block_size = (r.stat.blksize or 8192)
|
112
|
+
begin
|
113
|
+
i = 0
|
114
|
+
while true
|
115
|
+
block = r.sysread(block_size)
|
116
|
+
w.syswrite(block)
|
117
|
+
i += 1
|
118
|
+
@written_bytes += block.size
|
119
|
+
# @interval_proc.call if i % 10 == 0
|
120
|
+
end
|
121
|
+
rescue EOFError => e
|
122
|
+
# puts e.message, e.backtrace
|
123
|
+
end
|
124
|
+
}
|
125
|
+
}
|
126
|
+
unless FileTest.file?(dest)
|
127
|
+
raise "copy_file fails #{dest}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# incomplete substitute for cp -p
|
132
|
+
def copy(src, dest)
|
133
|
+
stat = File.stat(src)
|
134
|
+
copy_file(src, dest)
|
135
|
+
File.chmod(0200, dest) if windows?
|
136
|
+
File.utime(stat.atime, stat.mtime, dest)
|
137
|
+
File.chmod(stat.mode, dest) # not necessary. just to make sure
|
138
|
+
end
|
139
|
+
|
140
|
+
def convert_bytes(bytes)
|
141
|
+
if bytes < 1024
|
142
|
+
sprintf("%dB", bytes)
|
143
|
+
elsif bytes < 1024 * 1000 # 1000kb
|
144
|
+
sprintf("%.1fKB", bytes.to_f / 1024)
|
145
|
+
elsif bytes < 1024 * 1024 * 1000 # 1000mb
|
146
|
+
sprintf("%.1fMB", bytes.to_f / 1024 / 1024)
|
147
|
+
else
|
148
|
+
sprintf("%.1fGB", bytes.to_f / 1024 / 1024 / 1024)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def same_file?(f1, f2)
|
153
|
+
# File.real_file?(f1) and File.real_file?(f2) and
|
154
|
+
# File.size(f1) == File.size(f2) and File.mtime(f1) == File.mtime(f2)
|
155
|
+
real_file = File.real_file?(f1) and File.real_file?(f2)
|
156
|
+
same_size = File.size(f1) == File.size(f2)
|
157
|
+
|
158
|
+
# mtime1 = File.mtime(f1).strftime('%F %T.%N')
|
159
|
+
# mtime2 = File.mtime(f2).strftime('%F %T.%N')
|
160
|
+
same_mtime = File.mtime(f1).to_i == File.mtime(f2).to_i
|
161
|
+
# p "#{real_file} #{same_size} #{same_mtime}(#{mtime1}<=>#{mtime2})"
|
162
|
+
real_file and same_size and same_mtime
|
163
|
+
end
|
164
|
+
|
165
|
+
def detect_type(src, latest = nil)
|
166
|
+
type = "unsupported"
|
167
|
+
if File.real_directory?(src)
|
168
|
+
type = "directory"
|
169
|
+
else
|
170
|
+
if latest and File.real_file?(latest)
|
171
|
+
case File.ftype(src)
|
172
|
+
when "file"
|
173
|
+
same_file = same_file?(src, latest)
|
174
|
+
# p "same_file? #{src} #{latest} result=#{same_file}"
|
175
|
+
if same_file
|
176
|
+
type = "unchanged"
|
177
|
+
else
|
178
|
+
type = "updated"
|
179
|
+
end
|
180
|
+
when "link"
|
181
|
+
# the latest backup file is a real file but the
|
182
|
+
# current source file is changed to symlink.
|
183
|
+
type = "symlink"
|
184
|
+
end
|
185
|
+
else
|
186
|
+
case File.ftype(src)
|
187
|
+
when "file"
|
188
|
+
type = "new_file"
|
189
|
+
when "link"
|
190
|
+
type = "symlink"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
return type
|
195
|
+
end
|
196
|
+
|
197
|
+
def fmt(time)
|
198
|
+
time.strftime('%Y/%m/%d %H:%M:%S')
|
199
|
+
end
|
200
|
+
|
201
|
+
def chown_if_root(type, src, today)
|
202
|
+
return unless Process.uid == 0 and type != "unsupported"
|
203
|
+
if type == "symlink"
|
204
|
+
if File.respond_to?(:lchown)
|
205
|
+
stat = File.lstat(src)
|
206
|
+
File.lchown(stat.uid, stat.gid, today)
|
207
|
+
end
|
208
|
+
else
|
209
|
+
stat = File.stat(src)
|
210
|
+
File.chown(stat.uid, stat.gid, today)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def restore_dir_attributes(dirs)
|
215
|
+
dirs.each {|dir, stat|
|
216
|
+
File.utime(stat.atime, stat.mtime, dir)
|
217
|
+
File.chmod(stat.mode, dir)
|
218
|
+
}
|
219
|
+
end
|
220
|
+
|
221
|
+
def make_relative_path(path, base)
|
222
|
+
pattern = sprintf("^%s%s?", Regexp.quote(base), File::SEPARATOR)
|
223
|
+
path.sub(Regexp.new(pattern), "")
|
224
|
+
end
|
225
|
+
|
226
|
+
def fputs(file, msg)
|
227
|
+
puts msg
|
228
|
+
file.puts msg
|
229
|
+
end
|
230
|
+
|
231
|
+
def time_diff(start_time, end_time)
|
232
|
+
seconds_diff = (start_time - end_time).to_i.abs
|
233
|
+
|
234
|
+
hours = seconds_diff / 3600
|
235
|
+
seconds_diff -= hours * 3600
|
236
|
+
|
237
|
+
minutes = seconds_diff / 60
|
238
|
+
seconds_diff -= minutes * 60
|
239
|
+
|
240
|
+
seconds = seconds_diff
|
241
|
+
|
242
|
+
'%02d:%02d:%02d' % [hours, minutes, seconds]
|
243
|
+
end
|
244
|
+
|
245
|
+
def create_latest_symlink(dest, today)
|
246
|
+
# 最新のバックアップに"latest"というシンボリックリンクをはる(Windowsだと動かない)
|
247
|
+
latest_day = File.dirname(make_relative_path(today, dest))
|
248
|
+
latest_symlink = File.join(dest, "latest")
|
249
|
+
# puts "force_symlink #{latest_day} #{latest_symlink}"
|
250
|
+
File.force_symlink(latest_day, latest_symlink)
|
251
|
+
end
|
252
|
+
|
253
|
+
def same_directory?(src, dest)
|
254
|
+
src = File.expand_path(src)
|
255
|
+
dest = File.expand_path(dest)
|
256
|
+
return src == dest
|
257
|
+
end
|
258
|
+
|
259
|
+
def sub_directory?(src, dest)
|
260
|
+
src = File.expand_path(src)
|
261
|
+
dest = File.expand_path(dest)
|
262
|
+
src += File::SEPARATOR unless /#{File::SEPARATOR}$/.match(src)
|
263
|
+
return /^#{Regexp.quote(src)}/.match(dest)
|
264
|
+
end
|
265
|
+
|
266
|
+
def datedir(date)
|
267
|
+
s = File::SEPARATOR
|
268
|
+
sprintf "%d%s%02d%s%02d", date.year, s, date.month, s, date.day
|
269
|
+
end
|
270
|
+
|
271
|
+
def past_date?(year, month, day, t)
|
272
|
+
([year, month, day] <=> [t.year, t.month, t.day]) < 0
|
273
|
+
end
|
274
|
+
|
275
|
+
def to_win_path(path)
|
276
|
+
path.gsub(/\//, '\\')
|
277
|
+
end
|
278
|
+
|
279
|
+
def to_unix_path(path)
|
280
|
+
path.gsub(/\\/, '/')
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|