[webui] improve the services ui
[opensuse:build-service.git] / src / webui / app / controllers / package_controller.rb
1 require 'open-uri'
2 require 'project'
3
4 class PackageController < ApplicationController
5
6   include ApplicationHelper
7   include PackageHelper
8
9   before_filter :require_project, :except => [:add_person, :create_submit,
10     :edit_file, :import_spec, :rawlog, :remove_file, :remove_person,
11     :remove_url, :save, :save_modified_file, :save_person,
12     :set_url, :set_url_form, :update_build_log]
13   before_filter :require_package, :except => [:create_submit, :edit_file, :rawlog,
14     :save_modified_file, :save_new, :save_new_link, :update_build_log]
15
16   before_filter :load_current_requests
17   before_filter :require_login, :only => [:branch]
18   before_filter :require_meta, :only => [:edit_meta, :meta ]
19
20   def fill_email_hash
21     @email_hash = Hash.new
22     persons = [@package.each_person, @project.each_person].flatten.map{|p| p.userid.to_s}.uniq
23     persons.each do |person|
24       @email_hash[person] = Person.email_for_login(person)
25     end
26     @roles = Role.local_roles
27   end
28   private :fill_email_hash
29
30   def show
31     begin 
32       @buildresult = find_cached(Buildresult, :project => @project, :package => @package, :view => 'status', :expires_in => 5.minutes )
33     rescue => e
34       logger.error "No buildresult found for #{@project} / #{@package} : #{e.message}"
35     end
36     if @package.bugowner
37       @bugowner_mail = find_cached(Person, @package.bugowner ).email.to_s
38     elsif @project.bugowner
39       @bugowner_mail = find_cached(Person, @project.bugowner ).email.to_s
40     end
41     fill_status_cache unless @buildresult.blank?
42     linking_packages
43   end
44
45   def linking_packages
46     cache_string = "%s/%s_linking_packages" % [ @project, @package ]
47     Rails.cache.delete(cache_string) if discard_cache?
48     @linking_packages = Rails.cache.fetch( cache_string, :expires_in => 30.minutes) do
49        @package.linking_packages
50     end
51   end
52
53   def dependency
54     @arch = params[:arch]
55     @repository = params[:repository]
56     @drepository = params[:drepository]
57     @dproject = params[:dproject]
58     @filename = params[:filename]
59     @fileinfo = find_cached(Fileinfo, :project => params[:dproject], :package => '_repository', :repository => params[:drepository], :arch => @arch,
60       :filename => params[:dname], :view => 'fileinfo_ext')
61     @durl = nil
62   end
63
64   def binary
65     required_parameters :arch, :repository, :filename
66     @arch = params[:arch]
67     @repository = params[:repository]
68     @filename = params[:filename]
69     @fileinfo = find_cached(Fileinfo, :project => @project, :package => @package, :repository => @repository, :arch => @arch,
70       :filename => @filename, :view => 'fileinfo_ext')
71     unless @fileinfo
72       flash[:error] = "File \"#{@filename}\" could not be found in #{@repository}/#{@arch}"
73       redirect_to :controller => "package", :action => :binaries, :project => @project, 
74         :package => @package, :repository => @repository, :nextstatus => 404
75       return
76     end
77     @durl = "#{repo_url( @project, @repository )}/#{@fileinfo.arch}/#{@filename}" if @fileinfo.value :arch
78     @durl = "#{repo_url( @project, @repository )}/iso/#{@filename}" if (@fileinfo.value :filename) =~ /\.iso$/
79     if @durl and not file_available?( @durl )
80       # ignore files not available
81       @durl = nil
82     end 
83     if @user and !@durl
84       # only use API for logged in users if the mirror is not available
85       @durl = rpm_url( @project, @package, @repository, @arch, @filename )
86     end
87     logger.debug "accepting #{request.accepts.join(',')} format:#{request.format}"
88     # little trick to give users eager to download binaries a single click
89     if request.format != Mime::HTML and @durl
90       redirect_to @durl
91       return
92     end
93   end
94
95   def binaries
96     required_parameters :repository
97     @repository = params[:repository]
98     @buildresult = find_cached(Buildresult, :project => @project, :package => @package,
99       :repository => @repository, :view => ['binarylist', 'status'], :expires_in => 1.minute )
100     unless @buildresult
101       flash[:error] = "Package \"#{@package}\" has no build result for repository #{@repository}" 
102       redirect_to :controller => "package", :action => :show, :project => @project, :package => @package, :nextstatus => 404  
103       return
104     end
105     # load the flag details to disable links for forbidden binary downloads
106     @package = find_cached(Package, @package.name, :project => @project, :view => :flagdetails )
107   end
108
109   def users
110     fill_email_hash
111   end
112
113   def list_requests
114   end
115
116   def commit
117     render :partial => 'commit_item', :locals => {:rev => params[:revision] }
118   end
119
120   def files
121     @package.free_directory if discard_cache? || @revision != params[:rev] || @expand != params[:expand] || @srcmd5 != params[:srcmd5]
122     @revision = params[:rev]
123     @srcmd5   = params[:srcmd5]
124     @current_rev = Package.current_rev(@project, @package.name)
125     @expand = 1
126     @expand = begin Integer(params[:expand]) rescue 1 end if params[:expand]
127     set_file_details
128   end
129
130   def service_parameter_value
131     @values=Service.findAvailableParameterValues(params[:servicename], params[:parameter])
132     render :partial => 'service_parameter_value_selector',
133            :locals => { :servicename => params[:servicename], :parameter => params[:parameter], :number => params[:number], :value => params[:value], :setid => params[:setid] }
134   end
135
136   def source_history
137     # hard coded value for the number of visible commit items in browser
138     @visible_commits = 9
139     @maxrevision = Package.current_rev(@project, @package.name).to_i
140     @browserrevision = params[:rev]
141     @browserrevision = @maxrevision if not @browserrevision
142
143     # we need to fetch commits alltogether for the cache and not each single one
144     if params[:showall]
145       p = find_cached(Package, @package.name, :project => @project)
146       p.cacheAllCommits
147       @browserrevision = @visible_commits = @maxrevision
148     end
149   end
150
151   def add_service
152   end
153
154   def create_submit_request_dialog
155     @revision = Package.current_rev(@project, @package)
156   end
157
158   def create_submit_request
159     req = BsRequest.new(:type => "submit", :targetproject => params[:target_project], :targetpackage => params[:target_package],
160       :project => params[:project], :package => params[:package], :rev => params[:revision], :description => params[:description], :sourceupdate => params[:source_update])
161     begin
162       req.save(:create => true)
163     rescue ActiveXML::Transport::NotFoundError => e
164       message, code, api_exception = ActiveXML::Transport.extract_error_message e
165       flash[:error] = message
166       redirect_to :action => :rdiff, :oproject => params[:targetproject], :opackage => params[:targetpackage],
167         :project => params[:project], :package => params[:package]
168       return
169     end
170     Rails.cache.delete "requests_new"
171     redirect_to :controller => :request, :action => :show, :id => req.data["id"]
172   end
173
174   def service_parameter
175     begin
176       @serviceid = params[:serviceid]
177       @servicename = params[:servicename]
178       @services = find_cached(Service,  :project => @project, :package => @package )
179       @parameters = @services.getParameters(@serviceid)
180     rescue
181       @parameters = []
182     end
183   end
184
185   def update_parameters
186     required_parameters :project, :package, :serviceid
187     @project = params[:project]
188     @package = params[:package]
189     @serviceid = params[:serviceid]
190     @services = find_cached(Service,  :project => @project, :package => @package )
191
192     parameters=[]
193     params.keys.each do |key|
194       next unless key =~ /^parameter_/
195       name = key.gsub(/^parameter_([^_]*)_/, '')
196       parameters << { :name => name, :value => params[key] }
197     end
198
199     @services.setParameters( @serviceid, parameters )
200     @services.save
201     Service.free_cache :project => @project, :package => @package
202
203     redirect_to :action => 'files', :project => @project, :package => @package
204   end
205
206   def set_file_details
207     if not @revision and not @srcmd5
208       # on very first page load only
209       @revision = Package.current_rev(@project, @package)
210     end
211     if @srcmd5
212       @files = @package.files(@srcmd5, @expand)
213     else
214       @files = @package.files(@revision, @expand)
215     end
216
217     @spec_count = 0
218     @files.each do |file|
219       @spec_count += 1 if file[:ext] == "spec"
220       if file[:name] == "_link"
221         begin
222           @link = find_cached(Link, :project => @project, :package => @package, :rev => @revision )
223         rescue RuntimeError
224           # possibly thrown on bad link files
225         end
226       elsif file[:name] == "_service" or file[:name] == "_service_error"
227         begin
228           @services = find_cached(Service,  :project => @project, :package => @package )
229         rescue
230           @services = nil
231         end
232         @serviceerror = @services.error if @services and @services.error
233       end
234     end
235   end
236   private :set_file_details
237
238   def add_person
239     @roles = Role.local_roles
240     Package.free_cache :project => @project, :package => @package
241   end
242
243   def rdiff
244     required_parameters :project, :package
245     if params[:commit]
246       @opackage = params[:package]
247       @oproject = params[:project]
248       @rev = params[:commit]
249       @orev = (@rev.to_i - 1).to_s
250     else
251       required_parameters :opackage, :oproject
252       @opackage = params[:opackage]
253       @oproject = params[:oproject]
254     end
255     @rdiff = ''
256     path = "/source/#{CGI.escape(params[:project])}/#{CGI.escape(params[:package])}?" +
257       "opackage=#{CGI.escape(@opackage)}&oproject=#{CGI.escape(@oproject)}&unified=1&cmd=diff"
258     path += "&linkrev=#{CGI.escape(params[:linkrev])}" if params[:linkrev]
259     path += "&rev=#{CGI.escape(@rev)}" if @rev
260     path += "&orev=#{CGI.escape(@orev)}" if @orev
261     begin
262       @rdiff = frontend.transport.direct_http URI(path + "&expand=1"), :method => "POST", :data => ""
263     rescue ActiveXML::Transport::NotFoundError => e
264       message, code, api_exception = ActiveXML::Transport.extract_error_message e
265       flash.now[:error] = message
266       return
267     rescue ActiveXML::Transport::Error => e
268       message, code, api_exception = ActiveXML::Transport.extract_error_message e
269       flash.now[:warn] = message
270       begin
271         @rdiff = frontend.transport.direct_http URI(path + "&expand=0"), :method => "POST", :data => ""
272       rescue ActiveXML::Transport::Error => e
273         message, code, api_exception = ActiveXML::Transport.extract_error_message e
274         flash.now[:warn] = nil
275         flash.now[:error] = "Error getting diff: " + message
276         return
277       end
278     end
279     @lastreq = BsRequest.find_last_request(:targetproject => @oproject, :targetpackage => @opackage,
280       :sourceproject => params[:project], :sourcepackage => params[:package])
281     if @lastreq and @lastreq.state.name != "declined"
282       @lastreq = nil # ignore all !declined
283     end
284   end
285
286   def wizard_new
287     if params[:name]
288       if !valid_package_name_write? params[:name]
289         flash[:error] = "Invalid package name: '#{params[:name]}'"
290         redirect_to :action => 'wizard_new', :project => params[:project]
291       else
292         @package = Package.new( :name => params[:name], :project => @project )
293         if @package.save
294           redirect_to :action => 'wizard', :project => params[:project], :package => params[:name]
295         else
296           flash[:note] = "Failed to save package '#{@package}'"
297           redirect_to :controller => 'project', :action => 'show', :project => params[:project]
298         end
299       end
300     end
301   end
302
303   def wizard
304     files = params[:wizard_files]
305     fnames = {}
306     if files
307       logger.debug "files: #{files.inspect}"
308       files.each_key do |key|
309         file = files[key]
310         next if ! file.respond_to?(:original_filename)
311         fname = file.original_filename
312         fnames[key] = fname
313         # TODO: reuse code from PackageController#save_file and add_file.rhtml
314         # to also support fetching remote urls
315         @package.save_file :file => file, :filename => fname
316       end
317     end
318     other = params[:wizard]
319     if other
320       response = other.merge(fnames)
321     elsif ! fnames.empty?
322       response = fnames
323     else
324       response = nil
325     end
326     @wizard = Wizard.find(:project => params[:project],
327       :package => params[:package],
328       :response => response)
329   end
330
331
332   def save_new
333     valid_http_methods(:post)
334     @package_name = params[:name]
335     @package_title = params[:title]
336     @package_description = params[:description]
337
338     if !valid_package_name_write? params[:name]
339       flash[:error] = "Invalid package name: '#{params[:name]}'"
340       redirect_to :controller => :project, :action => 'new_package', :project => @project
341       return
342     end
343     if package_exists? @project, @package_name
344       flash[:error] = "Package '#{@package_name}' already exists in project '#{@project}'"
345       redirect_to :controller => :project, :action => 'new_package', :project => @project
346       return
347     end
348
349     @package = Package.new( :name => params[:name], :project => @project )
350     @package.title.text = params[:title]
351     @package.description.text = params[:description]
352     if params[:source_protection]
353       @package.add_element "sourceaccess"
354       @package.sourceaccess.add_element "disable"
355     end
356     if params[:disable_publishing]
357       @package.add_element "publish"
358       @package.publish.add_element "disable"
359     end
360     if @package.save
361       flash[:note] = "Package '#{@package}' was created successfully"
362       Rails.cache.delete("%s_packages_mainpage" % @project)
363       Rails.cache.delete("%s_problem_packages" % @project)
364       Package.free_cache( :all, :project => @project.name )
365       Package.free_cache( @package.name, :project => @project )
366       redirect_to :action => 'show', :project => params[:project], :package => params[:name]
367     else
368       flash[:note] = "Failed to create package '#{@package}'"
369       redirect_to :controller => 'project', :action => 'show', :project => params[:project]
370     end
371   end
372
373   def branch
374     valid_http_methods(:post)
375     begin
376       path = "/source/#{CGI.escape(params[:project])}/#{CGI.escape(params[:package])}?cmd=branch"
377       result = XML::Document.string frontend.transport.direct_http( URI(path), :method => "POST", :data => "" )
378       result_project = result.find_first( "/status/data[@name='targetproject']" ).content
379       result_package = result.find_first( "/status/data[@name='targetpackage']" ).content
380     rescue ActiveXML::Transport::Error => e
381       message, code, api_exception = ActiveXML::Transport.extract_error_message e
382       flash[:error] = message
383       redirect_to :controller => 'package', :action => 'show',
384         :project => params[:project], :package => params[:package] and return
385     end
386     flash[:success] = "Branched package #{@project} / #{@package}"
387     redirect_to :controller => 'package', :action => 'show',
388       :project => result_project, :package => result_package and return
389   end
390
391
392   def save_new_link
393     valid_http_methods(:post)
394     @linked_project = params[:linked_project].strip
395     @linked_package = params[:linked_package].strip
396     @target_package = params[:target_package].strip
397     @use_branch     = true if params[:branch]
398     @revision       = nil
399     @current_revision = true if params[:current_revision]
400
401     if !valid_package_name_read? @linked_package
402       flash[:error] = "Invalid package name: '#{@linked_package}'"
403       redirect_to :controller => :project, :action => 'new_package_link', :project => params[:project] and return
404     end
405
406     if !valid_project_name? @linked_project
407       flash[:error] = "Invalid project name: '#{@linked_project}'"
408       redirect_to :controller => :project, :action => 'new_package_link', :project => params[:project] and return
409     end
410
411     linked_package = Package.find( @linked_package, :project => @linked_project )
412     unless linked_package
413       flash[:error] = "Unable to find package '#{@linked_package}' in" +
414         " project '#{@linked_project}'."
415       redirect_to :controller => :project, :action => "new_package_link", :project => @project and return
416     end
417
418     @target_package = @linked_package if @target_package.blank?
419     if !valid_package_name_write? @target_package
420       flash[:error] = "Invalid target package name: '#{@target_package}'"
421       redirect_to :controller => :project, :action => "new_package_link", :project => @project and return
422     end
423     if package_exists? @project, @target_package
424       flash[:error] = "Package '#{@target_package}' already exists in project '#{@project}'"
425       redirect_to :controller => :project, :action => "new_package_link", :project => @project and return
426     end
427
428     if @current_revision
429       @revision = Package.current_xsrcmd5(@linked_project, @linked_package)
430       @revision = Package.current_rev(@linked_project, @linked_package) unless @revision
431     end
432
433     if @use_branch
434       logger.debug "link params doing branch: #{@linked_project}, #{@linked_package}"
435       begin
436         path = "/source/#{CGI.escape(@linked_project)}/#{CGI.escape(@linked_package)}?cmd=branch&target_project=#{CGI.escape(@project.name)}&target_package=#{CGI.escape(@target_package)}"
437         path += "&rev=#{CGI.escape(@revision)}" if @revision
438         result = XML::Document.string frontend.transport.direct_http( URI(path), :method => "POST", :data => "" )
439         flash[:success] = "Branched package #{@project.name} / #{@target_package}"
440       rescue ActiveXML::Transport::Error => e
441         message, code, api_exception = ActiveXML::Transport.extract_error_message e
442         flash[:error] = message
443       end
444     else
445       # construct container for link
446       package = Package.new( :name => @target_package, :project => @project )
447       package.title.text = linked_package.title.text
448
449       description = "This package is based on the package " +
450         "'#{@linked_package}' from project '#{@linked_project}'.\n\n"
451
452       description += linked_package.description.text if linked_package.description.text
453       package.description.text = description
454     
455       begin
456         saved = package.save
457       rescue ActiveXML::Transport::ForbiddenError => e
458         saved = false
459         message, code, api_exception = ActiveXML::Transport.extract_error_message e
460         flash[:error] = message
461         redirect_to :controller => 'project', :action => 'new_package_link',
462           :project => @project and return
463       end
464
465       unless saved
466         flash[:note] = "Failed to save package '#{package}'"
467         redirect_to :controller => 'project', :action => 'new_package_link',
468           :project => @project and return
469         logger.debug "link params: #{@linked_project}, #{@linked_package}"
470         link = Link.new( :project => @project,
471           :package => @target_package, :linked_project => @linked_project, :linked_package => @linked_package )
472         link.set_revision @revision if @revision
473         link.save
474         flash[:success] = "Successfully linked package '#{@linked_package}'"
475       end
476     end
477
478     Rails.cache.delete("%s_packages_mainpage" % @project)
479     Rails.cache.delete("%s_problem_packages" % @project)
480     Package.free_cache( :all, :project => @project.name )
481     Package.free_cache( @target_package, :project => @project )
482     redirect_to :controller => 'package', :action => 'show', :project => @project, :package => @target_package
483   end
484
485   def save
486     valid_http_methods(:post)
487     @package.title.text = params[:title]
488     @package.description.text = params[:description]
489     if @package.save
490       flash[:note] = "Package data for '#{@package.name}' was saved successfully"
491     else
492       flash[:note] = "Failed to save package '#{@package.name}'"
493     end
494     redirect_to :action => 'show', :project => params[:project], :package => params[:package]
495   end
496
497   def remove
498     valid_http_methods(:post)
499     begin
500       FrontendCompat.new.delete_package :project => @project, :package => @package
501       flash[:note] = "Package '#{@package}' was removed successfully from project '#{@project}'"
502       Rails.cache.delete("%s_packages_mainpage" % @project)
503       Rails.cache.delete("%s_problem_packages" % @project)
504       Package.free_cache( :all, :project => @project.name )
505       Package.free_cache( @package.name, :project => @project )
506     rescue ActiveXML::Transport::Error => e
507       message, code, api_exception = ActiveXML::Transport.extract_error_message e
508       flash[:error] = message
509     end
510     redirect_to :controller => 'project', :action => 'show', :project => @project
511   end
512
513   def add_file
514     set_file_details
515   end
516
517   def save_file
518     if request.method != :post
519       flash[:warn] = "File upload failed because this was no POST request. " +
520         "This probably happened because you were logged out in between. Please try again."
521       redirect_to :action => :files, :project => @project, :package => @package and return
522     end
523
524     file = params[:file]
525     file_url = params[:file_url]
526     filename = params[:filename]
527
528     if !file.blank?
529       # we are getting an uploaded file
530       filename = file.original_filename if filename.blank?
531
532       if !valid_file_name?(filename)
533         flash[:error] = "'#{filename}' is not a valid filename."
534         redirect_to :action => 'add_file', :project => params[:project], :package => params[:package] and return
535       end
536
537       # extra escaping of filename (workaround for rails bug)
538       @package.save_file :file => file, :filename => URI.escape(filename, "+")
539     elsif not file_url.blank?
540       # we have a remote file uri
541       @services = find_cached(Service, :project => @project, :package => @package )
542       unless @services
543         @services = Service.new( :project => @project, :package => @package )
544       end
545       if @services.addDownloadURL( file_url )
546          @services.save
547          Service.free_cache :project => @project, :package => @package
548       else
549          flash[:error] = "Failed to add URL #{file_url} to service."
550          redirect_to :action => 'add_file', :project => params[:project], :package => params[:package]
551          return
552       end
553     else
554       flash[:error] = 'No file or URI given.'
555       redirect_to :action => 'add_file', :project => params[:project], :package => params[:package]
556       return
557     end
558
559     if params[:addAsPatch]
560       link = Link.find( :project => @project, :package => @package )
561       if link
562         link.add_patch filename
563         link.save
564       end
565     elsif params[:applyAsPatch]
566       link = Link.find( :project => @project, :package => @package )
567       if link
568         link.apply_patch filename
569         link.save
570       end
571     end
572     flash[:success] = "The file #{filename} has been added."
573     @package.free_directory
574     redirect_to :action => :files, :project => @project, :package => @package
575   end
576
577   def add_or_move_service
578     id = params[:id]
579     @services = find_cached(Service,  :project => @project, :package => @package )
580     unless @services
581       @services = Service.new(:project => @project, :package => @package)
582     end
583
584     if id =~ /^new_service_/
585        id.gsub!( %r{^new_service_}, '' )
586        @services.addService( id, params[:position].to_i )
587        flash[:note] = "Service \##{id} added"
588     elsif id =~ /^service_/
589        id.gsub!( %r{^service_}, '' )
590        @services.moveService( id.to_i, params[:position].to_i )
591        flash[:note] = "Service \##{id} moved"
592     else
593        flash[:error] = "unkown object dropped"
594     end
595
596     @services.save
597     Service.free_cache :project => @project, :package => @package
598     redirect_to :action => :files, :project => @project, :package => @package and return
599   end
600
601   def execute_services
602     @services = find_cached(Service,  :project => @project, :package => @package )
603     @services.execute()
604     flash[:note] = "Service execution got triggered"
605     redirect_to :action => :files, :project => @project, :package => @package and return
606   end
607
608   def remove_service
609     required_parameters :id
610     id = params[:id].gsub( %r{^service_}, '' )
611     @services = find_cached(Service,  :project => @project, :package => @package )
612     unless @services
613       flash[:error] = "Service \##{id} not found"
614       redirect_to :action => :files, :project => @project, :package => @package 
615       return
616     end
617     @services.removeService( id )
618     @services.save
619     Service.free_cache :project => @project, :package => @package
620     Directory.free_cache( :project => @project, :package => @package )
621     flash[:note] = "Service \##{id} got removed"
622     redirect_to :action => :files, :project => @project, :package => @package
623   end
624
625   def remove_file
626     if request.method != :post
627       flash[:warn] = "File removal failed because this was no POST request. " +
628         "This probably happened because you were logged out in between. Please try again."
629       redirect_to :action => :files, :project => @project, :package => @package and return
630     end
631     if not params[:filename]
632       flash[:note] = "Removing file aborted: no filename given."
633       redirect_to :action => :files, :project => @project, :package => @package
634     end
635     filename = params[:filename]
636     # extra escaping of filename (workaround for rails bug)
637     escaped_filename = URI.escape filename, "+"
638     if @package.remove_file escaped_filename
639       flash[:note] = "File '#{filename}' removed successfully"
640       @package.free_directory
641       # TODO: remove patches from _link
642     else
643       flash[:note] = "Failed to remove file '#{filename}'"
644     end
645     redirect_to :action => :files, :project => @project, :package => @package
646   end
647
648   def save_person
649     valid_http_methods(:post)
650     if not valid_role_name? params[:userid]
651       flash[:error] = "Invalid username: #{params[:userid]}"
652       redirect_to :action => :add_person, :project => @project, :package => @package, :role => params[:role]
653       return
654     end
655     user = find_cached(Person, params[:userid] )
656     unless user
657       flash[:error] = "Unknown user '#{params[:userid]}'"
658       redirect_to :action => :add_person, :project => @project, :package => params[:package], :role => params[:role]
659       return
660     end
661     @package.add_person( :userid => params[:userid], :role => params[:role] )
662     if @package.save
663       flash[:note] = "Added user #{params[:userid]} with role #{params[:role]}"
664     else
665       flash[:note] = "Failed to add user '#{params[:userid]}'"
666     end
667     redirect_to :action => :users, :package => @package, :project => @project
668   end
669
670
671   def remove_person
672     valid_http_methods(:post)
673     @package.remove_persons( :userid => params[:userid], :role => params[:role] )
674     if @package.save
675       flash[:note] = "Removed user #{params[:userid]}"
676     else
677       flash[:note] = "Failed to remove user '#{params[:userid]}'"
678     end
679     redirect_to :action => :users, :package => @package, :project => @project
680   end
681
682
683   def edit_file
684     @project = params[:project]
685     @package = params[:package]
686     @filename = params[:file]
687     @comment = params[:comment]
688     @expand = params[:expand]
689     @srcmd5 = params[:srcmd5]
690     @file = params[:content] || frontend.get_source( :project => @project,
691       :package => @package, :filename => @filename, :rev => @srcmd5, :expand => @expand )
692     # render explicitly as in error case this is called
693     render :template => 'package/edit_file'
694   end
695
696   def view_file
697     @filename = params[:file] || ''
698     @expand = params[:expand]
699     @srcmd5 = params[:srcmd5]
700     @addeditlink = false
701     if @package.can_edit?( session[:login] )
702       @package.files(@srcmd5, @expand).each do |file|
703         if file[:name] == @filename
704           @addeditlink = file[:editable]
705           break
706         end
707       end
708     end
709     begin
710       @file = frontend.get_source( :project => @project.to_s,
711         :package => @package.to_s, :filename => @filename, :rev => @srcmd5 )
712     rescue ActiveXML::Transport::NotFoundError => e
713       flash[:error] = "File not found: #{@filename}"
714       redirect_to :action => :show, :package => @package, :project => @project
715     end
716   end
717
718   def save_modified_file
719     project = params[:project]
720     package = params[:package]
721     if request.method != :post
722       flash[:warn] = "Saving file failed because this was no POST request. " +
723         "This probably happened because you were logged out in between. Please try again."
724       redirect_to :action => :show, :project => project, :package => package and return
725     end
726     required_parameters :project, :package, :filename, :file
727     filename = params[:filename]
728     file = params[:file]
729     comment = params[:comment]
730     file.gsub!( /\r\n/, "\n" )
731     begin
732       frontend.put_file( file, :project => project, :package => package,
733         :filename => filename, :comment => comment )
734       flash[:note] = "Successfully saved file #{filename}"
735       Directory.free_cache( :project => project, :package => package )
736     rescue Timeout::Error => e
737       flash[:error] = "Timeout when saving file. Please try again."
738     rescue ActiveXML::Transport::Error => e
739       message, code, api_exception = ActiveXML::Transport.extract_error_message e
740       # if code == "validation_failed"
741       flash[:error] = message
742       params[:file] = filename
743       params[:content] = file
744       params[:comment] = comment
745       edit_file # :package => package, :project => project, :file => filename, :content => file, :comment => comment
746       return
747     end
748     redirect_to :action => :files, :package => package, :project => project
749   end
750
751   def rawlog
752     valid_http_methods :get
753     if CONFIG['use_lighttpd_x_rewrite']
754       headers['X-Rewrite-URI'] = "/build/#{params[:project]}/#{params[:repository]}/#{params[:arch]}/#{params[:package]}/_log"
755       headers['X-Rewrite-Host'] = FRONTEND_HOST
756       head(200) and return
757     end
758
759     headers['Content-Type'] = 'text/plain'
760     render :text => proc { |response, output|
761       maxsize = 1024 * 256
762       offset = 0
763       while true
764         begin
765           chunk = frontend.get_log_chunk(params[:project], params[:package], params[:repository], params[:arch], offset, offset + maxsize )
766         rescue ActiveXML::Transport::Error
767           chunk = ''
768         end
769         if chunk.length == 0
770           break
771         end
772         offset += chunk.length
773         output.write(chunk)
774       end
775     }
776   end
777
778   def escape_log(log)
779     log = CGI.escapeHTML(log)
780     log.gsub(/[\t]/, '    ').gsub(/[\n\r]/n,"<br/>\n").gsub(' ', '&ensp;')
781   end
782   private :escape_log
783
784   def live_build_log
785     @arch = params[:arch]
786     @repo = params[:repository]
787     begin
788       size = frontend.get_size_of_log(@project, @package, @repo, @arch)
789       logger.debug("log size is %d" % size)
790       @offset = size - 32 * 1024
791       @offset = 0 if @offset < 0
792       @initiallog = frontend.get_log_chunk( @project, @package, @repo, @arch, @offset, size)
793     rescue => e
794       logger.error "Got #{e.class}: #{e.message}; returning empty log."
795       @initiallog = ''
796     end
797     @offset = (@offset || 0) + @initiallog.length
798     @initiallog = escape_log(@initiallog)
799     @initiallog.gsub!(/([^a-zA-Z0-9&;<>\/\n \t()])/n) do
800       if $1[0].to_i < 32
801         ''
802       else
803         $1
804       end
805     end
806   end
807
808
809   def update_build_log
810     @project = params[:project]
811     @package = params[:package]
812     @arch = params[:arch]
813     @repo = params[:repository]
814     @initial = params[:initial]
815     @offset = params[:offset].to_i
816     @finished = false
817     maxsize = 1024 * 64
818
819     begin
820       log_chunk = frontend.get_log_chunk( @project, @package, @repo, @arch, @offset, @offset + maxsize)
821
822       if( log_chunk.length == 0 )
823         @finished = true
824       else
825         @offset += log_chunk.length
826         log_chunk = escape_log(log_chunk)
827       end
828
829     rescue Timeout::Error => ex
830       log_chunk = ""
831
832     rescue => e
833       log_chunk = "No live log available"
834       @finished = true
835     end
836
837     render :update do |page|
838
839       logger.debug 'finished ' + @finished.to_s
840
841       if @finished
842         page.call 'build_finished'
843         page.call 'hide_abort'
844       else
845         page.call 'show_abort'
846         page.insert_html :bottom, 'log_space', log_chunk
847         if log_chunk.length < maxsize || @initial == 0
848           page.call 'autoscroll'
849           page.delay(2) do
850             page.call 'refresh', @offset, 0
851           end
852         else
853           logger.debug 'call refresh without delay'
854           page.call 'refresh', @offset, @initial
855         end
856       end
857     end
858   end
859
860   def abort_build
861     params[:redirect] = 'live_build_log'
862     api_cmd('abortbuild', params)
863   end
864
865
866   def trigger_rebuild
867     valid_http_methods :delete
868     api_cmd('rebuild', params)
869   end
870
871   def wipe_binaries
872     valid_http_methods :delete
873     api_cmd('wipe', params)
874   end
875
876   def api_cmd(cmd, params)
877     options = {}
878     options[:arch] = params[:arch] if params[:arch]
879     options[:repository] = params[:repo] if params[:repo]
880     options[:project] = @project.to_s
881     options[:package] = @package.to_s
882
883     begin
884       frontend.cmd cmd, options
885     rescue ActiveXML::Transport::Error => e
886       message, code, api_exception = ActiveXML::Transport.extract_error_message e
887       flash[:error] = message
888       redirect_to :action => :show, :project => @project, :package => @package and return
889     end
890
891     logger.debug( "Triggered #{cmd} for #{@project}/#{@package}, options=#{options.inspect}" )
892     @message = "Triggered #{cmd} for #{@project}/#{@package}."
893     controller = 'package'
894     action = 'show'
895     if  params[:redirect] == 'monitor'
896       controller = 'project'
897       action = 'monitor'
898     end
899
900     unless request.xhr?
901       # non ajax request:
902       flash[:note] = @message
903       redirect_to :controller => controller, :action => action,
904         :project => @project, :package => @package
905     else
906       # ajax request - render default view: in this case 'trigger_rebuild.rjs'
907       return
908     end
909   end
910   private :api_cmd
911   
912   def import_spec
913     all_files = @package.files
914     all_files.each do |file|
915       @specfile_name = file[:name] if file[:name].grep(/\.spec/) != []
916     end
917     specfile_content = frontend.get_source(
918       :project => params[:project], :package => params[:package], :filename => @specfile_name
919     )
920
921     description = []
922     lines = specfile_content.split(/\n/)
923     line = lines.shift until line =~ /^%description\s*$/
924     description << lines.shift until description.last =~ /^%/
925     # maybe the above end-detection of the description-section could be improved like this:
926     # description << lines.shift until description.last =~ /^%\{?(debug_package|prep|pre|preun|....)/
927     description.pop
928
929     render :text => description.join("\n")
930     logger.debug "imported description from spec file"
931   end
932
933   def reload_buildstatus
934     # discard cache
935     Buildresult.free_cache( :project => @project, :package => @package, :view => 'status' )
936     @buildresult = find_cached(Buildresult, :project => @project, :package => @package, :view => 'status', :expires_in => 5.minutes )
937     fill_status_cache unless @buildresult.blank?
938     render :partial => 'buildstatus'
939   end
940
941
942   def set_url_form
943     if @package.has_element? :url
944       @new_url = @package.url.to_s
945     else
946       @new_url = 'http://'
947     end
948     render :partial => "set_url_form"
949   end
950
951   def edit_meta
952     render :template => "package/edit_meta"
953   end
954
955   def meta
956   end
957
958   def save_meta
959     begin
960       frontend.put_file(params[:meta], :project => @project, :package => @package, :filename => '_meta')
961     rescue ActiveXML::Transport::Error => e
962       message, code, api_exception = ActiveXML::Transport.extract_error_message e
963       flash[:error] = message
964       @meta = params[:meta]
965       edit_meta
966       return
967     end
968     
969     flash[:note] = "Config successfully saved"
970     Package.free_cache @package, :project => @project
971     redirect_to :action => :meta, :project => @project, :package => @package
972   end
973
974   def attributes
975     @attributes = find_cached(Attribute, {:project => @project.name, :package => @package.to_s}, :expires_in => 2.minutes)
976   end
977
978   def edit
979   end
980
981   def set_url
982     @package.set_url params[:url]
983     render :partial => 'url_line', :locals => { :url => params[:url] }
984   end
985
986   def remove_url
987     @package.remove_url
988     redirect_to :action => "show", :project => params[:project], :package => params[:package]
989   end
990
991   def repositories
992     @package = find_cached(Package, params[:package], :project => params[:project], :view => :flagdetails )
993   end
994
995   def change_flag
996     if request.post? and params[:cmd] and params[:flag]
997       frontend.source_cmd params[:cmd], :project => @project, :package => @package, :repository => params[:repository], :arch => params[:arch], :flag => params[:flag], :status => params[:status]
998     end
999     Package.free_cache( params[:package], :project => @project.name, :view => :flagdetails )
1000     if request.xhr?
1001       @package = find_cached(Package, params[:package], :project => @project.name, :view => :flagdetails )
1002       render :partial => 'shared/repositories_flag_table', :locals => { :flags => @package.send(params[:flag]), :obj => @package }
1003     else
1004       redirect_to :action => :repositories, :project => @project, :package => @package
1005     end
1006   end
1007
1008   private
1009
1010   def file_available? url, max_redirects=5
1011     uri = URI.parse( url )
1012     http = Net::HTTP.new(uri.host, uri.port)
1013     http.open_timeout = 15
1014     http.read_timeout = 15
1015     logger.debug "Checking url: #{url}"
1016     begin
1017       response =  http.head uri.path
1018       if response.code.to_i == 302 and response['location'] and max_redirects > 0
1019         return file_available? response['location'], (max_redirects - 1)
1020       end
1021       return response.code.to_i == 200 ? true : false
1022     rescue Object => e
1023       logger.error "Error in checking for file #{url}: #{e.message}"
1024       return false
1025     end
1026   end
1027
1028   def require_project
1029     if valid_project_name? params[:project]
1030       @project = find_cached(Project, params[:project], :expires_in => 5.minutes )
1031     end
1032     unless @project
1033       logger.error "Project #{params[:project]} not found"
1034       flash[:error] = "Project not found: \"#{params[:project]}\""
1035       redirect_to :controller => "project", :action => "list_public" and return
1036     end
1037   end
1038
1039   def require_package
1040     unless valid_package_name_read? params[:package]
1041       logger.error "Package #{@project}/#{params[:package]} not valid"
1042       flash[:error] = "\"#{params[:package]}\" is not a valid package name"
1043       redirect_to :controller => "project", :action => :packages, :project => @project, :nextstatus => 404
1044       return
1045     end
1046     @project ||= params[:project]
1047     unless params[:package].blank?
1048       @package = find_cached(Package, params[:package], :project => @project )
1049     end
1050     unless @package
1051       logger.error "Package #{@project}/#{params[:package]} not found"
1052       flash[:error] = "Package \"#{params[:package]}\" not found in project \"#{params[:project]}\""
1053       redirect_to :controller => "project", :action => :packages, :project => @project, :nextstatus => 404
1054     end
1055   end
1056
1057   def require_meta
1058     begin
1059       @meta = frontend.get_source(:project => params[:project], :package => params[:package], :filename => '_meta')
1060     rescue ActiveXML::Transport::NotFoundError
1061       flash[:error] = "Package _meta not found: #{params[:project]}/#{params[:package]}"
1062       redirect_to :controller => "project", :action => "show", :project => params[:project], :nextstatus => 404
1063     end
1064   end
1065
1066   def fill_status_cache
1067     @repohash = Hash.new
1068     @statushash = Hash.new
1069     @packagenames = Array.new
1070     @repostatushash = Hash.new
1071     @failures = 0
1072
1073     @buildresult.each_result do |result|
1074       @resultvalue = result
1075       repo = result.repository
1076       arch = result.arch
1077
1078       @repohash[repo] ||= Array.new
1079       @repohash[repo] << arch
1080
1081       # package status cache
1082       @statushash[repo] ||= Hash.new
1083       @statushash[repo][arch] = Hash.new
1084
1085       stathash = @statushash[repo][arch]
1086       result.each_status do |status|
1087         stathash[status.package] = status
1088         if ['unresolvable', 'failed', 'broken'].include? status.code
1089           @failures += 1
1090         end
1091       end
1092
1093       # repository status cache
1094       @repostatushash[repo] ||= Hash.new
1095       @repostatushash[repo][arch] = Hash.new
1096
1097       if result.has_attribute? :state
1098         if result.has_attribute? :dirty
1099           @repostatushash[repo][arch] = "outdated_" + result.state.to_s
1100         else
1101           @repostatushash[repo][arch] = result.state.to_s
1102         end
1103       end
1104
1105       @packagenames << stathash.keys
1106     end
1107
1108     if @buildresult and !@buildresult.has_element? :result
1109       @buildresult = nil
1110     end
1111
1112     return unless @buildresult
1113
1114     newr = Hash.new
1115     @buildresult.each_result.sort {|a,b| a.repository <=> b.repository}.each do |result|
1116       repo = result.repository
1117       if result.has_element? :status
1118         newr[repo] ||= Array.new
1119         newr[repo] << result.arch
1120       end
1121     end
1122
1123     @buildresult = Array.new
1124     newr.keys.sort.each do |r|
1125       @buildresult << [r, newr[r].flatten.sort]
1126     end
1127   end
1128
1129   def load_current_requests
1130     predicate = "state/@name='new' and action/target/@project='#{@project}' and action/target/@package='#{@package}'"
1131     @current_requests = Array.new
1132     coll = find_cached(Collection, :what => :request, :predicate => predicate, :expires_in => 1.minutes)
1133     coll.each_request do |req|
1134       @current_requests << req
1135     end
1136     @package_has_requests = !@current_requests.blank?
1137   end
1138
1139 end