View Single Post
Old 11-20-2010, 02:34 PM   #1
clanger9
Member
clanger9 doesn't litterclanger9 doesn't litter
 
Posts: 11
Karma: 138
Join Date: Nov 2010
Device: Kindle 3
Script to fetch & send to Kindle (Mac/Linux)

Hi all,

After having problems with Calibre intermittently failing to fetch and email news to my Kindle, I wrote a script to automate the process.

You can easily schedule the script on a Mac (with launchd) or Linux (with cron). It is a bit rough and ready but seems to work OK. The main feature is that it will retry fetching and sending a number of times. It will also notice if either of these processes gets stuck/hung and kill and restart the process after a specified time.

It is designed to work with a Kindle and a Gmail account, but it should be adaptable to work with anything that Calibre supports. You have to save your Gmail password in the file. This is horribly insecure, so please don't use your main email account (except for testing). Far better to create a dummy Gmail account for sending to the Kindle.

You will need to change KindleMail, GoogleUser and GooglePassword as a minimum. If you are on anything other than a Mac, you'll need to change BinDir and RecipeDir as well.

Have fun!

Code:
#!/bin/bash

### CHANGE THESE VALUES ###
KindleMail='<your kindle email>@free.kindle.com'
GoogleUser='<your gmail account>@gmail.com'
GooglePassword='<your gmail password>'
### CHANGE THESE VALUES ###

BinDir='/Applications/calibre.app/Contents/MacOS'
RecipeDir='/Applications/calibre.app/Contents/Resources/resources/recipes'

FetchTimeout=7200  # Allow up to 120 minutes to fetch news 
SendTimeout=300    # Allow up to 5 minutes to send email
MaxIterations=3

TempFile="/tmp/$1.mobi"
CmdFetch="$BinDir/ebook-convert $RecipeDir/$1.recipe $TempFile --output-profile kindle $2 $3"
CmdSend="$BinDir/calibre-smtp -r smtp.gmail.com --port 587 --username $GoogleUser --password $GooglePassword --a $TempFile $GoogleUser $KindleMail $1"

PN=`basename "$0"`
VER='1.07'

# Usage instructions
if [ ! $1 ]; then
	echo "error: you must specify a recipe file in $RecipeDir" 1>&2
	exit 1
elif [ ! -f $RecipeDir/$1.recipe ]; then
	echo "error: recipe file $RecipeDir/$1.recipe not found" 1>&2
	exit 1
fi

# Start fetch process
ErrorFlag=0
i=1
while [ $i -le $MaxIterations ]
do
	# Wait a number of seconds before retrying
	if [ $ErrorFlag -eq 1 ]; then
		PauseTime=$(expr 30 \* $i)
		echo "`date` $PN warning: failed to fetch $1, making attempt $i of $MaxIterations in $PauseTime seconds" 1>&2
		sleep $PauseTime
	fi
	
	# Start fetch task in the background and get its process id
	nice $CmdFetch >/dev/null 0<&1 2>&1 &
	TaskID1=$!

	# Set watchdog timer to kill fetch task after specified time
	( 
		sleep $FetchTimeout 
		kill $TaskID1 >/dev/null 0<&1 2>&1
	) >/dev/null 0<&1 2>&1 & 
	WatchdogID1=$!

	# Bring the fetch task back to the foreground
	trap "kill $TaskID1 $WatchdogID1 >/dev/null 0<&1 2>&1; exit" INT TERM EXIT
	wait $TaskID1
	TaskSuccess=$?
	trap - INT TERM EXIT
	kill $WatchdogID1 >/dev/null 0<&1 2>&1
	
	# Exit loop if task completed successfully
	if [ $TaskSuccess -eq 0 ]; then
		ErrorFlag=0
		break
	else
		ErrorFlag=1
		i=$(($i+1))
		continue
	fi
done

# If fetching failed, print error and quit
if [ $ErrorFlag -eq 1 ]; then
	rm $TempFile >/dev/null 0<&1 2>&1
	echo "`date` $PN error: failed to fetch $1, giving up" 1>&2
	exit 1
fi

# Start sending process

ErrorFlag=0
i=1
while [ $i -le $MaxIterations ]
do
	# Wait a number of seconds before retrying
    if [ $ErrorFlag -eq 1 ]; then
		PauseTime=$(expr 30 \* $i)
		echo "`date` $PN warning: failed to send $1, making attempt $i of $MaxIterations in $PauseTime seconds" 1>&2
		sleep $PauseTime
	fi
	
	# Start send task in the background and get its process id
	nice $CmdSend >/dev/null 0<&1 2>&1 &
	TaskID2=$!
	# Set watchdog timer to kill send task after specified time
	(
		sleep $SendTimeout
		kill $TaskID2 >/dev/null 0<&1 2>&1
	) >/dev/null 0<&1 2>&1 &
	WatchdogID2=$!

	# Bring the send task back to the foreground
	trap "kill $TaskID2 $WatchdogID2 >/dev/null 0<&1 2>&1; exit" INT TERM EXIT
	wait $TaskID2
	TaskSuccess=$?
	trap - INT TERM EXIT
	kill $WatchdogID2 >/dev/null 0<&1 2>&1
	
	# Exit loop if task completed successfully
	if [ $TaskSuccess -eq 0 ]; then
		ErrorFlag=0
		break
	else
		ErrorFlag=1
		i=$(($i+1))
		continue
	fi
done

# Clean up
kill $(jobs -pr) >/dev/null 0<&1 2>&1
rm $TempFile >/dev/null 0<&1 2>&1

# If sending failed, print error and quit
if [ $ErrorFlag -eq 1 ]; then
	echo "`date` $PN error: failed to send $1 to $KindleMail, giving up" 1>&2
	exit 2
else
	echo "`date` $PN sent $1 to $KindleMail"
	exit 0
fi

Last edited by clanger9; 02-12-2012 at 06:15 AM. Reason: Bug fixes, bump version to v1.07
clanger9 is offline   Reply With Quote