################################################################################
#
# pkg_upload.cygpart - cygport package uploading functions
#
# Copyright (C) 2014-2020 Cygport authors
# Provided by the Cygwin project <https://cygwin.com/>
#
# cygport 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 3 of the License, or
# (at your option) any later version.
#
# cygport 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 cygport.  If not, see <https://www.gnu.org/licenses/>.
#
################################################################################

#****** Chapter 13/Uploading
#  INTRODUCTION
#  Once your binary and source packages are built, in order to distribute them
#  to users you'll need to upload them to the Cygwin package archive.  Before
#  you can upload packages, you'll need to do the following things:
#  * Request upload privileges, and provide your ssh public key.  The
#    |html <a href="https://cygwin.com/package-upload.html">Uploading Packages</a>
#    page tells how to do that, and how to be notified in case of problems with
#    your uploads.
#  * After your key is approved, connect once to cygwin.com by
#    |html <tt>sftp cygwin@cygwin.com</tt>.
#    Verify that the host key fingerprint for cygwin.com matches one of the ones posted on the
#    |html <a href="https://cygwin.com/packaging/key.html">Providing an SSH key</a>
#    page. If it does, answer "yes" to continue connecting.  Then exit sftp.
#    This will store the host key fingerprint in your ~/.known_hosts file.
#    You have to do this once before uploading, or the upload will fail.
#  * Be listed on the
#    |html <a href="https://cygwin.com/cygwin-pkg-maint">Cygwin package maintainers list</a>
#    as the maintainer of your packages.  Ask on the cygwin-apps mailing list if
#    you need help getting listed.
#
#  Once those things are done, running
#  |html <tt>cygport upload</tt> (or <tt>cygport up</tt> for short)
#  will upload your finished packages to the package archive on cygwin.com.
#  After confirming that your package was successfully uploaded and merged
#  into the distribution, you should send an announcement email to
#  |html <tt>&lt;cygwin-announce AT cygwin.com&gt;</tt>
#  with information about your package and the changes therein.  The
#  |html <tt>cygport announce</tt> command will create a template announcement,
#  to which you can add release-specific details, and then send the email
#  if an SMTP server is configured.
#  SEE ALSO
#  SSH_KEY, EDITOR, SMTP_SENDER, SMTP_SERVER, SMTP_SERVER_PORT, SMTP_ENCRYPTION,
#  SMTP_USER, SMTP_PASS.
#****

__pkg_upload() {
	local archdir
	local connect_string
	local distsubdir
	local file
	local key_fingerprint
	local -i n=0
	local -a pkg_files
	local ready=true

	if defined _CYGPORT_RESTRICT_upload_
	then
		error "Package ${NAME} may not be uploaded"
	fi

	case $1 in
	full) ;;
	stage) ready= ;;
	*) error "Internal syntax error" ;;
	esac

	cd "${distdir}"

	# Check that binary and source package files exist

	pkg_files=("${NAME}/${NAME}-${PVR}-src.tar.${TAR_COMPRESSION_EXT}" "${NAME}/${NAME}-${PVR}-src.hint")

	while (( n < pkg_count ))
	do
		case ${pkg_name[${n}]} in
		${PN})  distsubdir= ;;
		*)      distsubdir=${pkg_name[${n}]} ;;
		esac

		pkg_files+=("${NAME}/${distsubdir}/${pkg_name[${n}]}-${PVR}-${ARCH}.tar.${TAR_COMPRESSION_EXT}" "${NAME}/${distsubdir}/${pkg_name[${n}]}-${PVR}-${ARCH}.hint")
		n+=1
	done

	if [ -d ${NAME}/${NAME}-debuginfo ]
	then
		pkg_files+=("${NAME}/${NAME}-debuginfo/${NAME}-debuginfo-${PVR}-${ARCH}.tar.${TAR_COMPRESSION_EXT}" "${NAME}/${NAME}-debuginfo/${NAME}-debuginfo-${PVR}-${ARCH}.hint")
	fi

	for file in "${pkg_files[@]}"
	do
		if [ ! -f "${file}" ]
		then
			error "Package file ${file} doesn't exist. Run \"cygport ${cygportfile} pkg\" to build it."
		fi
	done

	if ! defined SSH_KEY
	then
		# SSH key isn't known.
		# So let lftp prompt for a passphrase every time if needed.
		connect_string='sftp://cygwin@cygwin.com'
	else
		# SSH key is known, so load it into an ssh-agent.
		# Password prompting if any will be done when the key is loaded
		# into the ssh-agent, so tell lftp not to prompt.
		connect_string='sftp://cygwin:@cygwin.com'

		if ssh-add -l >/dev/null 2>/dev/null
		then
			# ssh-agent is already running. Get key fingerprint:
			key_fingerprint=$(ssh-keygen -l -f "$SSH_KEY" | grep -E -o '[0-9a-f]{2}(:[0-9a-f]{2}){15}|SHA256:[A-Za-z0-9+/=]{43}') \
			|| error "Can't read key fingerprint of ${SSH_KEY}. Not a private key file, or corresponding public key file is missing?"

			# Load key into ssh-agent, if it's not already loaded (prompts for passphrase):
			if ! ssh-add -l | grep -q "${key_fingerprint}"
			then
				ssh-add "${SSH_KEY}" 2>/dev/null \
				|| error "Couldn't load private key ${SSH_KEY}."
				# Unload key on exit:
				trap "ssh-add -d ${SSH_KEY} 2>/dev/null" EXIT
			fi
		else
			# ssh-agent isn't running. Start one:
			eval $(ssh-agent -s) >/dev/null || error "Couldn't start ssh-agent."
			# Kill it again on exit:
			trap 'eval $(ssh-agent -k) >/dev/null' EXIT

			# Load the private key into it (prompts for passphrase):
			ssh-add  "${SSH_KEY}" 2>/dev/null \
			|| error "Couldn't load private key ${SSH_KEY}."
		fi
	fi

	case ${ARCH} in
	i686)	archdir=x86 ;;
	x86_64)	archdir=x86_64 ;;
	noarch) archdir=noarch ;;
	*)  error "Don't know where to upload packages with architecture ${ARCH}" ;;
	esac

	__step "Running lftp ${connect_string}"

	lftp -f <(cat <<-EOF
		set cmd:fail-exit on
		set cmd:interactive on
		set net:max-retries 1
		open ${connect_string}
		cd /${archdir}/release
		rm -f ${NAME}/!ready || echo -n
		mirror -v -eR ${NAME}
		${ready:+put /dev/null -o ${NAME}/!ready}
EOF
	) || error "Upload failed"

	echo "Upload complete."
}

__pkg_announce() {
	local msg=$(mktemp -t cygwin-announce-${PF}.XXXXXX)
	local msgat=$(date +@%s)
	local -i n=0
	local s_have="s have"

	[ $pkg_count != 1 ] || s_have=" has"

	cat > ${msg} <<_EOF
From cygwin-announce-${PF} $(LANG=C date '+%a %b %d %H:%M:%S %Y' --date=${msgat})
From: ${SMTP_SENDER}
To: cygwin-announce@cygwin.com
Date: $(date -R --date=${msgat})
Message-Id: <$(date "+%Y%m%d%H%M%S.$$" --date=${msgat})-1-$(echo ${SMTP_SENDER} | sed 's|.*<\(.*\)>.*|\1|')>
Subject: ${NAME} ${PVR}

The following package${s_have} been uploaded to the Cygwin distribution:

_EOF

	while (( n < pkg_count ))
	do
		echo "* ${pkg_name[$n]}-${PVR}" >> ${msg}
		let n+=1
	done

	cat >> ${msg} <<_EOF

${DESCRIPTION}
_EOF

	${EDITOR:-vi} $msg || error "Editor exited abormally, aborting annoucement"

	perl <(cat <<EOF
use strict;
use MIME::Parser;
use Net::SMTP;

my \$smtp_server = "${SMTP_SERVER:-localhost}";
my \$smtp_encryption = "${SMTP_ENCRYPTION:-none}";
my \$smtp_user${SMTP_USER+ = '$SMTP_USER'};
my \$smtp_pass${SMTP_PASS+ = '$SMTP_PASS'};

my \$parser = new MIME::Parser;
\$parser->output_to_core(1);
my \$entity = \$parser->parse_open("$msg");

print "Sending announcement of ${NAME}-${PVR} via \$smtp_server\n";

my \$smtp = new Net::SMTP(\$smtp_server,
			  ${SMTP_SERVER_PORT+Port => ${SMTP_SERVER_PORT},}
			  SSL => \$smtp_encryption eq 'ssl')
	 or die "No mailserver at ".\$smtp_server;
if (\$smtp_encryption eq 'tls') {
	require Net::SMTP::SSL;
	\$smtp->command('STARTTLS');
	\$smtp->response();
	\$smtp->code == 220 or die "\$smtp_server does not support STARTTLS";
	\$smtp = Net::SMTP::SSL->start_SSL(\$smtp) or die "STARTTLS failed";
	\$smtp->hello(\$smtp_server);
}
if (defined \$smtp_user) {
	use Authen::SASL qw(Perl);
	\$smtp->auth(\$smtp_user, \$smtp_pass)
		or die "unable to set username and password";
}
\$smtp->mail(\$entity->head->get('From')) or die "unable to set sender";
\$smtp->to(\$entity->head->get('To')) or die "unable to address message";
\$smtp->data() or die "unable to start data send";
\$smtp->datasend(\$entity->as_string()) or die "Message send failed";
\$smtp->dataend() or die "Message end failed";
\$smtp->quit();
print "Announcement sent\n";
EOF
) && rm $msg || error "Sending announcement failed, mbox is $msg"
}

# protect functions
readonly -f __pkg_upload __pkg_announce
