package/src/package/context.cr

267 lines
5.7 KiB
Crystal
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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 theres 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