ro-bundle 0.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 +15 -0
- data/.gitignore +11 -0
- data/.ruby-env +1 -0
- data/.ruby-gemset +2 -0
- data/.ruby-version +2 -0
- data/.travis.yml +11 -0
- data/Changes.rdoc +129 -0
- data/Gemfile +11 -0
- data/Licence.rdoc +29 -0
- data/Rakefile +29 -0
- data/ReadMe.rdoc +57 -0
- data/bin/dir2ro +48 -0
- data/bin/ro-bundle-info +45 -0
- data/bin/verify-ro-bundle +27 -0
- data/bin/zip2ro +57 -0
- data/lib/ro-bundle.rb +45 -0
- data/lib/ro-bundle/exceptions.rb +30 -0
- data/lib/ro-bundle/file.rb +323 -0
- data/lib/ro-bundle/ro/agent.rb +73 -0
- data/lib/ro-bundle/ro/aggregate.rb +107 -0
- data/lib/ro-bundle/ro/annotation.rb +89 -0
- data/lib/ro-bundle/ro/directory.rb +120 -0
- data/lib/ro-bundle/ro/manifest.rb +338 -0
- data/lib/ro-bundle/ro/provenance.rb +153 -0
- data/lib/ro-bundle/util.rb +57 -0
- data/lib/ro-bundle/version.rb +13 -0
- data/ro-bundle.gemspec +43 -0
- data/test/data/HelloAnyone.robundle +0 -0
- data/test/data/empty-manifest.json +1 -0
- data/test/data/example3-manifest.json +40 -0
- data/test/data/invalid-manifest.json +5 -0
- data/test/data/invalid-manifest.robundle +0 -0
- data/test/helpers/fake_manifest.rb +23 -0
- data/test/helpers/fake_provenance.rb +32 -0
- data/test/helpers/list_tests.rb +22 -0
- data/test/tc_add_annotation.rb +571 -0
- data/test/tc_agent.rb +63 -0
- data/test/tc_aggregate.rb +116 -0
- data/test/tc_annotation.rb +84 -0
- data/test/tc_create.rb +170 -0
- data/test/tc_manifest.rb +221 -0
- data/test/tc_provenance.rb +121 -0
- data/test/tc_read.rb +66 -0
- data/test/tc_remove.rb +140 -0
- data/test/tc_util.rb +64 -0
- data/test/ts_ro_bundle.rb +28 -0
- metadata +217 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
#------------------------------------------------------------------------------
|
2
|
+
# Copyright (c) 2014 The University of Manchester, UK.
|
3
|
+
#
|
4
|
+
# BSD Licenced. See LICENCE.rdoc for details.
|
5
|
+
#
|
6
|
+
# Author: Robert Haines
|
7
|
+
#------------------------------------------------------------------------------
|
8
|
+
|
9
|
+
#
|
10
|
+
module ROBundle
|
11
|
+
|
12
|
+
# A class to represent an aggregated resource in a Research Object. It holds
|
13
|
+
# standard meta-data for either file or URI resources. An aggregate can only
|
14
|
+
# represent a file OR a URI resource, not both at once.
|
15
|
+
class Aggregate
|
16
|
+
include Provenance
|
17
|
+
|
18
|
+
# :call-seq:
|
19
|
+
# new(filename, mediatype = nil)
|
20
|
+
# new(URI)
|
21
|
+
#
|
22
|
+
# Create a new file or URI aggregate.
|
23
|
+
def initialize(object, second = nil)
|
24
|
+
@structure = {}
|
25
|
+
|
26
|
+
if object.instance_of?(Hash)
|
27
|
+
init_json(object)
|
28
|
+
else
|
29
|
+
init_file_or_uri(object)
|
30
|
+
|
31
|
+
if @structure[:file]
|
32
|
+
@structure[:mediatype] = second
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# :call-seq:
|
38
|
+
# file
|
39
|
+
#
|
40
|
+
# The path of this aggregate. It should start with '/'.
|
41
|
+
def file
|
42
|
+
@structure[:file]
|
43
|
+
end
|
44
|
+
|
45
|
+
# :call-seq:
|
46
|
+
# file_entry
|
47
|
+
#
|
48
|
+
# The path of this aggregate in "rubyzip" format, i.e. no leading '/'.
|
49
|
+
def file_entry
|
50
|
+
Util.strip_leading_slash(file)
|
51
|
+
end
|
52
|
+
|
53
|
+
# :call-seq:
|
54
|
+
# uri
|
55
|
+
#
|
56
|
+
# The URI of this aggregate. It should be an absolute URI.
|
57
|
+
def uri
|
58
|
+
@structure[:uri]
|
59
|
+
end
|
60
|
+
|
61
|
+
# :call-seq:
|
62
|
+
# mediatype
|
63
|
+
#
|
64
|
+
# For a file aggregate, its
|
65
|
+
# {IANA media type}[http://www.iana.org/assignments/media-types].
|
66
|
+
def mediatype
|
67
|
+
@structure[:mediatype]
|
68
|
+
end
|
69
|
+
|
70
|
+
# :call-seq:
|
71
|
+
# to_json(options = nil) -> String
|
72
|
+
#
|
73
|
+
# Write this Aggregate out as a json string. Takes the same options as
|
74
|
+
# JSON#generate.
|
75
|
+
def to_json(*a)
|
76
|
+
Util.clean_json(@structure).to_json(*a)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def structure
|
82
|
+
@structure
|
83
|
+
end
|
84
|
+
|
85
|
+
def init_json(object)
|
86
|
+
init_file_or_uri(object[:file] || object[:uri])
|
87
|
+
@structure = init_provenance_defaults(object)
|
88
|
+
|
89
|
+
if @structure[:file]
|
90
|
+
@structure[:mediatype] = object[:mediatype]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def init_file_or_uri(object)
|
95
|
+
if object.is_a?(String) && !Util.is_absolute_uri?(object)
|
96
|
+
name = object.start_with?("/") ? object : "/#{object}"
|
97
|
+
@structure[:file] = name
|
98
|
+
elsif Util.is_absolute_uri?(object)
|
99
|
+
@structure[:uri] = object.to_s
|
100
|
+
else
|
101
|
+
raise InvalidAggregateError.new(object)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
#------------------------------------------------------------------------------
|
2
|
+
# Copyright (c) 2014 The University of Manchester, UK.
|
3
|
+
#
|
4
|
+
# BSD Licenced. See LICENCE.rdoc for details.
|
5
|
+
#
|
6
|
+
# Author: Robert Haines
|
7
|
+
#------------------------------------------------------------------------------
|
8
|
+
|
9
|
+
#
|
10
|
+
module ROBundle
|
11
|
+
|
12
|
+
# A class to represent an Annotation in a Research Object.
|
13
|
+
class Annotation
|
14
|
+
include Provenance
|
15
|
+
|
16
|
+
# :call-seq:
|
17
|
+
# new(target, content = nil)
|
18
|
+
#
|
19
|
+
# Create a new Annotation with the specified "about" identifier. A new
|
20
|
+
# annotation ID is generated and set for the new annotation. The +content+
|
21
|
+
# parameter can be optionally used to set the file or URI that holds the
|
22
|
+
# body of the annotation.
|
23
|
+
#
|
24
|
+
# An annotation id is a UUID prefixed with "urn:uuid" as per
|
25
|
+
# {RFC4122}[http://www.ietf.org/rfc/rfc4122.txt].
|
26
|
+
def initialize(object, content = nil)
|
27
|
+
if object.instance_of?(Hash)
|
28
|
+
@structure = object
|
29
|
+
init_provenance_defaults(@structure)
|
30
|
+
else
|
31
|
+
@structure = {}
|
32
|
+
@structure[:about] = object
|
33
|
+
@structure[:annotation] = UUID.generate(:urn)
|
34
|
+
@structure[:content] = content
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# :call-seq:
|
39
|
+
# target
|
40
|
+
#
|
41
|
+
# The identifier for the annotated resource. This is considered the target
|
42
|
+
# of the annotation, that is the resource the annotation content is
|
43
|
+
# "somewhat about".
|
44
|
+
def target
|
45
|
+
@structure[:about]
|
46
|
+
end
|
47
|
+
|
48
|
+
# :call-seq:
|
49
|
+
# content
|
50
|
+
#
|
51
|
+
# The identifier for a resource that contains the body of the annotation.
|
52
|
+
def content
|
53
|
+
@structure[:content]
|
54
|
+
end
|
55
|
+
|
56
|
+
# :call-seq:
|
57
|
+
# content = new_content
|
58
|
+
#
|
59
|
+
# Set the content of this annotation.
|
60
|
+
def content=(new_content)
|
61
|
+
@structure[:content] = new_content
|
62
|
+
end
|
63
|
+
|
64
|
+
# :call-seq:
|
65
|
+
# annotation_id -> String
|
66
|
+
#
|
67
|
+
# Return the annotation id of this Annotation.
|
68
|
+
def annotation_id
|
69
|
+
@structure[:annotation]
|
70
|
+
end
|
71
|
+
|
72
|
+
# :call-seq:
|
73
|
+
# to_json(options = nil) -> String
|
74
|
+
#
|
75
|
+
# Write this Annotation out as a json string. Takes the same options as
|
76
|
+
# JSON#generate.
|
77
|
+
def to_json(*a)
|
78
|
+
Util.clean_json(@structure).to_json(*a)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def structure
|
84
|
+
@structure
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
#------------------------------------------------------------------------------
|
2
|
+
# Copyright (c) 2014 The University of Manchester, UK.
|
3
|
+
#
|
4
|
+
# BSD Licenced. See LICENCE.rdoc for details.
|
5
|
+
#
|
6
|
+
# Author: Robert Haines
|
7
|
+
#------------------------------------------------------------------------------
|
8
|
+
|
9
|
+
#
|
10
|
+
module ROBundle
|
11
|
+
|
12
|
+
# The managed .ro directory entry of a Research Object.
|
13
|
+
#
|
14
|
+
# For internal use only.
|
15
|
+
class RODir < ZipContainer::ManagedDirectory
|
16
|
+
|
17
|
+
DIR_NAME = ".ro" # :nodoc:
|
18
|
+
|
19
|
+
# :call-seq:
|
20
|
+
# new(manifest)
|
21
|
+
#
|
22
|
+
# Create a new .ro managed directory entry with the specified manifest
|
23
|
+
# file object.
|
24
|
+
def initialize(manifest)
|
25
|
+
@manifest = manifest
|
26
|
+
@annotations_directory = AnnotationsDir.new
|
27
|
+
|
28
|
+
super(DIR_NAME, :required => true,
|
29
|
+
:entries => [@manifest, @annotations_directory])
|
30
|
+
end
|
31
|
+
|
32
|
+
# :stopdoc:
|
33
|
+
def cleanup_annotation_data
|
34
|
+
container.glob("#{@annotations_directory.full_name}/*",
|
35
|
+
:include_hidden => true) do |file|
|
36
|
+
|
37
|
+
found = false
|
38
|
+
@manifest.annotations.each do |ann|
|
39
|
+
content_name = normalize_content_name(ann.content)
|
40
|
+
|
41
|
+
if content_name == file.name
|
42
|
+
found = true
|
43
|
+
break
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
container.remove(file.name, true) unless found
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_annotation_data(source, options)
|
52
|
+
uuid = UUID.generate
|
53
|
+
|
54
|
+
if options[:aggregate]
|
55
|
+
entry = uuid
|
56
|
+
content = "/#{uuid}"
|
57
|
+
else
|
58
|
+
mk_annotations_dir
|
59
|
+
content = "#{@annotations_directory.name}/#{uuid}"
|
60
|
+
entry = "#{full_name}/#{content}"
|
61
|
+
end
|
62
|
+
|
63
|
+
if ::File.exist?(source)
|
64
|
+
container.add(entry, source, options)
|
65
|
+
else
|
66
|
+
container.file.open(entry, "w") do |annotation|
|
67
|
+
annotation.write source.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
if options[:aggregate]
|
71
|
+
@manifest.add_aggregate(entry)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
content
|
76
|
+
end
|
77
|
+
# :startdoc:
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def mk_annotations_dir
|
82
|
+
dir_name = "#{full_name}/#{@annotations_directory.name}"
|
83
|
+
if container.find_entry(dir_name, :include_hidden => true).nil?
|
84
|
+
container.mkdir dir_name
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Convert an annotation content field into something compatible with the
|
89
|
+
# rubyzip file naming convention (i.e. full paths not prefixed with /).
|
90
|
+
def normalize_content_name(name)
|
91
|
+
return if name.nil?
|
92
|
+
|
93
|
+
if name.start_with?(@annotations_directory.name)
|
94
|
+
"#{full_name}/#{name}"
|
95
|
+
elsif name.start_with?("/#{@annotations_directory.full_name}")
|
96
|
+
name.slice(1, name.length)
|
97
|
+
else
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# The managed annotations directory within the .ro directory.
|
103
|
+
#
|
104
|
+
# For internal use only.
|
105
|
+
class AnnotationsDir < ZipContainer::ManagedDirectory
|
106
|
+
|
107
|
+
DIR_NAME = "annotations" # :nodoc:
|
108
|
+
|
109
|
+
# :call-seq:
|
110
|
+
# new
|
111
|
+
#
|
112
|
+
# Create a new annotations managed directory. The directory is hidden
|
113
|
+
# under normal circumstances.
|
114
|
+
def initialize
|
115
|
+
super(DIR_NAME, :hidden => true)
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,338 @@
|
|
1
|
+
#------------------------------------------------------------------------------
|
2
|
+
# Copyright (c) 2014 The University of Manchester, UK.
|
3
|
+
#
|
4
|
+
# BSD Licenced. See LICENCE.rdoc for details.
|
5
|
+
#
|
6
|
+
# Author: Robert Haines
|
7
|
+
#------------------------------------------------------------------------------
|
8
|
+
|
9
|
+
#
|
10
|
+
module ROBundle
|
11
|
+
|
12
|
+
# The manifest.json managed file entry for a Research Object.
|
13
|
+
class Manifest < ZipContainer::ManagedFile
|
14
|
+
include Provenance
|
15
|
+
|
16
|
+
FILE_NAME = "manifest.json" # :nodoc:
|
17
|
+
DEFAULT_CONTEXT = "https://w3id.org/bundle/context" # :nodoc:
|
18
|
+
DEFAULT_ID = "/" # :nodoc:
|
19
|
+
|
20
|
+
# :call-seq:
|
21
|
+
# new
|
22
|
+
#
|
23
|
+
# Create a new managed file entry to represent the manifest.json file.
|
24
|
+
def initialize
|
25
|
+
super(FILE_NAME, :required => true)
|
26
|
+
|
27
|
+
@edited = false
|
28
|
+
end
|
29
|
+
|
30
|
+
# :call-seq:
|
31
|
+
# context -> List of context URIs
|
32
|
+
#
|
33
|
+
# Return the list of @context URIs for this Research Object manifest.
|
34
|
+
def context
|
35
|
+
structure[:@context].dup
|
36
|
+
end
|
37
|
+
|
38
|
+
# :call-seq:
|
39
|
+
# add_context
|
40
|
+
#
|
41
|
+
# Add a URI to the front of the @context list.
|
42
|
+
def add_context(uri)
|
43
|
+
@edited = true
|
44
|
+
structure[:@context].insert(0, uri.to_s)
|
45
|
+
end
|
46
|
+
|
47
|
+
# :call-seq:
|
48
|
+
# id -> String
|
49
|
+
#
|
50
|
+
# An RO identifier (usually '/') indicating the relative top-level folder
|
51
|
+
# as the identifier.
|
52
|
+
def id
|
53
|
+
structure[:id]
|
54
|
+
end
|
55
|
+
|
56
|
+
# :call-seq:
|
57
|
+
# id = new_id
|
58
|
+
#
|
59
|
+
# Set the id of this Manifest.
|
60
|
+
def id=(new_id)
|
61
|
+
@edited = true
|
62
|
+
structure[:id] = new_id
|
63
|
+
end
|
64
|
+
|
65
|
+
# :call-seq:
|
66
|
+
# history -> List of history entry names
|
67
|
+
#
|
68
|
+
# Return a list of filenames that hold provenance information for this
|
69
|
+
# Research Object.
|
70
|
+
def history
|
71
|
+
structure[:history].dup
|
72
|
+
end
|
73
|
+
|
74
|
+
# :call-seq:
|
75
|
+
# add_history(entry)
|
76
|
+
#
|
77
|
+
# Add the given entry to the history list in this manifest.
|
78
|
+
# <tt>Errno:ENOENT</tt> is raised if the entry does not exist.
|
79
|
+
def add_history(entry)
|
80
|
+
raise Errno::ENOENT if container.find_entry(entry).nil?
|
81
|
+
|
82
|
+
# Mangle the filename according to the RO Bundle specification.
|
83
|
+
name = entry_name(entry)
|
84
|
+
dir = "#{@parent.full_name}/"
|
85
|
+
name = name.start_with?(dir) ? name.sub(dir, "") : "/#{name}"
|
86
|
+
|
87
|
+
@edited = true
|
88
|
+
structure[:history] << name
|
89
|
+
end
|
90
|
+
|
91
|
+
# :call-seq:
|
92
|
+
# aggregates -> List of aggregated resources.
|
93
|
+
#
|
94
|
+
# Return a list of all the aggregated resources in this Research Object.
|
95
|
+
def aggregates
|
96
|
+
structure[:aggregates].dup
|
97
|
+
end
|
98
|
+
|
99
|
+
# :call-seq:
|
100
|
+
# add_aggregate(entry) -> Aggregate
|
101
|
+
# add_aggregate(uri) -> Aggregate
|
102
|
+
#
|
103
|
+
# Add the given entry or URI to the list of aggregates in this manifest.
|
104
|
+
# <tt>Errno:ENOENT</tt> is raised if the entry does not exist.
|
105
|
+
#
|
106
|
+
# The Aggregate object added to the Research Object is returned.
|
107
|
+
def add_aggregate(entry)
|
108
|
+
unless entry.instance_of?(Aggregate)
|
109
|
+
unless Util.is_absolute_uri?(entry)
|
110
|
+
raise Errno::ENOENT if container.find_entry(entry).nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
entry = Aggregate.new(entry)
|
114
|
+
end
|
115
|
+
|
116
|
+
@edited = true
|
117
|
+
structure[:aggregates] << entry
|
118
|
+
entry
|
119
|
+
end
|
120
|
+
|
121
|
+
# :call-seq:
|
122
|
+
# remove_aggregate(filename)
|
123
|
+
# remove_aggregate(uri)
|
124
|
+
# remove_aggregate(Aggregate)
|
125
|
+
#
|
126
|
+
# Remove (unregister) an aggregate from this Research Object. If a
|
127
|
+
# filename is supplied then the file is no longer aggregated, but it is
|
128
|
+
# not deleted from the bundle by this method.
|
129
|
+
#
|
130
|
+
# Any annotations with the removed aggregate as their target are also
|
131
|
+
# removed from the RO.
|
132
|
+
def remove_aggregate(object)
|
133
|
+
removed = nil
|
134
|
+
|
135
|
+
if object.is_a?(Aggregate)
|
136
|
+
removed = structure[:aggregates].delete(object)
|
137
|
+
|
138
|
+
unless removed.nil?
|
139
|
+
removed = removed.file.nil? ? removed.uri : removed.file
|
140
|
+
end
|
141
|
+
else
|
142
|
+
removed = remove_aggregate_by_file_or_uri(object)
|
143
|
+
end
|
144
|
+
|
145
|
+
unless removed.nil?
|
146
|
+
remove_annotation(removed)
|
147
|
+
@edited = true
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# :call-seq:
|
152
|
+
# add_annotation(annotation) -> Annotation
|
153
|
+
# add_annotation(target, content = nil) -> Annotation
|
154
|
+
#
|
155
|
+
# Add an annotation to this Research Object. An annotation can either be
|
156
|
+
# an already created annotation object, or a pair of values to build a new
|
157
|
+
# annotation object explicitly.
|
158
|
+
#
|
159
|
+
# <tt>Errno:ENOENT</tt> is raised if the target of the annotation is not
|
160
|
+
# an annotatable resource in this RO.
|
161
|
+
#
|
162
|
+
# The Annotation object added to the Research Object is returned.
|
163
|
+
def add_annotation(object, content = nil)
|
164
|
+
if object.instance_of?(Annotation)
|
165
|
+
# If the supplied Annotation object is already registered then it is
|
166
|
+
# the annotation itself we are annotating!
|
167
|
+
if container.annotation?(object)
|
168
|
+
object = Annotation.new(object.annotation_id, content)
|
169
|
+
end
|
170
|
+
else
|
171
|
+
object = Annotation.new(object, content)
|
172
|
+
end
|
173
|
+
|
174
|
+
target = object.target
|
175
|
+
unless container.annotatable?(target)
|
176
|
+
raise Errno::ENOENT,
|
177
|
+
"'#{target}' is not a member of this Research Object or a URI."
|
178
|
+
end
|
179
|
+
|
180
|
+
@edited = true
|
181
|
+
structure[:annotations] << object
|
182
|
+
object
|
183
|
+
end
|
184
|
+
|
185
|
+
# :call-seq:
|
186
|
+
# remove_annotation(Annotation)
|
187
|
+
# remove_annotation(target)
|
188
|
+
# remove_annotation(id)
|
189
|
+
#
|
190
|
+
# Remove (unregister) annotations from this Research Object and return
|
191
|
+
# them. Return +nil+ if the annotation does not exist.
|
192
|
+
#
|
193
|
+
# Any annotation content that is stored in the .ro/annotations directory
|
194
|
+
# is automatically cleaned up when the RO is closed.
|
195
|
+
def remove_annotation(object)
|
196
|
+
if object.is_a?(Annotation)
|
197
|
+
removed = [structure[:annotations].delete(object)].compact
|
198
|
+
else
|
199
|
+
removed = remove_annotation_by_field(object)
|
200
|
+
end
|
201
|
+
|
202
|
+
removed.each do |ann|
|
203
|
+
id = ann.annotation_id
|
204
|
+
remove_annotation(id) unless id.nil?
|
205
|
+
end
|
206
|
+
|
207
|
+
@edited = true unless removed.empty?
|
208
|
+
end
|
209
|
+
|
210
|
+
# :call-seq:
|
211
|
+
# annotations
|
212
|
+
#
|
213
|
+
# Return a list of all the annotations in this Research Object.
|
214
|
+
def annotations
|
215
|
+
structure[:annotations].dup
|
216
|
+
end
|
217
|
+
|
218
|
+
# :call-seq:
|
219
|
+
# edited? -> true or false
|
220
|
+
#
|
221
|
+
# Has this manifest been altered in any way?
|
222
|
+
def edited?
|
223
|
+
@edited
|
224
|
+
end
|
225
|
+
|
226
|
+
# :call-seq:
|
227
|
+
# to_json(options = nil) -> String
|
228
|
+
#
|
229
|
+
# Write this Manifest out as a json string. Takes the same options as
|
230
|
+
# JSON#generate.
|
231
|
+
def to_json(*a)
|
232
|
+
Util.clean_json(structure).to_json(*a)
|
233
|
+
end
|
234
|
+
|
235
|
+
protected
|
236
|
+
|
237
|
+
# :call-seq:
|
238
|
+
# validate -> true or false
|
239
|
+
#
|
240
|
+
# Validate the correctness of the manifest file contents.
|
241
|
+
def validate
|
242
|
+
begin
|
243
|
+
structure
|
244
|
+
rescue JSON::ParserError, ROError
|
245
|
+
return false
|
246
|
+
end
|
247
|
+
|
248
|
+
true
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
# The internal structure of this class cannot be setup at construction
|
254
|
+
# time in the initializer as there is no route to its data on disk at that
|
255
|
+
# point. Once loaded, parts of the structure are converted to local
|
256
|
+
# objects where appropriate.
|
257
|
+
def structure
|
258
|
+
return @structure if @structure
|
259
|
+
|
260
|
+
begin
|
261
|
+
struct ||= JSON.parse(contents, :symbolize_names => true)
|
262
|
+
rescue Errno::ENOENT
|
263
|
+
struct = {}
|
264
|
+
end
|
265
|
+
|
266
|
+
@structure = init_defaults(struct)
|
267
|
+
end
|
268
|
+
|
269
|
+
def init_defaults(struct)
|
270
|
+
init_default_context(struct)
|
271
|
+
init_default_id(struct)
|
272
|
+
init_provenance_defaults(struct)
|
273
|
+
struct[:history] = [*struct.fetch(:history, [])]
|
274
|
+
struct[:aggregates] = [*struct.fetch(:aggregates, [])].map do |agg|
|
275
|
+
Aggregate.new(agg)
|
276
|
+
end
|
277
|
+
struct[:annotations] = [*struct.fetch(:annotations, [])].map do |ann|
|
278
|
+
Annotation.new(ann)
|
279
|
+
end
|
280
|
+
|
281
|
+
struct
|
282
|
+
end
|
283
|
+
|
284
|
+
def init_default_context(struct)
|
285
|
+
context = struct[:@context]
|
286
|
+
if context.nil?
|
287
|
+
@edited = true
|
288
|
+
struct[:@context] = [ DEFAULT_CONTEXT ]
|
289
|
+
else
|
290
|
+
struct[:@context] = [*context]
|
291
|
+
end
|
292
|
+
|
293
|
+
struct
|
294
|
+
end
|
295
|
+
|
296
|
+
def init_default_id(struct)
|
297
|
+
id = struct[:id]
|
298
|
+
if id.nil?
|
299
|
+
@edited = true
|
300
|
+
struct[:id] = DEFAULT_ID
|
301
|
+
end
|
302
|
+
|
303
|
+
struct
|
304
|
+
end
|
305
|
+
|
306
|
+
def remove_aggregate_by_file_or_uri(object)
|
307
|
+
aggregates.each do |agg|
|
308
|
+
if Util.is_absolute_uri?(object)
|
309
|
+
return structure[:aggregates].delete(agg).uri if object == agg.uri
|
310
|
+
else
|
311
|
+
if object == agg.file || object == agg.file_entry
|
312
|
+
return structure[:aggregates].delete(agg).file
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Return nil if nothing removed.
|
318
|
+
nil
|
319
|
+
end
|
320
|
+
|
321
|
+
def remove_annotation_by_field(object)
|
322
|
+
removed = []
|
323
|
+
|
324
|
+
annotations.each do |ann|
|
325
|
+
if ann.annotation_id == object ||
|
326
|
+
ann.target == object ||
|
327
|
+
ann.content == object
|
328
|
+
|
329
|
+
removed << structure[:annotations].delete(ann)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
removed
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|