fix xml generation for _patchinfo files
[opensuse:build-service.git] / src / api / app / controllers / source_controller.rb
1 require "rexml/document"
2
3 class SourceController < ApplicationController
4   validate_action :index => :directory, :packagelist => :directory, :filelist => :directory
5   validate_action :project_meta => :project, :package_meta => :package, :pattern_meta => :pattern
6  
7   skip_before_filter :extract_user, :only => [:file, :project_meta, :project_config] 
8
9   def index
10     projectlist
11   end
12
13   def projectlist
14     if request.post?
15       # a bit danguerous, never implment a command without proper permission check
16       dispatch_command
17     else
18       @dir = Project.find :all
19       render :text => @dir.dump_xml, :content_type => "text/xml"
20     end
21   end
22
23   def index_project
24     project_name = params[:project]
25     pro = DbProject.find_by_name project_name
26     if pro.nil?
27       render_error :status => 404, :errorcode => 'unknown_project',
28         :message => "Unknown project #{project_name}"
29       return
30     end
31     
32     if request.get?
33       @dir = Package.find :all, :project => project_name
34       render :text => @dir.dump_xml, :content_type => "text/xml"
35       return
36     elsif request.delete?
37       unless @http_user.can_modify_project?(pro)
38         logger.debug "No permission to delete project #{project_name}"
39         render_error :status => 403, :errorcode => 'delete_project_no_permission',
40           :message => "Permission denied (delete project #{project_name})"
41         return
42       end
43
44       #deny deleting if other packages use this as develproject
45       unless pro.develpackages.empty?
46         msg = "Unable to delete project #{pro.name}; following packages use this project as develproject: "
47         msg += pro.develpackages.map {|pkg| pkg.db_project.name+"/"+pkg.name}.join(", ")
48         render_error :status => 400, :errorcode => 'develproject_dependency',
49           :message => msg
50         return
51       end
52       #check all packages, if any get refered as develpackage
53       pro.db_packages.each do |pkg|
54         unless pkg.develpackages.empty?
55           msg = "Unable to delete package #{pkg.name}; following packages use this package as devel package: "
56           msg += pkg.develpackages.map {|dp| dp.db_project.name+"/"+dp.name}.join(", ")
57           render_error :status => 400, :errorcode => 'develpackage_dependency',
58             :message => msg
59           return
60         end
61       end
62
63       #find linking repos
64       lreps = Array.new
65       pro.repositories.each do |repo|
66         repo.linking_repositories.each do |lrep|
67           lreps << lrep
68         end
69       end
70
71       if lreps.length > 0
72         if params[:force] and not params[:force].empty?
73           #replace links to this projects with links to the "deleted" project
74           del_repo = DbProject.find_by_name("deleted").repositories[0]
75           lreps.each do |link_rep|
76             link_rep.path_elements.find(:all).each { |pe| pe.destroy }
77             link_rep.path_elements.create(:link => del_repo)
78             link_rep.save
79             #update backend
80             link_rep.db_project.store
81           end
82         else
83           lrepstr = lreps.map{|l| l.db_project.name+'/'+l.name}.join "\n"
84           render_error :status => 403, :errorcode => "repo_dependency",
85             :message => "Unable to delete project #{project_name}; following repositories depend on this project:\n#{lrepstr}\n"
86           return
87         end
88       end
89
90       #destroy all packages
91       pro.db_packages.each do |pack|
92         DbPackage.transaction do
93           logger.info "destroying package #{pack.name}"
94           pack.destroy
95         end
96       end
97
98       DbProject.transaction do
99         logger.info "destroying project #{pro.name}"
100         pro.destroy
101         logger.debug "delete request to backend: /source/#{pro.name}"
102         Suse::Backend.delete "/source/#{pro.name}"
103       end
104
105       render_ok
106       return
107     elsif request.post?
108       cmd = params[:cmd]
109       if @http_user.can_modify_project?(pro)
110         dispatch_command
111       else
112         render_error :status => 403, :errorcode => "cmd_execution_no_permission",
113           :message => "no permission to execute command '#{cmd}'"
114         return
115       end
116     else
117       render_error :status => 400, :errorcode => "illegal_request",
118         :message => "illegal POST request to #{request.request_uri}"
119     end
120   end
121
122   def index_package
123     valid_http_methods :get, :delete, :post
124     project_name = params[:project]
125     package_name = params[:package]
126     cmd = params[:cmd]
127
128     pkg = DbPackage.find_by_project_and_name(project_name, package_name)
129     unless pkg or DbProject.find_remote_project(project_name)
130       render_error :status => 404, :errorcode => "unknown_package",
131         :message => "unknown package '#{package_name}' in project '#{project_name}'"
132       return
133     end
134
135     if request.get?
136       pass_to_source
137       return
138     elsif request.delete?
139       if not @http_user.can_modify_package?(pkg)
140         render_error :status => 403, :errorcode => "delete_package_no_permission",
141           :message => "no permission to delete package #{package_name}"
142         return
143       end
144       
145       #deny deleting if other packages use this as develpackage
146       # Shall we offer a --force option here as well ?
147       # Shall we ask the other package owner accepting to be a devel package ?
148       unless pkg.develpackages.empty?
149         msg = "Unable to delete package #{pkg.name}; following packages use this package as devel package: "
150         msg += pkg.develpackages.map {|dp| dp.db_project.name+"/"+dp.name}.join(", ")
151         render_error :status => 400, :errorcode => 'develpackage_dependency',
152           :message => msg
153         return
154       end
155
156       DbPackage.transaction do
157         pkg.destroy
158         Suse::Backend.delete "/source/#{project_name}/#{package_name}"
159         if package_name == "_product"
160           update_product_autopackages
161         end
162       end
163       render_ok
164     elsif request.post?
165       if not ['diff', 'branch'].include?(cmd) and not @http_user.can_modify_package?(pkg)
166         render_error :status => 403, :errorcode => "cmd_execution_no_permission",
167           :message => "no permission to execute command '#{cmd}'"
168         return
169       end
170       
171       dispatch_command
172     end
173   end
174
175   # updates packages automatically generated in the backend after submitting a product file
176   def update_product_autopackages
177     backend_pkgs = Collection.find :package, :match => "@project='#{params[:project]}' and starts-with(@name,'_product:')"
178     b_pkg_index = backend_pkgs.each_package.inject(Hash.new) {|hash,elem| hash[elem.name] = elem; hash}
179     frontend_pkgs = DbProject.find_by_name(params[:project]).db_packages.find(:all, :conditions => "name LIKE '_product:%'")
180     f_pkg_index = frontend_pkgs.inject(Hash.new) {|hash,elem| hash[elem.name] = elem; hash}
181
182     all_pkgs = [b_pkg_index.keys, f_pkg_index.keys].flatten.uniq
183
184     wt_state = ActiveXML::Config.global_write_through
185     begin
186       ActiveXML::Config.global_write_through = false
187       all_pkgs.each do |pkg|
188         if b_pkg_index.has_key?(pkg) and not f_pkg_index.has_key?(pkg)
189           # new autopackage, import in database
190           Package.new(b_pkg_index[pkg].dump_xml, :project => params[:project]).save
191         elsif f_pkg_index.has_key?(pkg) and not b_pkg_index.has_key?(pkg)
192           # autopackage was removed, remove from database
193           f_pkg_index[pkg].destroy
194         end
195       end
196     ensure
197       ActiveXML::Config.global_write_through = wt_state
198     end
199   end
200
201   # /source/:project/_attribute/:attribute
202   # /source/:project/:package/:binary/_attribute/:attribute
203   def attribute_meta
204     valid_http_methods :get, :post, :delete
205     params[:user] = @http_user.login if @http_user
206
207     binary=nil
208     binary=params[:binary] if params[:binary]
209
210     if params[:package]
211       @attribute_container = DbPackage.find_by_project_and_name(params[:project], params[:package])
212       unless @attribute_container
213         render_error :message => "Unknown project '#{params[:project]}'",
214           :status => 404, :errorcode => "unknown_project"
215         return
216       end
217     else
218       @attribute_container = DbProject.find_by_name(params[:project])
219       unless @attribute_container
220         render_error :message => "Unknown project '#{params[:project]}'",
221           :status => 404, :errorcode => "unknown_project"
222         return
223       end
224     end
225
226     if request.get?
227       params[:binary]=binary if binary
228       render :text => @attribute_container.render_attribute_axml(params), :content_type => 'text/xml'
229       return
230     end
231
232     if request.post?
233       begin
234         req = BsRequest.new(request.body.read)
235         req.data # trigger XML parsing
236       rescue ActiveXML::ParseError => e
237         render_error :message => "Invalid XML",
238            :status => 400, :errorcode => "invalid_xml"
239         return
240       end
241     end
242
243     # permission checking
244     if params[:attribute]
245       aname = params[:attribute]
246       name_parts = aname.split /:/
247       if name_parts.length != 2
248         raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
249       end
250       if a=@attribute_container.find_attribute(name_parts[0],name_parts[1],binary)
251         unless @http_user.can_modify_attribute? a
252           render_error :status => 403, :errorcode => "change_attribute_no_permission", 
253             :message => "user #{user.login} has no permission to modify attribute"
254           return
255         end
256       else
257         unless request.post?
258           render_error :status => 403, :errorcode => "not_existing_attribute", 
259             :message => "Attempt to modify not existing attribute"
260           return
261         end
262         unless @http_user.can_create_attribute_in? @attribute_container, :namespace => name_parts[0], :name => name_parts[1]
263           render_error :status => 403, :errorcode => "change_attribute_no_permission", 
264             :message => "user #{user.login} has no permission to change attribute"
265           return
266         end
267       end
268     else
269       if request.post?
270         req.each_attribute do |a|
271           begin
272             can_create = @http_user.can_create_attribute_in? @attribute_container, :namespace => a.namespace, :name => a.name
273           rescue ActiveRecord::RecordNotFound => e
274             render_error :status => 404, :errorcode => "not_found",
275               :message => e.message
276             return
277           rescue User::ArgumentError => e
278             render_error :status => 400, :errorcode => "change_attribute_attribute_error",
279               :message => e.message
280             return
281           end
282           unless can_create
283             render_error :status => 403, :errorcode => "change_attribute_no_permission", 
284               :message => "user #{user.login} has no permission to change attribute"
285             return
286           end
287         end
288       else
289         render_error :status => 403, :errorcode => "internal_error", 
290           :message => "INTERNAL ERROR: unhandled request"
291         return
292       end
293     end
294
295     # execute action
296     if request.post?
297       req.each_attribute do |a|
298         begin
299           @attribute_container.store_attribute_axml(a, binary)
300         rescue DbProject::SaveError => e
301           render_error :status => 403, :errorcode => "save_error", :message => e.message
302           return
303         end
304       end
305       @attribute_container.store
306       render_ok
307     elsif request.delete?
308       @attribute_container.find_attribute(name_parts[0], name_parts[1],binary).destroy
309       @attribute_container.store
310       render_ok
311     else
312       render_error :message => "INTERNAL ERROR: Unhandled operation",
313         :status => 404, :errorcode => "unknown_operation"
314     end
315   end
316
317   # /source/:project/_pattern/:pattern
318   def pattern_meta
319     valid_http_methods :get, :put, :delete
320
321     params[:user] = @http_user.login if @http_user
322     
323     @project = DbProject.find_by_name params[:project]
324     unless @project
325       render_error :message => "Unknown project '#{params[:project]}'",
326         :status => 404, :errorcode => "unknown_project"
327       return
328     end
329
330     if request.get?
331       pass_to_source
332     else
333       # PUT and DELETE
334       permerrormsg = nil
335       if request.put?
336         permerrormsg = "no permission to store pattern"
337       elsif request.delete?
338         permerrormsg = "no permission to delete pattern"
339       end
340
341       unless @http_user.can_modify_project? @project
342         logger.debug "user #{user.login} has no permission to modify project #{@project}"
343         render_error :status => 403, :errorcode => "change_project_no_permission", 
344           :message => permerrormsg
345         return
346       end
347       
348       path = request.path + build_query_from_hash(params, [:rev, :user, :comment])
349       forward_data path, :method => request.method
350     end
351   end
352
353   # GET /source/:project/_pattern
354   def index_pattern
355     valid_http_methods :get
356
357     unless DbProject.find_by_name(params[:project])
358       render_error :message => "Unknown project '#{params[:project]}'",
359         :status => 404, :errorcode => "unknown_project"
360       return
361     end
362     
363     pass_to_source
364   end
365
366   def project_meta
367     project_name = params[:project]
368     if project_name.nil?
369       render_error :status => 400, :errorcode => 'missing_parameter',
370         :message => "parameter 'project' is missing"
371       return
372     end
373
374     if request.get?
375       @project = DbProject.find_by_name( project_name )
376
377       if @project
378   
379         allpacks = params[:allpacks] || false
380         if allpacks
381            render :text => proc { |response,output| 
382                                output.write("<fullmeta>\n")
383                                output.write(@project.to_axml)
384                                @project.db_packages.each do |pkg|
385                                        output.write(pkg.to_axml)
386                                end
387                                output.write("</fullmeta>\n")
388                            }, :content_type => 'text/xml'
389         else
390            render :text => @project.to_axml, :content_type => 'text/xml'
391         end
392       elsif DbProject.find_remote_project(project_name)
393         # project from remote buildservice, get metadata from backend
394         pass_to_backend
395       else
396         render_error :message => "Unknown project '#{project_name}'",
397           :status => 404, :errorcode => "unknown_project"
398       end
399       return
400     end
401
402     #authenticate
403     return unless extract_user
404
405     unless valid_project_name? project_name
406       render_error :status => 400, :errorcode => "invalid_project_name",
407         :message => "invalid project name '#{project_name}'"
408       return
409     end
410
411     if request.put?
412       # Need permission
413       logger.debug "Checking permission for the put"
414       allowed = false
415       request_data = request.raw_post
416
417       @project = DbProject.find_by_name( project_name )
418       if @project
419         #project exists, change it
420         unless @http_user.can_modify_project? @project
421           logger.debug "user #{user.login} has no permission to modify project #{@project}"
422           render_error :status => 403, :errorcode => "change_project_no_permission", 
423             :message => "no permission to change project"
424           return
425         end
426       else
427         #project is new
428         unless @http_user.can_create_project? project_name
429           logger.debug "Not allowed to create new project"
430           render_error :status => 403, :errorcode => 'create_project_no_permission',
431             :message => "not allowed to create new project '#{project_name}'"
432           return
433         end
434       end
435
436       p = Project.new(request_data, :name => project_name)
437
438       if p.name != project_name
439         render_error :status => 400, :errorcode => 'project_name_mismatch',
440           :message => "project name in xml data does not match resource path component"
441         return
442       end
443
444       if (p.has_element? :remoteurl or p.has_element? :remoteproject) and not @http_user.is_admin?
445         render_error :status => 403, :errorcode => "change_project_no_permission",
446           :message => "admin rights are required to change remoteurl or remoteproject"
447         return
448       end
449
450       p.add_person(:userid => @http_user.login) unless @project
451       p.save
452
453       render_ok
454     else
455       render_error :status => 400, :errorcode => 'illegal_request',
456         :message => "Illegal request: POST #{request.path}"
457     end
458   end
459
460   def project_config
461     valid_http_methods :get, :put
462
463     #check if project exists
464     unless (@project = DbProject.find_by_name(params[:project]))
465       render_error :status => 404, :errorcode => 'project_not_found',
466         :message => "Unknown project #{params[:project]}"
467       return
468     end
469
470     #assemble path for backend
471     path = request.path
472     unless request.query_string.empty?
473       path += "?" + request.query_string
474     end
475
476     if request.get?
477       forward_data path
478       return
479     end
480
481     #authenticate
482     return unless extract_user
483
484     if request.put?
485       unless @http_user.can_modify_project?(@project)
486         render_error :status => 403, :errorcode => 'put_project_config_no_permission',
487           :message => "No permission to write build configuration for project '#{params[:project]}'"
488         return
489       end
490
491       forward_data path, :method => :put, :data => request.raw_post
492       return
493     end
494   end
495
496   def project_pubkey
497     valid_http_methods :get, :delete
498
499     #check if project exists
500     unless (@project = DbProject.find_by_name(params[:project]))
501       render_error :status => 404, :errorcode => 'project_not_found',
502         :message => "Unknown project #{params[:project]}"
503       return
504     end
505
506     #assemble path for backend
507     path = request.path
508     unless request.query_string.empty?
509       path += "?" + request.query_string
510     end
511
512     if request.get?
513       forward_data path
514     elsif request.delete?
515       #check for permissions
516       unless @http_user.can_modify_project?(@project)
517         render_error :status => 403, :errorcode => 'delete_project_pubkey_no_permission',
518           :message => "No permission to delete public key for project '#{params[:project]}'"
519         return
520       end
521
522       forward_data path, :method => :delete
523       return
524     end
525   end
526
527   def package_meta
528     #TODO: needs cleanup/split to smaller methods
529     valid_http_methods :put, :get
530    
531     project_name = params[:project]
532     package_name = params[:package]
533
534     if project_name.nil?
535       render_error :status => 400, :errorcode => "parameter_missing",
536         :message => "parameter 'project' missing"
537       return
538     end
539
540     if package_name.nil?
541       render_error :status => 400, :errorcode => "parameter_missing",
542         :message => "parameter 'package' missing"
543       return
544     end
545
546     unless pro = DbProject.find_by_name(project_name)
547       if DbProject.find_remote_project(project_name)
548         pass_to_backend
549       else
550         render_error :status => 404, :errorcode => "unknown_project",
551           :message => "Unknown project '#{project_name}'"
552       end
553       return
554     end
555
556     if request.get?
557       unless pack = pro.db_packages.find_by_name(package_name)
558         render_error :status => 404, :errorcode => "unknown_package",
559           :message => "Unknown package '#{package_name}'"
560         return
561       end
562
563       render :text => pack.to_axml, :content_type => 'text/xml'
564     elsif request.put?
565       allowed = false
566       request_data = request.raw_post
567       # Try to fetch the package to see if it already exists
568       @package = Package.find( package_name, :project => project_name )
569       
570       if @package
571         # Being here means that the package already exists
572         allowed = permissions.package_change? @package
573         if allowed
574           @package = Package.new( request_data, :project => project_name, :name => package_name )
575         else
576           logger.debug "user #{user.login} has no permission to change package #{@package}"
577           render_error :status => 403, :errorcode => "change_package_no_permission",
578             :message => "no permission to change package"
579           return
580         end
581       else
582         # Ok, the package is new
583         allowed = permissions.package_create?( project_name )
584   
585         if allowed
586           #FIXME: parameters that get substituted into the url must be specified here... should happen
587           #somehow automagically... no idea how this might work
588           @package = Package.new( request_data, :project => project_name, :name => package_name )
589         else
590           # User is not allowed by global permission.
591           logger.debug "Not allowed to create new packages"
592           render_error :status => 403, :errorcode => "create_package_no_permission",
593             :message => "no permission to create package for project #{project_name}"
594           return
595         end
596       end
597       
598       if allowed
599         if( @package.name != package_name )
600           render_error :status => 400, :errorcode => 'package_name_mismatch',
601             :message => "package name in xml data does not match resource path component"
602           return
603         end
604
605         begin
606           @package.save
607         rescue DbPackage::CycleError => e
608           render_error :status => 400, :errorcode => 'devel_cycle', :message => e.message
609           return
610         end
611         render_ok
612       else
613         logger.debug "user #{user.login} has no permission to write package meta for package #@package"
614       end
615     end
616   end
617
618   def file
619     valid_http_methods :get, :delete, :put
620     project_name = params[:project]
621     package_name = params[:package]
622     file = params[:file]
623
624     path = "/source/#{project_name}/#{package_name}/#{file}"
625
626     if request.get?
627       #get file size
628       fpath = "/source/#{project_name}/#{package_name}" + build_query_from_hash(params, [:rev])
629       file_list = Suse::Backend.get(fpath)
630       regexp = file_list.body.match(/name=["']#{Regexp.quote file}["'].*size=["']([^"']*)["']/)
631       if regexp
632         fsize = regexp[1]
633         
634         path += build_query_from_hash(params, [:rev])
635         logger.info "streaming #{path}"
636        
637         headers.update(
638           'Content-Disposition' => %(attachment; filename="#{file}"),
639           'Content-Type' => 'application/octet-stream',
640           'Transfer-Encoding' => 'binary',
641           'Content-Length' => fsize
642         )
643         
644         render :status => 200, :text => Proc.new {|request,output|
645           backend_request = Net::HTTP::Get.new(path)
646           response = Net::HTTP.start(SOURCE_HOST,SOURCE_PORT) do |http|
647             http.request(backend_request) do |response|
648               response.read_body do |chunk|
649                 output.write(chunk)
650               end
651             end
652           end
653         }
654       else
655         forward_data path
656       end
657       return
658     end
659
660     #authenticate
661     return unless extract_user
662
663     params[:user] = @http_user.login
664     if request.put?
665       path += build_query_from_hash(params, [:user, :comment, :rev, :linkrev, :keeplink])
666       
667       allowed = permissions.package_change? package_name, project_name
668       if  allowed
669         Suse::Backend.put_source path, request.raw_post
670         pack = DbPackage.find_by_project_and_name(project_name, package_name)
671         pack.update_timestamp
672         logger.info "wrote #{request.raw_post.to_s.size} bytes to #{path}"
673         if package_name == "_product"
674           update_product_autopackages
675         end
676         render_ok
677       else
678         render_error :status => 403, :errorcode => 'put_file_no_permission',
679           :message => "Insufficient permissions to store file in package #{package_name}, project #{project_name}"
680       end
681     elsif request.delete?
682       path += build_query_from_hash(params, [:user, :comment, :rev, :linkrev, :keeplink])
683       
684       allowed = permissions.package_change? package_name, project_name
685       if  allowed
686         Suse::Backend.delete path
687         pack = DbPackage.find_by_project_and_name(project_name, package_name)
688         pack.update_timestamp
689         if package_name == "_product"
690           update_product_autopackages
691         end
692         render_ok
693       else
694         render_error :status => 403, :errorcode => 'delete_file_no_permission',
695           :message => "Insufficient permissions to delete file"
696       end
697     end
698   end
699
700   private
701
702   # POST /source?cmd=branch
703   def index_branch
704     # set defaults
705     mparams=params
706     if not params[:target_project]
707       mparams[:target_project] = "home:#{@http_user.login}:branches:#{params[:attribute].gsub(':', '_')}"
708       mparams[:target_project] += ":#{params[:package]}" if params[:package]
709     end
710     if not params[:update_project_attribute]
711       params[:update_project_attribute] = "OBS:UpdateProject"
712     end
713
714     # permission check
715     unless @http_user.can_create_project?(mparams[:target_project])
716       render_error :status => 403, :errorcode => "create_project_no_permission",
717         :message => "no permission to create project '#{mparams[:target_project]}' while executing branch command"
718       return
719     end
720
721     # find packages
722     if not params[:attribute]
723       render_error :status => 403, :errorcode => 'parameter_missing',
724          :message => "attribute parameter missing"
725       return
726     end
727     at = AttribType.find_by_name(params[:attribute])
728     if not at
729       render_error :status => 403, :errorcode => 'not_found',
730          :message => "The given attribute #{params[:attribute]} does not exist"
731       return
732     end
733     if params[:value]
734       @packages = DbPackage.find_by_attribute_type_and_value( at, params[:value], params[:package] )
735     else
736       @packages = DbPackage.find_by_attribute_type( at, params[:package] )
737     end
738
739     #create branch project
740     oprj = DbProject.find_by_name mparams[:target_project]
741     if oprj.nil?
742       DbProject.transaction do
743         oprj = DbProject.new :name => mparams[:target_project], :title => "Branch Project _FIXME_", :description => "_FIXME_"
744         oprj.add_user @http_user, "maintainer"
745         oprj.build_flags << BuildFlag.new( :status => "disable" )
746         oprj.save
747       end
748       Project.find(oprj.name).save
749     else
750       unless @http_user.can_modify_project?(oprj)
751         render_error :status => 403, :errorcode => "modify_project_no_permission",
752           :message => "no permission to modify project '#{mparams[:target_project]}' while executing branch by attribute command"
753         return
754       end
755     end
756
757     # create package branches
758     # collect also the needed repositories here
759     @packages.each do |p|
760     
761       # is a update project defined and a package there ?
762       pac = p
763       aname = params[:update_project_attribute]
764       name_parts = aname.split /:/
765       if name_parts.length != 2
766         raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
767       end
768       if a = p.db_project.find_attribute(name_parts[0], name_parts[1]) and a.values[0]
769          if pa = DbPackage.find_by_project_and_name( a.values[0].value, p.name )
770             pac = pa
771          end
772       end
773       proj_name = pac.db_project.name.gsub(':', '_')
774       pack_name = pac.name.gsub(':', '_')+"."+proj_name
775
776       # create branch package
777       if opkg = oprj.db_packages.find_by_name(pack_name)
778         render_error :status => 400, :errorcode => "double_branch_package",
779           :message => "branch target package already exists: #{oprj.name}/#{opkg.name}"
780         return
781       else
782         opkg = oprj.db_packages.new(:name => pack_name, :title => pac.title, :description => pac.description)
783         oprj.db_packages << opkg
784       end
785
786       # create repositories, if missing
787       pac.db_project.repositories.each do |repo|
788         orepo = oprj.repositories.create :name => proj_name+"_"+repo.name
789         orepo.architectures = repo.architectures
790         orepo.path_elements.create(:link => repo, :position => 1)
791         opkg.build_flags.create( :status => "enable", :repo => orepo.name )
792       end
793
794       Package.find(opkg.name, :project => oprj.name).save
795       # branch sources in backend
796       Suse::Backend.post "/source/#{oprj.name}/#{opkg.name}?cmd=branch&oproject=#{CGI.escape(pac.db_project.name)}&opackage=#{CGI.escape(pac.name)}", nil
797
798     end
799
800     # store project data in DB and XML
801     oprj.save!
802     Project.find(oprj.name).save
803
804     # all that worked ? :)
805     render_ok :data => {:targetproject => mparams[:target_project]}
806   end
807
808   # POST /source/<project>?cmd=createkey
809   def index_project_createkey
810     path = request.path + "?" + request.query_string
811     forward_data path, :method => :post
812   end
813
814   # POST /source/<project>?cmd=createpatchinfo
815   def index_project_createpatchinfo
816     if params[:name]
817       name=params[:name] if params[:name]
818     else
819       # FIXME, find source file name
820       name="test"
821     end
822     pkg_name = "_patchinfo:#{name}"
823     patchinfo_path = "#{request.path}/#{pkg_name}"
824
825     # request binaries in project from backend
826     binaries = list_all_binaries_in_path("/build/#{params[:project]}")
827
828     if binaries.length < 1 and not params[:force]
829       render_error :status => 400, :errorcode => "no_matched_binaries",
830         :message => "No binary packages were found in project repositories"
831       return
832     end
833
834     # FIXME: check for still building packages
835
836     # create patchinfo package
837     if not DbPackage.find_by_project_and_name( params[:project], pkg_name )
838       prj = DbProject.find_by_name( params[:project] )
839       pkg = DbPackage.new(:name => pkg_name, :title => "Patchinfo", :description => "Collected packages for update")
840       prj.db_packages << pkg
841       Package.find(pkg_name, :project => params[:project]).save
842     else
843       # shall we do a force check here ?
844     end
845
846     # create patchinfo XML file
847     node = Builder::XmlMarkup.new(:indent=>2)
848     xml = node.patchinfo(:name => name) do |n|
849       binaries.each do |binary|
850         node.binary(binary)
851       end
852       node.packager    @http_user.login
853       node.bugzilla    ""
854       node.swampid     ""
855       node.category    ""
856       node.rating      ""
857       node.summary     ""
858       node.description ""
859 # FIXME add all bugnumbers from attributes
860     end
861     backend_put( patchinfo_path+"/_patchinfo?user="+@http_user.login+"&comment=generated%20file%20by%20frontend", xml )
862
863     render_ok
864   end
865
866   def list_all_binaries_in_path path
867     d = backend_get(path)
868     data = REXML::Document.new(d)
869     binaries = []
870
871     data.elements.each("directory/entry") do |e|
872       name = e.attributes["name"]
873       list_all_binaries_in_path("#{path}/#{name}").each do |l|
874         binaries.push( l )
875       end
876     end
877     data.elements.each("binarylist/binary") do |b|
878       name = b.attributes["filename"]
879       # strip main name from binary
880       # patchinfos are only designed for rpms so far
881       binaries.push( name.sub(/-[^-]*-[^-]*.rpm$/, '' ) )
882     end
883
884     binaries.uniq!
885     return binaries
886   end
887
888   # POST /source/<project>/<package>?cmd=createSpecFileTemplate
889   def index_package_createSpecFileTemplate
890     specfile_path = "#{request.path}/#{params[:package]}.spec"
891     begin
892       backend_get( specfile_path )
893       render_error :status => 400, :errorcode => "spec_file_exists",
894         :message => "SPEC file already exists."
895       return
896     rescue ActiveXML::Transport::NotFoundError
897       specfile = File.read "#{RAILS_ROOT}/files/specfiletemplate"
898       backend_put( specfile_path, specfile )
899     end
900     render_ok
901   end
902
903   # POST /source/<project>/<package>?cmd=rebuild
904   def index_package_rebuild
905     project_name = params[:project]
906     package_name = params[:package]
907     repo_name = params[:repo]
908     arch_name = params[:arch]
909
910     path = "/build/#{project_name}?cmd=rebuild&package=#{package_name}"
911     
912     p = DbProject.find_by_name project_name
913     if p.nil?
914       render_error :status => 400, :errorcode => 'unknown_project',
915         :message => "Unknown project '#{project_name}'"
916       return
917     end
918
919     if p.db_packages.find_by_name(package_name).nil?
920       render_error :status => 400, :errorcode => 'unknown_package',
921         :message => "Unknown package '#{package_name}'"
922       return
923     end
924
925     if repo_name
926       path += "&repository=#{repo_name}"
927       if p.repositories.find_by_name(repo_name).nil?
928         render_error :status => 400, :errorcode => 'unknown_repository',
929           :message=> "Unknown repository '#{repo_name}'"
930         return
931       end
932     end
933
934     if arch_name
935       path += "&arch=#{arch_name}"
936     end
937
938     backend.direct_http( URI(path), :method => "POST", :data => "" )
939     render_ok
940   end
941
942   # POST /source/<project>/<package>?cmd=commit
943   def index_package_commit
944     params[:user] = @http_user.login if @http_user
945
946     path = request.path
947     path << build_query_from_hash(params, [:cmd, :user, :comment, :rev, :linkrev, :keeplink, :repairlink])
948     forward_data path, :method => :post
949
950     if params[:package] == "_product"
951       update_product_autopackages
952     end
953   end
954
955   # POST /source/<project>/<package>?cmd=commitfilelist
956   def index_package_commitfilelist
957     params[:user] = @http_user.login if @http_user
958
959     path = request.path
960     path << build_query_from_hash(params, [:cmd, :user, :comment, :rev, :linkrev, :keeplink, :repairlink])
961     forward_data path, :method => :post
962     
963     if params[:package] == "_product"
964       update_product_autopackages
965     end
966   end
967
968   # POST /source/<project>/<package>?cmd=diff
969   def index_package_diff
970     path = request.path
971     path << build_query_from_hash(params, [:cmd, :rev, :oproject, :opackage, :orev, :expand, :unified])
972     forward_data path, :method => :post
973   end
974
975   # POST /source/<project>/<package>?cmd=copy
976   def index_package_copy
977     params[:user] = @http_user.login if @http_user
978
979     pack = DbPackage.find_by_project_and_name(params[:project], params[:package])
980     if pack.nil? 
981       render_error :status => 404, :errorcode => 'unknown_package',
982         :message => "Unknown package #{params[:package]} in project #{params[:project]}"
983       return
984     end
985
986     #permission check
987     if not @http_user.can_modify_package?(pack)
988       render_error :status => 403, :errorcode => "cmd_execution_no_permission",
989         :message => "no permission to execute command 'copy'"
990       return
991     end
992
993     path = request.path
994     path << build_query_from_hash(params, [:cmd, :rev, :user, :comment, :oproject, :opackage, :orev, :expand])
995     
996     forward_data path, :method => :post
997   end
998
999   # POST /source/<project>/<package>?cmd=deleteuploadrev
1000   def index_package_deleteuploadrev
1001     params[:user] = @http_user.login if @http_user
1002
1003     path = request.path
1004     path << build_query_from_hash(params, [:cmd, :user, :comment])
1005     forward_data path, :method => :post
1006   end
1007
1008   # POST /source/<project>/<package>?cmd=linktobranch
1009   def index_package_linktobranch
1010     params[:user] = @http_user.login
1011     prj_name = params[:project]
1012     pkg_name = params[:package]
1013     pkg_rev = params[:rev]
1014     pkg_linkrev = params[:linkrev]
1015
1016     prj = DbProject.find_by_name prj_name
1017     pkg = prj.db_packages.find_by_name(pkg_name)
1018     if pkg.nil?
1019       render_error :status => 404, :errorcode => 'unknown_package',
1020         :message => "Unknown package #{pkg_name} in project #{prj_name}"
1021       return
1022     end
1023
1024     #convert link to branch
1025     rev = ""
1026     if not pkg_rev.nil? and not pkg_rev.empty?
1027          rev = "&rev=#{pkg_rev}"
1028     end
1029     linkrev = ""
1030     if not pkg_linkrev.nil? and not pkg_linkrev.empty?
1031          linkrev = "&linkrev=#{pkg_linkrev}"
1032     end
1033     Suse::Backend.post "/source/#{prj_name}/#{pkg_name}?cmd=linktobranch&user=#{CGI.escape(params[:user])}#{rev}#{linkrev}", nil
1034
1035     render_ok
1036   end
1037
1038   # POST /source/<project>/<package>?cmd=branch&target_project="optional_project"&target_package="optional_package"&update_project_attribute="alternative_attribute"
1039   def index_package_branch
1040     params[:user] = @http_user.login
1041     prj_name = params[:project]
1042     pkg_name = params[:package]
1043     pkg_rev = params[:rev]
1044     target_project = params[:target_project]
1045     target_package = params[:target_package]
1046     if not params[:update_project_attribute]
1047       params[:update_project_attribute] = "OBS:UpdateProject"
1048     end
1049     logger.debug "branch call of #{prj_name} #{pkg_name}"
1050
1051     prj = DbProject.find_by_name prj_name
1052     pkg = prj.db_packages.find_by_name(pkg_name)
1053     if pkg.nil?
1054       render_error :status => 404, :errorcode => 'unknown_package',
1055         :message => "Unknown package #{pkg_name} in project #{prj_name}"
1056       return
1057     end
1058
1059     # is a update project defined and a package there ?
1060     aname = params[:update_project_attribute]
1061     name_parts = aname.split /:/
1062     if name_parts.length != 2
1063       raise ArgumentError, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
1064     end
1065     if a = prj.find_attribute(name_parts[0], name_parts[1]) and a.values[0]
1066        if pa = DbPackage.find_by_project_and_name( a.values[0].value, pkg.name )
1067           pkg = pa
1068           pkg_name = pkg.name
1069           logger.debug "branch call found package in update project"
1070        end
1071     end
1072
1073     # validate and resolve devel package or devel project definitions
1074     if not params[:ignoredevel]
1075       pkg = pkg.resolve_devel_package
1076       pkg_name = pkg.name
1077       prj = pkg.db_project
1078       prj_name = prj.name
1079       logger.debug "devel project is #{prj_name} #{pkg_name}"
1080     end
1081
1082     # link against srcmd5 instead of plain revision
1083     unless pkg_rev.nil?
1084       path = "/source/#{params[:project]}/#{params[:package]}" + build_query_from_hash(params, [:rev])
1085       files = Suse::Backend.get(path)
1086       # get srcmd5 from the xml data
1087       match = files.body.match(/<directory['"=\w\s]+srcmd5=['"](\w{32})['"]['"=\w\s]*>/)
1088       if match
1089         pkg_rev = match[1]
1090       else
1091         # this should not happen
1092         render_error :status => 400, :errorcode => 'invalid_filelist',
1093           :message => "Unable parse filelist from backend"
1094         return
1095       end
1096     end
1097  
1098     oprj_name = "home:#{@http_user.login}:branches:#{prj_name}"
1099     opkg_name = pkg_name
1100     oprj_name = target_project unless target_project.nil?
1101     opkg_name = target_package unless target_package.nil?
1102
1103     unless @http_user.can_create_project?(oprj_name)
1104       render_error :status => 403, :errorcode => "create_project_no_permission",
1105         :message => "no permission to create project '#{oprj_name}' while executing branch command"
1106       return
1107     end
1108
1109     #create branch container
1110     oprj = DbProject.find_by_name oprj_name
1111     if oprj.nil?
1112       DbProject.transaction do
1113         oprj = DbProject.new :name => oprj_name, :title => prj.title, :description => prj.description
1114         oprj.add_user @http_user, "maintainer"
1115         prj.repositories.each do |repo|
1116           orepo = oprj.repositories.create :name => repo.name
1117           orepo.architectures = repo.architectures
1118           orepo.path_elements << PathElement.new(:link => repo, :position => 1)
1119         end
1120         oprj.save
1121       end
1122       Project.find(oprj_name).save
1123     end
1124
1125     #create branch package
1126     if opkg = oprj.db_packages.find_by_name(opkg_name)
1127       render_error :status => 400, :errorcode => "double_branch_package",
1128         :message => "branch target package already exists: #{oprj_name}/#{opkg_name}"
1129       return
1130     else
1131       opkg = DbPackage.new(:name => opkg_name, :title => pkg.title, :description => pkg.description)
1132       oprj.db_packages << opkg
1133     
1134       opkg.add_user @http_user, "maintainer"
1135       Package.find(opkg_name, :project => oprj_name).save
1136     end
1137
1138     #create branch of sources in backend
1139     rev = ""
1140     if not pkg_rev.nil? and not pkg_rev.empty?
1141          rev = "&rev=#{pkg_rev}"
1142     end
1143     Suse::Backend.post "/source/#{oprj_name}/#{opkg_name}?cmd=branch&oproject=#{CGI.escape(prj_name)}&opackage=#{CGI.escape(pkg_name)}#{rev}", nil
1144
1145     render_ok :data => {:targetproject => oprj_name, :targetpackage => opkg_name}
1146   end
1147
1148   def valid_project_name? name
1149     name =~ /^\w[-_+\w\.:]+$/
1150   end
1151
1152   def valid_package_name? name
1153     name =~ /^\w[-_+\w\.:]+$/
1154   end
1155
1156 end