#!/bin/sh

# contains functions used by more than one script

# TODO:

#  mangle $arch in PKBUILDs to contain i486, i586, i686

# find_pkgbuild package repository
# find the PKGBUILD of $package from $repository

find_pkgbuild() {

  local package="$1"
  local repository="$2"

  local PKGBUILD=''
  local repo
  local file
  local package_path

  if [ -f "${repo_paths__archlinux32}/${repository}/${package}/PKGBUILD" ]; then
    # If this package has some modification,
    repo="$(find_git_repository_to_package_repository "${repository}")"
    eval package_path="$(printf '$repo_paths__%s' "${repo}")/${package}"
    if ! [ -d "${package_path}" ]; then
      # create some dummy files if it is also new.
      mkdir -p "${package_path}/repos/${repository}-x86_64"
      touch "${package_path}/repos/${repository}-x86_64/PKGBUILD"
    fi
  fi

  for repo in ${repo_names}; do
    if [ "${repo}" = "archlinux32" ]; then
      # this is not a repository of packages
      continue
    fi
    eval package_path="$(printf '$repo_paths__%s' "${repo}")/${package}"
    if ! [ -d "${package_path}" ]; then
      continue
    fi
    PKGBUILD="$(
      ls "${package_path}/repos/${repository}-"*"/PKGBUILD" 2> /dev/null | \
        tr ' ' '\n' | \
        grep -v -- '-i686/PKGBUILD$' | \
        grep -v -- '[-/]\(staging\|testing\)-[^/]\+/PKGBUILD$' | \
        sort | \
        tail -n1
    )"
    if [ -n "${PKGBUILD}" ]; then
      echo "${PKGBUILD}"
      break
    fi
  done
}

# apply customizations to a package
#  (to be executed in the package's directory)

apply_package_customizations() {
  if [ ! -f 'PKGBUILD' ]; then
    >&2 echo 'PKGBUILD not found.'
    pwd
    exit 1
  fi

  local repo
  local package

  repo="$(pwd)"
  package="${repo%/*/*}"
  package="${package##*/}"
  repo="${repo##*/}"
  repo="${repo%-any}"
  repo="${repo%-x86_64}"

  if [ ! -f 'PKGBUILD.changes-applied' ]; then
    # add i686 to the arch list
    sed '/^arch=[^#]*any/!s|^\(arch=(\)\([^#]*)\)\s*\(#.*\)\?$|\1'"'i686'"' \2|' -i 'PKGBUILD'
    if [ -f "${repo_paths__archlinux32}/${repo}/${package}/PKGBUILD" ]; then
      # If this package has modifications (or is new), apply them now:
      # append PKGBUILD
      cat "${repo_paths__archlinux32}/${repo}/${package}/PKGBUILD" >> \
        'PKGBUILD'
      # copy (and overwrite) other files
      for file in "${repo_paths__archlinux32}/${repo}/${package}/"*; do
        if [ -f "${file}" ] && [ "${file##*/}" != 'PKGBUILD' ]; then
          cp "${file}" ./
        fi
      done
    fi
    touch 'PKGBUILD.changes-applied'
  fi
}

# find_repository_with_commit commit
# find the repository which has $commit

find_repository_with_commit() {

  local repository

  for repository in ${repo_names}; do
    if [ "$(eval git -C "$(printf '$repo_paths__%s' "${repository}")" cat-file -t "$1" 2> /dev/null)" = "commit" ]; then
      echo "${repository}"
      return 0
    fi
  done
  >&2 printf 'find_repository_with_commit: Cannot find repository with commit "%s"\n' "$1"
  exit 1

}

# find_git_repository_to_package_repository repository
# find the git repository which tracks the package repository $repository

find_git_repository_to_package_repository() {

  local repository

  for repository in ${repo_names}; do
    if [ "${repository}" = "archlinux32" ]; then
      continue
    fi
    if [ -n "$(
      (
        eval ls "$(printf '$repo_paths__%s' "${repository}")/"*"/repos" | \
          grep -v ':$' | \
          sed 's|-[^-]\+$||' | \
          sort -u
        echo "$1"
      ) | \
        sort | \
        uniq -d
      )" ]; then
      echo "${repository}"
      return 0
    fi
  done
  >&2 echo "can't find git repository with package repository '$1'"
  exit 1

}

# package_locked_or_blocked package git_revision mod_git_revision repository
# return if package - of given repository and revisions - is [locked or blocked]

package_locked_or_blocked() {
  [ -f "${work_dir}/package-states/$1.$2.$3.$4.locked" ] || \
    [ -f "${work_dir}/package-states/$1.$2.$3.$4.blocked" ]
}

# generate_package_metadata $package $git_revision $mod_git_revision $repository
# or
# generate_package_metadata $package.$git_revision.$mod_git_revision.$repository
# generate the meta data files of a package (dependencies, built packages, ...)

generate_package_metadata() {

  local package="$1"
  local git_revision="$2"
  local mod_git_revision="$3"
  local repository="$4"
  local file_prefix
  local file
  local PKGBUILD

  if [ $# -eq 1 ]; then
    # second form
    repository="${package##*.}"
    package="${package%.*}"
    mod_git_revision="${package##*.}"
    package="${package%.*}"
    git_revision="${package##*.}"
    package="${package%.*}"
  fi

  file_prefix="${work_dir}/package-infos/${package}.${git_revision}.${mod_git_revision}"

  if [ -e "${file_prefix}.builds" ] && \
    [ -e "${file_prefix}.depends" ] && \
    [ -e "${file_prefix}.needs" ] && \
    [ -e "${file_prefix}.packages" ]; then
    return 0
  fi

  if ! make_source_info "${package}" "${repository}" "${git_revision}" "${mod_git_revision}" "${file_prefix}.SRCINFO"; then
    echo "make_source_info failed."
    exit 1
  fi

  # otherwise this just calls for trouble
  sed -i '/=\s*$/d' "${file_prefix}.SRCINFO"

  # extract "builds" = provides \cup pkgname
  grep "$(printf '^\\(\tprovides\\|pkgname\\) = ')" "${file_prefix}.SRCINFO" | \
    cut -d= -f2 | \
    sed 's|^\s\+||; s|[<>]$||' | \
    sort -u > \
    "${file_prefix}.builds"

  # extract "packages" = pkgname
  grep '^pkgname = ' "${file_prefix}.SRCINFO" | \
    cut -d= -f2 | \
    sed 's|^\s\+||; s|[<>]$||' | \
    sort -u > \
    "${file_prefix}.packages"

  # extract "needs" = depends \setminus "builds"
  (
    grep "$(printf '^\tdepends = ')" "${file_prefix}.SRCINFO" | \
      cut -d= -f2 | \
      sed 's|^\s\+||; s|[<>]$||' | \
      sort -u
    sed 'p' "${file_prefix}.builds"
  ) | \
    sort | \
    uniq -u > \
    "${file_prefix}.needs"

  # extract "depends" = makedepends \cup checkdepends \cup depends
  (
    sed -n "$(printf '/^pkgname = /q;/^\tdepends = /p')" "${file_prefix}.SRCINFO"
    grep "$(printf '^\t\\(makedepends\\|checkdepends\\) = ')" "${file_prefix}.SRCINFO"
  ) | \
    cut -d= -f2 | \
    sed 's|^\s\+||; s|[<>]$||' | \
    sort -u > \
    "${file_prefix}.depends"

  rm "${file_prefix}.SRCINFO"

}

# delete_old_metadata
# delete old (=unneeded) meta data of packages

delete_old_metadata() {

  (
    ls -1 "${work_dir}/package-infos" | \
      sed '
        s|\.\([^.]\+\)\.\([^.]\+\)\.[^.]\+$| \1 \2|
      ' | \
      sort -u
    ls -1 "${work_dir}/package-states" | \
      sed '
        s|\.\([^.]\+\)\.\([^.]\+\)\(\.[^.]\+\)\{2\}$| \1 \2|
      ' | \
      sort -u | \
      sed 'p'
    cut -d' ' -f1,2,3 "${work_dir}/build-list" | \
      sed 'p'
  ) | \
    sort | \
    uniq -u | \
    while read -r pkg rev mod_rev; do
      rm -f "${work_dir}/package-infos/${pkg}.${rev}.${mod_rev}".*
    done
}

# repository_of_package $package.$repo_revision.$mod_repo_revision.$repository
# print which (stable) repository a package belongs to

repository_of_package() {
  local package="$1"
  local repository="${package##*.}"
  package="${package%.*}"
  local a32_rev="${package##*.}"
  package="${package%.*.*}"

  case "${repository}" in
    'multilib')
      if git -C "${repo_paths__archlinux32}" archive --format=tar "${a32_rev}" -- 'extra-from-multilib' | \
        tar -Ox | \
        grep -qFx "${package%.*.*.*}"; then
        echo 'extra'
      else
        echo 'community'
      fi
      ;;
    *)
      echo "${repository}"
  esac
}

# official_or_community $package.$repo_revision.$mod_repo_revision.$repository
# print wether the specified package is an official package (print
# nothing) or a community package (print 'community-')

official_or_community() {
  if [ "$(repository_of_package "$1")" = 'community' ]; then
    echo 'community-'
  fi
}

# ls_master_mirror $path
# list content of $path on the master mirror (via rsync)

ls_master_mirror() {

  local path="$1"

  ${master_mirror_command} \
    "${master_mirror_directory}/${path}/" | \
    grep -v '\s\.$' | \
    awk '{print $5}'

}

# remove_old_package_versions $directory $package_file
# removes all other versions of $package_file in $directory on the master mirror

remove_old_package_versions() {

  local directory="$1"
  local package="$2"

  local pkgname="${package%-*-*-*.pkg.tar.xz}"

  ${master_mirror_command} \
    --recursive \
    --delete \
    $( \
      ls_master_mirror "${directory}" | \
        grep "^$(str_to_regex "${pkgname}")\(-[^-]\+\)\{3\}\.pkg\.tar\.xz\(\.sig\)\?\$" | \
        grep -v "^$(str_to_regex "${package}")\(\.sig\)\?\$" | \
        sed 's|^|--include=|'
    ) \
    '--exclude=*' \
    ./ \
    "${master_mirror_directory}/${directory}/"

}

# wait_some_time $minimum $diff
# wait between minimum and minimum+diff seconds (diff defaults to 30)

wait_some_time() {
  local minimum=$1
  local diff=$2
  local random

  if [ -z "${diff}" ]; then
    diff=30
  fi

  random="$(
    dd if='/dev/urandom' count=1 2> /dev/null | \
      cksum | \
      cut -d' ' -f1
  )"

  sleep $((${minimum} + ${random} % ${diff}))
}

# str_to_regex $string
# escape dots for use in regex

str_to_regex() {
  echo "$1" | \
    sed 's|\.|\\.|g'
}

# make_source_info $package $repository $git_revision $mod_git_revision $output
# create .SRCINFO from PKGBUILD within git repositories, output to $output

make_source_info() {

  local package="$1"
  local repository="$2"
  local git_revision="$3"
  local mod_git_revision="$4"
  local output="$5"

  local git_repo="$(find_repository_with_commit "${git_revision}")"
  local PKGBUILD
  local PKGBUILD_mod
  local content

  if [ -z "${git_repo}" ]; then
    return 1
  fi

  PKGBUILD="$(
    eval git -C "$(printf '$repo_paths__%s' "${git_repo}")" archive "${git_revision}" -- "${package}/repos/" 2> /dev/null | \
      tar -t 2> /dev/null | \
      grep "^$(str_to_regex "${package}/repos/${repository}")"'-.*/PKGBUILD$' | \
      grep -v -- '-i686/PKGBUILD$' | \
      grep -v -- '[-/]\(staging\|testing\)-[^/]\+/PKGBUILD$' | \
      sort | \
      tail -n1
  )"

  PKGBUILD_mod="$(
    git -C "${repo_paths__archlinux32}" archive "${mod_git_revision}" 2> /dev/null | \
      tar -t "${repository}/${package}/PKGBUILD" 2> /dev/null
  )" || true

  if [ -z "${PKGBUILD}" ] && \
    [ -z "${PKGBUILD_mod}" ]; then
    >&2 printf 'Neither PKGBUILD nor modification of PKGBUILD found for package %s from %s, revisions %s and %s.\n' \
      "${package}" \
      "${repository}" \
      "${git_revision}" \
      "${mod_git_revision}"
    return 1
  fi

  if [ -n "${PKGBUILD}" ]; then
    content="$(
      eval git -C "$(printf '$repo_paths__%s' "${git_repo}")" archive "${git_revision}" -- "${PKGBUILD}" | \
        tar -Ox
    )"
  else
    unset content
  fi

  if [ -n "${PKGBUILD_mod}" ]; then
    content="$(
      printf '%s\n' \
        "${content}" \
        "$(
          git -C "${repo_paths__archlinux32}" archive "${mod_git_revision}" -- "${PKGBUILD_mod}" | \
            tar -Ox
        )"
    )"
  fi

  bail_out() {
    err=$?
    recursively_umount_and_rm "${overlays_dir}"
    exit ${err}
  }

  overlays_dir="$(mktemp -d)"

  sudo mount -t tmpfs none "${overlays_dir}" || \
    bail_out

  mkdir \
    "${overlays_dir}/lower" \
    "${overlays_dir}/upper" \
    "${overlays_dir}/work" \
    "${overlays_dir}/merged"

  echo "${content}" | \
    sed '/^\$Id\$$/d' > \
    "${overlays_dir}/lower/PKGBUILD"

  mkdir "${overlays_dir}/lower/etc"
  hexdump -n 16 -e '4/4 "%08x" 1 "\n"' /dev/urandom > \
    "${overlays_dir}/lower/etc/machine-id"

  sudo mount -t overlay overlay -o lowerdir="${overlays_dir}/lower":"/",upperdir="${overlays_dir}/upper",workdir="${overlays_dir}/work" "${overlays_dir}/merged" || \
    bail_out

  sudo systemd-nspawn -q \
    -D "${overlays_dir}/merged" \
    --register=no \
    ${base_dir}/bin/mksrcinfo || \
    bail_out

  sudo umount -l "${overlays_dir}/merged" || \
    bail_out

  mv \
    "${overlays_dir}/upper/.SRCINFO" \
    "${output}"

  sudo umount -l "${overlays_dir}" || \
    bail_out
  rmdir "${overlays_dir}"

}

# recursively_umount_and_rm $dir
# umount all mountpoints in $dir which are also in $dir's
#  filesystem, possibly also $dir itself and then
#  rm -rf --one-file-system $dir

recursively_umount_and_rm() {
  local dir="$1"

  if [ -z "${dir}" ]; then
    >&2 echo 'ERROR: recursively_umount_and_rm requires an argument'
    exit 42
  fi

  find "${dir}" \
    -xdev -depth -type d \
    -exec 'mountpoint' '-q' '{}' ';' \
    -exec 'sudo' 'umount' '-l' '{}' ';'
  rm -rf --one-file-system "${dir}"
}