qdumpfs 0.7.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d4d28c42fc54a4ba9ba806aa57c056774889663cfa011d1cb8ff6d259a3368b
4
- data.tar.gz: fcfa95f0abdbaf1a8b571cf088fa83811e1984f4bd7bcd1283e68bdad3ce894b
3
+ metadata.gz: d0d2013cd5b52dcc99390e498ce0f749b3bf4d74d5229f734606b7b8a47b221f
4
+ data.tar.gz: e6f9d32055e67cc2b8beed5354befbf52f02ff06448cfa827673fc46a771bda1
5
5
  SHA512:
6
- metadata.gz: d1af10ef6521b9f775a538dc666e06f4cc4222131413144c8eacd59d795242890db306411e34076b041ac356b0a2e6a0c74c50cf221e01550caf0b18260bd738
7
- data.tar.gz: 68351bac00ffe057beb25433f5d158c74814a2a20efa43cd15a8a608cc800f5655b6f41be2b1292e8ebc1b7b63e43f01add315daaa3503d047f4139e7dbf4848
6
+ metadata.gz: eaed232b7e0803f065d6dde3373745bc7459b7c006ad5f20b2ddeb1431d5adc473c35df9045a913c0fcfd524a92f5d0319fed33e5fa2f645607a5bb0488d1d73
7
+ data.tar.gz: e434c372a596e239aa97564b4820e5d3c0fb31802f33c9f4b37d031009811819268893462f48c9c3e25728f2302cc56fcd9c5c990ba869dc209edeb1171d0cd8
data/.gitignore CHANGED
@@ -16,3 +16,4 @@ test*.cmd
16
16
  /data/to2
17
17
 
18
18
 
19
+ /sync_d.cmd
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- qdumpfs (0.7.0)
4
+ qdumpfs (1.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- minitest (5.14.2)
10
- rake (13.0.1)
9
+ minitest (5.14.4)
10
+ rake (13.0.3)
11
11
 
12
12
  PLATFORMS
13
13
  ruby
data/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  # Qdumpfs
2
2
 
3
- qdumpfs is a modified version of pdumpfs.
3
+ pdumpfsの個人的改良版です。
4
4
 
5
- ## Installation
5
+ Gem化して最近のバージョンのバージョンのRubyに対応。コマンドの拡張などを行っています。
6
6
 
7
- gem install qdumpfs
7
+ ## インストール
8
8
 
9
- ## Usage
9
+ gem install qdumpfs
10
10
 
11
+ ## 使用方法
11
12
 
12
13
  ```
13
14
  Usage: qdumpfs [options] <source> <dest>
@@ -19,48 +20,53 @@ Options
19
20
  -e, --exclude=PATTERN exclude files/directories matching PATTERN
20
21
  -s, --exclude-by-size=SIZE exclude files larger than SIZE
21
22
  -w, --exclude-by-glob=GLOB exclude files matching GLOB
22
- -c, --command=COMMAND backup|sync|list|expire|verify|test
23
+ -c, --command=COMMAND backup|sync|list|expire|verify|delete
23
24
  -l, --limit=HOURS limit hours
24
25
  -k, --keep=KEEPARG ex: --keep 100Y12M12W30D (100years, 12months, 12weeks, 30days, default)
25
26
  ```
26
27
 
27
- ## Example
28
+ ## 実行例
29
+
30
+
31
+ バックアップを実行する場合。
28
32
 
29
- Backup your home directory, run the following command.
30
33
  ```
31
34
  qdumpfs /home/foo /backup
32
35
  ```
33
36
 
34
- You can specify command option(default is "backup").
37
+ "--command backup"オプションを明示することもできます。
38
+
35
39
  ```
36
40
  qdumpfs --command=backup /home/foo /backup
37
41
  ```
38
42
 
39
- Sync two backup directories.
43
+ "--command sync"でバックアップフォルダを同期できます。
40
44
  ```
41
45
  qdumpfs --command=sync /backup1 /backup2
42
46
  ```
43
47
 
44
- Sync two backup directories(limit 1 hours, keep 100Y12M12W30D).
48
+ バックアップフォルダの同期には膨大な時間が必要な場合があるため、実行時間を制限できます。以下は例えば1時間に制限する場合です。
45
49
  ```
46
50
  qdumpfs --command=sync --limit=1 /backup1 /backup2
47
51
  ```
48
52
 
49
- Sync two backup directories(limit 1 hours, keep specified backups only).
53
+ バックアップフォルダの同期で、1時間でかつ"100Y12M12W30D"を保存する場合のオプションです。
50
54
  ```
51
- qdumpfs --command=sync --limit=1 --keep=5Y6M7W10D --keep/backup1 /backup2
55
+ qdumpfs --command=sync --limit=1 --keep=5Y6M7W10D backup1 /backup2
52
56
  ```
53
57
 
54
- Expire backup directory.
58
+ "--command expire"で、"--keep="パターンに該当しないバックアップを削除できます。
55
59
  ```
56
- qdumpfs --command=expire --limit=1 --keep=5Y6M7W10D --keep/backup1 /backup2
60
+ qdumpfs --command=expire --limit=1 --keep=5Y6M7W10D backup1 /backup2
57
61
  ```
58
62
 
63
+ "--command delete"で、バックアップに存在する指定したパスを削除できます(間違えてバックアップした内容を削除したい場合などに使用)。
64
+ ```
65
+ qdumpfs --command=delete --delete-dir=backup1 --limit=1 r:/backup2
66
+ ```
59
67
 
60
68
  ## License
61
69
 
62
70
  qdumpfs is a free software with ABSOLUTELY NO WARRANTY under the terms of the GNU General Public License version 2.
63
71
 
64
72
 
65
-
66
-
data/lib/qdumpfs.rb CHANGED
@@ -45,9 +45,13 @@ module Qdumpfs
45
45
  opt.on('--delete-to=YYYYMMDD', 'delete backup to YYYY/MM/DD') {|v|
46
46
  opts[:delete_to] = Date.parse(v)
47
47
  }
48
+ opt.on('--delete-dir=DIRS', 'relative path from the snapshot root of the backup. ex: --delete-dir=foo,home/bar') {|v|
49
+ opts[:delete_dirs] = v.split(/,/)
50
+ }
48
51
  opt.on('--backup-at=YYYYMMDD', 'backup at YYYY/MM/DD') {|v|
49
52
  opts[:backup_at] = Date.parse(v)
50
53
  }
54
+ opt.on('-d', '--debug', 'debug mode') {|v| opts[:d] = v }
51
55
  opt.parse!(argv)
52
56
  option = Option.new(opts, argv)
53
57
  if opts[:v]
@@ -61,8 +65,10 @@ module Qdumpfs
61
65
  command = Command.new(option)
62
66
  command.run
63
67
  rescue => e
64
- # p e.message
65
- # p e.backtrace
68
+ if option.debug
69
+ p e.message
70
+ p e.backtrace
71
+ end
66
72
  puts opt.help
67
73
  exit
68
74
  end
@@ -85,8 +91,6 @@ module Qdumpfs
85
91
  verify
86
92
  elsif @opt.cmd == 'delete'
87
93
  delete('delete')
88
- # elsif @opt.cmd == 'test'
89
- # test
90
94
  else
91
95
  raise RuntimeError, "unknown command: #{@opt.cmd}"
92
96
  end
@@ -96,9 +100,15 @@ module Qdumpfs
96
100
  def log_result(src, today, elapsed)
97
101
  time = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
98
102
  bytes = convert_bytes(@written_bytes)
99
- msg = sprintf("%s: %s -> %s (in %.2f sec, %s written)\n",
100
- time, src, today, elapsed, bytes)
103
+ msg = sprintf("%s: %s -> %s (in %.2f sec, %s written)\n", time, src, today, elapsed, bytes)
101
104
  log(msg)
105
+ log("error files:\n")
106
+ i = 1
107
+ @error_files.each do |filename, reason|
108
+ msg = "#{i}. #{filename}\t#{reason}\n"
109
+ log(msg)
110
+ i += 1
111
+ end
102
112
  end
103
113
 
104
114
  def log(msg, console = true)
@@ -109,6 +119,10 @@ module Qdumpfs
109
119
  @opt.report(type, file_name)
110
120
  end
111
121
 
122
+ def report_error(file_name, reason)
123
+ @opt.report_error(file_name, reason)
124
+ end
125
+
112
126
  def update_file(src, latest, today)
113
127
  type = detect_type(src, latest)
114
128
  report(type, src)
@@ -133,11 +147,17 @@ module Qdumpfs
133
147
  end
134
148
 
135
149
  def filecount(dir)
136
- pscmd = 'Get-ChildItem -Recurse -File | Measure-Object | %{$_.Count}'
137
- cmd = "powershell -Command \"#{pscmd}\""
138
- result = nil
139
- Dir.chdir(dir) do
140
- result = `#{cmd}`
150
+ result = '0'
151
+ if windows?
152
+ pscmd = 'Get-ChildItem -Recurse -File | Measure-Object | %{$_.Count}'
153
+ cmd = "powershell -Command \"#{pscmd}\""
154
+ result = nil
155
+ Dir.chdir(dir) do
156
+ result = `#{cmd}`
157
+ result.chomp!
158
+ end
159
+ else
160
+ result = `find #{dir} | wc -l`
141
161
  result.chomp!
142
162
  end
143
163
  result.to_i
@@ -149,29 +169,6 @@ module Qdumpfs
149
169
  return src_count, dst_count
150
170
  end
151
171
 
152
- # def get_snapshots(target_dir)
153
- # # 指定したディレクトリに含まれるバックアップフォルダ(日付つき)を全て取得
154
- # dd = "[0-9][0-9]"
155
- # dddd = dd + dd
156
- # # FIXME: Y10K problem.
157
- # dirs = []
158
- # glob_path = File.join(target_dir, dddd, dd, dd)
159
- # Dir.glob(glob_path).sort.find {|dir|
160
- # day, month, year = File.split_all(dir).reverse.map {|x| x.to_i }
161
- # path = diro
162
- # if File.directory?(path) and Date.valid_date?(year, month, day) and
163
- # dirs << path
164
- # end
165
- # }
166
- # dirs
167
- # end
168
-
169
- # def get_snapshot_date(snapshot)
170
- # # バックアップディレクトリのパス(日付つき)から日付を取得して返す
171
- # day, month, year = File.split_all(snapshot).reverse.map {|x| x.to_i }
172
- # Time.new(year, month, day)
173
- # end
174
-
175
172
  def update_snapshot(src, latest, today)
176
173
  # バックアップの差分コピーを実行
177
174
  # src: コピー元ディレクトリ ex) i:/from/home
@@ -192,8 +189,9 @@ module Qdumpfs
192
189
  # ファイルのアップデート
193
190
  update_file(s, l, t)
194
191
  dirs[t] = File.stat(s) if File.ftype(s) == "directory"
195
- rescue Errno::ENOENT, Errno::EACCES => e
196
- wprintf("%s: %s", src, e.message)
192
+ rescue => e
193
+ report_error(s, e.message)
194
+ @error_files << [s, e.message]
197
195
  next
198
196
  end
199
197
  end
@@ -227,8 +225,9 @@ module Qdumpfs
227
225
  end
228
226
  chown_if_root(type, s, t)
229
227
  dirs[t] = File.stat(s) if File.ftype(s) == "directory"
230
- rescue Errno::ENOENT, Errno::EACCES => e
231
- wprintf("%s: %s", s, e.message)
228
+ rescue => e
229
+ report_error(s, e.message)
230
+ @error_files << [s, e.message]
232
231
  next
233
232
  end
234
233
  end
@@ -240,7 +239,7 @@ module Qdumpfs
240
239
 
241
240
  #コピー元のスナップショット
242
241
  src_snapshots = BackupDir.scan_backup_dirs(src)
243
- @opt.detect_keep_dirs(src_snapshots)
242
+ @opt.detect_expire_dirs(src_snapshots)
244
243
 
245
244
  # コピー先の最新スナップショット
246
245
  dst_snapshots = BackupDir.scan_backup_dirs(dst)
@@ -308,6 +307,7 @@ module Qdumpfs
308
307
  log("##### backup start #####")
309
308
 
310
309
  @written_bytes = 0
310
+ @error_files = []
311
311
  start_time = Time.now
312
312
  if @opt.backup_at
313
313
  start_time = to_time(@opt.backup_at)
@@ -364,6 +364,7 @@ module Qdumpfs
364
364
 
365
365
  start_time = Time.now
366
366
  @written_bytes = 0
367
+ @error_files = []
367
368
  src = @opt.src
368
369
  dst = @opt.dst
369
370
 
@@ -410,6 +411,10 @@ module Qdumpfs
410
411
 
411
412
  end_time = Time.now
412
413
  diff = time_diff(start_time, end_time)
414
+
415
+ elapsed = Time.now - start_time
416
+ log_result(src, dst, elapsed)
417
+
413
418
  log("##### sync end #{fmt(end_time)} diff=#{diff} last_sync_complete=#{last_sync_complete} #####")
414
419
  end
415
420
 
@@ -457,9 +462,7 @@ module Qdumpfs
457
462
 
458
463
  def delete(cmd)
459
464
  @opt.validate_directories(1)
460
-
461
465
  start_time = Time.now
462
- p @opt.limit_sec
463
466
  limit_time = start_time + (@opt.limit_sec)
464
467
  log("##### #{cmd} delete-from=#{@opt.delete_from} delete-to=#{@opt.delete_to} start #{fmt(start_time)} => limit_time=#{fmt(limit_time)} #####")
465
468
  @opt.dirs.each do |target_dir|
@@ -482,6 +485,29 @@ p @opt.limit_sec
482
485
  log("##### #{cmd} end #####")
483
486
  end
484
487
 
488
+ def rm_dir(path)
489
+ can_delete = true
490
+ if @opt.dry_run
491
+ can_delete = false
492
+ elsif !FileTest.directory?(path)
493
+ can_delete = false
494
+ end
495
+ msg = can_delete ? "...ok..." : "...ng..."
496
+ print "Deleting #{path} #{msg}"
497
+
498
+ return unless can_delete
499
+
500
+ #それ以外は日付バックアップディレクトリ全体を削除
501
+ if windows?
502
+ # Windowsの場合
503
+ win_backup_path = to_win_path(path)
504
+ system("rmdir /S /Q #{win_backup_path}")
505
+ else
506
+ # Linux/macOSの場合
507
+ system("rm -rf #{path}")
508
+ end
509
+ end
510
+
485
511
  def delete_target_dir(cmd, target_dir)
486
512
  target_dir = to_unix_path(target_dir)
487
513
  puts "<<<<< Target dir: #{target_dir} >>>>>"
@@ -494,40 +520,20 @@ p @opt.limit_sec
494
520
  else
495
521
  raise RuntimeError, "unknown command: #{cmd}"
496
522
  end
497
-
498
- # p @opt.keep_year
499
- # p @opt.keep_month
500
- # p @opt.keep_day
501
-
523
+
502
524
  snapshots.each do |snapshot|
503
525
  next if snapshot.keep
504
526
  t_start = Time.now
505
- print "Deleting #{snapshot.path} ..."
506
-
507
- unless @opt.dry_run
508
- #http://superuser.com/questions/19762/mass-deleting-files-in-windows/289399#289399
509
- ##### here
510
- #bundle exec ruby exe/qdumpfs --dry-run --keep=100Y36M30W30D --command expire f:/pc1/pdumpfs/users f:/pc1/pdumpfs/opt f:/pc1/pdumpfs/d
511
-
512
- if windows?
513
- # Windowsの場合
514
- win_backup_path = to_win_path(snapshot.path)
515
-
516
- # byenow = "byenow"
517
- # if which(byenow)
518
- # print " bynow"
519
- # system("byenow -y --delete-ntapi --one-liner #{snapshot.path}")
520
- # else
521
- # print " pass1"
522
- # system("del /F /S /Q #{win_backup_path} > nul")
523
- # print " pass2"
524
- # system("rmdir /S /Q #{win_backup_path}")
525
- # end
526
- system("rmdir /S /Q #{win_backup_path}")
527
- else
528
- # Linux/macOSの場合
529
- system("rm -rf #{snapshot.path}")
527
+
528
+ if @opt.delete_dirs.size > 0
529
+ #削除するディレクトリが指定されている場合日付バックアップディレクトリの下そのディレクトリを削除
530
+ @opt.delete_dirs.each do |delete_dir|
531
+ delete_path = File.join(snapshot.path, delete_dir)
532
+ rm_dir(delete_path)
530
533
  end
534
+ else
535
+ #それ以外はバックアップディレクトリ全体が対象
536
+ rm_dir(snapshot.path)
531
537
  end
532
538
 
533
539
  t_end = Time.now
@@ -551,60 +557,6 @@ p @opt.limit_sec
551
557
  end
552
558
  end
553
559
 
554
- # def delete
555
- # @opt.validate_directories(1)
556
-
557
- # start_time = Time.now
558
- # limit_time = start_time + (@opt.limit_sec)
559
- # log("##### delete start #{fmt(start_time)} => limit_time=#{fmt(limit_time)} #####")
560
- # @opt.dirs.each do |target_dir|
561
- # target_start = Time.now
562
- # delete_target_dir(target_dir)
563
- # target_end = Time.now
564
-
565
- # # 次回expireにかかる時間を最終expire時間の半分と予想
566
- # next_expire = (target_end - target_start) / 2
567
-
568
- # cur_time = Time.now
569
- # in_imit = (cur_time + next_expire) < limit_time
570
-
571
- # log("## cur_time=#{fmt(cur_time)} + next_expire=#{next_expire} < limit_time=#{fmt(limit_time)} in_limit=#{in_limit} ## ")
572
- # unless in_limit
573
- # break
574
- # end
575
- # end
576
- # log("##### delete end #####")
577
- # end
578
-
579
- # def delete_target_dir(target_dir)
580
- # target_dir = to_unix_path(target_dir)
581
- # puts "<<<<< Target dir: #{target_dir} >>>>>"
582
-
583
- # snapshots = BackupDir.scan_backup_dirs(target_dir)
584
- # @opt.detect_delete_dirs(snapshots)
585
-
586
- # snapshots.each do |snapshot|
587
- # next if snapshot.keep
588
- # t_start = Time.now
589
- # print "Deleting #{snapshot.path} ..."
590
-
591
- # unless @opt.dry_run
592
- # if windows?
593
- # # Windowsの場合
594
- # win_backup_path = to_win_path(snapshot.path)
595
- # system("rmdir /S /Q #{win_backup_path}")
596
- # else
597
- # # Linux/macOSの場合
598
- # system("rm -rf #{snapshot.path}")
599
- # end
600
- # end
601
- # end
602
-
603
- # t_end = Time.now
604
- # diff = (t_end - t_start).to_i
605
- # diff_hours = diff / 3600
606
- # puts " done[#{diff} seconds = #{diff_hours} hours]."
607
- # end
608
560
 
609
561
  end
610
562
 
@@ -141,14 +141,17 @@ module Qdumpfs
141
141
  @keep_day = $1.to_i if keep =~ /(\d+)D/
142
142
  @delete_from = @opts[:delete_from]
143
143
  @delete_to = @opts[:delete_to]
144
+ @delete_dirs = @opts[:delete_dirs] || []
144
145
  @backup_at = @opts[:backup_at]
145
146
  @today = Date.today
147
+ @debug = @opts[:d]
146
148
  end
147
149
  attr_reader :dirs, :src, :dst, :cmd
148
150
  attr_reader :keep_year, :keep_month, :keep_week, :keep_day
149
151
  attr_reader :logdir, :logpath, :verifypath
150
152
  attr_reader :logger, :matcher, :reporter, :interval_proc
151
- attr_reader :delete_from, :delete_to, :backup_at
153
+ attr_reader :delete_from, :delete_to, :delete_dirs, :backup_at
154
+ attr_reader :debug
152
155
 
153
156
  def report(type, filename)
154
157
  if @opts[:v]
@@ -173,6 +176,11 @@ module Qdumpfs
173
176
  end
174
177
  log(msg)
175
178
  end
179
+
180
+ def report_error(filename, reason)
181
+ msg = sprintf("err_file\t%s\t%s\n", filename, reason)
182
+ log(msg)
183
+ end
176
184
 
177
185
  def log(msg, console = true)
178
186
  return if (msg.nil? || msg == '')
data/lib/qdumpfs/util.rb CHANGED
@@ -73,14 +73,14 @@ module QdumpfsFind
73
73
  yield file.dup.taint
74
74
  begin
75
75
  s = File.lstat(file)
76
- rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG => e
76
+ rescue => e
77
77
  logger.print("File.lstat path=#{file} error=#{e.message}")
78
78
  next
79
79
  end
80
80
  if s.directory? then
81
81
  begin
82
82
  fs = Dir.entries(file, :encoding=>'UTF-8')
83
- rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG => e
83
+ rescue => e
84
84
  logger.print("Dir.entries path=#{file} error=#{e.message}")
85
85
  next
86
86
  end
@@ -107,23 +107,22 @@ module QdumpfsUtils
107
107
 
108
108
  # We don't use File.copy for calling @interval_proc.
109
109
  def copy_file(src, dest)
110
- File.open(src, 'rb') {|r|
111
- File.open(dest, 'wb') {|w|
112
- block_size = (r.stat.blksize or 8192)
113
- begin
110
+ begin
111
+ File.open(src, 'rb') {|r|
112
+ File.open(dest, 'wb') {|w|
113
+ block_size = (r.stat.blksize or 8192)
114
114
  i = 0
115
115
  while true
116
116
  block = r.sysread(block_size)
117
117
  w.syswrite(block)
118
118
  i += 1
119
119
  @written_bytes += block.size
120
- # @interval_proc.call if i % 10 == 0
121
120
  end
122
- rescue EOFError => e
123
- # puts e.message, e.backtrace
124
- end
121
+ }
125
122
  }
126
- }
123
+ rescue EOFError => e
124
+ # puts e.message, e.backtrace
125
+ end
127
126
  unless FileTest.file?(dest)
128
127
  raise "copy_file fails #{dest}"
129
128
  end
@@ -1,3 +1,4 @@
1
- module Qdumpfs
2
- VERSION = "0.7.0"
3
- end
1
+ module Qdumpfs
2
+ VERSION = "1.1.0"
3
+ end
4
+
data/run_list.sh ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+
3
+ curdir=$(dirname $0)
4
+ bundle exec ruby $curdir/exe/qdumpfs --command=list /Volumes/EXT/
5
+
data/run_qdumpfs.sh CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/bin/sh
2
2
 
3
3
  curdir=$(dirname $0)
4
- bundle exec ruby $curdir/exe/qdumpfs $*
4
+ bundle exec ruby $curdir/exe/qdumpfs "$@"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qdumpfs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - src
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-19 00:00:00.000000000 Z
11
+ date: 2021-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -86,6 +86,7 @@ files:
86
86
  - qdumpfs.gemspec
87
87
  - run2_qdumpfs.cmd
88
88
  - run_list.cmd
89
+ - run_list.sh
89
90
  - run_qdumpfs.cmd
90
91
  - run_qdumpfs.sh
91
92
  - run_sync.cmd