#!/bin/bash
# Copyright (C) James Budiono 2016
# License: GNU GPL Version 3 or later
#
# Use bash to avoid invoking sed for every little thing
#
# Sync all mime-handling managed by /usr/share/mime, /etc/mime.types, 
#    /etc/mailcap, /usr/share/applications/defaults.list and
#    mimeapps.list, ROX Filer, and defaultprograms.
#
# $1-chroot path, $2-mimetypes+mimeapps+mailcap or all
#
# Note to self: implementation notes
# 1. MIME types: Between /etc/mime.types and shared-mime, I consider 
#    shared-mime (/usr/shared/mime) authoritative. This is because
#    many file managers and widget libraries user it - Rox, and others.
#
#    So /etc/mime.types will be updated from /usr/share/mime/globs2.
#    However, sometimes /etc/mime.types has some MIME info that are not
#    not recorded by shared-mime; these usually show up as types without
#    any file extensions. We need to preserve this types too.
#
#    Note1: Rox is never a source for mime-types, it actually uses shared-mime.
#    Note2: "file" magic-file is not a source of MIME types. The magic-file
#           contains magic strings for detecting MIME type, but it in itself
#           does not provide the full list of MIME types.
#
# 2. MIME handlers: Between /etc/mailcap, desktop files, and 
#    and ROX File MIME handlers, I consider ROX Filer as the authoritative.
#
#    /etc/mailcap can only specify one handler for one mime-type, so 
#    it is out of question. 
#
#    Desktop files is actually the "official" default list but for them
#    to work, there must be a "MimeType" element in the desktop file to
#    specify what kind of MIME types a desktop file can handle, but
#    unfortunately a lot of Fatdog desktop files are missing this element,
#    making it unreliable.
#
#    Another reason is because there is no easy way to add/remove handlers;
#    editing a desktop file doesn't cut it. Rox on the other hand already
#    provide this by its "Set Run Action" menu and "Customise Menu".
#
#    So we will "merge" /etc/mailcap from Rox mime handlers. We keep existing
#    entries in mailcap that aren't replaced with Rox mime handlers. 
#    We could update MimeType element from desktop files with info from
#    Rox, but we avoid that (I don't want to update with desktop files
#    unless it is absolutely necessary).
#
#    Note1: This decision to make Rox authoritative unfortunately also makes us
#    depends unnecessarily on Rox. It means that if you use other 
#    file managers, you still have to use Rox to as the source of the
#    MIME handlers. 
#    To reduce this dependency, the function that obtains handlers from Rox
#    is isolated and returns a generic handler list; so it can be replaced
#    with others later.
#
#    Note2: We use mimeapps.list, but we symlink defaults.list to mimeapps.list
#    for maximum compatibility. 
#
# 3. Default application handlers: Between /usr/share/applications/defaults.list,
#    mimeapps.list, /etc/mailcap and ROX filer MIME handler, 
#    I consider ROX as the authoritative source.
#
#    Main reason is because Rox provides a GUI to handle that, while there is
#    no such thing for mimeapps.list. Also, mimeapps.list are already deprecated.
#
#    Aside from the benefit that Rox provides, as above, I want to note that
#    mimeapps.list are supposedly update-able from desktop files; 
#    and as discussed above Fatdog desktop files are usually of the shortened form.
#
#    How does defaultprogram goes into the picture? defaultprogram comes
#    as another indirection layer. This is how it work:
#
#    - without default program, if I want to associate .text and .c and .csv
#      files (text/plain, text/x-csrc, text/csv) to my favorite text editor
#      (say geany), I will have to associate it 3 times with Rox, one of each
#      MIME type. Sure if I use desktop files I can just edit geany.desktop and
#      specify MimeType=text/plain;text/x-csrc;text/csv but for this to take
#      effect it has to go to defaults.list; making sure that it wins over
#      all other programs that handles the same type. Tough stuff.
#      Now let's say I want to use vim instead of geany. What to do?
#      Repeat all the above again.
#
#    - with defaultprogram, you just create a symlink defaulttexteditor
#      that points to defaultprogram, and then define DEF_TEXTEDITOR=geany
#      in its config file.
#      Then in Rox filer assign all the MIME types to defaulttexteditor
#      (or, if you're adventurous, create defaulttexteditor.desktop
#      and update its MimeType, and then re-update defaults.list)
#      The next time you want to use vim instead of geany, just edit
#      defaultprogram config file and change DEF_TEXTEDITOR=vim.
#       

LANG=C # make it fly

# out: $AWK
find_best_awk() {
	type mawk > /dev/null && AWK=mawk && return
	type gawk > /dev/null && AWK=gawk && return
	type awk > /dev/null && AWK=awk && return
	type busybox > /dev/null && AWK="busybox awk" && return
}

# out: $READLINK
find_best_readlink() {
	type readlink > /dev/null && READLINK="readlink -e"
	! $READLINK / > /dev/null 2>&1 && READLINK="xargs -L1 busybox readlink -f" && return
	READLINK="xargs $READLINK"
}

# $1-ROOT, $2-what to do
get_vars() {
	ROOT=
	[ $1 ] && ROOT=$(readlink -f $1)
	GLOBS2=$ROOT/usr/share/mime/globs2
	MIMETYPES=$ROOT/etc/mime.types

	DEFAULTS_LIST=defaults.list
	MIMEAPPS_LIST=mimeapps.list

	MAILCAP=$ROOT/etc/mailcap
	DESKTOP_FILE_DIR=$ROOT/usr/share/applications
	ROX_HANDLER=$ROOT/etc/xdg/rox.sourceforge.net

	HOME=${HOME%%/} # eliminate HOME=/
	[ -z "$HOME" ] && HOME=/root
	LOCAL_MAILCAP=$ROOT$HOME/.mailcap
	LOCAL_DESKTOP_FILE_DIR=$ROOT$HOME/.local/share/applications
	LOCAL_ROX_HANDLER=$ROOT$HOME/.config/rox.sourceforge.net
		
	DESKTOP_DIRS="$DESKTOP_FILE_DIR $LOCAL_DESKTOP_FILE_DIR"
	
	## action
	case "$2" in
		all|"") TODO="mimetypes+mailcap+mimeapps" ;;
		*) TODO="$2" ;;
	esac
}

isroot() {
	test $(id -u) -eq 0
}

# update mime types
update_mime_types() {
	case "$TODO" in *mimetypes*) ;; *) return ;; esac
	! isroot && return # root only
	echo Updating $MIMETYPES
	local TMP=/tmp/fatdog-mime.$$

	# source: MIMETYPES, GLOBS2 (format: priority:mime-type:file-extension)
	# get those mime-types without extensions from existing mime.types,
	# for the rest use shared-mime
	! [ -e $MIMETYPES ] && echo > $MIMETYPES # make sure it exists
	{
		< $MIMETYPES $AWK '!/^#/ && NF<2 && !/^$/ { print "01:" $1}'
		cat $GLOBS2
	} | 
	$AWK -F: '
!/^#/ {
	sub(/\*\./,"",$3)
	sub(/\.\*/,"",$3)
	if (!match($3,/\*|\?/))
		ext[$1 ":" $2]=ext[$1 ":" $2] " " $3
}
END {
	for (p in ext) print p ext[p]
}'  | sort -r | sed 's/.*://' > $TMP
	mv $TMP $MIMETYPES
}

### Rox-specific functions START
# $1-rox config dir, out: stdout mime-associations (mime:apps)
get_mime_association_from_rox() {
	local ROX_HANDLER=$1 
	local HANDLED p h line

	# read through all known ROX mime associations
	local ROX_SENDTO="SendTo" ROX_URI="URI" ROX_MIME="MIME-types"
	{
		# read main handler output mime:handlers
		if [ -e $ROX_HANDLER/$ROX_MIME ]; then
			HANDLED=$( (cd $ROX_HANDLER/$ROX_MIME; find | sed '/^\.$/d;s|./||') )	
			for p in $HANDLED; do
				if test -L $ROX_HANDLER/$ROX_MIME/$p; then
					h=$(readlink $ROX_HANDLER/$ROX_MIME/$p)
					case "$h" in
						..*) h=$(readlink -f "$h") ;;
						*/*) ;;				
						*) h=$ROX_HANDLER/$ROX_MIME/"$h" ;;
					esac
				else
					h=$ROX_HANDLER/$ROX_MIME/$p
				fi
				echo "${p/_//}:${h#$ROOT}"
			done
		fi
		echo "inode/directory:/usr/bin/rox" # hardcoded

		# read uri handler
		if [ -e $ROX_HANDLER/$ROX_URI ]; then
			HANDLED=$( (cd $ROX_HANDLER/$ROX_URI; find | sed '/^\.$/d;s|./||') )	
			for p in $HANDLED; do
				if test -L $ROX_HANDLER/$ROX_URI/$p; then
					h=$(readlink $ROX_HANDLER/$ROX_URI/$p)
					case "$h" in
						..*) h=$(readlink -f "$h") ;;
						*/*) ;;				
						*) h=$ROX_HANDLER/$ROX_URI/"$h" ;;
					esac
				else
					h=$ROX_HANDLER/$ROX_URI/$p
				fi
				echo "x-scheme-handler/$p:${h#$ROOT}"
			done
		fi

		# read sendto handler
		if [ -e $ROX_HANDLER/$ROX_SENDTO ]; then
			while read -r line; do
				p=${line%%/*}
				h=${line#*/}
				if test -L $ROX_HANDLER/$ROX_SENDTO/"$line"; then
					h=$(readlink $ROX_HANDLER/$ROX_SENDTO/"$line")
					case "$h" in
						*usr/local/apps/Trash) continue ;; # skip Trash handler					
						..*) h=$(readlink -f "$h") ;;
						*/*) ;;				
						*) h=$ROX_HANDLER/$ROX_SENDTO/"$h" ;;
					esac
				else
					h=$ROX_HANDLER/$ROX_SENDTO/"$line"
				fi
				p=${p/_//}; p=${p#.}
				echo "$p:${h#$ROOT}"
			done << EOF
$(cd $ROX_HANDLER/$ROX_SENDTO; find | sed 's|./||;/\//!d;/Trash/d' )
EOF
		fi 
	}
}
### Rox-specific functions END

### Mailcap-specific functions
# $1-mailcap, out: stdout mime-associations (mime:apps)
get_mime_association_from_mailcap() {
	[ "$1" ] && [ -e $1 ] && 
	< $1 $AWK -F";" '
!/^#/ && !/^[ \t]*$/ { 
	sub(/^[ \t]*/,"",$2); 
	sub(/[ \t]+.*/,"",$2); 
	print $1 ":" $2 
}'
}
### Mailcap-specific functions END


# $1-desktop dirs, out: stdout: the mapping (bin:desktop)
get_desktop_binaries() {
	local line bin binname desktop
	
	while read -r line; do
		desktop=${line%%:*}
		bin=${line#*:}
		binname=${bin##*/}
		case "$bin" in
			*defaultterm) # is a generic wrapper we don't even want to show
				continue
				;;
			*AppRun) # is a special crap we don't want to process
				echo "$line" 
				;;
			*/*) # already have path - output version with and without path
				echo "$line" 
				echo "$desktop:$binname"
				;;
			*) # no path - find the full path and display with and without path
				bin=$(which "$bin" 2>/dev/null)
				echo "$line" 				
				[ "$bin" ] && echo "$desktop:$bin"
				;;
		esac		
	done << EOF
$($AWK -F"=" '
/^Exec=/ { sub(/[ \t].*/,"",$2); sub(/.*\//,"",FILENAME); print FILENAME ":" $2}
' $(find -H $1 -maxdepth 1 -name "*.desktop" | $READLINK)
)
EOF
}

# $1-ROX_HANDLER $2-MAILCAP $3-DESKTOP_DIRS $4-DESKTOP_FILE_DIR
update_mime_handlers() {
	local ROX_HANDLER=$1 MAILCAP=$2 DESKTOP_DIRS="$3" DESKTOP_FILE_DIR=$4
	local TMP=/tmp/fatdog-mime.$$
	# get mime association from authorised sources
	{
		get_mime_association_from_rox $ROX_HANDLER
		echo '###'
		get_mime_association_from_mailcap $MAILCAP
	} > $TMP
	#cat $TMP; return 1
	
	# output to mailcap - enable first entry, comment subsequent entry
	case "$TODO" in *mailcap*) 
	{
		echo Updating $MAILCAP
		mkdir -p ${MAILCAP%/*}; touch $MAILCAP
		< $TMP $AWK -F: '
!/###/ { if (exist[$1]) print "# " $1 "; " $2 " %s"
  else {
	exist[$1]=$2
    print $1 "; " $2 " %s" 
  }
}'      | sort -u > $MAILCAP
	} ;;
	esac
	
	# output to mimeapps
	case "$TODO" in *mimeapps*) 
	{
		echo Updating $DESKTOP_FILE_DIR/$MIMEAPPS_LIST
		get_desktop_binaries "$DESKTOP_DIRS" > $TMP-map
		mkdir -p $DESKTOP_FILE_DIR 
		touch $DESKTOP_FILE_DIR/$MIMEAPPS_LIST
		< $TMP $AWK -F: -v mapfile=$TMP-map '
BEGIN {
	# load map
	while (getline < mapfile) {
		map[$2]=$1
	}
	close(mapfile)
}
!/###/ {
	if (map[$2]) { 
		if (!dupcheck[$1$2]) {
			entries[$1]=entries[$1] map[$2] "; "
			dupcheck[$1$2]=1
		}
	}
}
END {
	print "[Default Applications]"
	for (p in entries) {
		print p "=" entries[p]
	}
}
'       | sort -u > $DESKTOP_FILE_DIR/$MIMEAPPS_LIST
		ln -sf $MIMEAPPS_LIST $DESKTOP_FILE_DIR/$DEFAULTS_LIST
	} ;;
	esac
	rm -f $TMP $TMP-map	
}

### main
find_best_awk
find_best_readlink
get_vars $1 $2
update_mime_types
update_mime_handlers $ROX_HANDLER $MAILCAP "$DESKTOP_DIRS" $DESKTOP_FILE_DIR
update_mime_handlers $LOCAL_ROX_HANDLER $LOCAL_MAILCAP "$DESKTOP_DIRS" $LOCAL_DESKTOP_FILE_DIR

