#!/bin/bash
# vacuums Firefox 3 SQLite databases
# - will take care of ALL Firefox profiles found
# - will not step on a running Firefox
# - should be run from cron or something, maybe once a day

# vacuum-firefox, by Chris Daniel  http://chrisdaniel.net
# Released under the BSD license (see below)
#
# Copyright (c) 2009, Chris Daniel
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 
#  * Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#  * Neither the name of the software nor the names of its contributors may be
#    used to endorse or promote products derived from this software without
#    specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


FIREFOX_PATH="$HOME/.mozilla/firefox"
PROFILE_INI="$FIREFOX_PATH/profiles.ini"

# you can list profiles by NAME (yeah, not path)
if [[ -n "$@" ]]; then
	PROFILES="$@"
fi

function xit() {
	warn $@
	exit 1
}

function info() {
	echo $@ 1>&2
}

function warn() {
	info "warning: $@"
}

# size in bytes
function sizeof() {
	du -sb $1 | awk '{ print $1 }' 2>/dev/null
}

# size in bytes, in human-readable format
function sizeof_h() {
	du --apparent-size -sh $1 | awk '{ print $1 }' 2>/dev/null
}

# total size of multiple files, in bytes
function total_sizeof() {
	du -sbc $@ | tail -n1 | awk '{ print $1 }' 2>/dev/null
}

# total size of multiple files, in human-readable format
function total_sizeof_h() {
	du --apparent-size -hsc $@ | tail -n1 | awk '{ print $1 }' 2>/dev/null
}


# fail if we do not have sqlite3 installed
if [[ -x `which sqlite3` ]]; then
	SQLITE=`which sqlite3`
else
	xit "no 'sqlite3' found in PATH"
fi

# if we don't already have a list of profiles (from args)
if [[ -z "$PROFILES" ]]; then
	PROFILES=`egrep '^Path=' $PROFILE_INI | cut -d= -f2`
fi

# do work for each profile
for profile in $PROFILES ; do
	PROFILE_PATH="$FIREFOX_PATH/$profile"
	
	# test for a running Firefox
	if [[ -h "$PROFILE_PATH/lock" ]]; then
		warn "Firefox is running with profile $profile; no work will be done"
		continue
	fi

	# check for sqlite files; this could be an old profile
	ls "$PROFILE_PATH"/*.sqlite >/dev/null 2>/dev/null || continue

	SIZE_TOTAL_BEFORE=`total_sizeof $PROFILE_PATH/*.sqlite`
	SIZE_TOTAL_BEFORE_HUMAN=`total_sizeof_h $PROFILE_PATH/*.sqlite`

	# treat each sqlite file individually
	for dbfile in "$PROFILE_PATH"/*.sqlite ; do
		# try getting a lock
		flock -nx "$dbfile" -c true
		if [[ $? -ne 0 ]]; then
			warn "something else has $dbfile locked; cannot VACUUM it"
			continue
		fi

		# gather file size for later comparison
		SIZE_BEFORE=`sizeof $dbfile`
		SIZE_BEFORE_HUMAN=`sizeof_h $dbfile`

		# okay, so vacuum
		$SQLITE "$dbfile" 'VACUUM; REINDEX;'
		if [[ $? -ne 0 ]]; then
			xit "VACUUM of $dbfile failed! something bad might happen!"
		else
			info "$dbfile VACUUM succeeded"
		fi

		SIZE_AFTER=`sizeof $dbfile`
		SIZE_AFTER_HUMAN=`sizeof_h $dbfile`

		echo "    size before vacuum: $SIZE_BEFORE bytes ($SIZE_BEFORE_HUMAN)"
		echo "    size after vacuum:  $SIZE_AFTER bytes ($SIZE_AFTER_HUMAN)"
		echo "    change: `echo $SIZE_AFTER - $SIZE_BEFORE | bc` bytes or `echo "scale=2; ($SIZE_AFTER/$SIZE_BEFORE-1)*100" | bc | sed 's/\.00//'`%"
	done

	SIZE_TOTAL_AFTER=`total_sizeof $PROFILE_PATH/*.sqlite`
	SIZE_TOTAL_AFTER_HUMAN=`total_sizeof_h $PROFILE_PATH/*.sqlite`

	echo "total statistics for profile $profile:"
	echo "  size before vacuum: $SIZE_TOTAL_BEFORE bytes ($SIZE_TOTAL_BEFORE_HUMAN)"
	echo "  size after vacuum:  $SIZE_TOTAL_AFTER bytes ($SIZE_TOTAL_AFTER_HUMAN)"
	echo "  change: `echo $SIZE_TOTAL_AFTER - $SIZE_TOTAL_BEFORE | bc` bytes or `echo "scale=2; ($SIZE_TOTAL_AFTER/$SIZE_TOTAL_BEFORE-1)*100" | bc | sed 's/\.00//'`%"


done

exit 0

