fsinv 0.1.2
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 +15 -0
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +73 -0
- data/bin/fsinv +420 -0
- data/fsinv.gemspec +29 -0
- data/lib/fsinv/basedescription.rb +101 -0
- data/lib/fsinv/directorydescription.rb +59 -0
- data/lib/fsinv/filedescription.rb +130 -0
- data/lib/fsinv/inventory.rb +72 -0
- data/lib/fsinv/lookuptable.rb +77 -0
- data/lib/fsinv.rb +306 -0
- metadata +198 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YzM2OGFhZGJlOWI5MmRjZDQyOWE4Y2E1NjgyYjk1ZDMxZTA3ZDVjMw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YWU5MjBiMDc5NTgzMDI5OWZlZDM4NDBkYmZhNTQ1Mjk2M2EyZmY3NQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
OWY5ZDk3NTJkMTVkYTYzOGU2ZTFiNDdhNjNmZjk1ZWJkNzczNjcyYmNiN2U4
|
10
|
+
NzUxNWQzYzcwYzcwMmEyYTM3MTQ1ZTM5NWVjM2EyN2NkY2UzMzdkYTU3NDUx
|
11
|
+
YTRkOWM5MDFjYjNmOGRlZmI5N2E3OGI5N2FjY2E5ZjM4ZDgzZWE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjRhMzFjNDY0NjcwZjNiNTRmMDVmYzUxZDE4MjJjZDUxMTNhZTUwMjc4NzIw
|
14
|
+
ZTAyNThiZTU1NWY4MWVjNDgyNTA2MDMwOGFhNDY3NTA0YWYwYzIzMjk5ZGI5
|
15
|
+
M2QzMjM5ODliMzFlNzIzYmYzZDRjN2I2YTVjMjZlOWY0YWEyZWI=
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Maximilian Irro
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# fsinv: file system inventory tool
|
2
|
+
|
3
|
+
imagine a very detailed README message here (please?)
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Just run ```gem install fsinv```
|
8
|
+
|
9
|
+
## Usage of the executable
|
10
|
+
|
11
|
+
Usage: fsinv.rb basepath1 [basepath2 [basepath3 [...]]] [options]
|
12
|
+
|
13
|
+
fsinv is used to index file systems. By default for each file/directory the size
|
14
|
+
in bytes as well as creation time (ctime) and modification time (mtime) are indexed.
|
15
|
+
|
16
|
+
Files additionally have their mime type, magic file description (see 'man file'),
|
17
|
+
OSX Finder tags (kMDItemUserTags) if run on osx, and a special 'fshugo' extended
|
18
|
+
file attribute (used by https://github.com/mpgirro/fshugo) stored as well.
|
19
|
+
|
20
|
+
Directories have also their xattr (osx, fshugo) stored, as well as a count of their
|
21
|
+
direct children files (file_count), direct children directories (dir_count) and a
|
22
|
+
general children item count (all dir/item count throughout their descendent hierarchie
|
23
|
+
tree)
|
24
|
+
|
25
|
+
Note that some files are ignored (like .AppleDouble, .DS_Store, Thumbs.db, etc.)
|
26
|
+
Additionally, some directories will only have reduced indizes, for their content
|
27
|
+
is huge of files, yet they are of lesser interest (like .git, .wine, etc.)
|
28
|
+
On OSX system, some items appear as files yet are in fact directories (.app, .bundle)
|
29
|
+
They will be marked as directories, but will only have their size calculated. Their
|
30
|
+
inner file hierarchie is also of lesser interrest.
|
31
|
+
|
32
|
+
Specific options:
|
33
|
+
|
34
|
+
-a, --all Save in all formats to the default destinations.
|
35
|
+
Equal to -b -j -q -x -y. Use -n to change the
|
36
|
+
file names of all target at once
|
37
|
+
|
38
|
+
-b, --binary [FILE] Dump iventory data stuctures in binary format.
|
39
|
+
Default destination is inventory.bin
|
40
|
+
|
41
|
+
-c, --crc32 Calculate CRC32 checksum for each file
|
42
|
+
|
43
|
+
-d, --db [FILE] Save inventory as SQLite database.
|
44
|
+
Default destination is inventory.db
|
45
|
+
|
46
|
+
-j, --json [FILE] Save inventory in JSON file format.
|
47
|
+
Default destination is inventory.json
|
48
|
+
|
49
|
+
-m, --md5 Calculate MD5 hashes for each file
|
50
|
+
|
51
|
+
-n, --name NAME This will change the name of the output files.
|
52
|
+
Default is 'inventory'. Specific targets for
|
53
|
+
file formats will overwrite this.
|
54
|
+
|
55
|
+
-p, --print FORMAT Print a format to stdout (json|yaml|xml)
|
56
|
+
|
57
|
+
-s, --silent Run in silent mode. No output or non-critical
|
58
|
+
error messages will be printed
|
59
|
+
|
60
|
+
-v, --verbose Run verbosely. This will output processed
|
61
|
+
filenames and error messages too
|
62
|
+
|
63
|
+
-x, --xml [FILE] Save inventory in XML file format.
|
64
|
+
Default destination is inventory.xml
|
65
|
+
|
66
|
+
-y, --yaml [FILE] Save inventory in YAML file format.
|
67
|
+
Default destination is inventory.yaml
|
68
|
+
|
69
|
+
-h, --help Show this message
|
70
|
+
|
71
|
+
## Usage as a library
|
72
|
+
|
73
|
+
Note: You must set ```Fsinv.options``` before using any Methods/Classes.
|
data/bin/fsinv
ADDED
@@ -0,0 +1,420 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding : utf-8 -*-
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
require 'fsinv'
|
7
|
+
|
8
|
+
DEFAULT_NAME = "inventory"
|
9
|
+
USAGE = "Usage: fsinv basepath1 [basepath2 [basepath3 [...]]] [options]"
|
10
|
+
|
11
|
+
Fsinv.options = {}
|
12
|
+
OptionParser.new do |opts|
|
13
|
+
opts.banner = USAGE
|
14
|
+
opts.separator ""
|
15
|
+
opts.separator "fsinv is used to index file systems. By default for each file/directory the size"
|
16
|
+
opts.separator "in bytes as well as creation time (ctime) and modification time (mtime) are indexed."
|
17
|
+
opts.separator ""
|
18
|
+
opts.separator "Files additionally have their mime type, magic file description (see 'man file'),"
|
19
|
+
opts.separator "OSX Finder tags (kMDItemUserTags) if run on osx, and a special 'fshugo' extended"
|
20
|
+
opts.separator "file attribute (used by https://github.com/mpgirro/fshugo) stored as well."
|
21
|
+
opts.separator ""
|
22
|
+
opts.separator "Directories have also their xattr (osx, fshugo) stored, as well as a count of their"
|
23
|
+
opts.separator "direct children files (file_count), direct children directories (dir_count) and a"
|
24
|
+
opts.separator "general children item count (all dir/item count throughout their descendent hierarchie"
|
25
|
+
opts.separator "tree)"
|
26
|
+
opts.separator ""
|
27
|
+
opts.separator "Multiple file system hierarchie trees can be indexed simultaniously, by using more than"
|
28
|
+
opts.separator "one basepath (see the usage)"
|
29
|
+
opts.separator ""
|
30
|
+
opts.separator "Note that some files are ignored (like .AppleDouble, .DS_Store, Thumbs.db, etc.)"
|
31
|
+
opts.separator "Additionally, some directories will only have reduced indizes (e.g. only their byte size,"
|
32
|
+
opts.separator "yet no children file list), for their content is huge of files, yet they are of lesser"
|
33
|
+
opts.separator "interest (like .git, .wine, etc.)"
|
34
|
+
opts.separator ""
|
35
|
+
opts.separator "On OSX system, some items appear as files yet are in fact directories (.app, .bundle)"
|
36
|
+
opts.separator "They will be marked as directories, but will only have their sizes calculated. Their"
|
37
|
+
opts.separator "inner file hierarchie is also of lesser interrest."
|
38
|
+
opts.separator ""
|
39
|
+
opts.separator "Specific options:"
|
40
|
+
opts.separator ""
|
41
|
+
|
42
|
+
opts.on("-a", "--all", "Save in all formats to the default destinations.
|
43
|
+
Equal to -b -j -q -x -y. Use -n to change the
|
44
|
+
file names of all target at once") do |all_flag|
|
45
|
+
Fsinv.options[:binary] = true
|
46
|
+
Fsinv.options[:json] = true
|
47
|
+
Fsinv.options[:db] = true
|
48
|
+
Fsinv.options[:xml] = true
|
49
|
+
Fsinv.options[:yaml] = true
|
50
|
+
end
|
51
|
+
opts.separator ""
|
52
|
+
|
53
|
+
opts.on("--binary [FILE]", "Dump iventory data stuctures in binary format.
|
54
|
+
Default destination is ~/#{DEFAULT_NAME}.bin") do |binary_file|
|
55
|
+
Fsinv.options[:binary] = true
|
56
|
+
Fsinv.options[:binary_file] = binary_file
|
57
|
+
end
|
58
|
+
opts.separator ""
|
59
|
+
|
60
|
+
opts.on("--crc32", "Calculate CRC32 checksum for each file") do |crc|
|
61
|
+
Fsinv.options[:crc32] = true
|
62
|
+
end
|
63
|
+
opts.separator ""
|
64
|
+
|
65
|
+
opts.on("--db [FILE]", "Save inventory as SQLite database.
|
66
|
+
Default destination is ~/#{DEFAULT_NAME}.db") do |sql_file|
|
67
|
+
Fsinv.options[:db] = true
|
68
|
+
Fsinv.options[:db_file] = sql_file
|
69
|
+
end
|
70
|
+
opts.separator ""
|
71
|
+
|
72
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
73
|
+
puts opts
|
74
|
+
exit
|
75
|
+
end
|
76
|
+
opts.separator ""
|
77
|
+
|
78
|
+
opts.on("-j", "--json [FILE]", "Save inventory in JSON file format.
|
79
|
+
Default destination is ~/#{DEFAULT_NAME}.json") do |json_file|
|
80
|
+
Fsinv.options[:json] = true
|
81
|
+
Fsinv.options[:json_file] = json_file
|
82
|
+
end
|
83
|
+
opts.separator ""
|
84
|
+
|
85
|
+
opts.on("--md5", "Calculate MD5 hash for each file") do |md5|
|
86
|
+
Fsinv.options[:md5] = true
|
87
|
+
end
|
88
|
+
opts.separator ""
|
89
|
+
|
90
|
+
opts.on("-n", "--name NAME", "This will change the name of the output files.
|
91
|
+
Default is '#{DEFAULT_NAME}'. Specific targets for
|
92
|
+
file formats will overwrite this.") do |name|
|
93
|
+
Fsinv.options[:name] = name
|
94
|
+
end
|
95
|
+
opts.separator ""
|
96
|
+
|
97
|
+
opts.on("-p", "--print FORMAT", [:json, :yaml, :xml], "Print a format to stdout (json|yaml|xml)") do |format|
|
98
|
+
Fsinv.options[:print] = true
|
99
|
+
Fsinv.options[:print_format] = format
|
100
|
+
end
|
101
|
+
opts.separator ""
|
102
|
+
|
103
|
+
opts.on("-s", "--silent", "Run in silent mode. No output or non-critical
|
104
|
+
error messages will be printed") do |s|
|
105
|
+
Fsinv.options[:silent] = s
|
106
|
+
end
|
107
|
+
opts.separator ""
|
108
|
+
|
109
|
+
opts.on("-v", "--verbose", "Run verbosely. This will output processed
|
110
|
+
filenames and error messages too") do |v|
|
111
|
+
Fsinv.options[:verbose] = v
|
112
|
+
end
|
113
|
+
opts.separator ""
|
114
|
+
|
115
|
+
opts.on_tail("--version", "Show version") do
|
116
|
+
puts ::Version.join('.')
|
117
|
+
exit
|
118
|
+
end
|
119
|
+
opts.separator ""
|
120
|
+
|
121
|
+
opts.on("--xml [FILE]", "Save inventory in XML file format.
|
122
|
+
Default destination is ~/#{DEFAULT_NAME}.xml") do |xml_file|
|
123
|
+
Fsinv.options[:xml] = true
|
124
|
+
Fsinv.options[:xml_file] = xml_file
|
125
|
+
end
|
126
|
+
opts.separator ""
|
127
|
+
|
128
|
+
opts.on("--yaml [FILE]", "Save inventory in YAML file format.
|
129
|
+
Default destination is ~/#{DEFAULT_NAME}.yaml") do |yaml_file|
|
130
|
+
Fsinv.options[:yaml] = true
|
131
|
+
Fsinv.options[:yaml_file] = yaml_file
|
132
|
+
end
|
133
|
+
opts.separator ""
|
134
|
+
|
135
|
+
end.parse! # do the parsing. do it now!
|
136
|
+
|
137
|
+
#p Fsinv.options
|
138
|
+
#p ARGV
|
139
|
+
|
140
|
+
if ARGV[0].nil?
|
141
|
+
puts "No basepath provided. At least one needed"
|
142
|
+
puts USAGE
|
143
|
+
exit
|
144
|
+
end
|
145
|
+
|
146
|
+
ARGV.each do |arg|
|
147
|
+
unless File.directory?(arg)
|
148
|
+
puts "Not a directory: #{arg}"
|
149
|
+
puts USAGE
|
150
|
+
exit
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
if Fsinv.options[:crc32]
|
155
|
+
begin
|
156
|
+
require 'digest/crc32'
|
157
|
+
rescue
|
158
|
+
puts "You have selected crc32 calculation option. This requires gem 'digest/crc32'."
|
159
|
+
puts "Install using 'gem install digest-crc'"
|
160
|
+
exit
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
if Fsinv.options[:md5]
|
165
|
+
begin
|
166
|
+
require 'digest/md5'
|
167
|
+
rescue
|
168
|
+
puts "You have selected md5 calculation option. This requires gem 'digest/md5'."
|
169
|
+
puts "Install using 'gem install digest'"
|
170
|
+
exit
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
file_structure = []
|
175
|
+
ARGV.each do |basepath|
|
176
|
+
file_structure << Fsinv.parse(basepath)
|
177
|
+
end
|
178
|
+
|
179
|
+
inventory = Fsinv::Inventory.new(file_structure)
|
180
|
+
|
181
|
+
unless Fsinv.options[:silent]
|
182
|
+
file_structure.each do |fs_tree|
|
183
|
+
size = fs_tree.bytes
|
184
|
+
puts "basepath: #{fs_tree.path}"
|
185
|
+
puts " size: #{Fsinv.pretty_SI_bytes(size)} (#{size} Bytes)"
|
186
|
+
puts " files: #{fs_tree.file_list.length}"
|
187
|
+
puts " items: #{fs_tree.item_count}"
|
188
|
+
end
|
189
|
+
if file_structure.length > 1
|
190
|
+
size = inventory.size
|
191
|
+
puts "total:"
|
192
|
+
puts " size: #{Fsinv.pretty_SI_bytes(size)} (#{size} Bytes)"
|
193
|
+
puts " items: #{inventory.item_count}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# this is the default output
|
198
|
+
unless (Fsinv.options[:binary] || Fsinv.options[:db] || Fsinv.options[:xml] || Fsinv.options[:yaml]) && Fsinv.options[:json].nil?
|
199
|
+
if Fsinv.options[:json_file].nil?
|
200
|
+
Fsinv.options[:json_file] = File.join(Dir.home,
|
201
|
+
if Fsinv.options[:name].nil?
|
202
|
+
"#{DEFAULT_NAME}.json"
|
203
|
+
else
|
204
|
+
"#{Fsinv.options[:name]}.json"
|
205
|
+
end
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
209
|
+
puts "writing JSON to #{Fsinv.options[:json_file]}" unless Fsinv.options[:silent]
|
210
|
+
|
211
|
+
begin
|
212
|
+
require 'json'
|
213
|
+
|
214
|
+
# monkey-patch for "JSON::NestingError: nesting is too deep"
|
215
|
+
module JSON
|
216
|
+
class << self
|
217
|
+
def parse(source, opts = {})
|
218
|
+
opts = ({:max_nesting => 100}).merge(opts)
|
219
|
+
Parser.new(source, opts).parse
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
json_data = Fsinv.inventory_to_json(inventory)
|
225
|
+
|
226
|
+
unless json_data.nil?
|
227
|
+
begin
|
228
|
+
file = File.open(Fsinv.options[:json_file], 'w')
|
229
|
+
file.write(json_data)
|
230
|
+
rescue
|
231
|
+
puts "error writing JSON file"
|
232
|
+
ensure
|
233
|
+
file.close unless file.nil?
|
234
|
+
end
|
235
|
+
end
|
236
|
+
rescue LoadError
|
237
|
+
puts "gem 'json' needed for JSON creation. Install using 'gem install json'"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
if Fsinv.options[:yaml]
|
242
|
+
if Fsinv.options[:yaml_file].nil?
|
243
|
+
Fsinv.options[:yaml_file] = File.join(Dir.home,
|
244
|
+
if Fsinv.options[:name].nil?
|
245
|
+
"#{DEFAULT_NAME}.yaml"
|
246
|
+
else
|
247
|
+
"#{Fsinv.options[:name]}.yaml"
|
248
|
+
end
|
249
|
+
)
|
250
|
+
end
|
251
|
+
puts "writing YAML to #{Fsinv.options[:yaml_file]}" unless Fsinv.options[:silent]
|
252
|
+
yaml_data = Fsinv.inventory_to_yaml(inventory)
|
253
|
+
unless yaml_data.nil?
|
254
|
+
begin
|
255
|
+
file = File.open(Fsinv.options[:yaml_file], 'w')
|
256
|
+
file.write(yaml_data)
|
257
|
+
rescue
|
258
|
+
puts "error writing YAML file"
|
259
|
+
ensure
|
260
|
+
file.close unless file.nil?
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
if Fsinv.options[:binary]
|
266
|
+
if Fsinv.options[:binary_file].nil?
|
267
|
+
Fsinv.options[:binary_file] = File.join(Dir.home,
|
268
|
+
if Fsinv.options[:name].nil?
|
269
|
+
"#{DEFAULT_NAME}.bin"
|
270
|
+
else
|
271
|
+
"#{Fsinv.options[:name]}.bin"
|
272
|
+
end
|
273
|
+
)
|
274
|
+
end
|
275
|
+
puts "writing binary dump to #{Fsinv.options[:binary_file]}" unless Fsinv.options[:silent]
|
276
|
+
begin
|
277
|
+
file = File.open(Fsinv.options[:binary_file], 'wb')
|
278
|
+
file.write(Marshal.dump(inventory))
|
279
|
+
rescue
|
280
|
+
puts "error writing binary dump file"
|
281
|
+
ensure
|
282
|
+
file.close unless file.nil?
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
if Fsinv.options[:db]
|
287
|
+
if Fsinv.options[:db_file].nil?
|
288
|
+
Fsinv.options[:db_file] = File.join(Dir.home,
|
289
|
+
if Fsinv.options[:name].nil?
|
290
|
+
"#{DEFAULT_NAME}.db"
|
291
|
+
else
|
292
|
+
"#{Fsinv.options[:name]}.db"
|
293
|
+
end
|
294
|
+
)
|
295
|
+
end
|
296
|
+
|
297
|
+
puts "writing database dump to #{Fsinv.options[:db_file]}" unless Fsinv.options[:silent]
|
298
|
+
`rm #{Fsinv.options[:db_file]}` if File.exists?(Fsinv.options[:db_file])
|
299
|
+
|
300
|
+
begin
|
301
|
+
|
302
|
+
require 'active_record'
|
303
|
+
|
304
|
+
ActiveRecord::Base.establish_connection(
|
305
|
+
:adapter => "sqlite3",
|
306
|
+
:database => Fsinv.options[:db_file]
|
307
|
+
)
|
308
|
+
|
309
|
+
ActiveRecord::Schema.define do
|
310
|
+
|
311
|
+
create_table :file_structures, force: true do |t|
|
312
|
+
t.datetime :created_at
|
313
|
+
t.datetime :updated_at
|
314
|
+
t.string :path
|
315
|
+
t.integer :bytes
|
316
|
+
t.datetime :ctime
|
317
|
+
t.datetime :mtime
|
318
|
+
t.string :entity_type
|
319
|
+
t.integer :file_count
|
320
|
+
t.integer :item_count
|
321
|
+
t.string :osx_tags
|
322
|
+
t.string :fshugo_tags
|
323
|
+
t.integer :mimetype
|
324
|
+
t.integer :magicdescr
|
325
|
+
end
|
326
|
+
|
327
|
+
create_table :fshugo_tags, force: true do |t|
|
328
|
+
t.string :tag
|
329
|
+
end
|
330
|
+
|
331
|
+
create_table :magic_descriptions, force: true do |t|
|
332
|
+
t.string :magicdescr
|
333
|
+
end
|
334
|
+
|
335
|
+
create_table :mime_types, force: true do |t|
|
336
|
+
t.string :mimetype
|
337
|
+
end
|
338
|
+
|
339
|
+
create_table :osx_tags, force: true do |t|
|
340
|
+
t.string :tag
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
344
|
+
|
345
|
+
class MimeType < ActiveRecord::Base
|
346
|
+
attr_accessor :mimetype
|
347
|
+
end
|
348
|
+
|
349
|
+
class MagicDescription < ActiveRecord::Base
|
350
|
+
attr_accessor :magicdescr
|
351
|
+
end
|
352
|
+
|
353
|
+
class FshugoTag < ActiveRecord::Base
|
354
|
+
attr_accessor :tag
|
355
|
+
end
|
356
|
+
|
357
|
+
class OsxTag < ActiveRecord::Base
|
358
|
+
attr_accessor :tag
|
359
|
+
end
|
360
|
+
|
361
|
+
class FileStructure < ActiveRecord::Base
|
362
|
+
attr_accessor :path, :bytes, :ctime, :mtime, :entity_type
|
363
|
+
attr_accessor :file_count, :item_count # used if referencing a directory
|
364
|
+
attr_accessor :mimetype, :magicdescr # used if referencing a file
|
365
|
+
attr_accessor :osx_tags, :fshugo_tags
|
366
|
+
|
367
|
+
serialize :osx_tags,Array # tags is text type, make it behave like an array
|
368
|
+
serialize :fshugo_tags,Array # tags is text type, make it behave like an array
|
369
|
+
end
|
370
|
+
|
371
|
+
inventory.mime_tab.val_map.each { |id, val| MimeType.create(:mimetype => val) }
|
372
|
+
inventory.magic_tab.val_map.each { |id, val| MagicDescription.create(:magicdescr => val) }
|
373
|
+
inventory.fshugo_tab.val_map.each { |id, val| FshugoTag.create(:tag => val) }
|
374
|
+
inventory.osx_tab.val_map.each { |id, val| OsxTag.create(:tag => val) }
|
375
|
+
inventory.file_structure.each { |fstruct| Fsinv.filestructure_to_db(fstruct) }
|
376
|
+
|
377
|
+
rescue SQLite3::Exception => e
|
378
|
+
puts e
|
379
|
+
rescue LoadError
|
380
|
+
puts "gem 'active_record' needed for DB creation. Install using 'gem install active_record'"
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
if Fsinv.options[:xml]
|
385
|
+
if Fsinv.options[:xml_file].nil?
|
386
|
+
Fsinv.options[:xml_file] = File.join(Dir.home,
|
387
|
+
if Fsinv.options[:name].nil?
|
388
|
+
"#{DEFAULT_NAME}.xml"
|
389
|
+
else
|
390
|
+
"#{Fsinv.options[:name]}.xml"
|
391
|
+
end
|
392
|
+
)
|
393
|
+
end
|
394
|
+
|
395
|
+
puts "writing XML to #{Fsinv.options[:xml_file]}" unless Fsinv.options[:silent]
|
396
|
+
|
397
|
+
#$progressbar = ProgressBar.new(inventory.file_structure.inject{ |arr,item| arr + item.item_count }, :bar, :counter)
|
398
|
+
xml_data = Fsinv.inventory_to_xml(inventory)
|
399
|
+
|
400
|
+
unless xml_data.nil?
|
401
|
+
begin
|
402
|
+
file = File.open(Fsinv.options[:xml_file], 'w')
|
403
|
+
file.write(xml_data)
|
404
|
+
rescue
|
405
|
+
puts "error writing XML file"
|
406
|
+
ensure
|
407
|
+
file.close unless file.nil?
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
if Fsinv.options[:print]
|
413
|
+
print_data = case Fsinv.options[:print_format]
|
414
|
+
when :json then Fsinv.inventory_to_json(inventory)
|
415
|
+
when :xml then Fsinv.inventory_to_xml(inventory)
|
416
|
+
when :yaml then Fsinv.inventory_to_yaml(inventory)
|
417
|
+
else nil
|
418
|
+
end
|
419
|
+
puts print_data unless print_data.nil?
|
420
|
+
end
|
data/fsinv.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fsinv'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'fsinv'
|
8
|
+
spec.version = Fsinv::VERSION
|
9
|
+
spec.date = '2014-08-14'
|
10
|
+
spec.summary = "file system inventory tool"
|
11
|
+
spec.description = "fsinv indexes file systems. It creates a complex inventory of one or more file system hierarchies and supports output formats like JSON, YAML, XML, binary (ruby marshall dump) and SQLite3 db (via active_record)."
|
12
|
+
spec.author = "Maximilian Irro"
|
13
|
+
spec.email = 'max@disposia.org'
|
14
|
+
spec.files = `git ls-files -z`.split("\x0")
|
15
|
+
spec.executables = ['fsinv']
|
16
|
+
spec.homepage = 'https://github.com/mpgirro/fsinv'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.required_ruby_version = '>= 1.9.3'
|
22
|
+
spec.add_dependency 'activerecord', '~> 3.2', '>=3.2.12'
|
23
|
+
spec.add_dependency 'mime-types', '~> 2.2', '>= 1.21'
|
24
|
+
spec.add_dependency 'nokogiri', '~> 1.6', '>= 1.6.2.1'
|
25
|
+
spec.add_dependency 'ruby-filemagic', '~> 0.6', '>= 0.6.0'
|
26
|
+
spec.add_dependency 'sqlite3', '~> 1.3', '>= 1.3.7'
|
27
|
+
spec.add_dependency 'ffi-xattr', '~> 0.1', '>= 0.1.2'
|
28
|
+
spec.add_dependency 'digest-crc', '~> 0.4', '>= 0.4.1'
|
29
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
|
2
|
+
require 'fsinv'
|
3
|
+
|
4
|
+
module Fsinv
|
5
|
+
|
6
|
+
class BaseDescription
|
7
|
+
|
8
|
+
include Fsinv
|
9
|
+
|
10
|
+
attr_accessor :path,:bytes,:ctime,:mtime,:osx_tags,:fshugo_tags
|
11
|
+
|
12
|
+
def initialize(path, reduced_scan = false)
|
13
|
+
@path = path.encode("UTF-8")
|
14
|
+
|
15
|
+
@bytes = 0
|
16
|
+
|
17
|
+
unless reduced_scan # don't do this if we only want to know file sizes (for pseudofiles, .git folders, etc)
|
18
|
+
@ctime = File.ctime(path) rescue (puts "error getting creation time for file #{path}" if Fsinv.options[:verbose])
|
19
|
+
@mtime = File.ctime(path) rescue (puts "error getting modification time for file #{path}" if Fsinv.options[:verbose])
|
20
|
+
@osx_tags = osx_tag_ids(path)
|
21
|
+
@fshugo_tags = fshugo_tag_ids(path)
|
22
|
+
else
|
23
|
+
@osx_tags = []
|
24
|
+
@fshugo_tags = []
|
25
|
+
end
|
26
|
+
end # initialize
|
27
|
+
|
28
|
+
def to_hash
|
29
|
+
p = sanitize_string(@path) rescue "path encoding broken" # there can be ArgumentError and UndefinedConversionError
|
30
|
+
h = {
|
31
|
+
"path" => p,
|
32
|
+
"bytes" => @bytes
|
33
|
+
}
|
34
|
+
h['ctime'] = @ctime unless @ctime.nil?
|
35
|
+
h['mtime'] = @mtime unless @mtime.nil?
|
36
|
+
h["osx_tags"] = @osx_tags unless @osx_tags.empty?
|
37
|
+
h["fshugo_tags"] = @fshugo_tags unless @fshugo_tags.empty?
|
38
|
+
return h
|
39
|
+
end # to_hash
|
40
|
+
|
41
|
+
def as_json(options = { })
|
42
|
+
return to_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_json(*a)
|
46
|
+
return as_json.to_json(*a )
|
47
|
+
end
|
48
|
+
|
49
|
+
def osx_tag_ids(file_path)
|
50
|
+
|
51
|
+
# well, we can only that if we are on osx, for the
|
52
|
+
# mechanism used is only avalable on that plattform
|
53
|
+
return [] unless /darwin/.match(RUBY_PLATFORM) # == osx
|
54
|
+
|
55
|
+
# if we had problem loading (or installing) ffi-xattr
|
56
|
+
# don't do the tags thing at all (fixes FreeBSD bug)
|
57
|
+
return [] unless Fsinv.options[:xattr]
|
58
|
+
|
59
|
+
# array with the kMDItemUserTags strings
|
60
|
+
# of the extended file attributes of 'path'
|
61
|
+
tags = %x{mdls -name 'kMDItemUserTags' -raw "#{file_path}"|tr -d "()\n"}.split(',').map { |tag|
|
62
|
+
tag.strip.gsub(/"(.*?)"/,"\\1")
|
63
|
+
}
|
64
|
+
# if there are now tags, mdls returns "null" -> we don't want this
|
65
|
+
if tags.length == 1 && tags[0] == "null"
|
66
|
+
return []
|
67
|
+
else
|
68
|
+
tag_ids = []
|
69
|
+
tags.each do |tag|
|
70
|
+
Fsinv.osx_tab.add(tag) unless Fsinv.osx_tab.contains?(tag)
|
71
|
+
tag_ids << Fsinv.osx_tab.get_id(tag)
|
72
|
+
end
|
73
|
+
return tag_ids
|
74
|
+
end
|
75
|
+
end # osx_tag_ids
|
76
|
+
|
77
|
+
|
78
|
+
def fshugo_tag_ids(file_path)
|
79
|
+
|
80
|
+
# if we had problem loading (or installing) ffi-xattr
|
81
|
+
# don't do the tags thing at all (fixes FreeBSD bug)
|
82
|
+
return [] unless Fsinv.options[:xattr]
|
83
|
+
|
84
|
+
xattr = Xattr.new(file_path)
|
85
|
+
unless xattr["fshugo"].nil?
|
86
|
+
tags = xattr["fshugo"].split(";")
|
87
|
+
tag_ids = []
|
88
|
+
tags.each do |tag|
|
89
|
+
Fsinv.fshugo_tab.add(tag) unless Fsinv.fshugo_tab.contains?(tag)
|
90
|
+
tag_ids << Fsinv.fshugo_tab.get_id(tag)
|
91
|
+
end
|
92
|
+
return tag_ids
|
93
|
+
#return tags
|
94
|
+
else
|
95
|
+
return []
|
96
|
+
end
|
97
|
+
end # fshugo_tag_ids
|
98
|
+
|
99
|
+
end # FileDefinition
|
100
|
+
|
101
|
+
end # Fsinv
|