require 'parameter_keeper'
class PatchesController < ApplicationController
PATCHES_PER_PAGE = 20
user_actions :add_item, :new_patch_version, :update, :destroy
ajax_updater_for :patch, :title
ajax_updater_for :patch, :description,
:render => lambda {|p| {:inline => "<%= textilize_without_paragraph(@patch.description) %>"}}
ajax_updater_for :patch, :program_id,
:render => lambda {|p| {:inline => "<%= link_to(@patch.program.name, @patch.program.url) %>"}}
def update_patch_applied_since_version
@patch = Patch.find(params[:id])
@patch.applied_since_version = params[:patch][:applied_since_version]
if @patch.applied_since_version
@patch.applied_at = Time.now
@patch.applied_by = current_user.id
else
@patch.applied_at = @patch.applied_by = nil
end
@patch.save
render :text => ""
end
# Mark as (logged-in) user actions all the ajax_updater_for related ones
user_actions :update_patch_title, :update_patch_description,
:update_patch_program_id, :update_patch_applied_since_version
def index
@n_patches = Patch.count
@n_unapplied_patches = Patch.count(Patch::UNAPPLIED_CONDITION)
@n_programs = Program.count
@latest_unapplied_patches = Patch.find(:all,
:conditions => Patch::UNAPPLIED_CONDITION,
:order => "created_at DESC",
:limit => 5)
end
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :action => :list }
def list
@paginator, @patches = paginate_patches
end
def my_patches
@paginator, @my_patches = paginate_my_patches
end
def unapplied
@paginator, @unapplied_patches = paginate_unapplied_patches
end
def show
edit
render :action => 'edit'
end
def edit
@patch = Patch.find(params[:id])
if params[:patch_extra] and params[:patch_extra][:version]
@patch_version = PatchVersion.find(params[:patch_extra][:version])
else
@patch_version = @patch.latest_version
end
end
def update
@patch = Patch.find(params[:id])
if @patch.update_attributes(params[:patch].merge(update_info))
flash[:notice] = 'Patch was successfully updated.'
redirect_to :action => 'show', :id => @patch
else
render :action => 'edit'
end
end
def destroy
item = Patch.find(params[:id])
patch_versions = item.patch_versions # Save before destroying item
if item.destroy
patch_versions.each do |pv|
begin
File.unlink(File.join(PATCH_REPOSITORY, pv.id.to_s))
rescue Errno::ENOENT # Ignore non-existent files
end
pv.destroy
end
redirect_to(:action => "list")
else
render_text "Couldn't destroy item"
end
end
# Non-scaffold methods
def add_item
# Uploaded file contents
patch_contents = params[:patch_file].readlines.join('')
if params[:new_item][:title] == '' or patch_contents == ''
flash[:error] = "Can't add patch without title or contents"
redirect_to(:action => "list")
return
end
# First, insert the application if it's a new one
if params[:new_item][:program_id].to_i == NEW_APPLICATION_ID
@new_item = Patch.new(params[:new_item])
@new_item_version = ParameterKeeper.new(params[:new_item_version])
# If there is an application with the same name, abort
if Program.find(:first, :conditions => ["name ILIKE :new_app_name", {:new_app_name => params[:new_app][:name]}])
flash[:error] = "Can't create two applications with the same name"
@new_item.program_id = NEW_APPLICATION_ID
@new_app = ParameterKeeper.new(params[:new_app])
@patch_file = ParameterKeeper.new(params[:new_app])
index
render :action => 'index'
return
end
app = Program.new(params[:new_app])
if app.save
params[:new_item][:program_id] = app.id
else
flash[:error] = "Couldn't insert new application"
redirect_to(:action => "list")
return
end
end
# Is it an update for an existent patch?
version = nil
if params[:patch] and params[:patch][:id] and Patch.exists? params[:patch][:id]
item = Patch.find(params[:patch][:id])
# Use patch title as the patch version comment
basic_version_attrs = {:comment => params[:new_item][:title]}.merge(params[:new_item_version])
version = item.patch_versions.build(basic_version_attrs)
else
item = Patch.new({:description => params[:new_item][:title],
:filename => params[:patch_file].original_filename}.merge(params[:new_item]).merge(create_info))
if item.save
version = item.patch_versions.build({:comment => 'Initial version'}.merge(params[:new_item_version]))
else
flash[:error] = "Couldn't add new patch"
redirect_to(:action => "list")
return
end
end
# Take care of the new patch version, in either case
version.text = patch_contents
unless version.save
item.destroy if item.patch_versions.empty?
flash[:error] = "Couldn't create new patch version"
redirect_to(:action => "list")
return
end
Notifier::deliver_new_patch(item)
redirect_to(:action => 'show', :id => item.id)
end
def new_patch_version
patch = Patch.find(params[:id])
patch_contents = params[:patch_file].readlines.join('')
if patch_contents == ''
flash[:error] = "Can't update patch without file"
redirect_to(:action => "list")
return
end
if patch
version = patch.patch_versions.build(params[:version].merge(create_info))
version.text = patch_contents
unless version.save
flash[:error] = "Can't create new patch version"
redirect_to(:action => "show", :id => patch)
return
end
end
redirect_to(:action => "show", :id => patch)
end
def view_patch
begin
@patch_version = PatchVersion.find(params[:id])
if params[:download].to_s != ''
@headers["Content-Type"] = "text/plain"
@headers["Content-Disposition"] = "attachment; filename=#{@patch_version.patch.filename}"
render :text => @patch_version.text
end
rescue Errno::ENOENT
render :text => "Sorry, couldn't retrieve patch number #{params[:id]}"
end
# Don't use the standard HTML menus and stuff
@only_basic_html = true
end
def view_versions
@item = Patch.find(params[:id])
if @item.has_patch_versions?
@versions = @item.patch_versions
else
render_text 'No versions available'
end
end
def search
if request.get?
@patches = []
@n_results = 0
else
conditions, vars = [], {}
# Sanitize parameters
params[:patch] ||= {}
params[:patch_extra] ||= {}
params[:patch_version] ||= {}
# Program and for_upstream_version
if params[:patch][:program_id].to_s != ''
conditions << "program_id = :program_id"
vars[:program_id] = params[:patch][:program_id]
if params[:patch_version][:for_upstream_version].to_s != ''
conditions << "patches.id IN (SELECT patch_id
FROM patch_versions
WHERE for_upstream_version = :f_u_v)"
vars[:f_u_v] = params[:patch_version][:for_upstream_version]
end
end
# Title/description
if params[:patch_extra][:description].to_s != ''
idx = 0
conditions +=
params[:patch_extra][:description].split(/\s+/).map do |word|
idx += 1
vars[:"w#{idx}"] = "%#{word}%"
"(title ILIKE :w#{idx} OR description ILIKE :w#{idx})"
end
end
# User/author
if params[:patch_extra][:author].to_s != ''
conditions << "created_by = :author_id"
vars[:author_id] = params[:patch_extra][:author]
end
# Applied?
case params[:patch_extra][:applied]
when '1'
conditions << Patch::APPLIED_CONDITION
when '0'
conditions << Patch::UNAPPLIED_CONDITION
end
# Grrrr....
if conditions.empty?
conditions << "1 = 1"
end
session[:search_opts] = {:conditions => [conditions.join(" AND "), vars],
:order => "program_name"}
@paginator, @patches = paginate_patch_search(session[:search_opts])
@n_results = PatchView.count(session[:search_opts][:conditions])
@patch_upstream_versions = [['Any', '']] + upstream_versions_for_program(params[:patch][:program_id].to_i)
# Small hacks to keep search options
params[:patch][:program_id] = params[:patch][:program_id].to_i
@patch_extra = ParameterKeeper.new(params[:patch_extra])
@patch_version = ParameterKeeper.new(params[:patch_version])
@patch = ParameterKeeper.new(params[:patch])
end
end
def search_results_archive
# Use the search options saved in the session object
send_patches Patch.find(:all, :conditions => session[:search_opts][:conditions])
end
def patches_rss
@headers["Content-Type"] = "application/xml"
@patches = PatchView.find(:all, :order => 'created_at DESC')
@title = 'PatchServer patches'
@description = 'New patches uploaded to PatchServer'
render :template => 'shared/patches_rss', :layout => false
end
def ajax_get_patches
@paginator, @patches = paginate_patches
render :inline => < :ajax_get_patches,
:paginator => @paginator,
:columns => 6) %>
EOD
end
def ajax_get_my_patches
@paginator, @patches = paginate_my_patches
render :inline => < :ajax_get_my_patches,
:paginator => @paginator,
:columns => 6) %>
EOD
end
def ajax_get_unapplied_patches
@paginator, @patches = paginate_unapplied_patches
render :inline => < :ajax_get_unapplied_patches,
:paginator => @paginator,
:columns => 5) %>
EOD
end
def ajax_get_patch_info
if Patch.exists? params[:patch][:id]
p = Patch.find(params[:patch][:id])
render :text => "Patch \##{p.id} (#{p.title})"
else
render :text => "No such patch '#{params[:patch][:id]}'"
end
end
def ajax_get_upstream_versions
@versions = upstream_versions_for_program(params[:program][:id].to_i)
render :inline => <
EOD
end
def ajax_get_patch_search
@paginator, @patches = paginate_patch_search(session[:search_opts])
render :inline => < :ajax_get_patch_search,
:paginator => @paginator,
:columns => 6) %>
EOD
end
def preferences
redirect_to :controller => '/' if current_user.nil?
@prefs = current_user.prefs_hash
end
def save_preferences
params[:pref].each_pair do |k,v|
u = UserPref.find_or_create_by_user_id_and_name(current_user.id, k)
u.value = v
u.save
end
flash[:notice] = 'Preferences saved'
redirect_to :action => 'preferences'
end
def add_comment
@patch = Patch.find(params[:id])
@patch.patch_comments.build(params[:comment])
unless @patch.save
flash[:error] = "Couldn't store comment"
end
redirect_to :action => 'edit', :id => @patch.id
end
protected
def paginate_patches(user_options={})
options = {:per_page => PATCHES_PER_PAGE,
:order => 'program_name, applied_since_version DESC, for_upstream_version DESC'}.merge(user_options)
@paginator, @patches = paginate :patch_views, options
end
def paginate_my_patches(user_options={})
options = {:per_page => PATCHES_PER_PAGE,
:conditions => ["created_by = :author_id", {:author_id => current_user.id}],
:order => '(applied_since_version IS NULL) DESC, program_name, applied_at, for_upstream_version DESC'}.merge(user_options)
@paginator, @patches = paginate :patch_views, options
end
def paginate_unapplied_patches(user_options={})
options = {:per_page => PATCHES_PER_PAGE,
:conditions => PatchView::UNAPPLIED_CONDITION,
:order => 'program_name, applied_since_version DESC, for_upstream_version DESC'}.merge(user_options)
@paginator, @patches = paginate :patch_views, options
end
def paginate_patch_search(user_options)
options = {:per_page => PATCHES_PER_PAGE,
:order => 'program_name, applied_since_version DESC, for_upstream_version DESC'}.merge(user_options)
@paginator, @patches = paginate :patch_views, options
end
def upstream_versions_for_program(id)
con = ActiveRecord::Base.connection
con.select_values("SELECT DISTINCT pv.for_upstream_version
FROM patches p
JOIN
patch_versions pv
ON pv.patch_id = p.id
WHERE p.program_id = #{con.quote(id)}
ORDER BY pv.for_upstream_version DESC")
end
def create_info
{:created_by => current_user.nil? ? 0 : current_user.id}
end
def update_info
{:updated_by => current_user.nil? ? 0 : current_user.id}
end
end