file-digests 0.0.8 → 0.0.13

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: 77a80b8a8ca297b757b13f991c2890c928a9fe9824319595489f273c2c13d212
4
- data.tar.gz: 9136567171037d0c0426d714d2999e3232335e26b9782bb1c1db9144cfe7ca8a
3
+ metadata.gz: b587b7a9fd95e7a09b488041e0e80ee91e95f685b32b159f80c0bf494eabc77b
4
+ data.tar.gz: 8b261afd681d6d6213b03c7dc65391e7533f04896e4e13f3c1bd73ddebca74a7
5
5
  SHA512:
6
- metadata.gz: ff53170d154b2cb03fb05c9681a43a1a596a4a807036344b9724ccff2a8e29551efb13e4e3130fbb7e7cbd931409f44a4ccb3a00279081796e7e1e9c6202882f
7
- data.tar.gz: 7f598540535e96c8f6bb26360e854a83e93f3d1c9d219de1eec440150cedd74cf8e6f4b8af5b065f1262fedd3602bf187f056e3f378772a4c15c16132aea79b6
6
+ metadata.gz: aa76de6ec0bae260aa6bf289ead5a1443769f7823ae8886c2730b1235fbc9721eb39b8be56167ce654e41f6df5c5e6a10c8f1c08c03282cb32d580c09749e398
7
+ data.tar.gz: a6ecefb33487aa3738b42922004d4b671b39172448a0af37d183ad6f41cf4c99be80083390bf28d695ba01badf2940a5e89cf4bf9220eca8ac9b00cba3faaa24
@@ -1,220 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'date'
4
- require 'set'
5
- require 'digest'
6
- require 'fileutils'
7
- require 'pathname'
8
- require 'sqlite3'
9
-
10
- def ensure_dir_exists path
11
- if File.exist?(path)
12
- unless File.directory?(path)
13
- raise "#{path} is not a directory"
14
- end
15
- else
16
- FileUtils.mkdir_p path
17
- end
18
- end
19
-
20
- def measure_time
21
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
22
- yield
23
- elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start).to_i
24
- puts "Elapsed time: #{elapsed / 3600}h #{(elapsed % 3600) / 60}m #{elapsed % 60}s" unless QUIET
25
- end
26
-
27
- def patch_path_string path
28
- Gem.win_platform? ? path.gsub(/\\/, '/') : path
29
- end
30
-
31
- class DigestDatabase
32
- def initialize path
33
- @db = SQLite3::Database.new(path.to_s)
34
-
35
- unless @db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name = 'digests'").length == 1
36
- @db.execute 'PRAGMA encoding = "UTF-8"'
37
- @db.execute "CREATE TABLE digests (
38
- id INTEGER PRIMARY KEY,
39
- filename TEXT,
40
- mtime TEXT,
41
- digest TEXT,
42
- digest_check_time TEXT)"
43
- @db.execute "CREATE UNIQUE INDEX digests_filename ON digests(filename)"
44
- end
45
-
46
- @db.execute 'PRAGMA journal_mode = "WAL"'
47
- @db.execute 'PRAGMA synchronous = "NORMAL"'
48
- @db.execute 'PRAGMA locking_mode = "EXCLUSIVE"'
49
- @db.execute 'PRAGMA cache_size = "5000"'
50
-
51
- @db.results_as_hash = true
52
- @missing_files = Hash[@db.prepare("SELECT filename, digest FROM digests").execute!]
53
- @new_files = {}
54
-
55
-
56
- @insert = @db.prepare("INSERT INTO digests (filename, mtime, digest, digest_check_time) VALUES (?, ?, ?, datetime('now'))")
57
- @find_by_filename = @db.prepare("SELECT id, mtime, digest FROM digests WHERE filename = ?")
58
- @touch_digest_check_time = @db.prepare("UPDATE digests SET digest_check_time = datetime('now') WHERE id = ?")
59
- @update_mtime_and_digest = @db.prepare("UPDATE digests SET mtime = ?, digest = ?, digest_check_time = datetime('now') WHERE id = ?")
60
- @update_mtime = @db.prepare("UPDATE digests SET mtime = ?, digest_check_time = datetime('now') WHERE id = ?")
61
- @delete_by_filename = @db.prepare("DELETE FROM digests WHERE filename = ?")
62
- end
63
-
64
- def insert_or_update file_path, mtime, digest
65
- result = @find_by_filename.execute file_path
66
-
67
- if found = result.next_hash
68
- raise "Multiple records found" if result.next
69
-
70
- @missing_files.delete(file_path)
71
-
72
- if found['digest'] == digest
73
- COUNTS[:good] += 1
74
- # puts "GOOD: #{file_path}" unless QUIET
75
- unless TEST_ONLY
76
- if found['mtime'] == mtime
77
- @touch_digest_check_time.execute found['id']
78
- else
79
- @update_mtime.execute mtime, found['id']
80
- end
81
- end
82
- else
83
- if found['mtime'] == mtime # Digest is different and mtime is the same
84
- COUNTS[:likely_damaged] += 1
85
- STDERR.puts "LIKELY DAMAGED: #{file_path}"
86
- else
87
- COUNTS[:updated] += 1
88
- puts "UPDATED: #{file_path}" unless QUIET
89
- unless TEST_ONLY
90
- @update_mtime_and_digest.execute mtime, digest, found['id']
91
- end
92
- end
93
- end
94
- else
95
- COUNTS[:new] += 1
96
- puts "NEW: #{file_path}" unless QUIET
97
- unless TEST_ONLY
98
- @new_files[file_path] = digest
99
- @insert.execute! file_path, mtime, digest
100
- end
101
- end
102
- end
103
-
104
- def process_missing_files
105
- @missing_files.delete_if do |filename, digest|
106
- if @new_files.value?(digest)
107
- COUNTS[:renamed] += 1
108
- unless TEST_ONLY
109
- @delete_by_filename.execute filename
110
- end
111
- true
112
- end
113
- end
114
-
115
- if (COUNTS[:missing] = @missing_files.length) > 0
116
- puts "MISSING FILES:"
117
- @missing_files.sort.to_h.each do |filename, digest|
118
- puts filename
119
- end
120
- unless TEST_ONLY
121
- puts "Remove missing files from the database (y/n)?"
122
- if STDIN.gets.strip == "y"
123
- @missing_files.each do |filename, digest|
124
- @delete_by_filename.execute filename
125
- end
126
- end
127
- end
128
- end
129
- end
130
- end
131
-
132
- class Checker
133
- def initialize files_path, digest_database_path
134
- @files_path = files_path
135
- ensure_dir_exists @files_path
136
-
137
- if digest_database_path
138
- @digest_database_path = digest_database_path
139
- ensure_dir_exists @digest_database_path.dirname
140
- else
141
- @digest_database_path = @files_path + '.file-digests.sqlite'
142
- @skip_file_digests_sqlite = true
143
- end
144
-
145
- @digest_database = DigestDatabase.new @digest_database_path
146
- end
147
-
148
- def check
149
- walk_files do |filename|
150
- begin
151
- process_file filename
152
- rescue => exception
153
- COUNTS[:exceptions] += 1
154
- STDERR.puts "EXCEPTION: #{filename}: #{exception.message}"
155
- end
156
- end
157
-
158
- @digest_database.process_missing_files
159
- end
160
-
161
- def walk_files
162
- Dir.glob(@files_path + '**' + '*', File::FNM_DOTMATCH) do |filename|
163
- next unless File.file? filename
164
- next if @skip_file_digests_sqlite && filename == '.file-digests.sqlite'
165
- next if @skip_file_digests_sqlite && filename == '.file-digests.sqlite-wal'
166
- next if @skip_file_digests_sqlite && filename == '.file-digests.sqlite-shm'
167
- yield filename
168
- end
169
- end
170
-
171
- def process_file filename
172
- @digest_database.insert_or_update(
173
- filename.delete_prefix(@files_path.to_s + '/'),
174
- File.mtime(filename).utc.strftime('%Y-%m-%d %H:%M:%S'),
175
- get_file_digest(filename)
176
- )
177
- end
178
-
179
- def get_file_digest filename
180
- File.open(filename, 'rb') do |io|
181
- digest = Digest::SHA512.new
182
- buffer = ""
183
- while io.read(40960, buffer)
184
- digest.update(buffer)
185
- end
186
- return digest.hexdigest
187
- end
188
- end
189
-
190
- end
191
-
192
3
  QUIET = (ENV["QUIET"] == "true")
193
4
  TEST_ONLY = (ENV["TEST_ONLY"] == "true")
194
5
 
195
- COUNTS = {good: 0, updated: 0, new: 0, missing: 0, renamed: 0, likely_damaged: 0, exceptions: 0}
196
-
197
- begin
198
- if ARGV[0]
199
- files_path = Pathname.new patch_path_string(ARGV[0])
200
- else
201
- files_path = Pathname.new patch_path_string(".")
202
- end
203
-
204
- digest_database_path = Pathname.new patch_path_string(ARGV[1]) if ARGV[1]
205
-
206
- measure_time do
207
- checker = Checker.new files_path, digest_database_path
208
- checker.check
209
- end
210
-
211
- if COUNTS[:likely_damaged] > 0 || COUNTS[:exceptions] > 0
212
- STDERR.puts "ERRORS WERE OCCURRED"
213
- end
214
-
215
- puts COUNTS.inspect
6
+ require 'file-digests'
216
7
 
217
- rescue => exception
218
- STDERR.puts "EXCEPTION: #{exception.message}"
219
- raise exception
220
- end
8
+ FileDigests.perform_check
@@ -1,220 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'date'
4
- require 'set'
5
- require 'digest'
6
- require 'fileutils'
7
- require 'pathname'
8
- require 'sqlite3'
9
-
10
- def ensure_dir_exists path
11
- if File.exist?(path)
12
- unless File.directory?(path)
13
- raise "#{path} is not a directory"
14
- end
15
- else
16
- FileUtils.mkdir_p path
17
- end
18
- end
19
-
20
- def measure_time
21
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
22
- yield
23
- elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start).to_i
24
- puts "Elapsed time: #{elapsed / 3600}h #{(elapsed % 3600) / 60}m #{elapsed % 60}s" unless QUIET
25
- end
26
-
27
- def patch_path_string path
28
- Gem.win_platform? ? path.gsub(/\\/, '/') : path
29
- end
30
-
31
- class DigestDatabase
32
- def initialize path
33
- @db = SQLite3::Database.new(path.to_s)
34
-
35
- unless @db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name = 'digests'").length == 1
36
- @db.execute 'PRAGMA encoding = "UTF-8"'
37
- @db.execute "CREATE TABLE digests (
38
- id INTEGER PRIMARY KEY,
39
- filename TEXT,
40
- mtime TEXT,
41
- digest TEXT,
42
- digest_check_time TEXT)"
43
- @db.execute "CREATE UNIQUE INDEX digests_filename ON digests(filename)"
44
- end
45
-
46
- @db.execute 'PRAGMA journal_mode = "WAL"'
47
- @db.execute 'PRAGMA synchronous = "NORMAL"'
48
- @db.execute 'PRAGMA locking_mode = "EXCLUSIVE"'
49
- @db.execute 'PRAGMA cache_size = "5000"'
50
-
51
- @db.results_as_hash = true
52
- @missing_files = Hash[@db.prepare("SELECT filename, digest FROM digests").execute!]
53
- @new_files = {}
54
-
55
-
56
- @insert = @db.prepare("INSERT INTO digests (filename, mtime, digest, digest_check_time) VALUES (?, ?, ?, datetime('now'))")
57
- @find_by_filename = @db.prepare("SELECT id, mtime, digest FROM digests WHERE filename = ?")
58
- @touch_digest_check_time = @db.prepare("UPDATE digests SET digest_check_time = datetime('now') WHERE id = ?")
59
- @update_mtime_and_digest = @db.prepare("UPDATE digests SET mtime = ?, digest = ?, digest_check_time = datetime('now') WHERE id = ?")
60
- @update_mtime = @db.prepare("UPDATE digests SET mtime = ?, digest_check_time = datetime('now') WHERE id = ?")
61
- @delete_by_filename = @db.prepare("DELETE FROM digests WHERE filename = ?")
62
- end
63
-
64
- def insert_or_update file_path, mtime, digest
65
- result = @find_by_filename.execute file_path
66
-
67
- if found = result.next_hash
68
- raise "Multiple records found" if result.next
69
-
70
- @missing_files.delete(file_path)
71
-
72
- if found['digest'] == digest
73
- COUNTS[:good] += 1
74
- # puts "GOOD: #{file_path}" unless QUIET
75
- unless TEST_ONLY
76
- if found['mtime'] == mtime
77
- @touch_digest_check_time.execute found['id']
78
- else
79
- @update_mtime.execute mtime, found['id']
80
- end
81
- end
82
- else
83
- if found['mtime'] == mtime # Digest is different and mtime is the same
84
- COUNTS[:likely_damaged] += 1
85
- STDERR.puts "LIKELY DAMAGED: #{file_path}"
86
- else
87
- COUNTS[:updated] += 1
88
- puts "UPDATED: #{file_path}" unless QUIET
89
- unless TEST_ONLY
90
- @update_mtime_and_digest.execute mtime, digest, found['id']
91
- end
92
- end
93
- end
94
- else
95
- COUNTS[:new] += 1
96
- puts "NEW: #{file_path}" unless QUIET
97
- unless TEST_ONLY
98
- @new_files[file_path] = digest
99
- @insert.execute! file_path, mtime, digest
100
- end
101
- end
102
- end
103
-
104
- def process_missing_files
105
- @missing_files.delete_if do |filename, digest|
106
- if @new_files.value?(digest)
107
- COUNTS[:renamed] += 1
108
- unless TEST_ONLY
109
- @delete_by_filename.execute filename
110
- end
111
- true
112
- end
113
- end
114
-
115
- if (COUNTS[:missing] = @missing_files.length) > 0
116
- puts "MISSING FILES:"
117
- @missing_files.sort.to_h.each do |filename, digest|
118
- puts filename
119
- end
120
- unless TEST_ONLY
121
- puts "Remove missing files from the database (y/n)?"
122
- if STDIN.gets.strip == "y"
123
- @missing_files.each do |filename, digest|
124
- @delete_by_filename.execute filename
125
- end
126
- end
127
- end
128
- end
129
- end
130
- end
131
-
132
- class Checker
133
- def initialize files_path, digest_database_path
134
- @files_path = files_path
135
- ensure_dir_exists @files_path
136
-
137
- if digest_database_path
138
- @digest_database_path = digest_database_path
139
- ensure_dir_exists @digest_database_path.dirname
140
- else
141
- @digest_database_path = @files_path + '.file-digests.sqlite'
142
- @skip_file_digests_sqlite = true
143
- end
144
-
145
- @digest_database = DigestDatabase.new @digest_database_path
146
- end
147
-
148
- def check
149
- walk_files do |filename|
150
- begin
151
- process_file filename
152
- rescue => exception
153
- COUNTS[:exceptions] += 1
154
- STDERR.puts "EXCEPTION: #{filename}: #{exception.message}"
155
- end
156
- end
157
-
158
- @digest_database.process_missing_files
159
- end
160
-
161
- def walk_files
162
- Dir.glob(@files_path + '**' + '*', File::FNM_DOTMATCH) do |filename|
163
- next unless File.file? filename
164
- next if @skip_file_digests_sqlite && filename == '.file-digests.sqlite'
165
- next if @skip_file_digests_sqlite && filename == '.file-digests.sqlite-wal'
166
- next if @skip_file_digests_sqlite && filename == '.file-digests.sqlite-shm'
167
- yield filename
168
- end
169
- end
170
-
171
- def process_file filename
172
- @digest_database.insert_or_update(
173
- filename.delete_prefix(@files_path.to_s + '/'),
174
- File.mtime(filename).utc.strftime('%Y-%m-%d %H:%M:%S'),
175
- get_file_digest(filename)
176
- )
177
- end
178
-
179
- def get_file_digest filename
180
- File.open(filename, 'rb') do |io|
181
- digest = Digest::SHA512.new
182
- buffer = ""
183
- while io.read(40960, buffer)
184
- digest.update(buffer)
185
- end
186
- return digest.hexdigest
187
- end
188
- end
189
-
190
- end
191
-
192
3
  QUIET = (ENV["QUIET"] == "true")
193
4
  TEST_ONLY = true
194
5
 
195
- COUNTS = {good: 0, updated: 0, new: 0, missing: 0, renamed: 0, likely_damaged: 0, exceptions: 0}
196
-
197
- begin
198
- if ARGV[0]
199
- files_path = Pathname.new patch_path_string(ARGV[0])
200
- else
201
- files_path = Pathname.new patch_path_string(".")
202
- end
203
-
204
- digest_database_path = Pathname.new patch_path_string(ARGV[1]) if ARGV[1]
205
-
206
- measure_time do
207
- checker = Checker.new files_path, digest_database_path
208
- checker.check
209
- end
210
-
211
- if COUNTS[:likely_damaged] > 0 || COUNTS[:exceptions] > 0
212
- STDERR.puts "ERRORS WERE OCCURRED"
213
- end
214
-
215
- puts COUNTS.inspect
6
+ require 'file-digests'
216
7
 
217
- rescue => exception
218
- STDERR.puts "EXCEPTION: #{exception.message}"
219
- raise exception
220
- end
8
+ FileDigests.perform_check
@@ -0,0 +1,251 @@
1
+
2
+ require 'date'
3
+ require 'set'
4
+ require 'digest'
5
+ require 'fileutils'
6
+ require 'pathname'
7
+ require 'sqlite3'
8
+
9
+ module FileDigests
10
+
11
+ def self.ensure_dir_exists path
12
+ if File.exist?(path)
13
+ unless File.directory?(path)
14
+ raise "#{path} is not a directory"
15
+ end
16
+ else
17
+ FileUtils.mkdir_p path
18
+ end
19
+ end
20
+
21
+ def self.measure_time
22
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
23
+ yield
24
+ elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start).to_i
25
+ puts "Elapsed time: #{elapsed / 3600}h #{(elapsed % 3600) / 60}m #{elapsed % 60}s" unless QUIET
26
+ end
27
+
28
+ def self.patch_path_string path
29
+ Gem.win_platform? ? path.gsub(/\\/, '/') : path
30
+ end
31
+
32
+ def self.perform_check
33
+ checker = Checker.new ARGV[0], ARGV[1]
34
+ checker.perform_check
35
+ end
36
+
37
+ class DigestDatabase
38
+ def initialize path
39
+ @db = SQLite3::Database.new path.to_s
40
+ @db.results_as_hash = true
41
+
42
+ execute 'PRAGMA journal_mode = "WAL"'
43
+ execute 'PRAGMA synchronous = "NORMAL"'
44
+ execute 'PRAGMA locking_mode = "EXCLUSIVE"'
45
+ execute 'PRAGMA cache_size = "5000"'
46
+
47
+ unless execute("SELECT name FROM sqlite_master WHERE type='table' AND name = 'digests'").length == 1
48
+ execute 'PRAGMA encoding = "UTF-8"'
49
+ execute "CREATE TABLE digests (
50
+ id INTEGER PRIMARY KEY,
51
+ filename TEXT,
52
+ mtime TEXT,
53
+ digest TEXT,
54
+ digest_check_time TEXT)"
55
+ execute "CREATE UNIQUE INDEX digests_filename ON digests(filename)"
56
+ end
57
+
58
+ @missing_files = Hash[@db.prepare("SELECT filename, digest FROM digests").execute!]
59
+ @new_files = {}
60
+
61
+ prepare_method :insert, "INSERT INTO digests (filename, mtime, digest, digest_check_time) VALUES (?, ?, ?, datetime('now'))"
62
+ prepare_method :find_by_filename, "SELECT id, mtime, digest FROM digests WHERE filename = ?"
63
+ prepare_method :touch_digest_check_time, "UPDATE digests SET digest_check_time = datetime('now') WHERE id = ?"
64
+ prepare_method :update_mtime_and_digest, "UPDATE digests SET mtime = ?, digest = ?, digest_check_time = datetime('now') WHERE id = ?"
65
+ prepare_method :update_mtime, "UPDATE digests SET mtime = ?, digest_check_time = datetime('now') WHERE id = ?"
66
+ prepare_method :delete_by_filename, "DELETE FROM digests WHERE filename = ?"
67
+ end
68
+
69
+ def insert_or_update file_path, mtime, digest, counters
70
+ result = find_by_filename file_path
71
+
72
+ if found = result.next_hash
73
+ raise "Multiple records found" if result.next
74
+
75
+ @missing_files.delete(file_path)
76
+
77
+ if found['digest'] == digest
78
+ counters[:good] += 1
79
+ # puts "GOOD: #{file_path}" unless QUIET
80
+ unless TEST_ONLY
81
+ if found['mtime'] == mtime
82
+ touch_digest_check_time found['id']
83
+ else
84
+ update_mtime mtime, found['id']
85
+ end
86
+ end
87
+ else
88
+ if found['mtime'] == mtime # Digest is different and mtime is the same
89
+ counters[:likely_damaged] += 1
90
+ STDERR.puts "LIKELY DAMAGED: #{file_path}"
91
+ else
92
+ counters[:updated] += 1
93
+ puts "UPDATED: #{file_path}" unless QUIET
94
+ unless TEST_ONLY
95
+ update_mtime_and_digest mtime, digest, found['id']
96
+ end
97
+ end
98
+ end
99
+ else
100
+ counters[:new] += 1
101
+ puts "NEW: #{file_path}" unless QUIET
102
+ unless TEST_ONLY
103
+ @new_files[file_path] = digest
104
+ insert file_path, mtime, digest
105
+ end
106
+ end
107
+ end
108
+
109
+ def process_missing_files counters
110
+ @missing_files.delete_if do |filename, digest|
111
+ if @new_files.value?(digest)
112
+ counters[:renamed] += 1
113
+ unless TEST_ONLY
114
+ delete_by_filename filename
115
+ end
116
+ true
117
+ end
118
+ end
119
+
120
+ if (counters[:missing] = @missing_files.length) > 0
121
+ puts "\nMISSING FILES:"
122
+ @missing_files.sort.to_h.each do |filename, digest|
123
+ puts filename
124
+ end
125
+ unless TEST_ONLY
126
+ puts "Remove missing files from the database (y/n)?"
127
+ if STDIN.gets.strip.downcase == "y"
128
+ @db.transaction do
129
+ @missing_files.each do |filename, digest|
130
+ delete_by_filename filename
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ def execute *args, &block
141
+ @db.execute *args, &block
142
+ end
143
+
144
+ def prepare_method name, query
145
+ variable = "@#{name}"
146
+ instance_variable_set(variable, @db.prepare(query))
147
+ define_singleton_method name do |*args, &block|
148
+ instance_variable_get(variable).execute(*args, &block)
149
+ end
150
+ end
151
+ end
152
+
153
+ class Checker
154
+ def initialize files_path, digest_database_path
155
+ @files_path = Pathname.new(FileDigests::patch_path_string(files_path || ".")).cleanpath
156
+ @prefix_to_remove = @files_path.to_s + '/'
157
+
158
+ raise "Files path must be a readable directory" unless (File.directory?(@files_path) && File.readable?(@files_path))
159
+
160
+ @digest_database_path = if digest_database_path
161
+ Pathname.new(FileDigests::patch_path_string(digest_database_path)).cleanpath
162
+ else
163
+ @files_path + '.file-digests.sqlite'
164
+ end
165
+
166
+ if File.directory?(@digest_database_path)
167
+ @digest_database_path = @digest_database_path + '.file-digests.sqlite'
168
+ end
169
+
170
+ if @files_path == @digest_database_path.dirname
171
+ @skip_file_digests_sqlite = true
172
+ end
173
+
174
+ FileDigests::ensure_dir_exists @digest_database_path.dirname
175
+
176
+ if File.exist?(@digest_database_path.dirname + '.file-digests.sha512')
177
+ @use_sha512 = true
178
+ end
179
+
180
+ @digest_database = DigestDatabase.new @digest_database_path
181
+ @counters = {good: 0, updated: 0, new: 0, missing: 0, renamed: 0, likely_damaged: 0, exceptions: 0}
182
+ end
183
+
184
+ def perform_check
185
+ FileDigests::measure_time do
186
+ walk_files do |filename|
187
+ process_file filename
188
+ end
189
+ end
190
+
191
+ @digest_database.process_missing_files @counters
192
+
193
+ if @counters[:likely_damaged] > 0 || @counters[:exceptions] > 0
194
+ STDERR.puts "ERRORS WERE OCCURRED"
195
+ end
196
+
197
+ puts @counters.inspect
198
+ end
199
+
200
+ def walk_files
201
+ Dir.glob(@files_path + '**' + '*', File::FNM_DOTMATCH) do |filename|
202
+ yield filename
203
+ end
204
+ end
205
+
206
+ def process_file filename
207
+ return if File.symlink? filename
208
+
209
+ stat = File.stat filename
210
+
211
+ return if stat.blockdev?
212
+ return if stat.chardev?
213
+ return if stat.directory?
214
+ return if stat.pipe?
215
+ unless stat.readable?
216
+ raise "File is not readable"
217
+ end
218
+ return if stat.socket?
219
+
220
+ if @skip_file_digests_sqlite
221
+ basename = File.basename(filename)
222
+ return if basename == '.file-digests.sha512'
223
+ return if basename == '.file-digests.sqlite'
224
+ return if basename == '.file-digests.sqlite-wal'
225
+ return if basename == '.file-digests.sqlite-shm'
226
+ end
227
+
228
+ @digest_database.insert_or_update(
229
+ filename.delete_prefix(@prefix_to_remove).encode('utf-8', universal_newline: true).unicode_normalize(:nfkc),
230
+ stat.mtime.utc.strftime('%Y-%m-%d %H:%M:%S'),
231
+ get_file_digest(filename),
232
+ @counters
233
+ )
234
+ rescue => exception
235
+ @counters[:exceptions] += 1
236
+ STDERR.puts "EXCEPTION: #{filename}: #{exception.message}"
237
+ end
238
+
239
+ def get_file_digest filename
240
+ File.open(filename, 'rb') do |io|
241
+ digest = (@use_sha512 ? Digest::SHA512 : Digest::SHA256).new
242
+ buffer = ""
243
+ while io.read(40960, buffer)
244
+ digest.update(buffer)
245
+ end
246
+ return digest.hexdigest
247
+ end
248
+ end
249
+
250
+ end
251
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: file-digests
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stanislav Senotrusov
@@ -34,6 +34,7 @@ extra_rdoc_files: []
34
34
  files:
35
35
  - bin/file-digests
36
36
  - bin/file-digests-test
37
+ - lib/file-digests.rb
37
38
  homepage: https://github.com/senotrusov/file-digests
38
39
  licenses:
39
40
  - Apache-2.0