diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index fe77892ed..cb654586d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,267 +1,5 @@ -# frozen_string_literal: true - -require 'api_connect' -require 'net/https' -require 'json' - -class OBSError < StandardError; end - class ApplicationController < ActionController::Base - before_action :validate_configuration - before_action :set_language - before_action :set_distributions - before_action :set_releases - before_action :set_baseproject - - helper :all # include all helpers, all the time - require 'rexml/document' - - class MissingParameterError < RuntimeError; end - - EXCEPTIONS_TO_IGNORE = [OBS::InvalidSearchTerm, - ApiConnect::Error, - ApplicationController::MissingParameterError, - Timeout::Error].freeze - - rescue_from Exception do |exception| - logger.error "Exception: #{exception.class}: #{exception.message}" - @message = exception.message - layout = request.xhr? ? false : 'application' - logger.error exception.backtrace.join("\n") unless EXCEPTIONS_TO_IGNORE.include? exception - render template: 'error', formats: [:html], layout: layout, status: 400 - end - - def prepare_appdata - @appdata = case @baseproject - when 'ALL' - tw = tumbleweed_appdata - stable = leap_appdata(@stable_version) - testing = @testing_version ? leap_appdata(@testing_version) : {} - legacy = @legacy_release ? leap_appdata(@legacy_release) : {} - # Overwriting entries is okay, appdata is not used for - # installation when @baseproject == 'ALL' - tw.merge(stable).merge(testing).merge(legacy) - when 'openSUSE:Factory' - tumbleweed_appdata - when "openSUSE:Leap:#{@stable_version}" - leap_appdata(@stable_version) - when "openSUSE:Leap:#{@testing_version}" - leap_appdata(@testing_version) - when "openSUSE:Leap:#{@legacy_release}" - leap_appdata(@legacy_release) - else - { apps: [], categories: Set.new } - end - end - - # TODO: Used to alter what OBS.search_published_binary returns, extract this into OBS. - def fix_package_projects - # Due to Leap 15.3's release model, there is no 1:1 distribution <> - # baseproject relation anymore. - # official packages: - # DISTRIBUTION_PROJECTS_OVERRIDE contains valid baseprojects - # -> treat as if it was from the distribution's main project, but save the - # link to the real project for the generation of the download page - # other packages: - # package.repo uses the distribution's default reponame - # -> only "fix" the baseproject for grouping purposes - @packages.each do |package| - dist = @distributions.find do |d| - projects = DISTRIBUTION_PROJECTS_OVERRIDE.fetch(d[:dist_id], nil) - projects&.include?(package.project) - end - - if dist - logger.debug("Match in override hash, changing #{package.baseproject} to #{dist[:project]}") - package.realproject = package.project - package.baseproject = dist[:project] - package.project = dist[:project] - else - repo = @distributions.find { |d| d[:reponame] == package.repository } - if repo - package.realproject = package.project - package.baseproject = repo[:project] - elsif package.repository == 'openSUSE_Leap_15.4' - leap154 = @distributions.find { |d| d[:dist_id] == '23178' } - next unless leap154 - - package.baseproject = leap154[:project] - elsif package.repository == 'openSUSE_Leap_15.5' - leap155 = @distributions.find { |d| d[:dist_id] == '23175' } - next unless leap155 - - package.baseproject = leap155[:project] - end - end - end - end - - # TODO: Used to alter what OBS.search_published_binary returns, extract this into OBS. - def filter_packages - # remove maintenance projects, they are not meant for end users - @packages.reject! { |p| p.project.include? 'openSUSE:Maintenance:' } - @packages.reject! { |p| p.project == 'openSUSE:Factory:Rebuild' } - @packages.reject! { |p| p.project.start_with?('openSUSE:Factory:Staging') } - - # only show packages - @packages.reject! { |p| p.type == 'ymp' } - - @packages.reject! { |p| /-devel/i.match?(p.name) } unless @search_devel - - unless @search_lang - @packages.reject! { |p| p.name.end_with?('-lang') || p.name.include?('-translations-') || p.name.include?('-l10n-') } - end - - @packages.reject! { |p| p.name.end_with?('-buildsymbols', '-debuginfo', '-debugsource') } unless @search_debug - - # filter out ports for different arch - if @baseproject.end_with?('ARM') - @packages.select! { |p| p.project.include?('ARM') || p.repository.include?('ARM') } - elsif @baseproject.end_with?('PowerPC') - @packages.select! { |p| p.project.include?('PowerPC') || p.repository.include?('PowerPC') } - else # x86 - @packages.reject! do |p| - p.repository.end_with?('_ARM', '_PowerPC', '_zSystems') || p.repository == 'ports' || - p.project.include?('ARM') || p.project.include?('PowerPC') || p.project.include?('zSystems') - end - end - end - - private - - def validate_configuration - config = Rails.configuration.x - layout = request.xhr? ? false : 'application' - - if config.api_username.blank? && config.opensuse_cookie.blank? - @message = _('The authentication to the OBS API has not been configured correctly.') - render template: 'error', formats: [:html], layout: layout, status: 503 - end - end - - def set_language - set_gettext_locale - requested_locale = FastGettext.locale - # if we don't have translations for the requested locale, try - # the short form without underscore - unless LANGUAGES.include? requested_locale - requested_locale = requested_locale.split('_').first - params[:locale] = LANGUAGES.include?(requested_locale) ? requested_locale : 'en' - set_gettext_locale - end - @lang = FastGettext.locale - end - - def load_releases - release_file_url = 'https://get.opensuse.org/api/v0/distributions.json' - Rails.cache.fetch('software-o-o/releases', expires_in: 10.minutes) do - JSON.parse(URI.parse(release_file_url).open.read)['Leap'].sort_by { |r| -r['upgrade-weight'] } - rescue StandardError => e - Rails.logger.error "Error while parsing releases entry in #{release_file_url}: #{e}" - next - end - rescue StandardError => e - Rails.logger.error "Error while parsing releases file #{release_file_url}: #{e}" - raise e - end - - def set_releases - @stable_version = nil - @testing_version = nil - @testing_state = nil - @legacy_release = nil - - # look for most current release - versions = load_releases - unless versions.blank? - if versions[0]['state'] == 'Stable' - @stable_version = versions[0]['version'].to_s - @legacy_release = versions[1]['version'].to_s - else - @testing_version = versions[0]['version'].to_s - @testing_state = versions[0]['state'].to_s - @stable_version = versions[1]['version'].to_s - @legacy_release = versions[2]['version'].to_s - end - end - end - - def tumbleweed_appdata - Rails.cache.fetch('appdata/tumbleweed', expires_in: 12.hours) do - Appdata.new('tumbleweed').data - end - end - - def leap_appdata(version) - Rails.cache.fetch("appdata/leap#{version}", expires_in: 12.hours) do - Appdata.new("leap/#{version}").data - end - end - - def set_distributions - @distributions = Rails.cache.fetch('distributions', expires_in: 120.minutes) do - load_distributions - end - rescue OBSError - @distributions = nil - @hide_search_box = true - flash[:error] = 'Connection to OBS is unavailable. Functionality of this site is limited.' - end - - def load_distributions - logger.debug 'Loading distributions' - loaded_distros = [] - begin - response = ApiConnect.get('public/distributions') - doc = REXML::Document.new response.body - doc.elements.each('distributions/distribution') do |element| - loaded_distros << parse_distribution(element) - end - loaded_distros.unshift({ name: 'ALL Distributions', project: 'ALL' }) - rescue Exception => e - logger.error "Error while loading distributions: #{e}" - raise OBSError.new, _('OBS Backend not available') - end - loaded_distros - end - - def parse_distribution(element) - dist = { - name: element.elements['name'].text, - project: element.elements['project'].text, - reponame: element.elements['reponame'].text, - repository: element.elements['repository'].text, - dist_id: element.attributes['id'] - } - logger.debug "Added Distribution: #{dist}" - dist - end - - def set_search_options - @search_term = params[:q] || '' - @search_devel = (cookies[:search_devel] == 'true') - @search_lang = (cookies[:search_lang] == 'true') - @search_debug = (cookies[:search_debug] == 'true') - end - - def set_baseproject - default_baseproject = "openSUSE:Leap:#{@stable_version}" - - if params[:baseproject].present? && valid_baseproject?(params[:baseproject]) - @baseproject = params[:baseproject] - update_baseproject_cookie(params[:baseproject]) - elsif cookies[:baseproject].present? && valid_baseproject?(cookies[:baseproject]) - @baseproject = cookies[:baseproject] - end - @baseproject = default_baseproject unless @baseproject.present? - end - - def valid_baseproject?(project) - @distributions.present? && @distributions.select { |d| d[:project] == project }.present? - end - - def update_baseproject_cookie(project) - cookies.delete :baseproject - cookies.permanent[:baseproject] = project + def set_distribution + @distribution = Distribution.find_by!(name: params[:distribution_name] || params[:distribution]) end end diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb new file mode 100644 index 000000000..eb3f67b48 --- /dev/null +++ b/app/controllers/main_controller.rb @@ -0,0 +1,9 @@ +class MainController < ApplicationController + before_action :set_distribution, only: :search + + def index; end + + def search + @packages = @distribution.packages.where('name ILIKE ?', "%#{params[:q]}%").order("LENGTH(name) ASC").order(:weight) + end +end diff --git a/app/controllers/package_controller.rb b/app/controllers/package_controller.rb deleted file mode 100644 index 9c0698b4a..000000000 --- a/app/controllers/package_controller.rb +++ /dev/null @@ -1,135 +0,0 @@ -# frozen_string_literal: true - -class PackageController < ApplicationController - before_action :set_search_options, only: %i[show categories] - before_action :prepare_appdata, :set_categories - - skip_before_action :set_language, only: %i[thumbnail screenshot] - - def show - @pkgname = params[:package] - raise MissingParameterError, 'Invalid parameter package' unless valid_package_name? @pkgname - - begin - raise OBSError if @distributions.nil? - - @search_term = params[:search_term] - - @packages = OBS.search_published_binary("\"#{@pkgname}\"") - # only show rpms - @packages.select! { |p| p.type != 'ymp' && p.quality != 'Private' } - fix_package_projects - @default_project_name = @distributions.find { |d| d[:project] == @baseproject }[:name] - default_update_projects = ["#{@baseproject}:Update", "#{@baseproject}:NonFree:Update"] - default_release_projects = [@baseproject, "#{@baseproject}:NonFree"] - @default_package = @packages.find { |p| default_update_projects.include?(p.project) } || - @packages.find { |p| default_release_projects.include?(p.project) } - - pkg_appdata = @appdata[:apps].find { |app| app[:pkgname].casecmp(@pkgname).zero? } - if pkg_appdata - @name = pkg_appdata[:name] - @appcategories = pkg_appdata[:categories] - @homepage = pkg_appdata[:homepage] - @appid = pkg_appdata[:id] - end - - @screenshot = url_for controller: :package, action: :screenshot, package: @pkgname, only_path: true - @thumbnail = url_for controller: :package, action: :thumbnail, package: @pkgname, only_path: true - - filter_packages - - @official_projects = @distributions.map { |d| d[:project] } - # get extra distributions that are not in the default distribution list - @extra_packages = @packages.reject { |p| @distributions.map { |d| d[:project] }.include? p.baseproject } - @extra_dists = @extra_packages.filter_map(&:baseproject).uniq.map { |d| { project: d } } - rescue OBSError - @hide_search_box = true - flash.now[:error] = _('Connection to OBS is unavailable. Functionality of this site is limited.') - end - end - - def explore - # Workaround to know in advance non-cached screenshots - # Ideally the apps structure should include Screenshot objects from the beginning - @apps = @appdata[:apps].reject do |a| - a[:screenshots].blank? || !Screenshot.new(a[:pkgname], a[:screenshots][0]).thumbnail_generated? - end - end - - def category - @category = params[:category] - raise MissingParameterError, 'Invalid parameter category' unless valid_package_name? @category - - mapping = @main_sections.select { |s| s[:id].casecmp(@category).zero? } - categories = (mapping.blank? ? [@category] : mapping.first[:categories]) - - app_pkgs = @appdata[:apps].reject { |app| (app[:categories].map(&:downcase) & categories.map(&:downcase)).blank? } - package_names_unsorted = app_pkgs.map { |p| p[:pkgname] }.uniq - @packagenames = package_names_unsorted.sort_by { |x| @appdata[:apps].find { |a| a[:pkgname] == x }[:name] } - - app_categories = app_pkgs.map { |p| p[:categories] }.flatten - reject_categories = %w[GNOME KDE Qt GTK] - @related_categories = app_categories.uniq.map { |c| { name: c, weight: app_categories.count { |v| v == c } } } - @related_categories = @related_categories.sort_by { |c| c[:weight] }.reverse.reject { |c| categories.include? c[:name] } - @related_categories = @related_categories.reject { |c| reject_categories.include? c[:name] } - - render 'search/find' - end - - def screenshot - image params[:package], :screenshot - end - - def thumbnail - image params[:package], :thumbnail - end - - private - - def valid_package_name?(name) - name =~ /^[[:alnum:]][-+~\w.:@]*$/ - end - - def image(pkgname, type) - @appdata[:apps].each do |app| - next unless app[:pkgname] == pkgname - next if app[:screenshots].blank? - - app[:screenshots].each do |image_url| - return redirect_to(image_url, allow_other_host: true) if type == :screenshot && image_url - next if image_url.blank? - - path = begin - screenshot = Screenshot.new(pkgname, image_url) - screenshot.thumbnail_path(fetch: true) - rescue OpenURI::HTTPError => e - Rails.logger.error "Error retrieving #{image_url}: #{e}" - next - end - return redirect_to "/#{path}" - end - end - - return head(404, 'content_type' => 'text/plain') if type == :screenshot - - # a screenshot object with nil url returns default thumbnails - screenshot = Screenshot.new(pkgname, nil) - path = screenshot.thumbnail_path(fetch: true) - url = ActionController::Base.helpers.asset_url(path) - redirect_to url - end - - # See https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html - def set_categories - @main_sections = [ - { name: _('Games'), id: 'Games', icon: 'games', categories: ['Game'] }, - { name: _('Development'), id: 'Development', icon: 'code', categories: ['Development'] }, - { name: _('Education & Science'), id: 'Education', icon: 'education', categories: %w[Education Science] }, - { name: _('Multimedia'), id: 'Multimedia', icon: 'multimedia', categories: %w[AudioVideo Audio Video] }, - { name: _('Graphics'), id: 'Graphics', icon: 'graphics', categories: ['Graphics'] }, - { name: _('Office & Productivity'), id: 'Office', icon: 'office', categories: ['Office'] }, - { name: _('Network'), id: 'Network', icon: 'network', categories: ['Network'] }, - { name: _('System & Utility'), id: 'Tools', icon: 'utils', categories: %w[Settings System Utility] } - ] - end -end diff --git a/app/controllers/packages_controller.rb b/app/controllers/packages_controller.rb new file mode 100644 index 000000000..5be0a7bdc --- /dev/null +++ b/app/controllers/packages_controller.rb @@ -0,0 +1,12 @@ +class PackagesController < ApplicationController + before_action :set_distribution + before_action :set_package, only: %i[show update] + + def show; end + + private + + def set_package + @package = @distribution.packages.find_by!(name: params[:name]) + end +end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 000000000..bacae8464 --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,3 @@ +class ApplicationJob < ActiveJob::Base +end + diff --git a/app/jobs/cache_screenshot_job.rb b/app/jobs/cache_screenshot_job.rb new file mode 100644 index 000000000..89c1e9040 --- /dev/null +++ b/app/jobs/cache_screenshot_job.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# Fetches one or more screenshots from an array of urls and attaches it to a Package +class CacheScreenshotJob < ApplicationJob + def perform(package_id, screenshot_urls = []) + package = Package.find_by(id: package_id) + return unless package + + package.screenshots.purge + + screenshot_urls.each do |url| + begin + response = http_connection(url: url).get + rescue Faraday::ConnectionFailed, Faraday::SSLError + logger.debug "Could not fetch #{url}..." + next + end + + unless response.success? + logger.debug "Could not fetch #{url}..." + next + end + + filename = File.basename(URI.parse(url).path) + filepath = Rails.root.join('tmp', 'file-cache', filename) + File.binwrite(filepath, response.body) + package.screenshots.attach(io: File.open(filepath), filename: filename, content_type: response.headers['content-type']) + File.delete(filepath) + end + package.increment('weight', package.screenshots_attachments.count) + package.save + end + + private + + def http_connection(url:) + Faraday.new(url) do |conn| + store = ActiveSupport::Cache.lookup_store(:file_store, Rails.root.join('tmp', 'http-cache')) + + conn.headers['User-Agent'] = 'software.opensuse.org' + conn.use Faraday::FollowRedirects::Middleware + conn.use :http_cache, store: store, serializer: Marshal + end + end +end diff --git a/app/models/appdata.rb b/app/models/appdata.rb deleted file mode 100644 index 1658f0691..000000000 --- a/app/models/appdata.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -require 'open-uri' -require 'open_uri_redirections' -require 'zlib' - -class Appdata - attr_reader :data - - def initialize(distribution) - @distribution = distribution - @data = load_appdata - end - - private - - def load_appdata - data = { apps: [], categories: Set.new } - %w[oss non-oss].each do |flavour| - xml = load_xml(flavour) - data[:apps].concat(parse_appdata_apps(xml)) - data[:categories].merge(parse_appdata_categories(xml)) - end - data - end - - def load_xml(flavour) - baseurl = if @distribution == 'tumbleweed' - "https://download.opensuse.org/tumbleweed/repo/#{flavour}/" - else - "https://download.opensuse.org/distribution/#{@distribution}/repo/#{flavour}/" - end - - index_url = "#{baseurl}/repodata/repomd.xml" - repomd = Nokogiri::XML(URI.open(index_url)).remove_namespaces! - href = repomd.xpath('/repomd/data[@type="appdata"]/location').attr('href').text - appdata_url = baseurl + href - Nokogiri::XML(Zlib::GzipReader.new(URI.open(appdata_url, allow_redirections: :all))) - # Broad except, could be network connection, missing 'href' attribute - rescue StandardError => e - Rails.logger.error("Error: #{e} -- appdata_url=#{appdata_url}") - Nokogiri::XML('') - end - - def parse_appdata_apps(xml) - apps = [] - xml.xpath('/components/component').each do |app| - appdata = {} - # Filter translated versions of name and summary out - appdata[:name] = app.xpath('name[not(@xml:lang)]').text - appdata[:summary] = app.xpath('summary[not(@xml:lang)]').text - appdata[:pkgname] = app.xpath('pkgname').text - appdata[:categories] = app.xpath('categories/category').map(&:text).reject { |c| c.match(/^X-/) }.uniq - appdata[:id] = app.xpath('id').text - appdata[:homepage] = app.xpath('url').text - appdata[:screenshots] = app.xpath('screenshots/screenshot/image').map(&:text) - apps << appdata - end - apps - end - - def parse_appdata_categories(xml) - xml.xpath('/components/component/categories/category') - .map(&:text).reject { |c| c.match(/^X-/) }.uniq - end -end diff --git a/app/models/category.rb b/app/models/category.rb new file mode 100644 index 000000000..c3ae6af59 --- /dev/null +++ b/app/models/category.rb @@ -0,0 +1,4 @@ +class Category < ApplicationRecord + has_and_belongs_to_many :packages + validates :name, presence: true +end diff --git a/app/models/distribution.rb b/app/models/distribution.rb new file mode 100644 index 000000000..305f1be20 --- /dev/null +++ b/app/models/distribution.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# A Linux Distribution +class Distribution < ApplicationRecord + has_many :repositories + has_many :packages, through: :repositories + + validates :vendor, :name, :version, :obs_repo_names, presence: true + + def sync + repositories.where(updateinfo: false).each(&:sync) + repositories.where(updateinfo: true).each(&:sync) + end + + def full_name + "#{vendor} #{name}" + end +end diff --git a/app/models/package.rb b/app/models/package.rb new file mode 100644 index 000000000..25c5190a4 --- /dev/null +++ b/app/models/package.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# A binary package of a Linux distribution repository +class Package < ApplicationRecord + belongs_to :repository + has_and_belongs_to_many :categories, -> { distinct } + has_many_attached :screenshots + has_many_attached :icons + + validates :name, :release, :architectures, :license, presence: true + validates :name, uniqueness: { scope: :repository } + serialize :architectures, type: Array + has_paper_trail on: [:update], only: [:release] + + SUPPORT_PACKAGE_NAME_PARTS = ['-branding-', '-extension-', '-plugin-', '-plugins-', '-trans-', '-translations-', '-traineddata-'].freeze + SUPPORT_PACKAGE_NAME_ENDINGS = ['-32bit', '-data', '-devel', '-devel-static', '-doc', '-docs', '-docs-html', '-debug', + '-compat', '-api', '-addons', + '-font', '-fonts', '-extra', '-extras', 'extension', '-extensions', '-example', '-examples', + '-headers', '-imports', '-icons', '-javadoc', + '-lang', '-lib', '-libs', '-locale', '-module', '-modules', + '-plugin', '-plugins', '-schema', '-schemas', '-source', '-src', '-support', '-systemd', + '-test', '-testresults', '-tests', '-testsuite', '-theme', '-themes', '-translations', + '-wallpaper', '-wallpapers'].freeze + SUPPORT_PACKAGE_NAME_BEGINNINGS = ['system-', 'php8-', 'php7-', 'aws-sdk-', 'ocaml-', 'aspell-', + 'patterns-', 'cross-', 'myspell-', 'libqt5-', 'maven-', + 'typelib-', 'qt6-', 'ruby2.5-', 'ghc-', 'perl-', 'python3-', 'texlive-'].freeze + + def title + name unless read_attribute(:title) + end + + def main_package? + SUPPORT_PACKAGE_NAME_PARTS.each do |part| + return false if name.include?(part) + end + + SUPPORT_PACKAGE_NAME_BEGINNINGS.each do |beginning| + return false if name.starts_with?(beginning) + end + + SUPPORT_PACKAGE_NAME_ENDINGS.each do |ending| + return false if name.ends_with?(ending) + end + + true + end +end diff --git a/app/models/repository.rb b/app/models/repository.rb new file mode 100644 index 000000000..307d89313 --- /dev/null +++ b/app/models/repository.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +# A distribution repository (repomd+appdata) with many packages +# https://github.com/openSUSE/libzypp/tree/master/zypp/parser/yum/schema +# https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html +# +class Repository < ApplicationRecord + belongs_to :distribution + has_many :packages + has_many_attached :repodata_files + + validates :url, presence: true + + def sync + if updateinfo? + sync_updateinfo + else + sync_primary + sync_appdata + set_weight + end + end + + def resync + update(revision: nil) + sync + end + + # private + + def sync_updateinfo + logger.debug('Syncing updateinfo...') + + updateinfo_xml.elements('update').each do |update_hash| + update_hash.deep_symbolize_keys! + update_hash[:pkglist][:collection][:package].each do |package_hash| + package = packages.find_by(name: package_hash[:name]) + + next unless package + + package.update!(release: package_hash[:version]) + end + end + end + + def sync_primary + logger.debug('Syncing primary...') + + primary_xml.elements('package').each do |package_hash| + package_hash.deep_symbolize_keys! + logger.debug("Trying to sync #{package_hash[:name]}...") + package = packages.find_or_create_by(name: package_hash[:name]) + + package.assign_attributes(package_hash.slice(:name, :url, :summary, :description)) + package.architectures << package_hash[:arch] + package.architectures = package.architectures.uniq.compact + package.license = package_hash[:format][:'rpm:license'] + package.release = package_hash[:version][:ver] + package.increment('weight') if package.main_package? + package.save! + end + cleanup_packages + end + + def sync_appdata + logger.debug('Trying to sync appdata...') + appdata_xml.elements('component').each do |component_hash| + component_hash.deep_symbolize_keys! + logger.debug("Trying to sync appdata for #{component_hash[:pkgname]}...") + package = packages.find_by(name: component_hash[:pkgname]) + next unless package + + # FIXME: Handle the translations of appdata.summary + summary = component_hash[:summary] + summary = summary.first if summary.is_a?(Array) + + # FIXME: Handle the translations of appdata.name + title = component_hash[:name] + title = title.first if title.is_a?(Array) + + # FIXME: Handle HTML components like

and translations.... + description = component_hash[:description] + + if component_hash[:screenshots] + screenshots = component_hash[:screenshots].elements('screenshot').map { |screenshot| screenshot['image']['_content'] } + screenshots.compact! + end + # FIXME: icons, how does that work with appdata-icons.tar.gz? + # XML is 128x128/4Pane.png + + if component_hash[:categories] + if component_hash[:categories][:category].is_a?(Array) + component_hash[:categories][:category].map { |category| package.categories << Category.find_or_create_by(name: category) } + else + package.categories << Category.find_or_create_by(name: component_hash[:categories][:category]) + end + end + + package.assign_attributes(title: title, summary: summary, description: description, appstream: true) + package.increment('weight', 10) + package.save! + CacheScreenshotJob.perform_later(package.id, screenshots) if screenshots&.any? + end + + true + end + + def cleanup_packages + packages_in_primary = primary_xml.elements('package').map { |package| package['name'] } + packages_in_db = packages.pluck(:name) + to_delete = packages_in_db.uniq - packages_in_primary.uniq + + to_delete.each do |package_name| + packages.find_by(name: package_name).delete + logger.debug("Deleted package #{package_name} from distribution #{distribution.name}") + end + end + + def repomd_xml + filename = fetch_by_type(type: 'repomd') + repomd_blob = repodata_files_blobs.find_by(filename: filename) + Xmlhash.parse(repomd_blob.download) + end + + def primary_xml + xml_by_type(type: 'primary') + end + + def appdata_xml + xml_by_type(type: 'appdata') + end + + def updateinfo_xml + xml_by_type(type: 'updateinfo') + end + + # FIXME: This method and everything below is a service... + def xml_by_type(type:) + empty_xml = Xmlhash.parse('') + + filename = fetch_by_type(type: type) + return empty_xml unless filename + + blob = repodata_files_blobs.find_by(filename: filename) + return empty_xml unless blob + + Xmlhash.parse(blob.download) + end + + def fetch_by_type(type:) + full_url = repodata_url + return fetch_repomd if type == 'repomd' + + filename = repomd_xml.elements('data').find { |element| element['type'] == type }['location']['href'].split('/').last + full_url << filename + return filename if repodata_files_blobs.find_by(filename: filename) + + response = http_connection(url: full_url.join('/')).get + raise "Could not fetch #{full_url.join('/')}" unless response.success? + + logger.debug("Successfully fetched #{full_url.join('/')}") + + filepath = Rails.root.join('tmp', 'file-cache', "#{id}-#{filename}") + File.binwrite(filepath, uncompress_gzip(response.body)) + repodata_files.attach(io: File.open(filepath), filename: filename, content_type: response.headers['content-type']) + File.delete(filepath) + + filename + end + + def repodata_url + [url, 'repodata'] + end + + def fetch_repomd + full_url = repodata_url + full_url << 'repomd.xml' + response = http_connection(url: full_url.join('/')).get + raise "Could not fetch #{full_url.join('/')}" unless response.success? + + logger.debug("Successfully fetched #{full_url.join('/')}") + + download_revision = Xmlhash.parse(response.body)['revision'] + + filename = "#{download_revision}-repomd.xml" + return filename if repodata_files_blobs.find_by(filename: filename) + + filepath = Rails.root.join('tmp', 'file-cache', "#{id}-#{filename}") + File.binwrite(filepath, response.body) + repodata_files.attach(io: File.open(filepath), filename: filename, content_type: response.headers['content-type']) + File.delete(filepath) + + update(revision: download_revision) + + filename + end + + def http_connection(url:) + Faraday.new(url) do |conn| + conn.headers['User-Agent'] = 'software.opensuse.org' + conn.use Faraday::FollowRedirects::Middleware + end + end + + # NOTE: Can't use the gzip Faraday middleware, it only considers the Content-Encoding header. + # But mirrors serve the gzip files, not the content of the repomd files encoded in gzip. + # So they only set the header "content-type"=>"application/x-gzip"... + def uncompress_gzip(body) + io = StringIO.new(body) + gzip_reader = Zlib::GzipReader.new(io, encoding: 'ASCII-8BIT') + gzip_reader.read + end +end diff --git a/app/models/screenshot.rb b/app/models/screenshot.rb deleted file mode 100644 index 6c8636dda..000000000 --- a/app/models/screenshot.rb +++ /dev/null @@ -1,103 +0,0 @@ -# frozen_string_literal: true - -require 'open-uri' -require 'mini_magick' - -# Class to cache and resize the screenshot of a given package -class Screenshot - THUMBNAIL_WIDTH = '600' - - # @return [String] name of the package - attr_reader :pkg_name - # @return [String] original (remote) location of the screenshot - attr_reader :source_url - - def initialize(pkg_name, source_url = nil) - @pkg_name = pkg_name - @source_url = source_url - end - - # Relative path of the thumbnail, ready to be passed to #image_tag - # - # If the thumbnail is already available locally or is one of the default - # images (i.e. there is no remote screenshot), it will return the correct - # path right away. - # - # If the screenshot is available remotely but the thumbnail is still not - # generated, it will generate the thumbnail before returning the url if - # :fetch is true or will return nil if :fetch is false. - # - # @return [String] - def thumbnail_path(fetch: true) - begin - generate_thumbnail unless cached? || !fetch || !source_url - # This is sensitive enough (depending on an external system) to - # justify an agressive rescue. #open can produce the following - # rescue Errno::ETIMEDOUT, Net::ReadTimeout, OpenURI::HTTPError => e - # And also there is a chance of exception generating the thumbnail - rescue Exception - raise unless Rails.env.production? - - Rails.logger.error("No screenshot fetched for: #{pkg_name}") - end - - if cached? - generated_thumbnail_path - else - default_thumbnail_path - end - end - - # @return [Boolean] true if a proper thumbnail is available (not a default thumbnail) - # - # This is useful for carousel screenshots, where we don't want to show default thumbnails. - def thumbnail_generated? - cached? - end - - protected - - def cached? - File.exist?(File.join(Rails.root, 'public', generated_thumbnail_path)) - end - - def generate_thumbnail - Rails.logger.debug("Fetching screenshot from #{source_url}") - img = MiniMagick::Image.open(source_url) - img.resize THUMBNAIL_WIDTH - file_path = File.join(Rails.root, 'public', generated_thumbnail_path) - img.write file_path - end - - # Path to the generated thumbnail image - # This is served from /public, and not from the asset pipeline. - def generated_thumbnail_path - "images/thumbnails/#{pkg_name}.png" - end - - # Default thumbnail path, based on the package name. - # This is served from the asset pipeline. - def default_thumbnail_path - file = case pkg_name - when /-devel$/, /-devel-/, /-debug$/, /-debuginfo/, /-debugsource/, /-kmp-/ - 'devel-package.png' - when /-lang$/, /-l10n-/, /-i18n-/, /-translations/ - 'lang-package.png' - when /-doc$/, /-help-/, /-javadoc$/ - 'doc-package.png' - when /^rubygem-/ - 'ruby-package.png' - when /^perl-/ - 'perl-package.png' - when /^python-/, /^python2-/, /^python3-/ - 'python-package.png' - when /^kernel-/ - 'kernel-package.png' - when /^openstack-/i - 'openstack-package.png' - else - 'package.png' - end - "default-screenshots/#{file}" - end -end diff --git a/app/views/download/_download.css.erb b/app/views/download/_download.css.erb deleted file mode 100644 index dc9d30f41..000000000 --- a/app/views/download/_download.css.erb +++ /dev/null @@ -1,40 +0,0 @@ - diff --git a/app/views/download/appliance.erb b/app/views/download/appliance.erb deleted file mode 100644 index c20de33b5..000000000 --- a/app/views/download/appliance.erb +++ /dev/null @@ -1,52 +0,0 @@ -<%= render(:partial => "download", :formats => [:css]) %> -

-
-<% if @data.blank? %> -

<%= _("No appliance data for %{project}") % { project: @project } %>

-<% else %> - <% unless @flavors.blank? %> -

<%= _("%s project") % [@project] %>

-

<%= _("Select the image type") %>

-
- <% @flavors.each do |flavor| %> - - - <% end %> -
- <% else %> -

<%= _("No appliances found in project %s") % @project %>.

- <% end %> - - -
- - <% @flavors.each do |flavor| %> -
-
-
-
- <% @data.select {|k,v| v[:flavor] == flavor }.each do |k,v| %> -
- -
<%= v[:flavor] + " " + _("Image:") %>
- - - -
- <% end %> -
-
-
-
- <% end %> -
- -<% end %> -
-
diff --git a/app/views/download/package.erb b/app/views/download/package.erb deleted file mode 100644 index 330642355..000000000 --- a/app/views/download/package.erb +++ /dev/null @@ -1,130 +0,0 @@ -<%= render(:partial => "download", :formats => [:css]) %> - -
-
- <% if @data.blank? %> -

<%= _("No data for %s / %s") % [ @project, @package] %>

- <% else %> - <% unless @flavors.blank? %> -

- <%= _("#{@package} from #{project_url(@project)} project") %>

-

<%= _("Select Your Operating System") %>

-
- <% @flavors.each do |flavor| %> - - <%end%> -
-
- <% @flavors.each do |flavor| %> -
-
-
-
- <% @data.select {|k,v| v.has_key?(:ymp) && v[:flavor] == flavor}.sort.reverse.each do |k,v|%> - - <% end %> -
-
- -
- <% @data.select {|k,v| v.has_key?(:repo) && !k.nil? && v[:flavor] == flavor}.sort.reverse.each do |k,v| %> - <% secure_apt_url = ['Debian', 'Raspbian'].include?(v[:flavor]) ? "https://wiki.debian.org/SecureApt" : "https://help.ubuntu.com/community/SecureApt" %> - <% if v[:flavor] == "Arch" %> - <% repo_name = @project.gsub(":", "_") + "_" + k %> -
<%= _("For Arch Linux, edit /etc/pacman.conf and add the following (note that the order of repositories in pacman.conf is important, since pacman always downloads the first found package):") %>
-
<%= "[#{repo_name}]" %>
-Server = <%= v[:repo].gsub(/(\w):(\w)/, '\1:/\2') %>$arch
-
-
<%= _("Then run the following as root") %>
-
-key=$(curl -fsSL <%= "#{v[:repo]}$(uname -m)/#{repo_name}.key" %>)
-fingerprint=$(gpg --quiet --with-colons --import-options show-only --import --fingerprint <<< "${key}" | awk -F: '$1 == "fpr" { print $10 }')
-
-pacman-key --init
-pacman-key --add - <<< "${key}"
-pacman-key --lsign-key "${fingerprint}"
-
-pacman -Sy <%= repo_name %>/<%= @package %>
- <% elsif ['Debian', 'Raspbian', 'Ubuntu'].include?(v[:flavor]) %> -
<%= (_("For %s run the following:") % k.gsub('_', ' ').html_safe).html_safe %>
-
<%= (_("Keep in mind that the owner of the key may distribute updates, packages and repositories that your system will trust (more information).") % secure_apt_url).html_safe %>
-
<%=
-                     # don't use apt-add-repository wrapper for Ubuntu for now, because it adds source repo which we don't provide
-                     #        "apt-add-repository deb #{v[:repo]} /\napt-get update\napt-get install #{@package}"
-                     "echo 'deb #{v[:repo].gsub(/(\w):(\w)/, '\1:/\2').gsub(/^https/, 'http')} /' | sudo tee /etc/apt/sources.list.d/#{@project}.list\ncurl -fsSL #{v[:repo]}Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/#{@project.gsub(':', '_')}.gpg > /dev/null\nsudo apt update\nsudo apt install #{@package}"
-                     %>
- <% else %> -
<%= (_("For %s run the following as root:") % k.gsub('_', ' ').html_safe).html_safe %>
-
<%=
-                     case v[:flavor]
-                     when 'openSUSE', 'SLE'
-                         "zypper addrepo #{v[:repo]}#{@project}.repo\nzypper refresh\nzypper install #{@package}"
-                     when 'Fedora'
-                       version = k.split("_").last
-                       if version == "Rawhide" or Integer(version) >= 22
-                         "dnf config-manager --add-repo #{v[:repo]}#{@project}.repo\ndnf install #{@package}"
-                       else
-                         "cd /etc/yum.repos.d/\nwget #{v[:repo]}#{@project}.repo\nyum install #{@package}"
-                       end
-                     when 'CentOS', 'RHEL', 'SL'
-                       "cd /etc/yum.repos.d/\nwget #{v[:repo]}#{@project}.repo\nyum install #{@package}"
-                     when 'Univention'
-                       "echo 'deb #{v[:repo].gsub(/(\w):(\w)/, '\1:/\2').gsub(/^https/, 'http')} /' > /etc/apt/sources.list.d/#{@project}.list\nwget -nv #{v[:repo]}Release.key -O Release.key\napt-key add - < Release.key\napt-get update\napt-get install #{@package}"
-                     when 'Mageia', 'Mandriva'
-                       version = k.split("_").last
-                       if version == "Cauldron" or Integer(version) >= 6
-                         "dnf config-manager --add-repo #{v[:repo]}#{@project}.repo\ndnf install #{@package}"
-                       else
-                         "urpmi.addmedia #{@project} #{v[:repo]}\nurpmi.update -a\nurpmi #{@package}"
-                       end
-                     else
-                       '?'
-                     end
-                     %>
- <% end %> - <% end %> -
-
- <% if not @package.nil? %> - -
-
- <% @data.select {|k,v| v.has_key?(:package) && !k.nil? && v[:flavor] == flavor}.sort.reverse.each do |k,v| %> -
-

<%= (_("Packages for %s:") % [("" + k.gsub('_', ' ') + "").html_safe]).html_safe %>

- <% v[:package].sort.each do |k,v| %> - - <% end %> -
- - <% end %> -
-
- <% end %> -
-
-
- <% end %> -
-
-<% else %> -

<%= _("No downloads found for %s in project %s") % [ @package, @project] %>.

-<% end %> -<% end %> -
- diff --git a/app/views/error.html.erb b/app/views/error.html.erb deleted file mode 100644 index c2db4b96a..000000000 --- a/app/views/error.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -<% @page_title = "Error" if @page_title.blank? -%> - -
- -
-

<%=h @message %>

-
-
diff --git a/app/views/layouts/_search_form.html.haml b/app/views/layouts/_search_form.html.haml new file mode 100644 index 000000000..6d8d1ba9a --- /dev/null +++ b/app/views/layouts/_search_form.html.haml @@ -0,0 +1,14 @@ +.search-box.py-5 + .container + = form_tag(search_path, method: :get) do + .row + .col-md + .row.m-0.input-group + .col-md-auto.p-0.input-group-prepend + = select_tag 'distribution', options_for_select(Distribution.all.map { |distribution| [distribution.full_name, distribution.name] }, @distribution), :class => 'btn custom-select' + .col.py-3.p-0.p-md-0.input-group-append + = text_field_tag 'q', @query, :id => "search-form", :class => 'form-control', :placeholder => _('Search packages...'), :autocomplete => "off", :autofocus => true, :size => "60" + .col-md-auto.btn-group.d-flex + %button#search-button.btn.btn-primary.ml-auto.d-flex.align-items-center{type: "submit"} + = icon "search" + %span.ml-1= _("Search") \ No newline at end of file diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 400975064..24086c7ad 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -27,7 +27,7 @@ %main.page-content.flex-fill#content - unless @hide_search_box - = render partial: 'search/find_form', + = render partial: 'layouts/search_form', locals: { baseproject: @baseproject } #search-result-container diff --git a/app/views/main/index.html.haml b/app/views/main/index.html.haml new file mode 100644 index 000000000..98287b6f5 --- /dev/null +++ b/app/views/main/index.html.haml @@ -0,0 +1,5 @@ +.container + .row + .col-md-12 + %p + TODO: This is the place to show the appstore interface... diff --git a/app/views/main/search.html.haml b/app/views/main/search.html.haml new file mode 100644 index 000000000..417d0d211 --- /dev/null +++ b/app/views/main/search.html.haml @@ -0,0 +1,23 @@ +.container + .row + .col-md-12 + #search-result.py-3 + - if @packages.blank? + #search-result-error + #msg.alert.alert-warning + = _("No packages found matching your search. ") + - unless @search_devel + %br/ + = _("You could try to extend your search to development packages or search for another base distribution (currently #{@distribution}).") + - else + #search-result-list.row + - @packages.each_with_index do |package, idx| + .col-sm-6.col-md-4 + .package-card.card.mb-4 + %a.card-img-top{href: distribution_package_path(@distribution.name, package.name), style: "background-image: url(https://software.opensuse.org/assets/default-screenshots/package-2df257d0952fd1c381027c9d7b97b6f0cb7acac30729d1fa7d3afc4bb59d621f.png)"} + .card-body + %h4.card-title + = link_to(distribution_package_path(distribution_name: @distribution.name, name: package.name)) do + = highlight(package.name, params[:q]) + %p.card-text + = highlight(package.summary, params[:q]) diff --git a/app/views/package/_download_rows.html.erb b/app/views/package/_download_rows.html.erb deleted file mode 100644 index 5d9b328c9..000000000 --- a/app/views/package/_download_rows.html.erb +++ /dev/null @@ -1,64 +0,0 @@ -<% packages.flatten.sort_by(&:project).group_by(&:project).each do |result| %> - <% if result.first == distro[:project] || result.first == "#{distro[:project]}:NonFree" - name = _("official release") - elsif result.first == "#{distro[:project]}:Update" || result.first == "#{distro[:project]}:NonFree:Update" - name = _("official update") - else - name = shorten result.first, 40 - end - - web_host = Rails.configuration.x.web_host - if result.last.first.package.nil? - pkg_link = "#{web_host}/project/show/#{CGI.escape result.first}" - else - pkg_link = "#{web_host}/package/show/#{CGI.escape result.first}/#{CGI.escape result.last.first.package}" - end -%> - -
-
- -
- <%= link_to name, pkg_link %> - <% if type.eql? 'official' %> - <%= _('Official') %> - <% end %> - <% if type.eql? 'experimental' %> - <%= _('Experimental') %> - <% end %> - <% if type.eql? 'community' %> - <%= _('Community') %> - <% end %> -
- -
- <%# only use the latest version, obs bug: some outdated versions still listed. %> - <% version = result.last.map{|r| r.version }.max %> - <%= shorten version, 23 %> -
- -
- <% release = result.last.select{|r| r.version == version }.map{|v| "#{v.release}".sub(".", "").to_i}.max %> - <% items = result.last.select{|r| r.version == version && "#{r.release}".sub(".", "").to_i == release } %> - <% archs = items.map{|item| human_arch( item.arch ) }.uniq.sort %> - <% archs << _("Source") if archs.delete(_("Source")) %> - - - <% unless type.eql? 'official' %> - <% if oneclick && distro[:project].match(/SUSE/) && !(archs.uniq == [_("Source")]) %> - <% url = url_for :controller => 'download', :action => 'ymp_without_arch_and_version', - :project => result.first, :repository => result.last.first.repository, :package => @pkgname, - :base => result.last.first.baseproject, :query => @pkgname%> - <%= icon "oci", "1.15em" %> <%= _("1 Click Install") %> - <% end %> - <% end %> - - <% project = result.last.first.fetch("realproject", result.first) %> - <%= link_to download_package_path(project: project, package: @pkgname), class: 'btn btn-sm btn-secondary' do %> - <%= icon "download", "1.15em" %> - <%= _('Expert Download') %> - <% end %> - -
-
-
-<% end -%> diff --git a/app/views/package/explore.html.erb b/app/views/package/explore.html.erb deleted file mode 100644 index 5308403f4..000000000 --- a/app/views/package/explore.html.erb +++ /dev/null @@ -1,46 +0,0 @@ -
- - <% if not @apps.blank? %> - - <%end%> - -
- <% @main_sections.each do |section| %> -
- <%= icon section[:icon], "1.5em" %> - <%= link_to section[:name], {:controller => :package, :action => :category, :category => section[:id]}, :class => 'text-body ml-2' %> -
- <% end %> -
- -
diff --git a/app/views/package/show.html.erb b/app/views/package/show.html.erb deleted file mode 100644 index a2117f667..000000000 --- a/app/views/package/show.html.erb +++ /dev/null @@ -1,137 +0,0 @@ -<%= render :partial => 'search/default_searches' if @search_term %> - -<% if @packages.blank? %> -
-
-

<%= _("Package %s not found...") % [@pkgname] %>

-

<%= _("Back to home page") %>

-
-
- -<% else %> - -
-
-
-
- - <%= @pkgname %> - -
-
-

<%= @name || @pkgname %>

- <% desc_package = search_for_description( @pkgname, @packages ) %> - <% unless desc_package.blank? %> - <% unless @appcategories.blank? %> -

- <%= @appcategories.size > 1 ? _("Categories") : _("Category") %>: - <% @appcategories.each do |cat| -%> - <%= link_to cat, {:controller => :package, :action => :category, :category => cat}, :class => 'badge badge-info' %> - <% end -%> -

- <% end -%> -

<%= desc_package.summary %>

-

<%= prepare_desc desc_package.description -%>

- - <%else %> -

<%= _("No description.") %>

- <% end -%> - - <% if @default_package.blank? %> - <%= _("There is no official package available for %s") % @default_project_name %> - <% else %> - <% - url = url_for :controller => 'download', :action => 'ymp_without_arch_and_version', :query => @pkgname, - :project => @default_package.project, :repository => @default_package.repository, :package => @pkgname, :base => @default_package.baseproject, :protocol => 'https' - %> -
    -
  • <%= _("Version") %> <%= shorten @default_package.version, 13 %>
  • -
  • <%= _("Size") %> <%= number_to_human_size desc_package[:size] if desc_package %>
  • -
  • <%= @default_project_name %>
  • -
- <% if @appid && @baseproject != "ALL" %> -
- - <%= icon "oci" %> - <%= _("Appstream Install") %> - -
- <% end %> - - <%= link_to download_package_path(project: @default_package.project, package: @pkgname), class: 'btn btn-lg btn-secondary' do %> - <%= icon "download" %> - <%= _('Expert Download') %> - <% end %> - <% end -%> -
-
-
-
- - <% unless @default_package.blank? %> -
- -
- <% end %> -
-

<%= _("Distributions") %>

- - <% @distributions.each do |distro| -%> - <% if (pkgs = @packages.select{|s| s.baseproject == distro[:project]}).size > 0 %> - -

<%= distro[:name] %>

- - - <% - official, pkgs = pkgs.partition{|s| s.project == distro[:project] || s.project == "#{distro[:project]}:NonFree" } - update, pkgs = pkgs.partition{|s| s.project == "#{distro[:project]}:Update" || s.project == "#{distro[:project]}:NonFree:Update"} - stable, pkgs = pkgs.partition{|s| s.quality == "Stable"} %> - - <%= render( :partial => 'package/download_rows', :locals => {:packages => official, :distro => distro, :oneclick => true, :type => 'official'} ) if update.blank? %> - <%= render :partial => 'package/download_rows', :locals => {:packages => update, :distro => distro, :oneclick => true, :type => 'official'} %> - <%= render :partial => 'package/download_rows', :locals => {:packages => stable, :distro => distro, :oneclick => true, :type => 'official'} %> - - <% - slug = distro[:project].downcase.strip.gsub('_', '-').gsub('/', '-').gsub(':', '-').gsub(/[^\w-]/, '') - home, pkgs = pkgs.partition{|s| s.project.match( /^home\:/ )} - devel = pkgs.reject{|s| @official_projects.include?( s.project ) || s.project.match( /^home\:/ ) || s.project.match( /#{distro[:project]}\:Update/ ) || - s.project.match( /#{distro[:project]}\:NonFree/ ) || s.project.match( /openSUSE\:Maintenance\:/ ) } %> - -

- <% devel_disabled = 'disabled' if devel.empty? %> - - <% home_disabled = 'disabled' if home.empty? %> - -

- -
- <%= render :partial => 'package/download_rows', :locals => {:packages => devel, :distro => distro, :oneclick => true, :type => 'experimental' } %> -
- -
- <%= render :partial => 'package/download_rows', :locals => {:packages => home, :distro => distro, :oneclick => true, :type => 'community'} %> -
- <% end -%> - <% end -%> - - <% unless @extra_dists.blank? %> - -

<%= _("Unsupported distributions") %>

- -
- <%= _("The following distributions are not officially supported. Use these packages at your own risk.") %> - -
- -
- <% @extra_dists.each do |distro| -%> -

<%= distro[:project] %>

- <% pks = @extra_packages.select{|p| p.baseproject == distro[:project] } %> - <%= render :partial => 'download_rows', :locals => {:packages => pks, :distro => distro, :oneclick => false, :type => 'unsupported'} %> - <% end %> -
- <% end %> - -
- -<% end -%> diff --git a/app/views/packages/_screenshot_carousel.html.haml b/app/views/packages/_screenshot_carousel.html.haml new file mode 100644 index 000000000..c37d341af --- /dev/null +++ b/app/views/packages/_screenshot_carousel.html.haml @@ -0,0 +1,16 @@ +- if @package.screenshots.any? + #carouselExampleIndicators.carousel.slide{"data-ride" => "carousel"} + %ol.carousel-indicators + %li.active{"data-slide-to" => "0", "data-target" => "#carouselExampleIndicators"} + %li{"data-slide-to" => "1", "data-target" => "#carouselExampleIndicators"} + %li{"data-slide-to" => "2", "data-target" => "#carouselExampleIndicators"} + .carousel-inner + - @package.screenshots.each_with_index do |screenshot, index| + - if index == 0 + .carousel-item.active + %img.img-fluid{alt: "...", src: "#{url_for(screenshot)}"}/ + - else + .carousel-item + %img.img-fluid{alt: "...", src: "#{url_for(screenshot)}"}/ +- else + %img.img-fluid{alt: "...", src: "https://software.opensuse.org/assets/default-screenshots/package-2df257d0952fd1c381027c9d7b97b6f0cb7acac30729d1fa7d3afc4bb59d621f.png"}/ \ No newline at end of file diff --git a/app/views/packages/show.html.haml b/app/views/packages/show.html.haml new file mode 100644 index 000000000..d3d52d095 --- /dev/null +++ b/app/views/packages/show.html.haml @@ -0,0 +1,33 @@ +%body + %header.my-5 + .container + .row + .col-md-6 + = render partial: 'screenshot_carousel' + .col-md-6 + %h1 + = @package.title + - categories = @package.categories.pluck(:name).join(',') + - if categories + %p + = @package.categories.pluck(:name).join(',') + %p + %strong + = @package.summary + %p + = @package.description + %ul.list-inline + %li.list-inline-item + = _("Version") + = @package.release + - if @package.appstream? + .appstream-install + %a#appstream-button.btn.btn-lg.btn-primary{href: "appstream://#{@package.name}"} + = icon "oci" + = _("Appstream Install") + - else + .zypper-install + %code + = "zypper in #{@package.name}" + + diff --git a/app/views/search/_category_header.html.erb b/app/views/search/_category_header.html.erb deleted file mode 100644 index cbc4fce2f..000000000 --- a/app/views/search/_category_header.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -
-

<%= @category %>

-
- -<% unless @related_categories.blank? %> - -<% end -%> diff --git a/app/views/search/_default_searches.html.erb b/app/views/search/_default_searches.html.erb deleted file mode 100644 index cb764a40a..000000000 --- a/app/views/search/_default_searches.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -<% if DEFAULT_SEARCHES[@search_term] %> -
-
- <%=DEFAULT_SEARCHES[@search_term].html_safe %> -
-
-<% end %> diff --git a/app/views/search/_find_form.html.erb b/app/views/search/_find_form.html.erb deleted file mode 100644 index d66608df3..000000000 --- a/app/views/search/_find_form.html.erb +++ /dev/null @@ -1,29 +0,0 @@ - -<%= render :partial => 'search/settings' %> diff --git a/app/views/search/_find_results.html.erb b/app/views/search/_find_results.html.erb deleted file mode 100644 index ba923ef72..000000000 --- a/app/views/search/_find_results.html.erb +++ /dev/null @@ -1,90 +0,0 @@ -
-
- - <%= render :partial => 'search/default_searches' if @search_term %> - - <%= render :partial => 'search/category_header' if @category %> - - <% if @packagenames.blank? %> - -
-
- <%= _("No packages found matching your search. ") %> - <% unless @search_devel %> -
- <%= _("You could try to extend your search to development packages or search for another base distribution (currently #{@baseproject}).") %> - <% end %> -
-
- - <% else %> - -
- - <% - @packagenames.each_with_index do |package, idx| - appdata_pkg = @appdata[:apps].select{|a| a[:pkgname] == package} - package_name = package - package_name = appdata_pkg.first[:name] unless ( appdata_pkg.blank? || appdata_pkg.first[:name].blank? ) - package_img = nil - package_img = appdata_pkg.first[:screenshots].first unless ( appdata_pkg.blank? || appdata_pkg.first[:screenshots].blank? ) - thumb_url = screenshot_thumb_url(package) - %> - -
-
- - - -
-

<%= link_to highlight(package_name, @search_term), :controller => :package, :action => :show, :package => package %>

-

- <% - if( appdata_pkg.blank? || appdata_pkg.first[:summary].blank? ) - desc_package = search_for_description(package, @packages) - unless desc_package.blank? - summary = desc_package.summary - end - else - summary = appdata_pkg.first[:summary] - end - - unless summary.blank? %> - <%= highlight( summary, @search_term) %> - <% end -%> -

- - <%= _("View") %> - - - <% - devel_pack_match = [package + "-devel", package + "-lang", package + "-debuginfo", package + "-debugsource", package + "-debuginfo-32bit", - package + "-debuginfo-x86", ] - devel_packages = @packagenames.select{|r| devel_pack_match.include? r } - sub_packages = @packagenames.sort.select{|name| ( !(devel_pack_match.include? name) && - name.start_with?("#{package}-") && @appdata[:apps].select{|a| a[:pkgname] == name}.blank? ) } -%> - - <% unless devel_packages.blank? && sub_packages.blank? %> - - <% end -%> -
-
-
- <% end -%> -
- <% end %> -
-
diff --git a/app/views/search/_settings.html.erb b/app/views/search/_settings.html.erb deleted file mode 100644 index b9f06cea1..000000000 --- a/app/views/search/_settings.html.erb +++ /dev/null @@ -1,43 +0,0 @@ - diff --git a/app/views/search/find.html.erb b/app/views/search/find.html.erb deleted file mode 100644 index d49ace483..000000000 --- a/app/views/search/find.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -<% @page_title = _("Search") -%> - -<% unless @packagenames.nil? %> - <%= render :partial => 'search/find_results' %> - -<% else %> - -<% end %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 932ae5b9e..7b5202a75 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,20 +1,15 @@ Rails.application.routes.draw do + CONS = { + name: %r{[^/]*}, + distribution_name: %r{[^/]*} + }.freeze - root to: 'package#explore' - resources :search, only: [:index] do - end - - controller :package do - get 'package/:package', action: :show, constraints: { package: /[-+~\w\.:\@]+/ } - get 'package/thumbnail/:package.png', action: :thumbnail, constraints: { package: /[-+~\w\.:\@]+/ } - get 'package/screenshot/:package.png', action: :screenshot, constraints: { package: /[-+~\w\.:\@]+/ } + root to: 'main#index' + get '/search' => 'main#search', as: :search - get 'explore', action: :explore - get 'packages', action: :explore - get 'appstore', action: :explore - get 'packages/:category', action: :category, constraints: { category: /[\w\-\.: ]+/ } - get 'appstore/:category', action: :category, constraints: { category: /[\w\-\.: ]+/ } + resources :distributions, only: [], param: :name do + resources :packages, only: :show, param: :name, constraints: CONS end namespace 'download' do @@ -69,7 +64,4 @@ get 'developer', to: redirect('/distributions/testing') get 'developer/:locale', to: redirect('/distributions/testing?locale=%{locale}') get '/promodvd', to: redirect('/distributions') - - # catch all other params as locales... - get '/:locale', to: 'package#explore' end