#!/bin/bash
# a7crypt v1.0.0 last mod 2012/01/09
# Latest version at <http://github.com/ryran/a7crypt>
# Copyright 2011, 2012 Ryan Sawhill <ryan@b19.org>
#
#    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 3 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 <gnu.org/licenses/gpl.html> for more details.
#-------------------------------------------------------------------------------
# System requirements: zenity and one of either gpg, gpg2, or openssl.
#
# The original goal of this project was to make symmetric text {en,de}cryption
# more accessible and easy to use. While GPG rocks (for both symmetric &
# public-key) if you're comfortable on the commandline, and there are GUI
# encryption options for key-based, there's not much out there for people who
# need to do the simplest kind of encryption -- with a shared passphrase. 
#  
# First I developed a super-simple wrapper for the commandline. (To see an
# evolution of that, check out a3crypt at <github.com/ryran/a7crypt>, which
# doesn't have any of the bells and whistles of a7crypt.) Once that was
# complete, I decided it was time to the fill the hole of a GUI for symmetric
# encryption, and began fleshing it out and adding features, quickly adding the
#  ability to pick files (and have the script automatically choose ASCII or
# binary output type based on the chosen file).
#
# It almost goes without saying, but from the beginning, a requirement of all
# this was that security not be in any way sacrificed for the convenience.
#
# To that end: No processes are run with arguments of a passphrase, nor are
# passphrases or message data stored on disk; the script creates a working dir
# in ram that is only readable by the current user and whenever message data or
# passphrases need to be stored, they are stored there and deleted as soon as
# they have served their purpose. (Note: We WILL fall-back to creating a
# working dir on disk in users' HOME if /dev/shm is unavailable for some crazy
# reason.)
#
# We of course want to ensure that all tmpfiles are cleaned up on exit, even
# though they're hidden away in directories only available to the user. A simple
# trap will cover exiting due to receiving a sigterm or sigint, but for sigkill,
# we need something more. So each time the script runs it creates lockfiles with
# its PID and the location of the temp working directory. That way, if an old
# instance of the script ever dies without cleaning up its potentially-sensitive
# data, we can take out the trash on the next run.
#
# And a final note: When lockfiles & temp directories are created by this
# script, they're named based on the script's current filename; not some static
# project name. This means that you can feel free to rename the script.
# Everything else will go along with the new name--you don't need to change
# anything in the content. Caveat: Don't be stupid & put spaces in the name. :)
#
# Feel free to hit me/the tracker up if you have any questions or suggestions!
#
# PS: If you like this you should check out a8crypt -- a gui frontend with more
# features implemented in Python & GTK. <http://github.com/ryran/a8crypt>
#-------------------------------------------------------------------------------

## If the filename of our script is /usr/local/bin/a7crypt, then this variable would end up being "a7crypt", which we will use throughout
zero=${0##*/} ; [[ -z ${zero##* *} ]] && zero=a7crypt


#===============================================================================
#-------------------------------------------------- 1) BEGIN SANITY CHECKS ---->

## Print help message if no X or if run with any arguments
if [[ -z $DISPLAY || $# -ne 0 ]]; then
  echo -e "$zero provides symmetric encryption/decryption using GPG, GPG2, or OpenSSL\nIt is meant to be run with no arguments from a GUI desktop"
  exit

## Ensure we have zenity
elif ! command -v zenity >/dev/null; then
  tty --silent || notify-send --urgency=critical "$zero Critical Error" "You tried to execute $zero but it relies on Zenity (a small helper-application you don't have installed) to produce GUI menus and dialog boxes. You can install 'zenity' using your add/remove software management application."
  echo -e "This program relies on Zenity (a small helper-application you don't have\ninstalled) to produce GUI menus and dialog boxes. You can install 'zenity' using\nyour add/remove software management application.\nCtrl-c to quit."
  sleep 20
  exit 255

## Need writable home
elif [[ ! -w $HOME ]]; then
  zenity --error --title="$zero: CRITICAL ERROR" --text="You are running $zero as $USER and your home directory ($HOME) is not writable. Cannot continue."
  exit 250
  
## Do we have gpg2?
elif command -v gpg2 >/dev/null; then
  mode=GPG2

## Next up, check for gpg
elif command -v gpg >/dev/null; then
  mode=GPG

## If no gpg funness, use openssl
elif command -v openssl >/dev/null; then
  mode=OpenSSL-AES

## On the off-chance that none of those three are on the system...
else
  zenity --error --title="You pulling my leg?" --text='Neither GPG, GPG2, nor OpenSSL were found in your $PATH..? Right. If you say so.'
  exit 250
fi


#===============================================================================
#------------------------------------------------ 2) SETUP OUR ENVIRONMENT ---->

## Uber-restrictive umask, because we can
umask 077

## Create settings dotdir in ~ if not already there
dotdir=$HOME/.$zero ; [[ ! -d $dotdir ]] && mkdir $dotdir

## Setup a private working directory in ram or in ~
if [[ -w /dev/shm/ ]]; then
  wd=$(mktemp -d /dev/shm/.$zero.$UID.XXX)
else
  wd=$(mktemp -d $dotdir/wd.XXX)
fi

## If there are existing lockfiles not associated with a running process, rm them and their stale working dirs
for lockfile in $dotdir/lock-*; do
  [[ $(</proc/${lockfile##*-}/cmdline) != /bin/bash$0 ]] && rm -rv $(readlink $lockfile) $lockfile
done 2>/dev/null

## Create new lockfile, e.g. ~/.a7crypt/lock-PID, which is a link to the working dir
lockfile=$dotdir/lock-$$ ; ln -s $wd $lockfile

## Set up trap to clean-up working dir & kill any child processes (zenity) on exit
trap "{ pkill -P $$; rm -r $wd $lockfile 2>/dev/null; }" EXIT

## Set filemanager for optionally looking at files
for filemgr in nautilus dolphin konqueror thunar pcmanfm firefox; do
  command -v $filemgr >/dev/null && break
done

## Set viewer for optionally looking at encrypted/decrypted text
for viewer in gedit gvim kedit; do
  command -v $viewer >/dev/null && break
done


#===============================================================================
#-------------------------------------------------- 3) ENUMERATE FUNCTIONS ---->

## Functions to initialize the variables for each mode (see beginning of MAIN function for $gpg enumeration)
init_env_GPG() {
  switch_to=OpenSSL
  encrypt="$gpg --batch --no-tty --yes --passphrase-file $wd/pass -o $wd/output -c --force-mdc --cipher-algo aes256"
  decrypt="$gpg --batch --no-tty --yes --passphrase-file $wd/pass -o $wd/output -d"
}
init_env_OPENSSL() {
  switch_to=GPG
  encrypt="openssl aes-256-cbc -pass file:$wd/pass -out $wd/output -salt"
  decrypt="openssl aes-256-cbc -pass file:$wd/pass -out $wd/output -d"
}


#---------------------------------------------- 3.1) FUNCTION: MAIN THREAD ---->
# MAIN manages menu & dialogs, encryption, decryption, & loading of UPDATE func.

MAIN() {

## Ensure buffers are clear
>$wd/pass; rm $wd/input $wd/output 2>/dev/null

## (re-)Initialize our variables, depending on which mode we're in
if [[ $mode = GPG2 ]]; then
  gpg="gpg2"
  init_env_GPG
elif [[ $mode = GPG ]]; then
  gpg="gpg --no-use-agent"
  init_env_GPG
elif [[ $mode = OpenSSL-AES ]]; then
  init_env_OPENSSL
fi

## Set main menu content for 'zenity --list'
menu_head_enc="[----- ENCRYPTION -----]"
menu_enc_text="  • Type or paste text"
menu_enc_file="  • Select file"
menu_head_dec="[----- DECRYPTION -----]"
menu_dec_text="  • Paste text"
menu_dec_file="  • Select file " ## Trailing space to make different than $menu_enc_file
menu_blanksep="  --------------------------"
menu_switchto="Switch to $switch_to-mode"
menu_ckupdate="Check online for update"

## Display main menu and grab the user's choice
menu_choice=$(zenity --list --hide-header --title="$zero [$mode]" --text="What would you like to do?" --column=1 "$menu_head_enc" "$menu_enc_text" "$menu_enc_file" "$menu_head_dec" "$menu_dec_text" "$menu_dec_file" "$menu_blanksep" "$menu_switchto" "$menu_ckupdate" --cancel-label="Quit" --height=340) || exit


#------------------------------------------------
## BEGIN PARSING MENU CHOICES

## Reload menu if user picks one of the header lines
if [[ $menu_choice = $menu_head_enc || $menu_choice = $menu_head_dec || $menu_choice = $menu_blanksep ]]; then
  MAIN

## Run our UPDATE function if they choose updates
elif [[ $menu_choice = $menu_ckupdate ]]; then
  UPDATE

#------------------------------------------------
## 3.1.1) ENCRYPT: TYPE/PASTE TEXT
elif [[ $menu_choice = $menu_enc_text ]]; then

  ## Warn user about openssl obscurity
  if [[ $mode = OpenSSL-AES && ! -f $dotdir/nowarn.openssl-txt ]]; then
    zenity --info --title="$zero [$mode]: NOTICE" --text="OpenSSL text-input mode uses <tt>'openssl aes-256-cbc -salt -a'</tt> to encrypt text to ASCII-armored (plain-text) output. OpenSSL doesn't add any pretty envelope info to its encrypted data like GPG does, meaning there are no GUI applications (AFAIK) that will know what to do with files containing said data.\n\n<span foreground='red' weight='bold'>In short, to decrypt data encrypted with this mode, you can either:</span>\n1) use this app\n2) use OpenSSL directly on a terminal, i.e., drop the data into a file of <i><tt>SOMENAME</tt></i> and run\n\t<tt>openssl aes-256-cbc -a -d -in <i>SOMENAME</i></tt>\n\n<b>[Press Enter or Esc if you can't see OK button.]</b>"
    touch $dotdir/nowarn.openssl-txt
  fi

  ## Encrypt command for openssl needs -a (ASCII) and this doesn't hurt gpg
  encrypt="$encrypt -a"

  ## Ask for text message to be encrypted
  zenity --text-info --editable --title="$zero [$mode]: Type or paste text to be encrypted" --width=800 --height=600 --no-wrap >$wd/input || MAIN

  ## Keep prompting until user actually enters some data
  until [[ -s $wd/input ]]; do
    zenity --text-info --editable --title="$zero [$mode]: You must enter text to be encrypted" --width=800 --height=600 --no-wrap >$wd/input || MAIN
  done

  ## Ask for encryption passphrase
  ## TODO: I designed this to be fast and simple; I don't like the idea of having to enter a passphrase twice, but I also don't like the idea of accidentally mistyping a long passphrase. That's why I decided not to use --hide-text to hide the passphrase when doing encryption. Need to get feedback from users on this choice though.
  zenity --entry --title="$zero [$mode]" --text="Enter passphrase very carefully" --ok-label="Encrypt" >$wd/pass || MAIN

  ## Keep prompting until user enters a passphrase (zenity --entry inserts a newline even if no input so we can't use "test -s" like we can with --text-info)
  until [[ $(wc -c <$wd/pass) -gt 1 ]]; do
    zenity --entry --title="$zero [$mode]" --text="You must enter a passphrase" --ok-label="Encrypt" >$wd/pass || MAIN
  done

  ## Start a progress bar dialog for those really big jobs or slow machines (this gives the user feedback and a chance to cancel)
  coproc zenity --progress --pulsate --title="$mode: Please wait" --text="Encrypting text..." --auto-kill --auto-close; echo 1 >&${COPROC[1]}

  ## Run our encrytion; if failed, save & report back errors, cleanup, and go to main menu
  if ! $encrypt <$wd/input 2>$wd/errlog; then
    >$wd/pass; >$wd/input ## Clear passphrase & input buffers ASAP
    echo 100 >&${COPROC[1]} ## Set our progress dialog to 100% so it closes
    zenity --error --title="$zero [$mode]: ENCRYPTION ERROR!" --text="$(<$wd/errlog)" --no-wrap
    MAIN
  fi

  ## If encryption succeeded... 
  >$wd/pass; >$wd/input ## Clear passphrase & input buffers ASAP
  echo 100 >&${COPROC[1]} ## Set our progress dialog to 100% so it closes

  ## Display encrypted data back to user, loop back to main menu if they don't hit quit
  zenity --text-info --title="$zero [$mode]: Your encrypted text" --width=650 --height=400 --no-wrap --cancel-label="Show in txt editor" --ok-label="Menu" <$wd/output || { $viewer $wd/output; sleep 3; }
  MAIN


#------------------------------------------------
## 3.1.2) ENCRYPT: SELECT FILE
elif [[ $menu_choice = $menu_enc_file ]]; then

  ## Warn user about openssl obscurity
  if [[ $mode = OpenSSL-AES && ! -f $dotdir/nowarn.openssl-file ]]; then
    zenity --info --title="$zero [$mode]: NOTICE" --text="OpenSSL file-input mode uses <tt>'openssl aes-256-cbc -salt'</tt> to encrypt non-text files to binary output and the same plus an option of <tt>'-a'</tt> to encrypt text files to ASCII-armored (plain-text) output. (Output files get the same name with an extension of <tt>'aes256'</tt> appended.) OpenSSL doesn't add any pretty envelope info to its encrypted data like GPG does, meaning there are no GUI applications (AFAIK) that will know what to do with files containing said data.\n\n<span foreground='red' weight='bold'>In short, to decrypt files encrypted with this mode, you can either:</span>\n1) use this app\n2) use OpenSSL directly on a terminal, e.g.,\n<tt>  openssl aes-256-cbc -a -d -in <i>FILE.txt.aes256</i></tt>\n\t(to decrypt the ASCII-files)\n<tt>  openssl aes-256-cbc -d -in <i>FILE.zip.aes256</i></tt>\n\t(to decrypt the binary files)\n\n<b>[Press Enter or Esc if you can't see OK button.]</b>"
    touch $dotdir/nowarn.openssl-file
  fi

  ## Get file to encrypt via file-selection dialog
  file_to_encrypt=$(zenity --file-selection --title="$zero [$mode]: Select file to be encrypted") || MAIN

  ## Keep prompting until user chooses a proper file
  while [[ ! -s $file_to_encrypt || ! -r $file_to_encrypt ]]; do
    file_to_encrypt=$(zenity --file-selection --title="$zero [$mode]: Could not read file; select another") || MAIN
  done
  
  ## Detect whether input is binary or text and set output mode and output filename accordingly
  file_type=$(file -b -e soft "$file_to_encrypt") ## Get filetype
  if [[ ${file_type%% *} = ASCII ]]; then
    encrypt="$encrypt -a"
    ascii_yesno="ASCII-armored output" ; encrypted_file=$file_to_encrypt.asc
  else
    ascii_yesno="binary output" ; encrypted_file=$file_to_encrypt.gpg
  fi
  [[ $mode = OpenSSL-AES ]] && encrypted_file=$file_to_encrypt.aes256
    
  ## Now that we know what our input and output files are going to be, setup symlinks
  ln -sf "$file_to_encrypt" $wd/input
  ln -sf "$encrypted_file" $wd/output

  ## Ask for encryption passphrase
  zenity --entry --title="$zero [$mode]" --text="Enter passphrase very carefully" --ok-label="Encrypt" >$wd/pass || MAIN

  ## Keep prompting until user enters a passphrase (zenity --entry inserts a newline even if no input so we can't use "test -s" like we can with --text-info)
  until [[ $(wc -c <$wd/pass) -gt 1 ]]; do
    zenity --entry --title="$zero [$mode]" --text="You must enter a passphrase" --ok-label="Encrypt" >$wd/pass || MAIN
  done

  ## Start a progress bar dialog for those really big jobs or slow machines (this gives the user feedback and a chance to cancel)
  coproc zenity --progress --pulsate --title="$mode: Please wait" --text="Encrypting $file_to_encrypt..." --auto-kill --auto-close; echo 1 >&${COPROC[1]}

  ## Run our encrytion; if failed, save & report back errors, cleanup, and go to main menu
  if ! $encrypt <$wd/input 2>/$wd/errlog; then
    >$wd/pass ## Clear the passphrase buffer
    echo 100 >&${COPROC[1]} ## Set our progress dialog to 100% so it closes
    zenity --error --title="$zero [$mode]: ENCRYPTION ERROR!" --text="$(<$wd/errlog)" --no-wrap
    MAIN
  fi

  ## If encryption succeeded... 
  >$wd/pass ## Clear the passphrase buffer
  echo 100 >&${COPROC[1]} ## Set our progress dialog to 100% so it closes

  ## Report final encrypted filename back to user; give them a chance to see the file in their filemanager (hopefully)
  echo -en "Successfully encrypted [ $file_to_encrypt ] with $ascii_yesno. Saved encrypted copy to:\n$encrypted_file" | zenity --text-info --title="$zero [$mode]: Your encrypted file is ready" --width=500 --height=160 --cancel-label="Show file" --ok-label="Menu" || $filemgr "${encrypted_file%/*}"
  MAIN


#------------------------------------------------
## 3.1.3) DECRYPT: PASTE ENCRYPTED ASCII
elif [[ $menu_choice = $menu_dec_text ]]; then

  ## Decrypt command for openssl needs -a (ASCII) and this doesn't hurt gpg
  decrypt="$decrypt -a"

  ## Ask for text message to be decrypted
  zenity --text-info --editable --title="$zero [$mode]: Paste $mode-encrypted message" --width=650 --height=400 --no-wrap >$wd/input || MAIN

  ## Keep prompting until user actually enters some data
  until [[ -s $wd/input ]]; do
    zenity --text-info --editable --title="$zero [$mode]: You must enter an encrypted message" --width=650 --height=400 --no-wrap >$wd/input || MAIN
  done

  ## Ask for decryption passphrase
  zenity --entry --title="$zero [$mode]" --text="Enter passphrase" --hide-text --ok-label="Decrypt" >$wd/pass || MAIN

  ## Keep prompting until user enters a passphrase (zenity --entry inserts a newline even if no input so we can't use "test -s" like we can with --text-info)
  until [[ $(wc -c <$wd/pass) -gt 1 ]]; do
    zenity --entry --title="$zero [$mode]" --text="You must enter a passphrase" --hide-text --ok-label="Decrypt" >$wd/pass || MAIN
  done

  ## Start a progress bar dialog for those really big jobs or slow machines (this gives the user feedback and a chance to cancel)
  coproc zenity --progress --pulsate --title="$mode: Please wait" --text="Decrypting text..." --auto-kill --auto-close; echo 1 >&${COPROC[1]}

  ## Run our decrytion; if failed, save & report back errors, cleanup, and go to main menu
  if ! $decrypt <$wd/input 2>$wd/errlog; then
    >$wd/pass; >$wd/input ## Clear passphrase & input buffers ASAP
    echo 100 >&${COPROC[1]} ## Set our progress dialog to 100% so it closes
    zenity --error --title="$zero [$mode]: DECRYPTION ERROR!" --text="$(<$wd/errlog)" --no-wrap
    MAIN
  fi

  ## If decryption succeeded... 
  >$wd/pass; >$wd/input ## Clear passphrase & input buffers ASAP
  echo 100 >&${COPROC[1]} ## Set our progress dialog to 100% so it closes

  ## Display decrypted data back to user, loop back to main menu if they don't hit quit
  zenity --text-info --title="$zero [$mode]: Your decrypted text" --width=650 --height=600 --no-wrap --cancel-label="Show in txt editor" --ok-label="Menu" <$wd/output || { $viewer $wd/output; sleep 3; }
  MAIN


#------------------------------------------------
## 3.1.4) DECRYPT: SELECT FILE
elif [[ $menu_choice = $menu_dec_file ]]; then

  ## Get file to decrypt via file-selection dialog
  file_to_decrypt=$(zenity --file-selection --title="$zero [$mode]: Select file to be decrypted") || MAIN

  ## Keep prompting until user chooses a proper file
  while [[ ! -s $file_to_decrypt || ! -r $file_to_decrypt ]]; do
    file_to_decrypt=$(zenity --file-selection --title="$zero [$mode]: Could not read file; select another") || MAIN
  done

  ## Try to figure out what to suggest for the save dialog box
  ## We start with a default, but if the file ends in these extensions, set the suggestion to the filename minus that extension
  decrypted_file_suggestion=${file_to_decrypt}-decrypted
  for ext in .asc .gpg .pgp .aes256; do
    [[ $ext = ${file_to_decrypt/%*$ext/$ext} ]] && { decrypted_file_suggestion=${file_to_decrypt%$ext}; break; }
  done

  ## Prompt user for where to save decrypted file
  decrypted_file=$(zenity --file-selection --save --confirm-overwrite --filename="$decrypted_file_suggestion" --title="$zero [$mode]: Choose decrypted file name of '${file_to_decrypt##*/}'") || MAIN

  ## Prevent user from nuking their file by reading and writing to it at the same time
  while [[ $file_to_decrypt = $decrypted_file ]]; do
    zenity --error --title="I just saved your life" --text="Source and destination files cannot be the same\!" 
    decrypted_file=$(zenity --file-selection --save --confirm-overwrite --filename="$decrypted_file_suggestion" --title="$zero [$mode]: Choose decrypted file name of '${file_to_decrypt##*/}'") || MAIN
  done

  ## Set ASCII option for openssl, if needed
  if [[ $mode = OpenSSL-AES ]]; then
    file_type=$(file -b -e soft "$file_to_decrypt")
    [[ ${file_type%% *} = ASCII ]] && decrypt="$decrypt -a"
  fi

  ## Now that we know what our input and output files are going to be, setup symlinks
  ln -sf "$file_to_decrypt" $wd/input
  ln -sf "$decrypted_file" $wd/output
  
  ## Ask for decryption passphrase
  zenity --entry --title="$zero [$mode]" --text="Enter passphrase" --hide-text --ok-label="Decrypt" >$wd/pass || MAIN

  ## Keep prompting until user enters a passphrase (zenity --entry inserts a newline even if no input so we can't use "test -s" like we can with --text-info)
  until [[ $(wc -c <$wd/pass) -gt 1 ]]; do
    zenity --entry --title="$zero [$mode]" --text="You must enter a passphrase" --hide-text --ok-label="Decrypt" >$wd/pass || MAIN
  done

  ## Start a progress bar dialog for those really big jobs or slow machines (this gives the user feedback and a chance to cancel)
  coproc zenity --progress --pulsate --title="$mode: Please wait" --text="Decrypting $file_to_decrypt..." --auto-kill --auto-close; echo 1 >&${COPROC[1]}

  ## Run our decryption; if failed, save & report back errors, cleanup, and go to main menu
  if ! $decrypt <$wd/input 2>/$wd/errlog; then
    >$wd/pass ## Clear the passphrase buffer
    echo 100 >&${COPROC[1]} ## Set our progress dialog to 100% so it closes
    zenity --error --title="$zero [$mode]: DECRYPTION ERROR!" --text="$(<$wd/errlog)" --no-wrap
    MAIN
  fi

  ## If decryption succeeded... 
  >$wd/pass ## Clear the passphrase buffer
  echo 100 >&${COPROC[1]} ## Set our progress dialog to 100% so it closes

  ## Report final decrypted filename back to user; give them a chance to see the file in their filemanager (hopefully)
  echo -en "Successfully decrypted [ $file_to_decrypt ] to new file:\n$decrypted_file" | zenity --text-info --title="$zero [$mode]: Your decrypted file is ready" --width=500 --height=160 --cancel-label="Show file" --ok-label="Menu" || $filemgr "${decrypted_file%/*}"
  MAIN


#------------------------------------------------
## 3.1.5) SWITCH MODES
else

  ## If switching to openssl mode, ensure we have openssl (haha) before setting our mode
  if [[ $switch_to = OpenSSL ]]; then
    if command -v openssl >/dev/null; then
      mode=OpenSSL-AES
    else
      zenity --error --title="Missing OpenSSL" --text="OpenSSL not found in your \$PATH. That's so shocking I almost don't believe it. In any case, if you really want to try it out, you can install it using your software-management application." --width=400
    fi

  ## If switching to gpg mode, check for gpg2 first; otherwise gpg; in the unlikely event of neither, display an error
  elif [[ $switch_to = GPG ]]; then
    if command -v gpg2 >/dev/null; then
      mode=GPG2
    elif command -v gpg >/dev/null; then
      mode=GPG
    else
      zenity --error --title="Missing GPG" --text="Neither GPG nor GPG2 were found in your \$PATH. You can install either of them using your software-management application." --width=400
    fi
  fi

  ## Reload the main menu
  MAIN
fi
}



#-------------------------------------------------- 3.2) FUNCTION: UPDATES ---->
# UPDATE is called by MAIN in order to check for & optionally download updates

UPDATE() {
  ## Set some variables
  red="<span foreground='red' weight='bold'>"; blue="<span foreground='blue'>"
  orange="<span foreground='orange'>"; end_color="</span>"
  file_dL=$dotdir/latest_update
  diff_file=$dotdir/latest_changes.patch
  
  ## Make sure we CAN download something
  if command -v wget >/dev/null; then
    downloader=wget; out='-O'
  elif command -v curl >/dev/null; then
    downloader=curl; out='-o'
  else
    zenity --error --title="$zero [UPDATE]" --text="${red}Need either <tt>wget</tt> or <tt>curl</tt> installed to perform the download\!${end_color}"
    MAIN
  fi

  ## Start a progress bar
  coproc zenity --progress --pulsate --title="$zero [UPDATE]" --text="Downloading latest version of a7crypt from github..." --auto-kill --auto-close
  echo 1 >&${COPROC[1]}

  ## Download latest version of a7crypt with wget or curl
  if ! $downloader https://raw.github.com/ryran/a7crypt/master/a7crypt $out $file_dL; then
    echo 100 >&${COPROC[1]}
    zenity --error --title="$zero [UPDATE]" --text="${red}There was a problem downloading the file\! Try running $zero from a terminal for more info.${end_color}"
    MAIN
  fi

  ## Close progress bar
  echo 100 >&${COPROC[1]}

  ## Grab version strings from files
  version=$(head -n2 $0 | grep -o "a7crypt v.*")
  file_dL_version=$(head -n2 $file_dL | grep -o "a7crypt v.*")

  ## Compare currently running a7crypt with downloaded file -- if they're the same ...
  if diff -u $0 $file_dL >$diff_file; then
    zenity --info --title="$zero [UPDATE]" --text="<tt>$0</tt> is the same version as what is on github, i.e.,\n\t${blue}$version${end_color}"
    MAIN

  ## Else, if they're different, give user a chance to look at differences
  else
    if ! zenity --question --title="$zero [UPDATE]" --text="The version you are running reports as:\n\t${blue}<tt>$version</tt>${end_color}\nThe version on github appears to be different and reports as:\n\t${orange}<tt>$file_dL_version</tt>${end_color}" --cancel-label="Show differences" --ok-label="Next"; then
      $viewer $diff_file
      sleep 4
    fi

    ## Confirm update
    zenity --question --title="$zero [UPDATE]" --text="Replace <tt>$0</tt> with the latest version?" --cancel-label="Back to Menu" --ok-label="UPDATE" || MAIN
  
    ## If script location is writable, then let's do the do and save output to errlog
    if [[ -w $0 ]]; then
      { cp -v $0 $dotdir/$zero.bak; cp -v $file_dL $0; chmod -v +x $0; } >$wd/errlog 2>&1

    ## Otherwise we have to use a GUI su/sudo program, so first we search & pick one
    else
      for Gsu in beesu kdesu gksu ktsuss NO_tenemos_Gsu; do
        command -v $Gsu >/dev/null && break
      done

      ## If we don't have one, display error + return to menu 
      [[ $Gsu = NO_tenemos_Gsu ]] && { zenity --error --title="$zero [UPDATE]" --text="${orange}Unable to authenticate as root in order to modify <tt>$0</tt>. You can re-run $zero with root privileges from a terminal (<tt>sudo $zero</tt> or <tt>su -lc a7crypt</tt>)${end_color}"; MAIN; }
    
      ## Finally, run our commands with whichever GUI su program we found, saving output to errlog
      $Gsu "cp -v $0 $dotdir/$zero.bak; cp -v $file_dL $0; chmod -v +x $0" >$wd/errlog 2>&1 ## TODO: Need to test this with different programs; perhaps output should be inside quotes
    fi

    rm -v $dotdir/nowarn* 2>/dev/null ## TODO: Decide if really want this. (There might be important updates to warning messages?)

    ## Pipe some messages + the errlog to a zenity window for the user
    { echo "Backing up current version and replacing it with downloaded version..."; cat $wd/errlog; echo -n 'ALL DONE!'; } |
      zenity --text-info --title="$zero [UPDATE]" --no-wrap --cancel-label="Quit" --ok-label="Re-load $zero new version" --width=510 --height=300 && exec $0 || exit
  fi
  ## Script should never get here, but...
  MAIN
}


#===============================================================================
#---------------------------------------------- 4) BEGIN USER-INTERACTION! ---->

## First-run information
if [[ ! -f $dotdir/nowarn.1strun ]]; then
  zenity --info --title="$zero: First-run message" --text="This app is designed to be a frontend for utilities that provide symmetric (i.e., one passphrase used for both) encryption and decryption of files or blocks of text. It doesn't set any limits on the size of the input, so be warned: if e.g., you paste in a 150 MB text file for encryption, $zero will do it, but you'll lose upwards of 150 megs of RAM temporarily as it stores input in /dev/shm to speed operations. So you'd be better off using the file-selection mode for giant text files, as the file will be fed directly into the encryption program.\n\nAlso, a random tip: When in the main menu, you can double-click to make your choice; you don't have to use the OK button. Enjoy\!\n\n<b>[Press Enter or Esc if you can't see OK button.]</b>"
  touch $dotdir/nowarn.1strun
fi

## All setup is complete & we're ready to start the main menu (function above)
MAIN