library/packages/src/lib/y2packager/licenses_fetchers/archive.rb
# ------------------------------------------------------------------------------
# Copyright (c) 2018 SUSE LLC, All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of version 2 of the GNU General Public License as published by the
# Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# ------------------------------------------------------------------------------
require "y2packager/licenses_fetchers/base"
require "tmpdir"
Yast.import "Pkg"
module Y2Packager
module LicensesFetchers
# Base class for licenses fetchers based on some kind of license
# archive.
#
# It takes care of looking up the licenses in the unpacked
# archive and manages a temporary cache directory.
#
# The actual unpacking and provisioning of the archive file itself must
# be done in a derived class.
class Archive < Base
# Acceptance is not needed if the file exists
NO_ACCEPTANCE_FILE = "no-acceptance-needed".freeze
# Fallback license file
FALLBACK_LICENSE_FILE = "LICENSE.TXT".freeze
# Return available locales for product's license
#
# @return [Array<String>] Language codes ("de_DE", "en_US", etc.)
def locales
return [] if !archive_exists?
@locales ||=
begin
unpack_archive
license_files = find_case_insensitive(archive_dir, "LICENSE.*.TXT")
# NOTE: despite the use of the case-insensitive flag, the captured group will be
# returned as it is.
languages = license_files.map { |path| path[/LICENSE.(\w*).TXT/i, 1] }
languages << DEFAULT_LANG
languages.compact.uniq
end
end
# Determine whether the license should be accepted or not
#
# @return [Boolean] true if the license acceptance is required
def confirmation_required?
unpack_archive
find_path_for(archive_dir, NO_ACCEPTANCE_FILE).nil?
end
# Explicit destructor to clean up temporary dir
#
# @param dir [String] Temporary directory where licenses were unpacked
def self.finalize(dir)
proc { FileUtils.remove_entry_secure(dir) }
end
private
attr_reader :archive_dir
# Check if a license archive exists
#
# Will be overloaded by the actual implementation.
#
# @return [Boolean] True, if an archive exists
def archive_exists?
false
end
# Unpack license archive
#
# The idea is to unpack the archive once and keep the temporary directory.
#
# This is only a stub that provides the temporary directory. The
# actual archive unpacking has to be done by the derived class.
#
# @return [String] Archive directory
def unpack_archive
return @archive_dir if @archive_dir
@archive_dir = Dir.mktmpdir("yast-licenses-")
ObjectSpace.define_finalizer(self, self.class.finalize(@archive_dir))
@archive_dir
end
# Return the license content for a language
#
# The license archive is extracted to a temporary directory. When a
# license for a language "xx_XX" is not found, fall back to "xx".
#
# @see license_file
#
# @param lang [String] Language code
#
# @return [Array<String, String>, nil] Array containing content and language code
def license_content_for(lang)
return nil if !archive_exists?
unpack_archive
license_file = license_path(archive_dir, lang) || fallback_path(archive_dir)
if license_file.nil?
log.error("#{lang} license file not found for #{product_name}")
return nil
end
File.read(license_file)
end
# Return license file path for the given languages
#
# When a license for a language "xx_XX" is not found, it will fallback to "xx".
#
# @param directory [String] Directory where licenses were unpacked
# @param lang [String] Language code
#
# @return [String, nil] The first licence path for given languages or nil
def license_path(directory, lang)
candidate_langs = [lang]
candidate_langs << lang.split("_", 2).first if lang
candidate_langs.uniq!
log.info("Searching for a #{candidate_langs.join(",")} license translation in #{directory}")
find_path_for(directory, "LICENSE.{#{candidate_langs.join(",")}}.TXT")
end
# Return the fallback license file path
#
# Looking for a license file without language code
#
# @param directory [String] Directory where licenses were unpacked
#
# @return [String, nil] The fallback license path
def fallback_path(directory)
log.info("Searching for a fallback #{FALLBACK_LICENSE_FILE} file in #{directory}")
find_path_for(directory, FALLBACK_LICENSE_FILE)
end
# Return the path for the given file in specified directory
#
# @param (see #find_case_insensitive)
# @return [String, nil] The file path; nil if was not found
def find_path_for(directory, fileglob)
find_case_insensitive(directory, fileglob).first
end
# Return paths for the given file glob in specified directory
#
# @param directory [String] Directory, whole subtree is searched
# @param fileglob [String] File name, with glob characters like `*` `{a,b}`, case insensitive
# @return [Array<String>] All found file paths
def find_case_insensitive(directory, fileglob)
files = Dir.glob(File.join(directory, "**", "*"))
files.find_all do |fn|
File.fnmatch?(File.join(directory, "**", fileglob), fn, File::FNM_CASEFOLD | File::FNM_EXTGLOB)
end
end
end
end
end