3504: Support copy and moving collections on new apiserver
[arvados.git] / apps / workbench / app / controllers / actions_controller.rb
1 class ActionsController < ApplicationController
2
3   @@exposed_actions = {}
4   def self.expose_action method, &block
5     @@exposed_actions[method] = true
6     define_method method, block
7   end
8
9   def model_class
10     ArvadosBase::resource_class_for_uuid(params[:uuid])
11   end
12
13   def show
14     @object = model_class.andand.find(params[:uuid])
15     if @object.is_a? Link and
16         @object.link_class == 'name' and
17         ArvadosBase::resource_class_for_uuid(@object.head_uuid) == Collection
18       redirect_to collection_path(id: @object.uuid)
19     elsif @object
20       redirect_to @object
21     else
22       raise ActiveRecord::RecordNotFound
23     end
24   end
25
26   def post
27     params.keys.collect(&:to_sym).each do |param|
28       if @@exposed_actions[param]
29         return self.send(param)
30       end
31     end
32     redirect_to :back
33   end
34
35   expose_action :copy_selections_into_project do
36     move_or_copy :copy
37   end
38
39   expose_action :move_selections_into_project do
40     move_or_copy :move
41   end
42
43   def move_or_copy action
44     uuids_to_add = params["selection"]
45     uuids_to_add = [ uuids_to_add ] unless uuids_to_add.is_a? Array
46     uuids_to_add.
47       collect { |x| ArvadosBase::resource_class_for_uuid(x) }.
48       uniq.
49       each do |resource_class|
50       resource_class.filter([['uuid','in',uuids_to_add]]).each do |src|
51         if resource_class == Collection and not Collection.attribute_info.include?(:name)
52           dst = Link.new(owner_uuid: @object.uuid,
53                          tail_uuid: @object.uuid,
54                          head_uuid: src.uuid,
55                          link_class: 'name',
56                          name: src.uuid)
57         else
58           case action
59           when :copy
60             dst = src.dup
61             if dst.respond_to? :'name='
62               if dst.name
63                 dst.name = "Copy of #{dst.name}"
64               else
65                 dst.name = "Copy of unnamed #{dst.class_for_display.downcase}"
66               end
67             end
68             if resource_class == Collection
69               dst.manifest_text = Collection.select([:manifest_text]).where(uuid: src.uuid).first.manifest_text
70             end
71           when :move
72             dst = src
73           else
74             raise ArgumentError.new "Unsupported action #{action}"
75           end
76           dst.owner_uuid = @object.uuid
77           dst.tail_uuid = @object.uuid if dst.class == Link
78         end
79         begin
80           dst.save!
81         rescue
82           dst.name += " (#{Time.now.localtime})" if dst.respond_to? :name=
83           dst.save!
84         end
85       end
86     end
87     redirect_to @object
88   end
89
90   def arv_normalize mt, *opts
91     r = ""
92     IO.popen(['arv-normalize'] + opts, 'w+b') do |io|
93       io.write mt
94       io.close_write
95       while buf = io.read(2**16)
96         r += buf
97       end
98     end
99     r
100   end
101
102   expose_action :combine_selected_files_into_collection do
103     lst = []
104     files = []
105     params["selection"].each do |s|
106       a = ArvadosBase::resource_class_for_uuid s
107       m = nil
108       if a == Link
109         begin
110           m = CollectionsHelper.match(Link.find(s).head_uuid)
111         rescue
112         end
113       else
114         m = CollectionsHelper.match(s)
115       end
116
117       if m and m[1] and m[2]
118         lst.append(m[1] + m[2])
119         files.append(m)
120       end
121     end
122
123     collections = Collection.where(uuid: lst)
124
125     chash = {}
126     collections.each do |c|
127       c.reload()
128       chash[c.uuid] = c
129     end
130
131     combined = ""
132     files.each do |m|
133       mt = chash[m[1]+m[2]].manifest_text
134       if m[4]
135         combined += arv_normalize mt, '--extract', m[4][1..-1]
136       else
137         combined += chash[m[1]+m[2]].manifest_text
138       end
139     end
140
141     normalized = arv_normalize combined
142     normalized_stripped = arv_normalize combined, '--strip'
143
144     require 'digest/md5'
145
146     d = Digest::MD5.new()
147     d << normalized_stripped
148     newuuid = "#{d.hexdigest}+#{normalized_stripped.length}"
149
150     env = Hash[ENV].
151       merge({
152               'ARVADOS_API_HOST' =>
153               arvados_api_client.arvados_v1_base.
154               sub(/\/arvados\/v1/, '').
155               sub(/^https?:\/\//, ''),
156               'ARVADOS_API_TOKEN' => Thread.current[:arvados_api_token],
157               'ARVADOS_API_HOST_INSECURE' =>
158               Rails.configuration.arvados_insecure_https ? 'true' : 'false'
159             })
160
161     IO.popen([env, 'arv-put', '--raw'], 'w+b') do |io|
162       io.write normalized_stripped
163       io.close_write
164       while buf = io.read(2**16)
165       end
166     end
167
168     newc = Collection.new({:uuid => newuuid, :manifest_text => normalized})
169     newc.save!
170
171     chash.each do |k,v|
172       l = Link.new({
173                      tail_uuid: k,
174                      head_uuid: newuuid,
175                      link_class: "provenance",
176                      name: "provided"
177                    })
178       l.save!
179     end
180
181     redirect_to controller: 'collections', action: :show, id: newc.uuid
182   end
183
184 end