cmis_active 0.3.7
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/AUTHORS +9 -0
- data/LICENSE +26 -0
- data/README.md +55 -0
- data/Rakefile +34 -0
- data/TODO +7 -0
- data/VERSION.yml +5 -0
- data/active_cmis.gemspec +79 -0
- data/lib/active_cmis.rb +30 -0
- data/lib/active_cmis/acl.rb +181 -0
- data/lib/active_cmis/acl_entry.rb +26 -0
- data/lib/active_cmis/active_cmis.rb +87 -0
- data/lib/active_cmis/atomic_types.rb +245 -0
- data/lib/active_cmis/attribute_prefix.rb +35 -0
- data/lib/active_cmis/collection.rb +206 -0
- data/lib/active_cmis/document.rb +356 -0
- data/lib/active_cmis/exceptions.rb +82 -0
- data/lib/active_cmis/folder.rb +36 -0
- data/lib/active_cmis/internal/caching.rb +86 -0
- data/lib/active_cmis/internal/connection.rb +241 -0
- data/lib/active_cmis/internal/utils.rb +82 -0
- data/lib/active_cmis/ns.rb +18 -0
- data/lib/active_cmis/object.rb +563 -0
- data/lib/active_cmis/policy.rb +13 -0
- data/lib/active_cmis/property_definition.rb +179 -0
- data/lib/active_cmis/query_result.rb +40 -0
- data/lib/active_cmis/rel.rb +17 -0
- data/lib/active_cmis/relationship.rb +49 -0
- data/lib/active_cmis/rendition.rb +86 -0
- data/lib/active_cmis/repository.rb +327 -0
- data/lib/active_cmis/server.rb +121 -0
- data/lib/active_cmis/type.rb +200 -0
- data/lib/active_cmis/version.rb +10 -0
- metadata +132 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
module ActiveCMIS
|
2
|
+
# Default logger: no output
|
3
|
+
# @return [Logger]
|
4
|
+
def self.default_logger
|
5
|
+
@default_logger ||= Logger.new(nil)
|
6
|
+
end
|
7
|
+
|
8
|
+
# server_url and repository_id are required options
|
9
|
+
#
|
10
|
+
# server_login, server_password and server_auth can be used to authenticate against the server,
|
11
|
+
# server_auth is optional and defaults to :basic
|
12
|
+
#
|
13
|
+
# You can also authenticate to the repository, by replacing server_ with repository_, by default
|
14
|
+
# the repository will use the same authentication parameters as the server
|
15
|
+
#
|
16
|
+
# The amoung of logging can be configured by setting log_level (default WARN), this can be done either
|
17
|
+
# by naming a Logger::Severity constant or the equivalent integer
|
18
|
+
#
|
19
|
+
# The destination of the logger output can be set with log_file (defaults to STDOUT), (should not contain ~)
|
20
|
+
#
|
21
|
+
# Default locations for the config file are: ./cmis.yml and .cmis.yml in that order
|
22
|
+
# @return [Repository]
|
23
|
+
def self.connect(config)
|
24
|
+
if config.is_a? Hash
|
25
|
+
if config.has_key? "log_file"
|
26
|
+
trace_file = config["log_file"]
|
27
|
+
if trace_file == "-"
|
28
|
+
trace_file = STDOUT
|
29
|
+
end
|
30
|
+
logger = Logger.new(trace_file)
|
31
|
+
else
|
32
|
+
logger = default_logger
|
33
|
+
end
|
34
|
+
if config.has_key? "log_level"
|
35
|
+
logger.level = (Logger.const_get(config["log_level"].upcase) rescue config["log_level"].to_i)
|
36
|
+
else
|
37
|
+
logger.level = Logger::WARN
|
38
|
+
end
|
39
|
+
|
40
|
+
if user_name = config["server_login"] and password = config["server_password"]
|
41
|
+
auth_type = config["server_auth"] || :basic
|
42
|
+
authentication_info = [auth_type, user_name, password]
|
43
|
+
end
|
44
|
+
server = Server.new(config["server_url"], logger, authentication_info, { :timeout => config["timeout"], :ssl_verfiy => config["ssl_verfiy"] })
|
45
|
+
if user_name = config["repository_login"] and password = config["repository_password"]
|
46
|
+
auth_type = config["repository_auth"] || :basic
|
47
|
+
authentication_info = [auth_type, user_name, password]
|
48
|
+
end
|
49
|
+
repository = server.repository(config["repository_id"], authentication_info)
|
50
|
+
return repository
|
51
|
+
else
|
52
|
+
raise "Configuration does not have correct format (#{config.class} is not a hash)"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Will search for a given configuration in a file, and return the equivalent Repository
|
57
|
+
#
|
58
|
+
# The options that can be used are the same as for the connect method
|
59
|
+
#
|
60
|
+
# Default locations for the config file are: ./cmis.yml and .cmis.yml in that order
|
61
|
+
# @return [Repository]
|
62
|
+
def self.load_config(config_name, file = nil)
|
63
|
+
if file.nil?
|
64
|
+
["cmis.yml", File.join(ENV["HOME"], ".cmis.yml")].each do |sl|
|
65
|
+
if File.exist?(sl)
|
66
|
+
file ||= sl
|
67
|
+
end
|
68
|
+
end
|
69
|
+
if file.nil?
|
70
|
+
raise "No configuration provided, and none found in standard locations"
|
71
|
+
end
|
72
|
+
elsif !File.exist?(file)
|
73
|
+
raise "Configuration file #{file} does not exist"
|
74
|
+
end
|
75
|
+
|
76
|
+
config = YAML.load_file(file)
|
77
|
+
if config.is_a? Hash
|
78
|
+
if config = config[config_name]
|
79
|
+
connect(config)
|
80
|
+
else
|
81
|
+
raise "Configuration not found in file"
|
82
|
+
end
|
83
|
+
else
|
84
|
+
raise "Configuration file #{file} does not have right format (not a hash)"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
module ActiveCMIS
|
2
|
+
module AtomicType
|
3
|
+
class CommonBase
|
4
|
+
def cmis2rb(value)
|
5
|
+
if value.children.empty? && value.attribute("nil")
|
6
|
+
nil
|
7
|
+
else
|
8
|
+
_cmis2rb(value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
def rb2cmis(xml, value)
|
12
|
+
if value.nil?
|
13
|
+
xml.value("xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", "xsi:nil" => "true")
|
14
|
+
else
|
15
|
+
_rb2cmis(xml, value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
def can_handle?(value)
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class String < CommonBase
|
24
|
+
attr_reader :max_length
|
25
|
+
def initialize(max_length = nil)
|
26
|
+
@max_length = max_length
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"String"
|
31
|
+
end
|
32
|
+
|
33
|
+
def _cmis2rb(value)
|
34
|
+
value.text
|
35
|
+
end
|
36
|
+
def _rb2cmis(xml, value)
|
37
|
+
v = value.to_s
|
38
|
+
if max_length && max_length > 0 && v.length > max_length #xCMIS says maxLength=0
|
39
|
+
raise Error::InvalidArgument.new("String representation is longer than maximum (max: #{max_length}, string: \n'\n#{v}\n')\n")
|
40
|
+
end
|
41
|
+
xml["c"].value v
|
42
|
+
end
|
43
|
+
def can_handle?(value)
|
44
|
+
value.respond_to?(:to_s)
|
45
|
+
end
|
46
|
+
|
47
|
+
private :_cmis2rb, :_rb2cmis
|
48
|
+
end
|
49
|
+
|
50
|
+
# Qarning: Precision is ignored?
|
51
|
+
class Decimal < CommonBase
|
52
|
+
attr_reader :precision, :min_value, :max_value
|
53
|
+
def initialize(precision = nil, min_value = nil, max_value = nil)
|
54
|
+
@precision, @min_value, @max_value = precision, min_value, max_value
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
"Decimal"
|
59
|
+
end
|
60
|
+
|
61
|
+
def _cmis2rb(value)
|
62
|
+
value.text.to_f
|
63
|
+
end
|
64
|
+
def _rb2cmis(xml, value)
|
65
|
+
v = value.to_f
|
66
|
+
if (min_value && v < min_value) || (max_value && v > max_value)
|
67
|
+
raise Error::InvalidArgument.new("OutOfBounds: #{v} should be between #{min_value} and #{max_value}")
|
68
|
+
end
|
69
|
+
xml["c"].value("%f" % v)
|
70
|
+
end
|
71
|
+
def can_handle?(value)
|
72
|
+
value.respond_to?(:to_s)
|
73
|
+
end
|
74
|
+
|
75
|
+
private :_cmis2rb, :_rb2cmis
|
76
|
+
end
|
77
|
+
|
78
|
+
class Integer < CommonBase
|
79
|
+
attr_reader :min_value, :max_value
|
80
|
+
def initialize(min_value = nil, max_value = nil)
|
81
|
+
@min_value, @max_value = min_value, max_value
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
"Integer"
|
86
|
+
end
|
87
|
+
|
88
|
+
def _cmis2rb(value)
|
89
|
+
value.text.to_i
|
90
|
+
end
|
91
|
+
def _rb2cmis(xml, value)
|
92
|
+
v = value.to_i
|
93
|
+
if (min_value && v < min_value) || (max_value && v > max_value)
|
94
|
+
raise Error::InvalidArgument.new("OutOfBounds: #{v} should be between #{min_value} and #{max_value}")
|
95
|
+
end
|
96
|
+
xml["c"].value("%i" % v)
|
97
|
+
end
|
98
|
+
def can_handle?(value)
|
99
|
+
value.respond_to?(:to_i)
|
100
|
+
end
|
101
|
+
|
102
|
+
private :_cmis2rb, :_rb2cmis
|
103
|
+
end
|
104
|
+
|
105
|
+
class DateTime < CommonBase
|
106
|
+
attr_reader :resolution
|
107
|
+
|
108
|
+
@instances ||= {}
|
109
|
+
def self.new(precision = TIME)
|
110
|
+
raise ArgumentError.new("Got precision = #{precision.inspect}") unless [YEAR, DATE, TIME].include? precision.to_s.downcase
|
111
|
+
@instances[precision] ||= super
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_s
|
115
|
+
"DateTime"
|
116
|
+
end
|
117
|
+
|
118
|
+
def initialize(resolution)
|
119
|
+
@resolution = resolution
|
120
|
+
end
|
121
|
+
YEAR = "year"
|
122
|
+
DATE = "date"
|
123
|
+
TIME = "time"
|
124
|
+
|
125
|
+
def _cmis2rb(value)
|
126
|
+
case @resolution
|
127
|
+
when YEAR, DATE; ::DateTime.parse(value.text).to_date
|
128
|
+
when TIME; ::DateTime.parse(value.text)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
def _rb2cmis(xml, value)
|
132
|
+
# FIXME: respect resolution, I still have to find out how to do that
|
133
|
+
xml["c"].value(value.strftime("%Y-%m-%dT%H:%M:%S%Z"))
|
134
|
+
end
|
135
|
+
def can_handle?(value)
|
136
|
+
value.respond_to?(:strftime)
|
137
|
+
end
|
138
|
+
|
139
|
+
private :_cmis2rb, :_rb2cmis
|
140
|
+
end
|
141
|
+
|
142
|
+
class Singleton < CommonBase
|
143
|
+
def self.new
|
144
|
+
@singleton ||= super
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class Boolean < Singleton
|
149
|
+
def self.xml_to_bool(value)
|
150
|
+
case value
|
151
|
+
when "true", "1"; true
|
152
|
+
when "false", "0"; false
|
153
|
+
else raise ActiveCMIS::Error.new("An invalid boolean was found in CMIS")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_s
|
158
|
+
"Boolean"
|
159
|
+
end
|
160
|
+
|
161
|
+
def _cmis2rb(value)
|
162
|
+
self.class.xml_to_bool(value.text)
|
163
|
+
end
|
164
|
+
def _rb2cmis(xml, value)
|
165
|
+
xml["c"].value( (!!value).to_s )
|
166
|
+
end
|
167
|
+
def can_handle?(value)
|
168
|
+
[true, false].include?(value)
|
169
|
+
end
|
170
|
+
|
171
|
+
private :_cmis2rb, :_rb2cmis
|
172
|
+
end
|
173
|
+
|
174
|
+
class URI < Singleton
|
175
|
+
def to_s
|
176
|
+
"Uri"
|
177
|
+
end
|
178
|
+
|
179
|
+
def _cmis2rb(value)
|
180
|
+
URI.parse(value.text)
|
181
|
+
end
|
182
|
+
def _rb2cmis(xml, value)
|
183
|
+
xml["c"].value( value.to_s )
|
184
|
+
end
|
185
|
+
def can_handle?(value)
|
186
|
+
value.respond_to?(:to_s)
|
187
|
+
end
|
188
|
+
|
189
|
+
private :_cmis2rb, :_rb2cmis
|
190
|
+
end
|
191
|
+
|
192
|
+
class ID < Singleton
|
193
|
+
def to_s
|
194
|
+
"Id"
|
195
|
+
end
|
196
|
+
|
197
|
+
def _cmis2rb(value)
|
198
|
+
value.text
|
199
|
+
end
|
200
|
+
def _rb2cmis(xml, value)
|
201
|
+
case value
|
202
|
+
when ::ActiveCMIS::Object; value.id
|
203
|
+
else xml["c"].value( value.to_s )
|
204
|
+
end
|
205
|
+
end
|
206
|
+
def can_handle?(value)
|
207
|
+
value.class < ::ActiveCMIS::Object || value.respond_to?(:to_s)
|
208
|
+
end
|
209
|
+
|
210
|
+
private :_cmis2rb, :_rb2cmis
|
211
|
+
end
|
212
|
+
|
213
|
+
class HTML < Singleton
|
214
|
+
def to_s
|
215
|
+
"Html"
|
216
|
+
end
|
217
|
+
|
218
|
+
def _cmis2rb(value)
|
219
|
+
value.children
|
220
|
+
end
|
221
|
+
def _rb2cmis(xml, value)
|
222
|
+
# FIXME: Test that this works
|
223
|
+
xml["c"].value value
|
224
|
+
end
|
225
|
+
def can_handle?(value)
|
226
|
+
true # FIXME: this is probably incorrect
|
227
|
+
end
|
228
|
+
|
229
|
+
private :_cmis2rb, :_rb2cmis
|
230
|
+
end
|
231
|
+
|
232
|
+
# Map of XML property elements to the corresponding AtomicTypes
|
233
|
+
MAPPING = {
|
234
|
+
"propertyString" => ActiveCMIS::AtomicType::String,
|
235
|
+
"propertyBoolean" => ActiveCMIS::AtomicType::Boolean,
|
236
|
+
"propertyId" => ActiveCMIS::AtomicType::ID,
|
237
|
+
"propertyDateTime" => ActiveCMIS::AtomicType::DateTime,
|
238
|
+
"propertyInteger" => ActiveCMIS::AtomicType::Integer,
|
239
|
+
"propertyDecimal" => ActiveCMIS::AtomicType::Decimal,
|
240
|
+
"propertyHtml" => ActiveCMIS::AtomicType::HTML,
|
241
|
+
"propertyUri" => ActiveCMIS::AtomicType::URI,
|
242
|
+
}
|
243
|
+
|
244
|
+
end
|
245
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ActiveCMIS
|
2
|
+
# A class used to get and set attributes that have a prefix like cmis: in their attribute IDs
|
3
|
+
class AttributePrefix
|
4
|
+
# @return [Object] The object that the attribute getting and setting will take place on
|
5
|
+
attr_reader :object
|
6
|
+
# @return [String]
|
7
|
+
attr_reader :prefix
|
8
|
+
|
9
|
+
# @private
|
10
|
+
def initialize(object, prefix)
|
11
|
+
@object = object
|
12
|
+
@prefix = prefix
|
13
|
+
end
|
14
|
+
|
15
|
+
# For known attributes will act as a getter and setter
|
16
|
+
def method_missing(method, *parameters)
|
17
|
+
string = method.to_s
|
18
|
+
if string[-1] == ?=
|
19
|
+
assignment = true
|
20
|
+
string = string[0..-2]
|
21
|
+
end
|
22
|
+
attribute = "#{prefix}:#{string}"
|
23
|
+
if object.class.attributes.keys.include? attribute
|
24
|
+
if assignment
|
25
|
+
object.update(attribute => parameters.first)
|
26
|
+
else
|
27
|
+
object.attribute(attribute)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
# TODO: perhaps here we should try to look a bit further to see if there is a second :
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
module ActiveCMIS
|
2
|
+
|
3
|
+
# A Collection represents an atom feed, and can be used to lazily load data through paging
|
4
|
+
class Collection
|
5
|
+
include Internal::Caching
|
6
|
+
include ::Enumerable
|
7
|
+
|
8
|
+
# The repository that contains this feed
|
9
|
+
# @return [Repository]
|
10
|
+
attr_reader :repository
|
11
|
+
# The basic link that represents the beginning of this feed
|
12
|
+
# @return [URI]
|
13
|
+
attr_reader :url
|
14
|
+
|
15
|
+
def initialize(repository, url, first_page = nil, &map_entry)
|
16
|
+
@repository = repository
|
17
|
+
@url = URI.parse(url)
|
18
|
+
|
19
|
+
@next = @url
|
20
|
+
@elements = []
|
21
|
+
@pages = []
|
22
|
+
|
23
|
+
@map_entry = map_entry || Proc.new do |e|
|
24
|
+
ActiveCMIS::Object.from_atom_entry(repository, e)
|
25
|
+
end
|
26
|
+
|
27
|
+
if first_page
|
28
|
+
@next = first_page.xpath("at:feed/at:link[@rel = 'next']/@href", NS::COMBINED).first
|
29
|
+
@pages[0] = first_page
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Integer] The length of the collection
|
34
|
+
def length
|
35
|
+
receive_page
|
36
|
+
if @length.nil?
|
37
|
+
i = 1
|
38
|
+
while @next
|
39
|
+
receive_page
|
40
|
+
i += 1
|
41
|
+
end
|
42
|
+
@elements.length
|
43
|
+
else
|
44
|
+
@length
|
45
|
+
end
|
46
|
+
end
|
47
|
+
alias size length
|
48
|
+
cache :length
|
49
|
+
|
50
|
+
def empty?
|
51
|
+
at(0)
|
52
|
+
@elements.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Array]
|
56
|
+
def to_a
|
57
|
+
while @next
|
58
|
+
receive_page
|
59
|
+
end
|
60
|
+
@elements
|
61
|
+
end
|
62
|
+
|
63
|
+
def at(index)
|
64
|
+
index = sanitize_index(index)
|
65
|
+
if index < @elements.length
|
66
|
+
@elements[index]
|
67
|
+
elsif index > length
|
68
|
+
nil
|
69
|
+
else
|
70
|
+
while @next && @elements.length < index
|
71
|
+
receive_page
|
72
|
+
end
|
73
|
+
@elements[index]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def [](index, length = nil)
|
78
|
+
if length
|
79
|
+
index = sanitize_index(index)
|
80
|
+
range_get(index, index + length - 1)
|
81
|
+
elsif Range === index
|
82
|
+
range_get(sanitize_index(index.begin), index.exclude_end? ? sanitize_index(index.end) - 1 : sanitize_index(index.end))
|
83
|
+
else
|
84
|
+
at(index)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
alias slice []
|
88
|
+
|
89
|
+
def first
|
90
|
+
at(0)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Gets all object and returns last
|
94
|
+
def last
|
95
|
+
at(-1)
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [Array]
|
99
|
+
def each
|
100
|
+
length.times { |i| yield self[i] }
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return [Array]
|
104
|
+
def reverse_each
|
105
|
+
(length - 1).downto(0) { |i| yield self[i] }
|
106
|
+
end
|
107
|
+
|
108
|
+
# @return [String]
|
109
|
+
def inspect
|
110
|
+
"#<Collection %s>" % url
|
111
|
+
end
|
112
|
+
|
113
|
+
# @return [String]
|
114
|
+
def to_s
|
115
|
+
to_a.to_s
|
116
|
+
end
|
117
|
+
|
118
|
+
# @return [Array]
|
119
|
+
def uniq
|
120
|
+
to_a.uniq
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return [Array]
|
124
|
+
def sort
|
125
|
+
to_a.sort
|
126
|
+
end
|
127
|
+
|
128
|
+
# @return [Array]
|
129
|
+
def reverse
|
130
|
+
to_a.reverse
|
131
|
+
end
|
132
|
+
|
133
|
+
## @return [void]
|
134
|
+
def reload
|
135
|
+
@pages = []
|
136
|
+
@elements = []
|
137
|
+
@next = @url
|
138
|
+
__reload
|
139
|
+
end
|
140
|
+
|
141
|
+
# Attempts to delete the collection.
|
142
|
+
# This may not work on every collection, ActiveCMIS does not (yet) try to check this client side.
|
143
|
+
#
|
144
|
+
# For folder collections 2 options are available, again no client side checking
|
145
|
+
# is done to see whether the collection can handle these options
|
146
|
+
#
|
147
|
+
# @param [Hash] options
|
148
|
+
# @option options [String] :unfileObjects Parameter valid for items collection of folder
|
149
|
+
# "delete" (default): Delete all fileable objects
|
150
|
+
# "unfile": unfile all fileable objects
|
151
|
+
# "deleteSingleFiled": Delete all fileable objects, whose only parent folder is the current folder. Unfile all other objects from this folder
|
152
|
+
# @option options [Bool] :continueOnFailure default = false, if false abort on failure of single document/folder, else try to continue with deleting child folders/documents
|
153
|
+
def destroy(options = {})
|
154
|
+
if options.empty?
|
155
|
+
conn.delete(@url)
|
156
|
+
else
|
157
|
+
unfileObjects = options.delete(:unfileObjects)
|
158
|
+
continueOnFailure = options.delete(:continueOnFailure)
|
159
|
+
raise ArgumentError("Unknown parameters #{options.keys.join(', ')}") unless options.empty?
|
160
|
+
|
161
|
+
|
162
|
+
# XXX: have less cumbersome code, more generic and more efficient code
|
163
|
+
new_url = @url
|
164
|
+
new_url = Internal::Utils.append_parameters(new_url, :unfileObjects => unfileObjects) unless unfileObjects.nil?
|
165
|
+
new_url = Internal::Utils.append_parameters(new_url, :continueOnFailure => continueOnFailure) unless continueOnFailure.nil?
|
166
|
+
|
167
|
+
# TODO: check that we can handle 200,202,204 responses correctly
|
168
|
+
conn.delete(@url)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def sanitize_index(index)
|
175
|
+
index < 0 ? size + index : index
|
176
|
+
end
|
177
|
+
|
178
|
+
def range_get(from, to)
|
179
|
+
(from..to).map { |i| at(i) }
|
180
|
+
end
|
181
|
+
|
182
|
+
def receive_page(i = nil)
|
183
|
+
i ||= @pages.length
|
184
|
+
@pages[i] ||= begin
|
185
|
+
return nil unless @next
|
186
|
+
xml = conn.get_xml(@next)
|
187
|
+
|
188
|
+
@next = xml.xpath("at:feed/at:link[@rel = 'next']/@href", NS::COMBINED).first
|
189
|
+
@next = @next.nil? ? nil : @next.text
|
190
|
+
|
191
|
+
new_elements = xml.xpath('at:feed/at:entry', NS::COMBINED).map(&@map_entry)
|
192
|
+
@elements.concat(new_elements)
|
193
|
+
|
194
|
+
num_items = xml.xpath("at:feed/cra:numItems", NS::COMBINED).first
|
195
|
+
@length ||= num_items.text.to_i if num_items # We could also test on the repository
|
196
|
+
|
197
|
+
xml
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def conn
|
202
|
+
repository.conn
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end
|