require "colorize" require "uuid" require "file_utils" require "uri" require "weird-crystal-base" require "./weird.cr" require "./configuration.cr" require "./repository.cr" class Package::Context < Weird::Base property root = "/" # Stores informations about already installed packages, their # manifests, and so on. @database = Database.new @configuration = Configuration.new @cache_directory = "/var/cache/package" def initialize() self.root = "/" end def root=(@root) @database.root = "#{@root}#{Database::PATH_FROM_ROOT}" @configuration = Configuration.new "#{@root}#{Configuration::PATH_FROM_ROOT}" @configuration.repositories.each &.import_index(self) end def install(package : Package, data_directory : String) end # FIXME: Should atomic upgrades be handled here? def install(files : Array(String)) work_dirs = Hash(String, String).new manifests = Hash(String, Manifest).new packages = Hash(String, Package).new files.each do |file| work_dir = get_work_directory data_dir = "#{work_dir}/data" work_dirs[file] = work_dir Weird.extract file, work_dir, data_dir package = Package.new "#{work_dir}/control.spec" packages[file] = package if is_installed? package.to_atom raise ::Package::Exception.new "Package '#{package.to_atom.to_s}' is already installed." end Manifest.new("#{work_dir}/manifest").tap do |manifest| manifest.no_collisions! self manifests[file] = manifest end # FIXME: Check signatures (not implemented in packages ATM). end files.each do |file| info "Installing '#{file}'" work_dir = work_dirs[file] data_dir = "#{work_dir}/data" package = packages[file] manifest = manifests[file] manifest.each do |entry| if entry.type == "directory" && Dir.exists? entry.file next end debug "++ #{entry.file}" entry.install data_dir, @root end @database << {package, manifest} FileUtils.rm_r work_dir end end def install(file : String) install [file] end def install(atoms : Array(Atom)) unchecked_atoms = Deque.new atoms packages_to_install = Array(Tuple(Repository, Package)).new while unchecked_atoms.size > 0 atom = unchecked_atoms.pop tuple = get_installable atom if tuple.nil? raise Exception.new "Could not find '#{atom.to_s}'." end packages_to_install << tuple _, package = tuple package.dependencies.each do |atom| unless is_installed? atom unchecked_atoms.push atom end end end #pp! packages_to_install.map &.[1].to_atom.to_s packages_to_install.reverse.each do |tuple| download tuple end packages_to_install.reverse.each do |tuple| repository, package = tuple url = "#{repository.uri.path}/#{package.file_path}" install url end end def install(atom : Atom) install [atom] end def upgrade(atom : Atom) warning "(unimplemented) upgrade(Atom)" end def download(tuple : Tuple(Repository, Package)) repository, package = tuple uri = repository.uri if uri.scheme == "http" || uri.scheme == "https" FileUtils.mkdir_p @cache_directory destination = "#{get_cache_directory}/#{package.file_path}" full_url = "#{repository.uri.to_s}/#{package.file_path}" debug "Downloading '#{package.file_path}'" # FIXME: Check errors, probably. :| File.write destination, HTTP::Client.get(full_url).body package.file_path = destination end end def get_installable(atom : Atom) @configuration.repositories.each do |repository| match = repository.find &.matches?(atom) return {repository, match} if match end end def remove(packages : Array(Package)) # First part of this function is about extracting # reverse-dependencies. Once we have them all, we remove # everything one by one. all_packages = @database.get_all_packages packages_to_remove = [] of Package unchecked_packages = Deque(Package).new packages.each do |package| unchecked_packages << package end while unchecked_packages.size > 0 package = unchecked_packages.pop packages_to_remove << package all_packages .select(&.dependencies.any? do |dependency| # FIXME: Check full atoms and not just names. # FIXME: Checking there’s no collision with world files is probably done around here. packages_to_remove.find &.matches? dependency end) .each do |p| if unchecked_packages.find &.==(p) next end if packages_to_remove.find &.==(p) next end unchecked_packages << p end end packages_to_remove.reverse.each do |package| manifest = @database.get_manifest package info "Removing '#{package.to_atom.to_s}'" manifest.reverse.each do |entry| if entry.type == "directory" && (!Dir.exists?(entry.file) || !Dir.empty?(entry.file)) next end debug "-- #{entry.file}" entry.remove @root end @database.remove package end end def remove(package : Package) remove [package] end def remove(atoms : Array(Atom)) remove atoms.map { |atom| @database.get_package atom } end def remove(atom : Atom) remove [atom] end def installed_packages : Array(Package) end def is_installed?(atom : Atom) @database.is_installed? atom end def update_repository_cache @configuration.repositories.each do |repository| repository.download_index self end end def download_to_cache(atom : Atom, url : String) end def database_directory end def get_work_directory directory = "/tmp/package-#{UUID.random}" FileUtils.mkdir_p directory directory end def get_indices_cache directory = "#{@root}/var/cache/package" FileUtils.mkdir_p directory directory end # Should this really be the same place? :shrug: def get_cache_directory directory = "#{@root}/var/cache/package" FileUtils.mkdir_p directory directory end end