Merge branch 'master' into 4194-keep-logging
[arvados.git] / apps / workbench / app / models / arvados_resource_list.rb
1 class ArvadosResourceList
2   include ArvadosApiClientHelper
3   include Enumerable
4
5   attr_reader :resource_class
6
7   def initialize resource_class=nil
8     @resource_class = resource_class
9     @fetch_multiple_pages = true
10     @arvados_api_token = Thread.current[:arvados_api_token]
11     @reader_tokens = Thread.current[:reader_tokens]
12   end
13
14   def eager(bool=true)
15     @eager = bool
16     self
17   end
18
19   def limit(max_results)
20     if not max_results.nil? and not max_results.is_a? Integer
21       raise ArgumentError("argument to limit() must be an Integer or nil")
22     end
23     @limit = max_results
24     self
25   end
26
27   def offset(skip)
28     @offset = skip
29     self
30   end
31
32   def order(orderby_spec)
33     @orderby_spec = orderby_spec
34     self
35   end
36
37   def select(columns=nil)
38     # If no column arguments were given, invoke Enumerable#select.
39     if columns.nil?
40       super()
41     else
42       @select ||= []
43       @select += columns
44       self
45     end
46   end
47
48   def filter _filters
49     @filters ||= []
50     @filters += _filters
51     self
52   end
53
54   def where(cond)
55     @cond = cond.dup
56     @cond.keys.each do |uuid_key|
57       if @cond[uuid_key] and (@cond[uuid_key].is_a? Array or
58                              @cond[uuid_key].is_a? ArvadosBase)
59         # Coerce cond[uuid_key] to an array of uuid strings.  This
60         # allows caller the convenience of passing an array of real
61         # objects and uuids in cond[uuid_key].
62         if !@cond[uuid_key].is_a? Array
63           @cond[uuid_key] = [@cond[uuid_key]]
64         end
65         @cond[uuid_key] = @cond[uuid_key].collect do |item|
66           if item.is_a? ArvadosBase
67             item.uuid
68           else
69             item
70           end
71         end
72       end
73     end
74     @cond.keys.select { |x| x.match /_kind$/ }.each do |kind_key|
75       if @cond[kind_key].is_a? Class
76         @cond = @cond.merge({ kind_key => 'arvados#' + arvados_api_client.class_kind(@cond[kind_key]) })
77       end
78     end
79     self
80   end
81
82   def fetch_multiple_pages(f)
83     @fetch_multiple_pages = f
84     self
85   end
86
87   def results
88     if !@results
89       @results = []
90       self.each_page do |r|
91         @results.concat r
92       end
93     end
94     @results
95   end
96
97   def results=(r)
98     @results = r
99     @items_available = r.items_available if r.respond_to? :items_available
100     @result_limit = r.limit if r.respond_to? :limit
101     @result_offset = r.offset if r.respond_to? :offset
102     @results
103   end
104
105   def to_ary
106     results
107   end
108
109   def each(&block)
110     if not @results.nil?
111       @results.each &block
112     else
113       self.each_page do |items|
114         items.each do |i|
115           block.call i
116         end
117       end
118     end
119     self
120   end
121
122   def first
123     results.first
124   end
125
126   def last
127     results.last
128   end
129
130   def [](*x)
131     results.send('[]', *x)
132   end
133
134   def |(x)
135     if x.is_a? Hash
136       self.to_hash | x
137     else
138       results | x.to_ary
139     end
140   end
141
142   def to_hash
143     Hash[self.collect { |x| [x.uuid, x] }]
144   end
145
146   def empty?
147     self.first.nil?
148   end
149
150   def items_available
151     results
152     @items_available
153   end
154
155   def result_limit
156     results
157     @result_limit
158   end
159
160   def result_offset
161     results
162     @result_offset
163   end
164
165   # Obsolete method retained during api transition.
166   def links_for item_or_uuid, link_class=false
167     []
168   end
169
170   protected
171
172   def each_page
173     api_params = {
174       _method: 'GET'
175     }
176     api_params[:where] = @cond if @cond
177     api_params[:eager] = '1' if @eager
178     api_params[:select] = @select if @select
179     api_params[:order] = @orderby_spec if @orderby_spec
180     api_params[:filters] = @filters if @filters
181
182
183     item_count = 0
184     offset = @offset || 0
185     @result_limit = nil
186     @result_offset = nil
187
188     begin
189       api_params[:offset] = offset
190       api_params[:limit] = (@limit - item_count) if @limit
191
192       res = arvados_api_client.api(@resource_class, '', api_params,
193                                    arvados_api_token: @arvados_api_token,
194                                    reader_tokens: @reader_tokens)
195       items = arvados_api_client.unpack_api_response res
196
197       @items_available = items.items_available if items.respond_to?(:items_available)
198       @result_limit = items.limit if (@fetch_multiple_pages == false) and items.respond_to?(:limit)
199       @result_offset = items.offset if (@fetch_multiple_pages == false) and items.respond_to?(:offset)
200
201       break if items.nil? or not items.any?
202
203       item_count += items.size
204       if items.respond_to?(:offset)
205         offset = items.offset + items.size
206       else
207         offset = item_count
208       end
209
210       yield items
211
212       break if @limit and item_count >= @limit
213       break if items.respond_to? :items_available and offset >= items.items_available
214     end while @fetch_multiple_pages
215     self
216   end
217
218 end