#!/bin/bash
#
# Index the offline mirror:
/offline.db.tar.gz and offline.files.tar.gz
# from every package in , the same databases one repo-add call writes.
#
# index-offline-mirror.sh [jobs]
#
# repo-add handles one package at a time, and for each it hashes the archive
# and lists its files, which means decompressing all of it. A database is only
# a tar of one directory per package, so the packages are dealt out to one
# repo-add per CPU, each writing its own database, and the pieces are put
# together afterwards.
set -euo pipefail
dir=$(realpath "$1")
jobs=${2:-$(nproc)}
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT
mapfile -t packages < <(find "$dir" -maxdepth 1 -name '*.pkg.tar.*' ! -name '*.sig' | sort)
(( ${#packages[@]} > 0 )) || { echo "index-offline-mirror: no packages in $dir" >&2; exit 1; }
(( jobs > ${#packages[@]} )) && jobs=${#packages[@]}
(( jobs >= 1 )) || jobs=1
# Deal the sorted list out round-robin: big and small packages spread evenly.
for (( i = 0; i < jobs; i++ )); do mkdir "$work/$i"; done
for (( i = 0; i < ${#packages[@]}; i++ )); do
printf '%s\0' "${packages[i]}" >>"$work/$(( i % jobs ))/list"
done
pids=()
for (( i = 0; i < jobs; i++ )); do
xargs -0 -a "$work/$i/list" repo-add -q "$work/$i/offline.db.tar.gz" >"$work/$i/log" 2>&1 &
pids+=($!)
done
failed=0
for (( i = 0; i < jobs; i++ )); do
if ! wait "${pids[i]}"; then
failed=1
echo "index-offline-mirror: repo-add failed on part $i:" >&2
cat "$work/$i/log" >&2
fi
done
(( failed == 0 )) || exit 1
mkdir "$work/db" "$work/files"
for (( i = 0; i < jobs; i++ )); do
bsdtar -xf "$work/$i/offline.db.tar.gz" -C "$work/db"
bsdtar -xf "$work/$i/offline.files.tar.gz" -C "$work/files"
done
# One repo-add keeps a single entry per package name. Apart, two versions of
# one name would both survive, so that is an error here rather than a choice.
entries=$(find "$work/db" -mindepth 1 -maxdepth 1 -type d | wc -l)
names=$(cat "$work"/db/*/desc | awk '/^%NAME%$/ { getline; print }' | sort -u | wc -l)
if (( entries != ${#packages[@]} || names != entries )); then
echo "index-offline-mirror: ${#packages[@]} packages gave $entries entries for $names names; two versions of one package?" >&2
exit 1
fi
rm -f "$dir"/offline.db* "$dir"/offline.files*
for kind in db files; do
find "$work/$kind" -mindepth 1 -maxdepth 1 -type d -printf '%P\n' | sort |
bsdtar -czf "$dir/offline.$kind.tar.gz" -C "$work/$kind" -T -
ln -s "offline.$kind.tar.gz" "$dir/offline.$kind"
done
echo "Indexed ${#packages[@]} packages with $jobs repo-add processes."