class PackagesController < ApplicationController # Allow legacy /uploadfile URL without CSRF protection protect_from_forgery :except => :legacy_uploadfile def list @packages = query_packages.paginate(page: params[:page], per_page: 6) end def grid @packages = query_packages.paginate(page: params[:page], per_page: 24) end def details # TODO: Get only screenshots visible to the user (admin or owner or approved) @package = Package.find_by(name: params[:name]) unless @package @packagename = params[:name] render 'notfound' end # The first page of screenshots (if there are multiple at all) shows the # first image twice as large. So the first page contains 5 images while # the second and further page contain 6 images. @page = params[:page] @screenshots = screenshots_visible_to_user(@package).paginate(page: @page, per_page: 6) end # Show upload form for new images def upload @package = Package.find_by!(name: params[:name]) end # POST target of the screenshots upload form. # Receives uploaded images. Checks if they are valid. Asks for description. # This action saves the screenshots already if they are valid. The user is # then given the chance to comment on and delete the screenshots again. def upload_receive # Create a pseudo user account for the user. # The user won't know that an account is created. # But this makes it easier to track who screenshots belong to. # User.create_pseudo_user unless user_signed_in? @package = Package.find_by!(name: params[:name]) @valid_images = [] @invalid_images = [] # If Javascript is disabled a user may submit an empty selection of images. if params[:file] == nil flash[:error] = "You have not selected any images." redirect_to upload_path return end params[:file].each do |img| # Log.log "Uploaded img=#{img.path}" new_screenshot = @package.screenshots.new(image: img) # BUG: ActiveStorage will accept the broken PNG during upload! # Check if the image was valid if new_screenshot.valid? new_screenshot.uploaderhash = session.id.to_s new_screenshot.uploaderip = request.remote_ip new_screenshot.version = @package.version # ActiveStorage does not yet create a file checksum automatically. # Let's do that. It helps detect duplicate uploads later. new_screenshot.image_fingerprint = Digest::MD5.hexdigest(img.path) # Can the upload get approved automatically? if user_signed_in? new_screenshot.user = current_user new_screenshot.approve! if auto_approve? end new_screenshot.simage_derivatives! Log.log "Derivatives created" new_screenshot.save! Log.log "Screenshot #{new_screenshot.id} uploaded successfully. ip=#{session[:ip]}. user-token=#{session[:token]}. user-name=#{current_user}" @valid_images.push new_screenshot else Log.log "Screenshot #{new_screenshot.image_file_name} invalid (#{new_screenshot.errors[:image]})." @invalid_images.push new_screenshot end end errors = [] @invalid_images.each do |image| # errors << "The image #{image.image_file_name} #{image.errors[:image].join(' and ')}." errors << "The image #{image.image_file_name} is not valid." flash[:error] = errors.join(" ") end # Redirect back to upload form if all uploads were invalid unless @valid_images.any? Log.log "No valid images uploaded. Back to upload form." redirect_to(upload_path, error: errors) and return end # Show a list of invalid uploads by default. Or redirect to the review page # if all uploads were okay. # redirect_to upload_review_path unless @invalid_images # TODO # if @invalid_images… # ' #{image.image_file_name} (#{image.errors[:image].join(' and ')}) redirect_to package_path # render :details end # Legacy action to upload an image along with metadata. # This was used in Debshots 1.x as the default upload method. # This method allows that old-style way to upload screenshots. # It is probably used by screenshotting tools. # # Parameters: # - packagename # - version # - description # - file def legacy_uploadfile @package = Package.find_by!(name: params[:packagename]) new_screenshot = @package.screenshots.new(image: params[:file]) # Check if the image was valid if new_screenshot.valid? # new_screenshot.uploaderhash = session[:token] new_screenshot.uploaderip = session[:ip] new_screenshot.version = @package.version new_screenshot.save Log.log "Screenshot #{new_screenshot.id} uploaded successfully from legacy upload form." redirect_to package_path(params[:packagename]) else Log.log "Screenshot upload rejected. Errors: #{new_screenshot.errors.to_a}" head :not_acceptable end end def delete_screenshot # Is the user allowed to delete the screenshot? @screenshot = Screenshot.find(params[:id]) # Check if the user is allowed to change this screenshot # - Is this the user's own screenshot? (anonymous) if @screenshot.user == current_user or current_user.is_admin? logger.debug "User #{current_user} deletes screenshot #{@screenshot}" @screenshot.destroy flash['notice'] = "Screenshot deleted." redirect_back(fallback_location: package_path(name: @screenshot.package.name)) else head :forbidden end end def approve_screenshot @screenshot = Screenshot.find(params[:id]) @screenshot.approve! flash['notice'] = "Screenshot approved." redirect_back(fallback_location: package_path(name: @screenshot.package.name)) end # Returns a 160x120 thumbnail image if posssible. # If the package is not found it returns a dummy image along with status 404. # If the package is found but has no screenshots then it also returns a # dummy image along with status 404. def thumbnail @package = Package.find_by(name: params[:name]) unless @package thumbnail404 return end # Called as /thumbnail-with-version/:name/:version if params[:version] @screenshot = @package.best_screenshot_for_version(params[:version]) # Called as /thumbnail/:name else @screenshot = @package.screenshots.first end # Return a 404 if the package has no screenshots or the image was not found unless @screenshot and @screenshot.image.path thumbnail404 return end # Send the thumbnail (uses X-Sendfile or similar if possible) send_file @screenshot.image.path(:thumb), type: "image/png", disposition: 'inline' end # Returns a large screenshot image if posssible. # If the package is not found it returns a dummy image along with status 404. # If the package is found but has no screenshots then it also returns a # dummy image along with status 404. def screenshot @package = Package.find_by(name: params[:name]) unless @package screenshot404 return end # Called as /thumbnail-with-version/:name/:version if params[:version] @screenshot = @package.best_screenshot_for_version(params[:version]) # Called as /thumbnail/:name else @screenshot = @package.screenshots.first end # Return a 404 if the package has no screenshots or the image was not found unless @screenshot and @screenshot.image.path screenshot404 return end # Send the thumbnail (uses X-Sendfile or similar if possible) send_file @screenshot.image.path(:large), type: "image/png", disposition: 'inline' end # Receives a form with a simple text field 'description' so that users can update # the description of their screenshot. def update_screenshot_description # TODO: Check permissions to do that @screenshot = Screenshot.find(params[:id]) # @screenshot.description = params[:description] @screenshot.update params_screenshot_description @screenshot.save! flash['notice'] = "Description updated." redirect_back(fallback_location: package_path(name: @screenshot.package.name)) end # Receive an anonymous report from a user to have a screenshot removed. def report_screenshot @screenshot = Screenshot.find(params[:id]) # if verify_recaptcha @screenshot.delete_reason = params[:delete_reason] @screenshot.markedfordelete = true if @screenshot.valid? @screenshot.save! flash['notice'] = "Screenshot reported. The moderators will deal with it." else errors = @screenshot.errors.to_a.join(' and ') flash['alert'] = "Sorry. #{errors}" end # end redirect_back(fallback_location: package_path) end # Show an HTML partial with reviews of this package from the Ubuntu API # def reviews # expires_in 1.day, public: true # # @reviews = Package.find_by_name!(params[:name]).ubuntu_reviews # @reviews = get_ubuntu_reviews params[:name] # render '_reviews', layout: false # end private # Send a dummy thumbnail reading "No screenshot available. Sorry." def thumbnail404 send_file Rails.root.join('public/images/dummy/thumbnail404.png'), type: "image/png", disposition: 'inline', status: 404 end def screenshot404 send_file Rails.root.join('public/images/dummy/screenshot404.png'), type: "image/png", disposition: 'inline', status: 404 end # Return packages matching the criteria given by parameters def query_packages packages = Package.includes(:screenshots).order(visits: :desc) # text search if params[:search].present? logger.debug "Searching for #{params[:search]}" packages = packages.general_search(params[:search]) end case params[:show] when 'with' packages = packages.with_screenshots logger.debug 'Limiting packages to those with screenshots' when 'without' packages = packages.without_screenshots logger.debug 'Limiting packages to those without screenshots' end return packages end # Store a random identifier and the client's IP address in the session # for later identification. # def create_user_token # session[:token] ||= SecureRandom.hex # session[:ip] ||= request.remote_ip # end # Get reviews of this package from the Ubuntu API def get_ubuntu_reviews(packagename) # Use the URL defined in the configuration to get a JSON string url = Rails.configuration.ubuntu_reviews_api_url % packagename logger.debug "Loading Ubuntu reviews for package #{packagename} from #{url}" body = open(url).read # Turn JSON into a Ruby data structure json = JSON.parse(body) # Only show english reviews # TODO: Support further languages json = json.select {|x| x['language']=='en'} # Sort by 'usefulness_total' (how many people found this review useful) json = json.sort { |x,y| y['usefulness_total'].to_i <=> x['usefulness_total'].to_i} return json end def params_screenshot_description params.require(:screenshot).permit(:description) end # Do uploads from this user get approved automatically? def auto_approve? # Anonymous users need to go through moderation return false unless user_signed_in? # Admins do not need moderation return true if current_user.is_admin? # Debian developers do not need moderation return true if current_user.provider == 'debian-sso' # After one successfully approved screenshot users do not need moderation return true if current_user.approved_screenshots.count > 0 # Any other user's upload must be moderated return false end def screenshots_visible_to_user(package) if user_signed_in? and current_user.is_admin? # User is an admin and can view all screenshots package.screenshots # TODO: User logins and assigned screenshots will come in a later version # elsif user_signed_in? # package.screenshots.where(id: (current_user.screenshots.select(:id))) | \ # package.screenshots.where(approved: true).order('created_at DESC') else package.screenshots.where(uploaderhash: session.id.to_s).or( package.screenshots.where(approved: true) ) .order('created_at DESC') end end end