#!/usr/bin/env bash
# -*- coding: utf-8 -*-

# msidiff - compare two MSI files table content with diff
# (originally based on rpmdev-diff)
#
# Copyright (c) 2004-2010 Ville Skyttä <ville.skytta@iki.fi>
# Copyright (c) 2013 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

set -e

unset CDPATH
tmpdir=
diffopts=
list=
long=
tables=
diffcopts=-Nup
diffoopts=-U0

trap cleanup EXIT
cleanup()
{
    set +e
    [ -z "$tmpdir" -o ! -d "$tmpdir" ] || rm -rf "$tmpdir"
}

version()
{
    cat <<EOF
"UNKNOWN"
EOF
}

help()
{
    cat <<EOF
msidiff diffs contents of two MSI files.
EOF
    usage
    echo ""
    echo "Report bugs to <"https://gitlab.gnome.org/GNOME/msitools/issues">."
}

usage()
{
    cat <<EOF
Usage: msidiff [OPTION]... [DIFF-OPTIONS] FROM-MSI TO-MSI

Options:
  -t, --tables     Diff MSI tables as text.  This is the default.
  -l, --list       Diff lists of files.
  -L, --long-list  Diff long lists (akin to 'find -ls') of files.
  -h, --help       Print help message and exit.
  -v, --version    Print version information and exit.
  diff-options     Options passed to diff(1).  The first repeated argument of
                   the above or the first argument starting with a '-' but not
                   one of the above starts diff-options, the first one not
                   starting with it ends them.  Default: $diffcopts for contents
                   (in addition to -r which will always be passed), -U0 for
                   others.

More than one of -t, -l or -L may be specified.
EOF
}

while true ; do
    case $1 in
        -t|--tables)
            if [[ $tables$diffopts ]] ; then
                diffopts+=" $1"
            else
                tables=true
            fi
            ;;
        -l|--list)
            if [[ $list$diffopts ]] ; then
                diffopts+=" $1"
            else
                list=true
            fi
            ;;
        -L|--long-list)
            if [[ $long$diffopts ]] ; then
                diffopts+=" $1"
            else
                long=true
            fi
            ;;
        -h|--help)
            if [[ $diffopts ]] ; then
                diffopts+=" $1"
            else
                help
                exit 0
            fi
            ;;
        -v|--version)
            if [[ $diffopts ]] ; then
                diffopts+=" $1"
            else
                version
                exit 0
            fi
            ;;
        -*)
            diffopts+=" $1"
            ;;
        *)
            break
            ;;
    esac
    shift
done
if [[ $# -lt 2 ]] ; then
    usage
    exit 1
fi
for file in "$1" "$2" ; do
    if [[ ! -f $file ]] ; then
        [[ -e $file ]] && \
            echo "Error: not a regular file: '$file'" >&2 ||
            echo "Error: file does not exist: '$file'" >&2
        exit 1
    fi
done

tmpdir=`mktemp -d ${TMPDIR:-/tmp}/msidiff.XXXXXX`

mkdir "$tmpdir/old" "$tmpdir/new"
msidump --signature --tables --directory "$tmpdir/old" $1 >/dev/null
msidump --signature --tables --directory "$tmpdir/new" $2 >/dev/null
if ${list:-false} || ${long:-false} ; then
  msiextract --directory "$tmpdir/old/files" $1 >/dev/null
  msiextract --directory "$tmpdir/new/files" $2 >/dev/null
fi

cd "$tmpdir"

# Did the archives uncompress into base dirs?
if [[ $(ls -1d old/* | wc -l) -eq 1 ]] ; then
  old=$(ls -1d old/*)
else
  old=old
fi
if [[ $(ls -1d new/* | wc -l) -eq 1 ]] ; then
  new=$(ls -1d new/*)
else
  new=new
fi

# Fixup base dirs to the same level.
if [[ $(basename "$old") != $(basename "$new") ]] ; then
  if [[ $old != old ]] ; then
    mv "$old" .
    old=`basename "$old"`
  fi
  if [[ $new != new ]] ; then
    mv "$new" .
    new=`basename "$new"`
  fi
fi

# Tables mode is the default.
if [[ -z $list$tables$long ]] ; then
    tables=true
else
    tables=${tables:-false}
fi
list=${list:-false}
long=${long:-false}

# Here we go.

if $tables ; then
    set +e
    diff -r ${diffopts:-$diffcopts} "$old" "$new"
    [[ $? -eq 0 || $? -eq 1 ]] || exit $?
    set -e
fi


if $list ; then
    find "$old/files" | sort | cut -d/ -f 3- -s > "$old.files"
    find "$new/files" | sort | cut -d/ -f 3- -s > "$new.files"
    set +e
    diff ${diffopts:-$diffoopts} "$old.files" "$new.files"
    [[ $? -eq 0 || $? -eq 1 ]] || exit $?
    set -e
fi

if $long ; then
    find "$old/files" -ls | \
        perl -pe "s|^(?:[\d\s]*)(\S+)(?:\s+\d+)(.+)$|\1\2| ;
                  s|.*\s\Q$old/files\E$|| ; s|(\s)\Q$old/files/\E|\1|" | \
        sort > "$old.files"
    find "$new/files" -ls | \
        perl -pe "s|^(?:[\d\s]*)(\S+)(?:\s+\d+)(.+)$|\1\2| ;
                  s|.*\s\Q$new/files\E$|| ; s|(\s)\Q$new/files/\E|\1|" | \
        sort > "$new.files"
    set +e
    diff ${diffopts:-$diffoopts} "$old.files" "$new.files"
    [[ $? -eq 0 || $? -eq 1 ]] || exit $?
    set -e
fi
