Merge branch '1969-persistent-switch'
[arvados.git] / apps / workbench / app / controllers / collections_controller.rb
1 class CollectionsController < ApplicationController
2   skip_before_filter :find_object_by_uuid, :only => [:provenance]
3   skip_before_filter :check_user_agreements, :only => [:show_file]
4
5   def show_pane_list
6     %w(Files Attributes Metadata Provenance_graph Used_by JSON API)
7   end
8
9   def set_persistent
10     case params[:value]
11     when 'persistent', 'cache'
12       persist_links = Link.filter([['owner_uuid', '=', current_user.uuid],
13                                    ['link_class', '=', 'resources'],
14                                    ['name', '=', 'wants'],
15                                    ['tail_uuid', '=', current_user.uuid],
16                                    ['head_uuid', '=', @object.uuid]])
17       logger.debug persist_links.inspect
18     else
19       return unprocessable "Invalid value #{value.inspect}"
20     end
21     if params[:value] == 'persistent'
22       if not persist_links.any?
23         Link.create(link_class: 'resources',
24                     name: 'wants',
25                     tail_uuid: current_user.uuid,
26                     head_uuid: @object.uuid)
27       end
28     else
29       persist_links.each do |link|
30         link.destroy || raise
31       end
32     end
33
34     respond_to do |f|
35       f.json { render json: @object }
36     end
37   end
38
39   def index
40     if params[:search].andand.length.andand > 0
41       tags = Link.where(any: ['contains', params[:search]])
42       @collections = (Collection.where(uuid: tags.collect(&:head_uuid)) |
43                       Collection.where(any: ['contains', params[:search]])).
44         uniq { |c| c.uuid }
45     else
46       if params[:limit]
47         limit = params[:limit].to_i
48       else
49         limit = 100
50       end
51
52       if params[:offset]
53         offset = params[:offset].to_i
54       else
55         offset = 0
56       end
57
58       @collections = Collection.limit(limit).offset(offset)
59     end
60     @links = Link.limit(1000).
61       where(head_uuid: @collections.collect(&:uuid))
62     @collection_info = {}
63     @collections.each do |c|
64       @collection_info[c.uuid] = {
65         tag_links: [],
66         wanted: false,
67         wanted_by_me: false,
68         provenance: [],
69         links: []
70       }
71     end
72     @links.each do |link|
73       @collection_info[link.head_uuid] ||= {}
74       info = @collection_info[link.head_uuid]
75       case link.link_class
76       when 'tag'
77         info[:tag_links] << link
78       when 'resources'
79         info[:wanted] = true
80         info[:wanted_by_me] ||= link.tail_uuid == current_user.uuid
81       when 'provenance'
82         info[:provenance] << link.name
83       end
84       info[:links] << link
85     end
86     @request_url = request.url
87   end
88
89   def show_file
90     opts = params.merge(arvados_api_token: Thread.current[:arvados_api_token])
91     if r = params[:file].match(/(\.\w+)/)
92       ext = r[1]
93     end
94     self.response.headers['Content-Type'] =
95       Rack::Mime::MIME_TYPES[ext] || 'application/octet-stream'
96     self.response.headers['Content-Length'] = params[:size] if params[:size]
97     self.response.headers['Content-Disposition'] = params[:disposition] if params[:disposition]
98     self.response_body = FileStreamer.new opts
99   end
100
101   def show
102     return super if !@object
103     @provenance = []
104     @output2job = {}
105     @output2colorindex = {}
106     @sourcedata = {params[:uuid] => {uuid: params[:uuid]}}
107     @protected = {}
108
109     colorindex = -1
110     any_hope_left = true
111     while any_hope_left
112       any_hope_left = false
113       Job.where(output: @sourcedata.keys).sort_by { |a| a.finished_at || a.created_at }.reverse.each do |job|
114         if !@output2colorindex[job.output]
115           any_hope_left = true
116           @output2colorindex[job.output] = (colorindex += 1) % 10
117           @provenance << {job: job, output: job.output}
118           @sourcedata.delete job.output
119           @output2job[job.output] = job
120           job.dependencies.each do |new_source_data|
121             unless @output2colorindex[new_source_data]
122               @sourcedata[new_source_data] = {uuid: new_source_data}
123             end
124           end
125         end
126       end
127     end
128
129     Link.where(head_uuid: @sourcedata.keys | @output2job.keys).each do |link|
130       if link.link_class == 'resources' and link.name == 'wants'
131         @protected[link.head_uuid] = true
132         if link.tail_uuid == current_user.uuid
133           @is_persistent = true
134         end
135       end
136     end
137     Link.where(tail_uuid: @sourcedata.keys).each do |link|
138       if link.link_class == 'data_origin'
139         @sourcedata[link.tail_uuid][:data_origins] ||= []
140         @sourcedata[link.tail_uuid][:data_origins] << [link.name, link.head_uuid]
141       end
142     end
143     Collection.where(uuid: @sourcedata.keys).each do |collection|
144       if @sourcedata[collection.uuid]
145         @sourcedata[collection.uuid][:collection] = collection
146       end
147     end
148
149     Collection.where(uuid: @object.uuid).each do |u|
150       puts request
151       @prov_svg = ProvenanceHelper::create_provenance_graph(u.provenance, "provenance_svg",
152                                                             {:request => request,
153                                                               :direction => :bottom_up,
154                                                               :combine_jobs => :script_only}) rescue nil
155       @used_by_svg = ProvenanceHelper::create_provenance_graph(u.used_by, "used_by_svg",
156                                                                {:request => request,
157                                                                  :direction => :top_down,
158                                                                  :combine_jobs => :script_only,
159                                                                  :pdata_only => true}) rescue nil
160     end
161   end
162
163   protected
164   class FileStreamer
165     def initialize(opts={})
166       @opts = opts
167     end
168     def each
169       return unless @opts[:uuid] && @opts[:file]
170       env = Hash[ENV].
171         merge({
172                 'ARVADOS_API_HOST' =>
173                 $arvados_api_client.arvados_v1_base.
174                 sub(/\/arvados\/v1/, '').
175                 sub(/^https?:\/\//, ''),
176                 'ARVADOS_API_TOKEN' =>
177                 @opts[:arvados_api_token],
178                 'ARVADOS_API_HOST_INSECURE' =>
179                 Rails.configuration.arvados_insecure_https ? 'true' : 'false'
180               })
181       IO.popen([env, 'arv-get', "#{@opts[:uuid]}/#{@opts[:file]}"],
182                'rb') do |io|
183         while buf = io.read(2**20)
184           yield buf
185         end
186       end
187       Rails.logger.warn("#{@opts[:uuid]}/#{@opts[:file]}: #{$?}") if $? != 0
188     end
189   end
190 end