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