dpn-bagit 0.2.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 +11 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +56 -0
- data/Rakefile +11 -0
- data/dpn-bagit.gemspec +27 -0
- data/lib/dpn/bagit.rb +12 -0
- data/lib/dpn/bagit/bag.rb +180 -0
- data/lib/dpn/bagit/bag/dpn_info_txt.rb +90 -0
- data/lib/dpn/bagit/defaults.config.yml +56 -0
- data/lib/dpn/bagit/ext/bagit.rb +2 -0
- data/lib/dpn/bagit/ext/bagit/bag.rb +18 -0
- data/lib/dpn/bagit/ext/bagit/valid.rb +43 -0
- data/lib/dpn/bagit/serialized_bag.rb +61 -0
- data/lib/dpn/bagit/settings.rb +39 -0
- data/lib/dpn/bagit/uuidv4_validator.rb +31 -0
- data/lib/dpn/bagit/version.rb +5 -0
- metadata +133 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: d5a6f986ec20bb19602ea9315ea0772533fc3ec8
|
|
4
|
+
data.tar.gz: d2d72c4c43f783b266f7a690d95a6fb2d688df1b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f45f698de918656e556a9369c161b97b02964a443c0514722ee57d98e9b9783d0c4055b5c70fa71e3d36e3035492ca0b3bb7aa5bcc6c9364ad8bb75e980ad034
|
|
7
|
+
data.tar.gz: b077d9d908ce84d2c6996c26480728eaa958133bc8273e195e03f0177d3b9ca848cb15dbc3fccb0714c5345c340e6fe45635a01ba89512f046397fd9cb06cdb0
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Bryan Hockey
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# DPN::Bagit
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
A ruby implementation of the DPN BagIt spec, which is itself an extension of the
|
|
6
|
+
[BagIt spec](https://confluence.ucop.edu/display/Curation/BagIt). Built on top of
|
|
7
|
+
[tipr/bagit](https://github.com/tipr/bagit).
|
|
8
|
+
|
|
9
|
+
This project is currently quite rough. At present, only deserialization from .tar
|
|
10
|
+
and validation work.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Add this line to your application's Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem 'dpn-bagit'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
And then execute:
|
|
22
|
+
|
|
23
|
+
$ bundle
|
|
24
|
+
|
|
25
|
+
Or install it yourself as:
|
|
26
|
+
|
|
27
|
+
$ gem install dpn-bagit
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require 'dpn-bagit'
|
|
33
|
+
|
|
34
|
+
bag = DPN::Bagit::Bag.new(location_of_bag)
|
|
35
|
+
if bag.empty? == false
|
|
36
|
+
if bag.valid?
|
|
37
|
+
fixity = bag.fixity
|
|
38
|
+
uuid = bag.uuid
|
|
39
|
+
size = bag.size
|
|
40
|
+
else
|
|
41
|
+
puts bag.errors
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
bag = DPN::Bagit::SerializedBag.new(location_of_tarball).unserialize!
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## Contributing
|
|
51
|
+
|
|
52
|
+
1. Fork it ( https://github.com/malakai97/dpn-bagit/fork )
|
|
53
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
54
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
55
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
56
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/dpn-bagit.gemspec
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'dpn/bagit/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "dpn-bagit"
|
|
8
|
+
spec.version = DPN::Bagit::VERSION
|
|
9
|
+
spec.authors = ["Bryan Hockey"]
|
|
10
|
+
spec.email = ["bhock@umich.edu"]
|
|
11
|
+
|
|
12
|
+
spec.summary = %q{An implementation of the DPN Bagit spec.}
|
|
13
|
+
# spec.homepage = "TODO: Put your gem's website or public repo URL here."
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
17
|
+
spec.bindir = "exe"
|
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.8"
|
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
23
|
+
spec.add_development_dependency "test-unit"
|
|
24
|
+
|
|
25
|
+
spec.add_runtime_dependency "configliere"
|
|
26
|
+
spec.add_runtime_dependency "bagit", "~>0.3.2"
|
|
27
|
+
end
|
data/lib/dpn/bagit.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require "bundler/setup"
|
|
2
|
+
require "dpn/bagit/version"
|
|
3
|
+
require "dpn/bagit/bag"
|
|
4
|
+
require "dpn/bagit/serialized_bag"
|
|
5
|
+
require "dpn/bagit/settings"
|
|
6
|
+
require "dpn/bagit/uuidv4_validator"
|
|
7
|
+
|
|
8
|
+
module DPN
|
|
9
|
+
module Bagit
|
|
10
|
+
# Your code goes here...
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
require "find"
|
|
2
|
+
require "dpn/bagit/settings"
|
|
3
|
+
require "dpn/bagit/ext/bagit"
|
|
4
|
+
require "dpn/bagit/uuidv4_validator"
|
|
5
|
+
require "dpn/bagit/bag/dpn_info_txt"
|
|
6
|
+
|
|
7
|
+
# A wrapper for an unserialized Bag-It bag on disk. Does not support serialized bags.
|
|
8
|
+
# Once created, a Bag object will not change with changes made to the underlying filesystem
|
|
9
|
+
# bag; in that case, a new Bag object should be created.
|
|
10
|
+
class DPN::Bagit::Bag
|
|
11
|
+
private
|
|
12
|
+
def initialize(_location)
|
|
13
|
+
@settings = DPN::Bagit::Settings.instance.config
|
|
14
|
+
@bag = ::BagIt::Bag.new(_location)
|
|
15
|
+
@location = _location
|
|
16
|
+
@cachedValidity = nil
|
|
17
|
+
@cachedFixity = nil
|
|
18
|
+
@cachedSize = nil
|
|
19
|
+
@validationErrors = []
|
|
20
|
+
@dpnObjectID = nil
|
|
21
|
+
|
|
22
|
+
@dpnInfo = DPNInfoTxt.new(self.dpn_info_file_location())
|
|
23
|
+
if @dpnInfo[:dpnObjectID] == nil
|
|
24
|
+
@dpnObjectID = File.basename(_location)
|
|
25
|
+
else
|
|
26
|
+
@dpnObjectID = @dpnInfo[:dpnObjectID]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
public
|
|
32
|
+
# Get the net fixity of the Bag.
|
|
33
|
+
# @param algorithm [Symbol] Algorithm to use.
|
|
34
|
+
# @return [String] The fixity of the tagmanifest-<alg>.txt file.
|
|
35
|
+
def fixity(algorithm)
|
|
36
|
+
if @cachedFixity == nil
|
|
37
|
+
case algorithm
|
|
38
|
+
when :sha256
|
|
39
|
+
digest = Digest::SHA256
|
|
40
|
+
path = File.join(@location, "tagmanifest-sha256.txt")
|
|
41
|
+
if File.exists?(path)
|
|
42
|
+
@cachedFixity = digest.file(path).hexdigest
|
|
43
|
+
else
|
|
44
|
+
@cachedFixity = ""
|
|
45
|
+
@cachedValidity = false
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
raise ArgumentError, "Unknown algorithm."
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
return @cachedFixity
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Returns the total size of the Bag.
|
|
57
|
+
# @return [Fixnum] Apparent size of the Bag in bytes.
|
|
58
|
+
def size()
|
|
59
|
+
if @cachedSize == nil
|
|
60
|
+
size = 0
|
|
61
|
+
Find.find(self.location) do |f|
|
|
62
|
+
if File.file?(f) or File.directory?(f)
|
|
63
|
+
size += File.size(f)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
@cachedSize = size
|
|
67
|
+
end
|
|
68
|
+
return @cachedSize
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Returns the local file location of the Bag.
|
|
73
|
+
# @return [String] The location, which can be relative or absolute.
|
|
74
|
+
def location()
|
|
75
|
+
return @location
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Returns the uuid of the bag, according to dpn-info.txt.
|
|
80
|
+
# @return [String]
|
|
81
|
+
def uuid()
|
|
82
|
+
return @dpnObjectID
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Checks that all required files are present, no extraneous files are present, and all file digests
|
|
87
|
+
# match manifests.
|
|
88
|
+
# @return [Boolean] True if valid, false otherwise.
|
|
89
|
+
def valid?()
|
|
90
|
+
if @cachedValidity == nil
|
|
91
|
+
if @bag.valid? == false
|
|
92
|
+
#@validationErrors.push("Underlying bag is invalid.")
|
|
93
|
+
@validationErrors.push(@bag.errors.full_messages)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if File.exists?(@bag.fetch_txt_file) == true
|
|
97
|
+
@validationErrors.push("The file fetch.txt is present and unsupported.")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if Pathname.new(@bag.bag_dir).basename.to_s != @dpnInfo[:dpnObjectID]
|
|
101
|
+
@validationErrors.push("The name of the root directory does not match the #{@settings[:bag][:dpn_info][:dpnObjectID][:name]}.")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if File.exists?(@bag.manifest_file("sha256")) == true
|
|
105
|
+
if File.readable?(@bag.manifest_file("sha256")) == false
|
|
106
|
+
@validationErrors.push("The file manifest-sha256.txt exists but cannot be read.")
|
|
107
|
+
end
|
|
108
|
+
else
|
|
109
|
+
@validationErrors.push("The file manifest-sha256.txt does not exist.")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
if File.exists?(@bag.tagmanifest_file("sha256")) == true
|
|
113
|
+
if File.readable?(@bag.tagmanifest_file("sha256")) == false
|
|
114
|
+
@validationErrors.push("The file tagmanifest-sha256.txt exists but cannot be read.")
|
|
115
|
+
end
|
|
116
|
+
else
|
|
117
|
+
@validationErrors.push("The file tagmanifest-sha256.txt does not exist.")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
if @dpnInfo[:version].to_i <= 0
|
|
121
|
+
@validationErrors.push("Version must be > 0.")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
uuidValidator = DPN::Bagit::UUID4Validator.new(true)
|
|
125
|
+
if uuidValidator.isValid?(@dpnInfo[:dpnObjectID]) == false
|
|
126
|
+
@validationErrors.push("#{@settings[:bag][:dpn_info][:dpnObjectID][:name]} with value \"#{@dpnInfo[:dpnObjectID]}\" is not a valid UUIDv4.")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
if uuidValidator.isValid?(@dpnInfo[:firstVersionObjectID]) == false
|
|
130
|
+
@validationErrors.push("#{@settings[:bag][:dpn_info][:firstVersionObjectID][:name]} with value \"#{@dpnInfo[:firstVersionObjectID]}\" is not a valid UUIDv4.")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
if @dpnInfo[:previousVersionObjectID] != "" && uuidValidator.isValid?(@dpnInfo[:previousVersionObjectID]) == false
|
|
134
|
+
@validationErrors.push("#{@settings[:bag][:dpn_info][:previousVersionObjectID][:name]} with value \"#{@dpnInfo[:previousVersionObjectID]}\" is not a valid UUIDv4.")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
@dpnInfo[:rightsObjectIDs].each do |id|
|
|
138
|
+
if uuidValidator.isValid?(id) == false
|
|
139
|
+
@validationErrors.push("#{@settings[:bag][:dpn_info][:rightsObjectIDs][:name]} value of \"#{id}\" is not a valid UUIDv4.")
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
@dpnInfo[:interpretiveObjectIDs].each do |id|
|
|
144
|
+
if uuidValidator.isValid?(id) == false
|
|
145
|
+
@validationErrors.push("#{@settings[:bag][:dpn_info][:interpretiveObjectIDs][:name]} value of \"#{id}\" is not a valid UUIDv4.")
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
if @validationErrors.empty? == true and @dpnInfo.getErrors.empty? == true
|
|
151
|
+
@cachedValidity = true
|
|
152
|
+
else
|
|
153
|
+
@cachedValidity = false
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
return @cachedValidity
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# Returns validation errors. The list is not populated until a call to {#isValid?} has been made.
|
|
162
|
+
# @return [Array<String>] The errors.
|
|
163
|
+
def errors()
|
|
164
|
+
return @dpnInfo.getErrors() + @validationErrors
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# Returns true if the Bag contains no files.
|
|
169
|
+
# @return [Boolean] True if empty, false otherwise.
|
|
170
|
+
def empty?()
|
|
171
|
+
return @bag.empty?
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
protected
|
|
175
|
+
# Get the path of the dpn-info.txt file for this bag.
|
|
176
|
+
# @return [String]
|
|
177
|
+
def dpn_info_file_location()
|
|
178
|
+
return File.join(@bag.bag_dir, @settings[:bag][:dpn_dir], @settings[:bag][:dpn_info][:name])
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require "dpn/bagit/settings"
|
|
2
|
+
|
|
3
|
+
class DPN::Bagit::Bag
|
|
4
|
+
# A wrapper for the dpn-info.txt file within the bag. Once created, it does not change with
|
|
5
|
+
# changes made to the underlying txt file; in that case, a new Bag should be created.
|
|
6
|
+
# @private
|
|
7
|
+
class DPNInfoTxt
|
|
8
|
+
private
|
|
9
|
+
def initialize(fileLocation)
|
|
10
|
+
@settings = DPN::Bagit::Settings.instance.config
|
|
11
|
+
@dpnInfoKeysArrays = @settings[:bag][:dpn_info][:arrays]
|
|
12
|
+
@dpnInfoKeysNonArrays = @settings[:bag][:dpn_info][:non_arrays]
|
|
13
|
+
@dpnInfoErrors = []
|
|
14
|
+
@dpnInfo = {}
|
|
15
|
+
self.validate!(fileLocation)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
def validate!(fileLocation)
|
|
20
|
+
@dpnInfoKeysArrays.each do |key|
|
|
21
|
+
@dpnInfo[key.to_sym] = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if File.exists?(fileLocation)
|
|
25
|
+
if File.readable?(fileLocation)
|
|
26
|
+
contents = nil
|
|
27
|
+
File.open(fileLocation, 'r') do |file|
|
|
28
|
+
contents = file.read
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@dpnInfoKeysNonArrays.each do |key|
|
|
32
|
+
key = key.to_sym #does nothing if settings correctly configured
|
|
33
|
+
pattern = @settings[:bag][:dpn_info][key][:regex] + @settings[:bag][:dpn_info][:capture]
|
|
34
|
+
regex = Regexp.new(pattern)
|
|
35
|
+
match = regex.match(contents)
|
|
36
|
+
if match == nil or match[1] == nil
|
|
37
|
+
@dpnInfoErrors.push("dpn-info.txt does not have the tag #{@settings[:bag][:dpn_info][key][:name]}")
|
|
38
|
+
else
|
|
39
|
+
@dpnInfo[key] = match[1]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if @dpnInfo[key].respond_to?(:strip!)
|
|
43
|
+
@dpnInfo[key].strip!
|
|
44
|
+
@dpnInfo[key].downcase!
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
@dpnInfoKeysArrays.each do |key|
|
|
50
|
+
key = key.to_sym #does nothing if settings correctly configured
|
|
51
|
+
pattern = @settings[:bag][:dpn_info][key][:regex] + @settings[:bag][:dpn_info][:capture]
|
|
52
|
+
regex = Regexp.new(pattern)
|
|
53
|
+
if regex.match(contents) == nil
|
|
54
|
+
@dpnInfoErrors.push("dpn-info.txt does not have the tag #{@settings[:bag][:dpn_info][key][:name]}")
|
|
55
|
+
@dpnInfo[key] = []
|
|
56
|
+
else
|
|
57
|
+
@dpnInfo[key] = contents.scan(regex)
|
|
58
|
+
@dpnInfo[key].flatten!
|
|
59
|
+
@dpnInfo[key].each_index do |i|
|
|
60
|
+
@dpnInfo[key][i].strip!
|
|
61
|
+
@dpnInfo[key][i].downcase!
|
|
62
|
+
if @dpnInfo[key][i] == ''
|
|
63
|
+
@dpnInfo[key].delete_at(i)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
@dpnInfoErrors.push("dpn-info.txt exists, but cannot be opened for reading.")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
else
|
|
73
|
+
@dpnInfoErrors.push("dpn-info.txt cannot be found.")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
public
|
|
78
|
+
# Returns a list of any errors encountered on creation and validation.
|
|
79
|
+
# @return [Array<String]
|
|
80
|
+
def getErrors()
|
|
81
|
+
return @dpnInfoErrors
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Get the value associated with the given field.
|
|
85
|
+
# @param key [Symbol]
|
|
86
|
+
def [](key)
|
|
87
|
+
return @dpnInfo[key.to_sym]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
:bag:
|
|
2
|
+
:max_size: 268435456000 # 250 GB
|
|
3
|
+
:dpn_dir: dpn-tags
|
|
4
|
+
:dpn_info:
|
|
5
|
+
:name: dpn-info.txt
|
|
6
|
+
:non_arrays:
|
|
7
|
+
- dpnObjectID
|
|
8
|
+
- localName
|
|
9
|
+
- firstNodeName
|
|
10
|
+
- firstNodeAddress
|
|
11
|
+
- firstNodeContactName
|
|
12
|
+
- firstNodeContactEmail
|
|
13
|
+
- version
|
|
14
|
+
- previousVersionObjectID
|
|
15
|
+
- firstVersionObjectID
|
|
16
|
+
- objectTypeName
|
|
17
|
+
:arrays:
|
|
18
|
+
- rightsObjectIDs
|
|
19
|
+
- interpretiveObjectIDs
|
|
20
|
+
:capture: "\\:[ \t]*(.*)"
|
|
21
|
+
:dpnObjectID:
|
|
22
|
+
:name: DPN-Object-ID
|
|
23
|
+
:regex: DPN-Object-ID
|
|
24
|
+
:localName:
|
|
25
|
+
:name: Local-ID
|
|
26
|
+
:regex: Local-ID
|
|
27
|
+
:firstNodeName:
|
|
28
|
+
:name: First-Node-Name
|
|
29
|
+
:regex: First-Node-Name
|
|
30
|
+
:firstNodeAddress:
|
|
31
|
+
:name: First-Node-Address
|
|
32
|
+
:regex: First-Node-Address
|
|
33
|
+
:firstNodeContactName:
|
|
34
|
+
:name: First-Node-Contact-Name
|
|
35
|
+
:regex: First-Node-Contact-Name
|
|
36
|
+
:firstNodeContactEmail:
|
|
37
|
+
:name: First-Node-Contact-Email
|
|
38
|
+
:regex: First-Node-Contact-Email
|
|
39
|
+
:version:
|
|
40
|
+
:name: Version-Number
|
|
41
|
+
:regex: Version-Number
|
|
42
|
+
:previousVersionObjectID:
|
|
43
|
+
:name: Previous-Version-Object-ID
|
|
44
|
+
:regex: Previous-Version-Object-ID
|
|
45
|
+
:firstVersionObjectID:
|
|
46
|
+
:name: First-Version-Object-ID
|
|
47
|
+
:regex: First-Version-Object-ID
|
|
48
|
+
:objectTypeName:
|
|
49
|
+
:name: Object-Type
|
|
50
|
+
:regex: Object-Type
|
|
51
|
+
:interpretiveObjectIDs:
|
|
52
|
+
:name: Interpretive-Object-ID
|
|
53
|
+
:regex: Interpretive-Object-ID
|
|
54
|
+
:rightsObjectIDs:
|
|
55
|
+
:name: Rights-Object-ID
|
|
56
|
+
:regex: Rights-Object-ID
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'bagit/fetch'
|
|
2
|
+
require 'bagit/file'
|
|
3
|
+
require 'bagit/info'
|
|
4
|
+
require 'bagit/manifest'
|
|
5
|
+
require 'bagit/string'
|
|
6
|
+
require 'bagit/valid'
|
|
7
|
+
|
|
8
|
+
module BagIt
|
|
9
|
+
|
|
10
|
+
# Represents the state of a bag on a filesystem
|
|
11
|
+
class Bag
|
|
12
|
+
|
|
13
|
+
# Return the paths to each bag file relative to bag_dir
|
|
14
|
+
def bag_files
|
|
15
|
+
Dir.glob(File.join(data_dir,'**','*'), File::FNM_DOTMATCH).select { |f| File.file? f}
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'bagit'
|
|
2
|
+
|
|
3
|
+
module BagIt
|
|
4
|
+
module Validity
|
|
5
|
+
def consistent?
|
|
6
|
+
(manifest_files|tagmanifest_files).each do |mf|
|
|
7
|
+
# get the algorithm implementation
|
|
8
|
+
readAlgo = /manifest-(.+).txt$/.match(File.basename(mf))[1]
|
|
9
|
+
algo = case readAlgo
|
|
10
|
+
when /sha1/i
|
|
11
|
+
Digest::SHA1
|
|
12
|
+
when /md5/i
|
|
13
|
+
Digest::MD5
|
|
14
|
+
when /sha256/i
|
|
15
|
+
Digest::SHA256
|
|
16
|
+
when /sha384/i
|
|
17
|
+
Digest::SHA384
|
|
18
|
+
when /sha512/i
|
|
19
|
+
Digest::SHA512
|
|
20
|
+
else
|
|
21
|
+
errors.add :consistency, "Algorithm #{readAlgo} is invalid or unsupported."
|
|
22
|
+
return false
|
|
23
|
+
end
|
|
24
|
+
# Check every file in the manifest
|
|
25
|
+
File.open(mf) do |io|
|
|
26
|
+
io.each_line do |line|
|
|
27
|
+
expected, path = line.chomp.split(/\s+/, 2)
|
|
28
|
+
file = File.join(bag_dir, path)
|
|
29
|
+
if File.exist? file
|
|
30
|
+
actual = algo.file(file).hexdigest
|
|
31
|
+
if expected != actual
|
|
32
|
+
errors.add :consistency, "expected #{file} to have #{algo}: #{expected}, actual is #{actual}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
errors.on(:consistency).nil?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require "dpn/bagit/bag"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# A wrapper for a serialized Bag-It bag on disk.
|
|
5
|
+
# Once created, will not change with changes made to the underlying filesystem
|
|
6
|
+
# bag; in that case, a new object should be created.
|
|
7
|
+
class DPN::Bagit::SerializedBag
|
|
8
|
+
|
|
9
|
+
# Create a SerializedBag
|
|
10
|
+
# @param _location [String] Path to the file.
|
|
11
|
+
def initialize(_location)
|
|
12
|
+
if File.exists?(_location) == false
|
|
13
|
+
raise ArgumentError, "File does not exist!"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@location = _location
|
|
17
|
+
@cachedFixity = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Returns the size of the serialized bag.
|
|
22
|
+
# @return [Fixnum] Apparent size of the bag in bytes.
|
|
23
|
+
def size()
|
|
24
|
+
return File.size(@location)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Returns the local file location of the Bag.
|
|
29
|
+
# @return [String] The location, which can be relative or absolute.
|
|
30
|
+
def location()
|
|
31
|
+
return @location
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Returns the fixity of the serialized version of the bag.
|
|
36
|
+
# @param algorithm [Symbol] The algorithm to use for calculation.
|
|
37
|
+
# @return [String] The fixity of the file.
|
|
38
|
+
def fixity(algorithm)
|
|
39
|
+
if @cachedFixity == nil
|
|
40
|
+
case algorithm
|
|
41
|
+
when :sha256
|
|
42
|
+
digest = Digest::SHA256
|
|
43
|
+
else
|
|
44
|
+
raise ArgumentError, "Unknown algorithm."
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@cachedFixity = digest.file(@location).hexdigest
|
|
48
|
+
end
|
|
49
|
+
return @cachedFixity
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Unserialize the bag into the local filesystem. This object
|
|
54
|
+
# is unchanged. Requires sufficient permissions and disk space.
|
|
55
|
+
# @return [Bag] A bag made from the unserialized object.
|
|
56
|
+
def unserialize!()
|
|
57
|
+
`/bin/tar -xf #{@location} -C #{File.dirname(@location)}`
|
|
58
|
+
name = File.basename(@location).to_s.sub(/\..*/,'') # remove the file extension
|
|
59
|
+
return DPN::Bagit::Bag.new(File.join(File.dirname(@location), name))
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require "configliere"
|
|
2
|
+
require "bundler"
|
|
3
|
+
|
|
4
|
+
# A class that manages the various settings required by dpn-bagit.
|
|
5
|
+
class DPN::Bagit::Settings
|
|
6
|
+
include Singleton
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@config = nil
|
|
10
|
+
config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
public
|
|
14
|
+
def config
|
|
15
|
+
if @config == nil
|
|
16
|
+
@config = Configliere::Param.new
|
|
17
|
+
@config[:root] = get_project_root
|
|
18
|
+
@config.read File.join @config[:root], "/lib/dpn/bagit/defaults.config.yml"
|
|
19
|
+
@config.resolve!
|
|
20
|
+
end
|
|
21
|
+
@config
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def [](key)
|
|
25
|
+
config[key]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def []=(key,value)
|
|
29
|
+
config[key] = value
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
# Get the path to the project root.
|
|
34
|
+
def get_project_root
|
|
35
|
+
return Bundler.rubygems.find_name('dpn-bagit').first.full_gem_path
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# A class that validates whether a string is a v4 UUID or not.
|
|
2
|
+
class DPN::Bagit::UUID4Validator
|
|
3
|
+
# Create a new validator.
|
|
4
|
+
# @param dashes [Boolean, NilClass]
|
|
5
|
+
# If set to true, dashes are required.
|
|
6
|
+
# If set to false, dashes are disallowed.
|
|
7
|
+
# If set to nil, dashes are optional.
|
|
8
|
+
# @return [UUID4Validator]
|
|
9
|
+
def initialize(dashes = nil)
|
|
10
|
+
if dashes == nil
|
|
11
|
+
@pattern = /^[A-Fa-f0-9]{8}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{4}-?[A-Fa-f0-9]{12}\Z/
|
|
12
|
+
elsif dashes == true
|
|
13
|
+
@pattern = /^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}\Z/
|
|
14
|
+
else
|
|
15
|
+
@pattern = /^[A-Fa-f0-9]{8}[A-Fa-f0-9]{4}[A-Fa-f0-9]{4}[A-Fa-f0-9]{4}[A-Fa-f0-9]{12}\Z/
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Check if the given string is valid according to this validator.
|
|
20
|
+
# This test is case-insensitive.
|
|
21
|
+
# @param uuid [String] The uuid to test.
|
|
22
|
+
# @return [Boolean]
|
|
23
|
+
def isValid?(uuid)
|
|
24
|
+
if @pattern.match(uuid)
|
|
25
|
+
#matches a v4 uuid, case-insensitive, with or without dashes.
|
|
26
|
+
return true
|
|
27
|
+
else
|
|
28
|
+
return false
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dpn-bagit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Bryan Hockey
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2015-05-01 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.8'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.8'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: test-unit
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: configliere
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: bagit
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 0.3.2
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 0.3.2
|
|
83
|
+
description:
|
|
84
|
+
email:
|
|
85
|
+
- bhock@umich.edu
|
|
86
|
+
executables: []
|
|
87
|
+
extensions: []
|
|
88
|
+
extra_rdoc_files: []
|
|
89
|
+
files:
|
|
90
|
+
- ".gitignore"
|
|
91
|
+
- ".rspec"
|
|
92
|
+
- ".travis.yml"
|
|
93
|
+
- Gemfile
|
|
94
|
+
- LICENSE.txt
|
|
95
|
+
- README.md
|
|
96
|
+
- Rakefile
|
|
97
|
+
- dpn-bagit.gemspec
|
|
98
|
+
- lib/dpn/bagit.rb
|
|
99
|
+
- lib/dpn/bagit/bag.rb
|
|
100
|
+
- lib/dpn/bagit/bag/dpn_info_txt.rb
|
|
101
|
+
- lib/dpn/bagit/defaults.config.yml
|
|
102
|
+
- lib/dpn/bagit/ext/bagit.rb
|
|
103
|
+
- lib/dpn/bagit/ext/bagit/bag.rb
|
|
104
|
+
- lib/dpn/bagit/ext/bagit/valid.rb
|
|
105
|
+
- lib/dpn/bagit/serialized_bag.rb
|
|
106
|
+
- lib/dpn/bagit/settings.rb
|
|
107
|
+
- lib/dpn/bagit/uuidv4_validator.rb
|
|
108
|
+
- lib/dpn/bagit/version.rb
|
|
109
|
+
homepage:
|
|
110
|
+
licenses:
|
|
111
|
+
- MIT
|
|
112
|
+
metadata: {}
|
|
113
|
+
post_install_message:
|
|
114
|
+
rdoc_options: []
|
|
115
|
+
require_paths:
|
|
116
|
+
- lib
|
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - ">="
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '0'
|
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
|
+
requirements:
|
|
124
|
+
- - ">="
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
version: '0'
|
|
127
|
+
requirements: []
|
|
128
|
+
rubyforge_project:
|
|
129
|
+
rubygems_version: 2.4.5
|
|
130
|
+
signing_key:
|
|
131
|
+
specification_version: 4
|
|
132
|
+
summary: An implementation of the DPN Bagit spec.
|
|
133
|
+
test_files: []
|