267 lines
5.7 KiB
Crystal
267 lines
5.7 KiB
Crystal
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
|
||
|