diff options
Diffstat (limited to 'bin')
43 files changed, 3696 insertions, 0 deletions
diff --git a/bin/adjust_dpms b/bin/adjust_dpms new file mode 100755 index 0000000..c8e8a0f --- /dev/null +++ b/bin/adjust_dpms @@ -0,0 +1,55 @@ +#!/bin/bash +# Script to adjust dpms settings when running certain programs, +# that don't use fullscreen, but should not be "blanked" in between. +# Programs are defined in separate config file ~/.config/dpms +# Run as timed user script (i.e. adjust_dpms@.service) with systemd +# from /etc/systemd/system/timer-minutely.target.wants + +export DISPLAY=:0.0 + +config="/home/$(whoami)/.config/dpms" +blankoff=0 + +# check for config file existence +if [[ -f $config ]];then + . /home/dave/.config/dpms +else + echo "Config file not readable $config" + exit 1 +fi + +# loop all programs and check for running instances +for i in "${programs[@]}" +do + pid=$(pidof -x $i) + re='^[0-9]+$' + if [[ $pid =~ $re ]]; then + echo "$i running ($pid)" + blankoff=1 + fi +done + +# if valuable programs are running, don't use screen blanking +if [ $blankoff -gt 0 ]; then + echo "Some program requires screensaver to be off." + # print currently set values for screensaver timeout to tmp file + echo "s $(xset q|grep timeout|awk '{print $2}') $(xset q|grep timeout|awk '{print $4}')" > $adjust_dpms + echo "dpms $(xset q|grep Standby|awk '{print $2}') $(xset q|grep Standby|awk '{print $4}') $(xset q|grep Standby|awk '{print $6}')" >> $adjust_dpms + # disable screensaver and dpms + xset s off + xset -dpms +else + xsetq=$(xset q|grep timeout|awk '{print $2}') + # if the timeout is still 0, set it to its former value, or just switch on screensaver/dpms again + if [[ $xsetq -eq 0 ]];then + if [[ -f "$adjust_dpms" ]];then + xset $(head -n1 $adjust_dpms) + xset $(tail -n1 $adjust_dpms) + rm -rf $adjust_dpms + else + xset +dpms + xset s on + fi + fi +fi + diff --git a/bin/androidbackup.sh b/bin/androidbackup.sh new file mode 100755 index 0000000..ee95f01 --- /dev/null +++ b/bin/androidbackup.sh @@ -0,0 +1,143 @@ +#!/bin/bash + +if [ -e $HOME/bin/functions.sh ] +then + . $HOME/bin/functions.sh +else + exit 0 +fi + +## CONFIGURATION +ADB=adb + +function help() +{ + echo "Android data backup/restore" + echo "Usage:" + echo -e "\t [backup|restoredata|restoreapk] [device]" + exit 0 +} + +function _backup() +{ + adb -s $DEVICE pull /data/system/packages.list . + cat packages.list | while read line + do + read name dir <<< $(echo $line | awk '{ print $1 " " $4 }') + #echo $dir + apk=$($ADB shell bash -c "ls /data/app/${name}* 2> /dev/null") + if [ -n $exists ] + then + echo $name $apk + else + echo LALALALALAAA $name + fi + echo "" + done +} + +function _restoreapk() { + mkdir -p done skipped + apps=$(ls -1 | grep -c apk) + num=1 + for i in *.apk + do + if [ $i = "done" -o $i = "skipped" ] + then + continue + fi + echo_green "Application $i ($num/$apps)" + + name=$(echo $i | sed 's/-[0-9].apk//') + existing=$($ADB shell sh -c "ls /data/app*/${name}* 2> /dev/null") + echo $existing + if [ -n "$existing" ] + then + echo $i already installed + mv $i skipped + continue + fi + echo " Restore apk? [1|0] (default 1)" + echo -en " " + read inst + inst=${inst:=1} + + if [ $inst -eq 1 ] + then + $ADB install $i + mv $i done + else + echo -e " Skipping...\n\n" + mv $i skipped + fi + let num=num+1 + done +} + +function _restoredata() +{ + mkdir -p done skipped + apps=$(ls -l | grep -v skipped | grep -v done | grep -c '^d') + num=1 + for i in * + do + if [ $i = "done" -o $i = "skipped" ] + then + continue + fi + + echo_green "Application $i ($num/$apps)" + if [ $(adb shell ls -l /data/data | grep -c $i) -lt 1 ] + then + echo_red " not installed, skipping.\n\n" + mv $i skipped + continue + fi + + echo " Restore data? [1|0] (default 0)" + echo -en " " + read inst + inst=${inst:=0} + + if [ $inst -eq 1 ] + then + if [ $i = "com.android.providers.telephony" ] + then + echo -e "SMS/CALLS" + $ADB push $i/databases/mmssms.db /data/data/$i/databases + $ADB shell chmod 660 /data/data/$i/databases/mmssms.db + $ADB shell toolbox chown 1001:1001 /data/data/$i/databases/mmssms.db + $ADB shell killall system_server + mv $i done + else + own=$($ADB shell ls -l /data/data/ | grep $i | head -n 1 | awk '{ print $3}') + echo -e " User $own\n\n" + mute $ADB push $i /data/data/$i + mute $ADB shell chown $own:$own /data/data/$i/* + mute $ADB shell chown $own:$own /data/data/$i/*/* + mute $ADB shell chown $own:$own /data/data/$i/*/*/* + mv $i done + fi + else + echo -e " Skipping...\n\n" + mv $i skipped + fi + let num=num+1 + done +} + +case $1 in + backup) + _backup + ;; + restoredata) + _restoredata + ;; + restoreapk) + _restoreapk + ;; + *) + help + exit 0 + ;; +esac diff --git a/bin/atmux b/bin/atmux new file mode 100755 index 0000000..7601693 --- /dev/null +++ b/bin/atmux @@ -0,0 +1,122 @@ +#!/bin/bash + +if [ -e $HOME/bin/functions.sh ] +then + . $HOME/bin/functions.sh +else + echo "functions.sh not found" + exit 0 +fi + +# override tmux +tmux='tmux' + +## CONFIGURATION +# session name +sn=${1:-atmux} + +# try to attach earlier session +tmux attach-session -t $sn && exit 0 +#or continue + +# default path +case $sn in + # window definitions + # path:command:custom-name + "thesis") + dpath=/home/konni/dropbox/documents/uni/12ss/BA/breite-quellen/thesis + windows="-:-" + windows+="matlab:echo bla" + ;; + "mako") + dpath=/extra/src/cyanogenmod-10.1 + dcmd=". environment_mako" + windows="-:-" + windows+="-:-" + windows+=" device/lge/mako:-" + windows+=" out/target/product/mako:-" + ;; + "ville_dev") + dpath=/extra/src/cyanogenmod-10.1 + dcmd=". environment_ville" + windows="-:-" + windows+=" -:-" + windows+=" device/htc/ville:-" + windows+=" device/htc/msm8960-common:-" + windows+=" kernel/htc/msm8960:-" + windows+=" vendor/htc:-" + windows+=" out/target/product/ville:-" + ;; + "ville") + dpath=/extra/src/cyanogenmod-jellybean + dcmd=". environment_ville" + windows="-:-" + windows+=" -:-" + windows+=" device/htc/ville:-" + windows+=" device/htc/msm8960-common:-" + windows+=" kernel/htc/msm8960:-" + windows+=" vendor/htc:-" + windows+=" out/target/product/ville:-" + ;; + *) + dpath=$HOME + windows="-:-" +esac + + +# enter default path +mute pushd $dpath + +# prepare windows +num=1 +for window in $windows +do + # parse configuration + read wpath wcmd <<< $(echo $window | sed 's/:/\ /g') + + # set path + unset tpath + if [ $wpath != "-" ] + then + tpath=$wpath + fi + + + if [ $num -eq 1 ] + then + # start new session + $tmux new-session -d -s "$sn" $tname + # set default path for new windows + $tmux set-option -t "$sn" default-path $dpath + else + # create new window in session + $tmux new-window -t "$sn:$num" $tname + fi + + # execute default command + if [ -n "$dcmd" ] + then + $tmux send-keys -t "$n:$num" "$dcmd" C-m + fi + + # change path + if [ -n "$tpath" ] + then + $tmux send-keys -t "$n:$num" "cd $tpath" C-m + fi + + # execute custom command + unset tcmd + if [ "$wcmd" != "-" ] + then + tcmd=$wcmd + $tmux send-keys -t "$n:$num" "$tcmd" C-m + fi + + let num=num+1 +done + +# select window #1 and attach to session +$tmux select-window -t "$sn:1" +$tmux attach-session -t "$sn" + diff --git a/bin/autostart b/bin/autostart new file mode 100755 index 0000000..9d70ad0 --- /dev/null +++ b/bin/autostart @@ -0,0 +1,23 @@ +#!/bin/bash +# Auto starting various programs post lightdm + +export DISPLAY=:0 +export XAUTHORITY=/home/dave/.Xauthority + +KERNEL_NAME=$(uname -r) +if [[ "$KERNEL_NAME" != *rt* ]]; then + pid=$(pidof dropbox) + re='^[0-9]+$' + if [[ $pid =~ $re ]];then + echo "Dropbox alread running. Restarting..." + dropbox stop && dropbox start & + else + dropbox start & + fi +# pulseaudio --start & +# nm-applet & +# firewall-applet & +else + pulseaudio --kill & + qjackctl & +fi diff --git a/bin/backup-settings b/bin/backup-settings new file mode 100755 index 0000000..1a9cc12 --- /dev/null +++ b/bin/backup-settings @@ -0,0 +1,19 @@ +#! /bin/sh + +etcTMP=/tmp/etc.tgz +etcIF=/etc +OF=/home/dave/ownCloud/backup/settings +gpgEND=.gpg + +echo "Backing up /etc directory." +tar cfPz $etcTMP $etcIF +echo "Encrypting compressed directory." +su - dave -c "gpg -e -r 'David Runge <david.runge@frqrec.com>' $etcTMP" +echo "Changing permissions on file." +chown dave:dave $etcTMP$gpgEND +echo "Moving backup to ownCloud." +mv -f $etcTMP$gpgEND $OF +echo "Cleaning up." +rm $etcTMP +echo "Done." + diff --git a/bin/check-rt b/bin/check-rt new file mode 100755 index 0000000..5dc04cd --- /dev/null +++ b/bin/check-rt @@ -0,0 +1,11 @@ +#!/bin/env bash +#Check if the kernel name has -rt in it (we're running Realtime Kernel) + +if [[ $(uname -r) == *-rt* ]];then + echo "Success: Running $(uname -r)." + exit 0 +else + echo "Failure: Running $(uname -r)." + exit 1 +fi + diff --git a/bin/check-vanilla b/bin/check-vanilla new file mode 100755 index 0000000..6b4bdd4 --- /dev/null +++ b/bin/check-vanilla @@ -0,0 +1,11 @@ +#!/bin/env bash +#Check if the kernel name has -rt in it (we're running Realtime Kernel) + +if [[ $(uname -r) == *-rt* ]];then + echo "Failure: Running $(uname -r)." + exit 1 +else + echo "Success: Running $(uname -r)." + exit 0 +fi + diff --git a/bin/checkip b/bin/checkip new file mode 100755 index 0000000..0e9d345 --- /dev/null +++ b/bin/checkip @@ -0,0 +1,4 @@ +#!/bin/bash +#echo "$(curl -s http://checkip.dyndns.org | sed 's/[a-zA-Z/<> :]//g') +#wget -qO- http://ipecho.net/plain ; echo +dig +short myip.opendns.com @resolver1.opendns.com diff --git a/bin/connect_to_jack b/bin/connect_to_jack new file mode 100644 index 0000000..e17ccc2 --- /dev/null +++ b/bin/connect_to_jack @@ -0,0 +1,12 @@ +#!/bin/sh + +case "$HOSTNAME" in + "pitheunlord") + jack_connect SuperCollider:out_1 alsa_out:playback_1 & + jack_connect SuperCollider:out_2 alsa_out:playback_2 & + ;; + "beagleclone") + ;; + "dvzrv") + ;; +esac diff --git a/bin/flac2mp3 b/bin/flac2mp3 new file mode 100755 index 0000000..9dd970b --- /dev/null +++ b/bin/flac2mp3 @@ -0,0 +1,19 @@ +find -name *.flac -print0 | while read -d $'\0' a + + +do +OUTF=`echo "$a" | sed s/\.flac$/.mp3/g` + +ARTIST=`metaflac "$a" --show-tag=ARTIST | sed s/.*=//g` +TITLE=`metaflac "$a" --show-tag=TITLE | sed s/.*=//g` +ALBUM=`metaflac "$a" --show-tag=ALBUM | sed s/.*=//g` +GENRE=`metaflac "$a" --show-tag=GENRE | sed s/.*=//g` +TRACKNUMBER=`metaflac "$a" --show-tag=TRACKNUMBER | sed s/.*=//g` +DATE=`metaflac "$a" --show-tag=DATE | sed s/.*=//g` + +flac -c -d "$a" | lame -m j -q 0 --vbr-new -V 0 -s 44.1 - "$OUTF" +id3 -t "$TITLE" -T "${TRACKNUMBER:-0}" -a "$ARTIST" -A "$ALBUM" -y "$DATE" -g "${GENRE:-12}" "$OUTF" + +done + + diff --git a/bin/functions.sh b/bin/functions.sh new file mode 100755 index 0000000..951def5 --- /dev/null +++ b/bin/functions.sh @@ -0,0 +1,176 @@ +#!/bin/bash +# some functions + +# zsh color output +# echo "$FG[46]$FX[blink]Hello, World" + +function echo_green() { + echo -e '\E[1;32m'$*'\E[0m' +} + +function echo_red() { + echo -e '\E[1;31m'$*'\E[0m' +} + +function echo_magenta() { + echo -e '\E[1;35m'$*'\E[0m' +} + +function echo_cyan() { + echo -e '\E[1;36m'$*'\E[0m' +} + +# log text ... +function log() { + date=`date "+%Y-%m-%d %H:%M:%S"` + if [ -n $VERBOSE ] + then + echo $date $@ | tee -a $LOGFILE + fi + [ -n $LOGFILE ] && echo $date $@ >> $LOGFILE +} + +function log_green() { + date=`date "+%Y-%m-%d %H:%M:%S"` + if [ -n $VERBOSE ] + then + echo_green $date $@ + fi + [ -n $LOGFILE ] && echo $date $@ >> $LOGFILE +} + +function log_red() { + date=`date "+%Y-%m-%d %H:%M:%S"` + if [ -n $VERBOSE ] + then + echo_red $date $@ + fi + [ -n $LOGFILE ] && echo $date $@ >> $LOGFILE +} + +function log_magenta() { + date=`date "+%Y-%m-%d %H:%M:%S"` + if [ -n $VERBOSE ] + then + echo_magenta $date $@ + fi + [ -n $LOGFILE ] && echo $date $@ >> $LOGFILE +} + +function log_cyan() { + date=`date "+%Y-%m-%d %H:%M:%S"` + if [ -n $VERBOSE ] + then + echo_cyan $date $@ + fi + [ -n $LOGFILE ] && echo $date $@ >> $LOGFILE +} + +function notify() { + title="$1" + text="$2" + echo 'naughty.notify({title = "'$title'", text = "'$text'"})' | awesome-client +} + +# mute command +function mute() { + $@ > /dev/null 2> /dev/null +} + +# run pidfile command +function run() { + pidfile=$1 + shift + command=$@ + + exec $command > /dev/null 2> /dev/null & + pid=$! + sleep 0.25 + npid=$(pidof -s $1) + if [ -n "$npid" ] && [ $npid -ne $pid ] + then + pid=$npid + fi + echo $pid > $pidfile + log_green executed $command"\n" +} + +# run_once $pidfile $command[] +function run_once() { + pidfile=/tmp/run/lock/$1 + shift + command=$@ + + mkdir -p $(dirname $pidfile) + + if [ -f $pidfile ]; + then + pid=`head -n 1 $pidfile` + running=$(ps -u $(whoami) -p $pid | grep '^[ ]*'$pid) + if [ -z "$running" ] + then + log_cyan stale pidfile found + rm -f $pidfile + + npid=$(pidof -s $1) + if [ -n "$npid" ] + then + log_green "pidfile updated\n" + echo $npid > $pidfile + else + run $pidfile $command + fi + + else + log_cyan "active pidfile found\n" + fi + else + pid=`ps | grep "$command" | grep -v grep | head -n 1 | awk '{ print $1 }'` + if [ -z "$pid" ] + then + log_green not running + run $pidfile $command + else + log_cyan "pidfile updated\n" + echo $pid > $pidfile + fi + fi +} + +# run_auto $file +function run_auto() { + file=$1 + . $file + + cur_host=`hostname` + case $cur_host in + annoyance|silence|remembrance) + cur_area=private + ;; + c*|s*) + cur_area=unixpool + ;; + esac + + if [ $area != $cur_area -a $area != "any" ] + then + log_magenta invalid_area $(basename $file)"\n" + return + fi + + if [ $host != $cur_host -a $host != "any" ] + then + log_magenta invalid_host $(basename $file)"\n" + return + fi + + if [ $allow_multiple -eq 1 ] + then + log_green run $(basename $file) + run /dev/null $command + else + log_green run_once $(basename $file) + run_once `basename $file` $command + fi +} + diff --git a/bin/gpg2mutt b/bin/gpg2mutt new file mode 100755 index 0000000..d9db27b --- /dev/null +++ b/bin/gpg2mutt @@ -0,0 +1,69 @@ +#!/bin/bash +# ~/.mutt/generate_pgp_auto +# Generate mutt pgp_auto* send-hooks from gpg pubring. +# Redirect output to file and source that in muttrc. +# Add the global hook _before_ sourcing the list: +# send-hook . 'reset pgp_autoencrypt' +# -=*# created by erAck #*=- CopyLeft Eike Rathke 2008-01-08T01:36+0100 + +# At least in an UTF-8 environment sed gets confused by 8-bit characters in +# real names and doesn't match the address anymore, an empty LANG variable +# works around. +LANG= + +# Output file +output="$HOME/.mutt/gpg-auto.rc" + +# if the file exists, delete it +if [ -f "${output}" ]; then + rm "${output}" +fi + +# 2nd gpg colon field: +# d := disabled (deprecated - use the 'D' in field 12 instead) +# e := expired +# r := revoked + +# Note that the following lines are part of the sed script passed by the shell +# and may not contain the ' character! Hence the double backslash in mail +# addresses to escape the regex . dot meta character for Mutt. +#gpg --list-keys --with-colons --fixed-list-mode --no-secmem-warning +gpg --list-keys --with-colons --fixed-list-mode --no-secmem-warning | sed -ne ' + +:START + +# ignore d|e|r keys +/^pub:[der]:/ b IGNORE + +# ignore disabled keys, D in last field (12) +/^pub:.*D[^:]*:$/ b IGNORE + +# take keys with encryption capability (E in last field), ignore without and +# other records like ^tru: +#/^pub:.*E[^:]*:$/ ! b IGNORE + +# extract uids and convert address to mutt hook and print +:EXTRACT +# ignore non-uid or no address +/^uid:[^der]:[^<]*<\([^:>]\+@[^:>]\+\)>/ ! b NUSKIP +# extract address +# somehow the colon part after \)> is needed to not produce a trailing : in output +# sed buffer problem? +s/^uid:[^der]:[^<]*<\([^:>]\+@[^:>]\+\)>[^:]*:/\1/ +# escape dot meta characters, with escaped backslash for mutt +s/\./\\\\./g +# print hook +s/\(.*\)/send-hook "!~l ~t \1" "set crypt_autoencrypt"/p +:NUSKIP +n +/^pub:/ b START +b EXTRACT + +# ignore entire key with uid/sub/... until next pub is encountered +:IGNORE +n +/^pub:/ b START +b IGNORE + +' | egrep -v 'WhatYouDontWantInThisList@example\\\\\.org' | sort -u > ${output} +# Note the triple escaped backslash! diff --git a/bin/letter b/bin/letter new file mode 100644 index 0000000..87e2c34 --- /dev/null +++ b/bin/letter @@ -0,0 +1,71 @@ +#!/bin/bash +# Create a letter + +# Configuration +template=~/documents/letter_template.tex +dir=~/documents/`date +%Y` +texdir=$dir/tex +editor=/usr/bin/vim +reader=/usr/bin/mupdf + +function mkpdf () { + pdflatex -shell-escape $1.tex + EXT=(aux log) + for i in ${EXT[*]} + do +rm -v $1.$i + done +} + +# Preparation +mkdir -p $dir +mkdir -p $texdir + +named=0 +while [ $named -eq 0 ] +do +read -p "Enter document name: " REPLY + docname=`echo $REPLY | sed 's/\ /_/g'` + docdate="`date +%Y-%m-%d`" + doc=$dir/${docdate}_${docname} + tex=$dir/${docdate}_${docname}.tex + pdf=$dir/${docdate}_${docname}.pdf + if [ -e $pdf ] + then +read -p "Document already exists. Overwrite? (y/N) " REPLY + [ "$REPLY" == "y" ] || continue +fi + +read -p "Create document \"$pdf\"? (Y/n) " REPLY + [ "$REPLY" == "y" -o x"$REPLY" == x ] && named=1 +done + +cd $dir +cp $template $tex + +$editor $tex +mkpdf $doc +$reader $pdf + +finished=0 +while [ $finished -eq 0 ] +do +read -p "Finished editing? (Y/n) " REPLY + if [ "$REPLY" == "y" -o x"$REPLY" == x ] + then +finished=1 + else + $editor $tex + mkpdf $doc + $reader $pdf + fi +done + +read -p "Keep a copy of the .tex file? (Y/n) " REPLY +if [ "$REPLY" == "y" -o x"$REPLY" == x ] +then +mv $tex $texdir +else +rm $tex +fi + diff --git a/bin/lid-switch-action b/bin/lid-switch-action new file mode 100755 index 0000000..a2779b7 --- /dev/null +++ b/bin/lid-switch-action @@ -0,0 +1,198 @@ +#!/bin/bash +# What action to take on closing the laptop lid, +# depending on whether a screen is attached or not. +# This implies setting HandleLidSwitch=ignore in /etc/systemd/logind.conf +# Call this script from /etc/acpi/handler.sh (or other properly set up script +# for handling button/lid acpi event) with parameter 0 and 1 for close and open respectively + +set -e -u + +# Find current Xsession user through loginctl list-sessions (excluding lightdm) +# TODO: make available for desktop manager, too! +declare x_user="$(loginctl list-sessions --no-legend| sed -e 's/\s\{2,\}/ /g;s/^[[:space:]]*//' | \ + cut -d ' ' -f 3| grep -v "lightdm"|uniq)" + +# Fail if there is no user set +if [ -z "$x_user" ]; then + echo "error no user!" + exit 1 +fi + +# Export Xorg DISPLAY and XAUTHORITY +# TODO: Get number of X DISPLAY from tmp +#declare x_display="/tmp/.X11-unix/X*" +export DISPLAY=:0 +export XAUTHORITY=/home/$x_user/.Xauthority + +# Define the log file +declare log="/home/$x_user/.cache/$(basename $0)" +if [[ ! -w "$log" ]]; then + touch "$log" + chown ${x_user}:${x_user} "$log" +# exec > "$log" 2>&1 +fi + +declare -a arguments=( ) +declare -a connections=( ) +declare -a active_connections=( ) +declare -a available_connections=( ) +declare -a non_primary=( "eDP1" ) +declare -a pan=( "DP2-1" "eDP1" ) +declare -a pan_vertical=( 'empty' 'empty' ) # with empty placeholders! +declare internal="eDP1" +declare current_primary="" +declare action="" + +# declare keywords (xrandr arguments) +declare pre_output=" --output " +declare post_auto=" --auto " +declare post_primary=" --primary " +declare post_pos_rightof=" --right-of" +declare post_pos_leftof=" --left-of" +declare post_off=" --off " + +function setup_xscreen() +{ + local outputs=( ) + local connection_counter=0 + case "$action" in + "open") + if [ ${#available_connections[@]} -gt 1 ]; then + for connection in ${pan[@]} + do + echo "$connection" >> $log + set +e + find_component_in "$connection" ${available_connections[@]} + if [ $? -eq 0 ]; then + echo "$connection is amongst the available outputs." >> $log + # adding --output argument + outputs=( ${outputs[@]:+${outputs[@]}} "$pre_output" ) + # adding name of output + outputs=( ${outputs[@]:+${outputs[@]}} "$connection" ) + # check whether output should not be primary in a multi output setup + find_component_in "$connection" ${non_primary[@]} + if [ $? -eq 0 ]; then + echo "$connection is setup for non-primary." >> $log + else + echo "$connection is NOT setup for non-primary." >> $log + # adding --primary argument + outputs=( ${outputs[@]:+${outputs[@]}} "$post_primary" ) + fi + if [ $connection_counter -gt 0 ]; then + # adding a --right-of <connection before this one> argument + outputs=( ${outputs[@]:+${outputs[@]}} "$post_pos_rightof" "${pan[$((connection_counter-1))]}" ) + fi + # adding --auto argument + outputs=( ${outputs[@]:+${outputs[@]}}"$post_auto" ) + connection_counter=$(( $connection_counter + 1 )) + fi + set -e + done + fi + ;; + "close") + if [ ${#available_connections[@]} -gt 1 ]; then + for connection in ${pan[@]} + do + echo "$connection" >> $log + set +e + find_component_in "$connection" ${available_connections[@]} + if [ $? -eq 0 ]; then + echo "$connection is amongst the available outputs." >> $log + # adding --output argument + outputs=( ${outputs[@]:+${outputs[@]}} "$pre_output" ) + # adding name of output + outputs=( ${outputs[@]:+${outputs[@]}} "$connection" ) + # if the connection is not the internal output + if [ $connection != $internal ]; then + # check whether output should not be primary in a multi output setup + find_component_in "$connection" ${non_primary[@]} + if [ $? -eq 0 ]; then + echo "$connection is setup for non-primary." >> $log + else + echo "$connection is NOT setup for non-primary." >> $log + # adding --primary argument + outputs=( ${outputs[@]:+${outputs[@]}} "$post_primary" ) + fi + if [ $connection_counter -gt 0 ]; then + # adding a --right-of <connection before this one> argument + outputs=( ${outputs[@]:+${outputs[@]}} "$post_pos_rightof" "${pan[$((connection_counter-1))]}" ) + fi + # adding --auto argument + outputs=( ${outputs[@]:+${outputs[@]}}"$post_auto" ) + connection_counter=$(( $connection_counter + 1 )) + else + # adding a --off argument for the interal screen + outputs=( ${outputs[@]:+${outputs[@]}} "$post_off" ) + fi + fi + set -e + done + fi + ;; + esac +echo "Calling: xrandr ${outputs[@]}" >> $log +xrandr ${outputs[@]} +} + +function find_component_in() +{ + local -r component=$1 + shift + local value + for value in "$@" + do + [[ $value == "$component" ]] && return 0 + done + return 1 +} + +function get_current_x_information() +{ + # get current xrandr settings + current_xrandr=$(xrandr) + # get current primary output from xrandr + current_primary=$(echo "$current_xrandr" | \ + grep -wE '(\bconnected.*primary.*[0-9]{1,5}\x[0-9]{1,5}\+[0-9]{1,5}\+[0-9]{1,5})' | \ + cut -d ' ' -f 1) + echo "Current primary output: $current_primary" >> $log + # get all current active connections + active_connections=( "${active_connections[@]:+${active_connections[@]}}" $(echo "$current_xrandr" | \ + grep -wE '(\bconnected.*[0-9]{1,5}\x[0-9]{1,5}\+[0-9]{1,5}\+[0-9]{1,5})' | cut -d ' ' -f 1) ) + echo "Current active connections: ${active_connections[@]}" >> $log + # get all currently available connections + available_connections=( "${available_connections[@]:+${available_connections[@]}}" $(echo "$current_xrandr" | \ + grep -wE '(\bconnected.*)' | cut -d ' ' -f 1) ) + echo "Currently available connections: ${available_connections[@]}" >> $log + # get all available connections + connections=( "${connections[@]:+${connections[@]}}" $(echo "$current_xrandr"| grep -v 'Screen' | cut -d ' ' -f 1)) + echo "All current connections: ${connections[@]}" >> $log +} + +function get_action() +{ + arguments=( "${arguments[@]:+${arguments[@]}}" "${@:+$@}" ) +# echo "${@:+$@}" >> $log +# echo "${arguments[@]:+${arguments[@]}}" >> $log + if [ ${#arguments[@]} -gt 0 ];then + if [ "${arguments[0]}" = "button/lid" ]; then + case "${arguments[2]}" in + "open") + action="open" + ;; + "close") + action="close" + ;; + esac + elif [ "${arguments[0]}" = "login" ]; then + action="open" + fi + fi + echo "Action set: $action" >> $log +} + +# pass arguments to the script to the get_action function +get_action "${@:+$@}" +get_current_x_information +setup_xscreen + diff --git a/bin/lightdm-display-setup b/bin/lightdm-display-setup new file mode 100755 index 0000000..94ce0a5 --- /dev/null +++ b/bin/lightdm-display-setup @@ -0,0 +1,4 @@ +#!/bin/bash +# Script to run as lightdm "display-setup-script" + +runuser -l dave -c 'autorandr -c' diff --git a/bin/lowercase b/bin/lowercase new file mode 100755 index 0000000..09273b0 --- /dev/null +++ b/bin/lowercase @@ -0,0 +1,23 @@ +#!/bin/bash + +# lowerext.sh + +while read f; do + if [[ "$f" = *.* ]]; then + # Extract the basename + b="${f%.*}" + + # Extract the extension + x="${f##*.}" + + # Convert the extension to lower case + # Note: this only works in recent versions of Bash + l="${x,,}" + + if [[ "$x" != "$l" ]]; then + mv "$f" "$b.$l" + fi + else + continue + fi +done diff --git a/bin/mirssi b/bin/mirssi new file mode 100755 index 0000000..6a9ceaa --- /dev/null +++ b/bin/mirssi @@ -0,0 +1,12 @@ +#!/bin/bash +# start notification daemon +# connect to server and open port +# kill notification daemon + +PATH=$HOME/bin/:$PATH +notify_irssi_server.pl &> /dev/null & +pid=$! + +ssh -R 7090:localhost:7090 sleepmap + +kill $pid diff --git a/bin/monitor-hotplug b/bin/monitor-hotplug new file mode 100755 index 0000000..6c2d1d6 --- /dev/null +++ b/bin/monitor-hotplug @@ -0,0 +1,7 @@ +#!/bin/bash +# Script to be called upon hotplug (change) udev event, to acquire correct resolution for connected screens + +export DISPLAY=:0 +export XAUTHORITY=/home/dave/.Xauthority + +/usr/bin/autorandr -c & diff --git a/bin/notify_irssi_server.pl b/bin/notify_irssi_server.pl new file mode 100755 index 0000000..e1c7b80 --- /dev/null +++ b/bin/notify_irssi_server.pl @@ -0,0 +1,62 @@ +#!/usr/bin/perl +# +use IO::Socket; +my $sock = new IO::Socket::INET ( + LocalHost => '127.0.0.1', + LocalPort => '7090', + Proto => 'tcp', + Listen => 1, + Reuse => 1, +); +die "Could not create socket: $!\n" unless $sock; + +while(true) { + my $new_sock = $sock->accept(); + while(<$new_sock>) { + my ($server, $channel, $text, $active) = split(/:::/); + #print "$_\n"; + my ($second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings) = localtime(); + # zero padding + if ($hour < 10) { + $hour = "0$hour"; + } + if ($minute < 10) { + $minute = "0$minute"; + } + if ($channel =~ m/#/ ) { + $timeout = 10; + } + else { + if ($channel =~ m/bitlbee/ ) { + $timeout = 10; + } + else { + $timeout = 5; + } + } + + ## filter invalid chars + # allowed: [#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + $text =~ s/[^\x01-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]//go; + # # restricted:[#x1-#x8][#xB-#xC][#xE-#x1F][#x7F-#x84][#x86-#x9F] + $text =~ s/[\x01-\x08\x0B-\x0C\x0E-\x1F\x7F-\x84\x86-\x9F]//go; + + ## replace otr information + $text =~ s/^03/OTR:\ /; + + ## remove ' + $text =~ s/\'//g; + + ## remove " + $text =~ s/\"//g; + + print "message({action=\"new\", timeout=$timeout, active=$active, title=\"$hour:$minute $channel\", text=\"$text\"})\n"; + ## AWESOME custom function + system("echo 'message({action=\"new\", timeout=$timeout, active=$active, title=\"$hour:$minute $channel\", text=\"$text\"})' | awesome-client"); + ## AWESOME + # system("echo 'notify_irssi=naughty.notify({timeout = $timeout, title = \"$hour:$minute $channel\", text = \"$text\"})' | awesome-client"); + ## NOTIFY-SEND timeout in ms + # system("notify-send -t $timeout*1000 \"$hour:$minute $channel\" \"$text\""); + } +} +close($sock); diff --git a/bin/notify_mpd b/bin/notify_mpd new file mode 100755 index 0000000..8bdcf8c --- /dev/null +++ b/bin/notify_mpd @@ -0,0 +1,42 @@ +#!/bin/bash +# TODO: notify-send on non availabilty of mpc +mpc "$@" > /dev/null +lines=`mpc | wc -l` + +if [ $lines -gt 1 ]; +then + line1="`mpc | head -n 1`" + line2="`mpc | head -n 3 | tail -n 2 | sed 's/[a-z]*\:\ off//g'`" + + status="`echo $line2 | awk '{ print $1 }'`" + position="`echo $line2 | awk '{ print $3 }'`" + position="($position)" + volume="[`echo $line2 | awk '{ print $5 $6 }' | sed 's/volume\://' | sed 's/repeat\://'`]" + + repeat="`echo $line2 | grep -c 'repeat: on' | sed 's/1/r/' | sed 's/0//'`" + random="`echo $line2 | grep -c 'random: on' | sed 's/1/z/' | sed 's/0//'`" + single="`echo $line2 | grep -c 'single: on' | sed 's/1/s/' | sed 's/0//'`" + consume="`echo $line2 | grep -c 'consume: on' | sed 's/1/c/' | sed 's/0//'`" + flags="[$repeat$random$single$consume]" + + title="$line1" + text="$status $position $flags $volume" + +else + line="`mpc | sed 's/[a-z]*\:\ off//g'`" + + volume="[`echo $line | awk '{ print $2 }'`]" + + repeat="`echo $line | grep -c 'repeat: on' | sed 's/1/r/' | sed 's/0//'`" + random="`echo $line | grep -c 'random: on' | sed 's/1/z/' | sed 's/0//'`" + single="`echo $line | grep -c 'single: on' | sed 's/1/s/' | sed 's/0//'`" + consume="`echo $line | grep -c 'consume: on' | sed 's/1/c/' | sed 's/0//'`" + flags="[$repeat$random$single$consume]" + + title="not playing" + text="$flags $volume" +fi + +## NOTIFY-SEND +notify-send -t 2000 "$title" "$text" + diff --git a/bin/nouveau2nvidia b/bin/nouveau2nvidia new file mode 100755 index 0000000..a71e6b7 --- /dev/null +++ b/bin/nouveau2nvidia @@ -0,0 +1,57 @@ +#!/bin/bash +# nouveau -> nvidia + +set -e +# check if root +if [[ $EUID -ne 0 ]]; then + echo "You must be root to run this script. Aborting..."; + exit 1; +fi + +echo "Switching MODULES line in /etc/mkinitcpio.conf" +sed -i 's/MODULES="dm-mod nouveau"/#MODULES="dm-mod nouveau"/' /etc/mkinitcpio.conf +sed -i 's/#*MODULES="dm-mod nvidia"/MODULES="dm-mod nvidia"/' /etc/mkinitcpio.conf + +echo "Uninstalling nouveau drivers, installing nvidia drivers." + +declare -i NVIDIAINSTALL +NVIDIART=$(pacman -Qi|grep Name|grep linux-rt) + +if [[ -n "$NVIDIART" ]]; then + NVIDIAINSTALL=$NVIDIAINSTALL+1 + echo "Seems that linux-rt is installed. Marking nvidia-rt for installation." +fi + +pacman -Rdds --noconfirm nouveau-dri xf86-video-nouveau mesa-libgl lib32-nouveau-dri lib32-mesa-libgl + +case $NVIDIAINSTALL in + 0) + pacman -S --noconfirm nvidia lib32-nvidia-libgl + ;; + 1) + pacman -S --noconfirm nvidia lib32-nvidia-libgl + aura -A --noconfirm nvidia-rt + ;; +esac + +echo "Switching X11 settings." +NVIDIACONF="/etc/X11/xorg.conf.d/20-nvidia.conf" +NOUVEAUCONF="/etc/X11/xorg.conf.d/20-nouveau.conf" +BKP=".bkp" + +if [[ -f $NOUVEAUCONF ]]; then + mv $NOUVEAUCONF "$NOUVEAUCONF$BKP" +fi + +if [ -f "$NVIDIACONF$BKP" ]; then + mv "$NVIDIACONF$BKP" $NVIDIACONF +fi + + +echo "Building new initramfs images." +mkinitcpio -p linux + +if [[ -f "/etc/mkinitcpio.d/linux-rt.preset" ]]; then + mkinitcpio -p linux-rt +fi + diff --git a/bin/nvidia2nouveau b/bin/nvidia2nouveau new file mode 100755 index 0000000..e862d8c --- /dev/null +++ b/bin/nvidia2nouveau @@ -0,0 +1,58 @@ +#!/bin/bash +# nvidia -> nouveau + +set -e +# check if root +if [[ $EUID -ne 0 ]]; then + echo "You must be root to run this script. Aborting..."; + exit 1; +fi + +echo "Switching MODULES line in /etc/mkinitcpio.conf" +sed -i 's/#*MODULES="dm-mod nouveau"/MODULES="dm-mod nouveau"/' /etc/mkinitcpio.conf +sed -i 's/MODULES="dm-mod nvidia"/#MODULES="dm-mod nvidia"/' /etc/mkinitcpio.conf + +echo "Uninstalling nvidia drivers, installing nouveau drivers." + +declare -i NVIDIAINSTALL +NVIDIART=$(pacman -Qi|grep Name|grep nvidia-rt) + +if [[ -n "$NVIDIART" ]]; then + NVIDIAINSTALL=$NVIDIAINSTALL+1 + echo "Seems that nvidia-rt is installed. Marked for removal." +fi + +case $NVIDIAINSTALL in + 0) + pacman -Rdds --noconfirm nvidia nvidia-libgl lib32-nvidia-libgl + ;; + 1) + pacman -Rdds --noconfirm nvidia nvidia-rt nvidia-libgl lib32-nvidia-libgl + ;; +esac + +pacman -S --noconfirm nouveau-dri xf86-video-nouveau lib32-nouveau-dri + +echo "Switching X11 settings." +NVIDIACONF="/etc/X11/xorg.conf.d/20-nvidia.conf" +NOUVEAUCONF="/etc/X11/xorg.conf.d/20-nouveau.conf" +BKP=".bkp" + +if [ -f $NVIDIACONF ]; then + mv $NVIDIACONF "$NVIDIACONF$BKP" +fi + +if [[ -f "$NOUVEAUCONF$BKP" ]]; then + mv "$NOUVEAUCONF$BKP" $NOUVEAUCONF +fi + +echo "Building new initramfs images." +mkinitcpio -p linux + +if [[ -f "/etc/mkinitcpio.d/linux-rt.preset" ]]; then + mkinitcpio -p linux-rt +fi + +if [[ -f "/etc/mkinitcpio.d/linux-mainline.preset" ]]; then + mkinitcpio -p linux-rt +fi diff --git a/bin/nvsetup b/bin/nvsetup new file mode 100755 index 0000000..23adeb5 --- /dev/null +++ b/bin/nvsetup @@ -0,0 +1,10 @@ +#!/bin/bash + +SCREENCONNECTED=$(xrandr|grep DP-1) +if [[ -n "$(lsmod|grep nouveau)" ]]; then + if [[ "$SCREENCONNECTED" == *connected* && "$SCREENCONNECTED" != *disconnected* ]]; then + echo "Setup DP-1" +# sleep 5 + xrandr --output DP-1 --auto --primary --output LVDS-1 --auto --right-of DP-1 + fi +fi diff --git a/bin/pacman-disowned b/bin/pacman-disowned new file mode 100755 index 0000000..df1b5da --- /dev/null +++ b/bin/pacman-disowned @@ -0,0 +1,16 @@ +#!/bin/sh + +tmp=${TMPDIR-/tmp}/pacman-disowned-$UID-$$ +db=$tmp/db +fs=$tmp/fs + +mkdir "$tmp" +trap 'rm -rf "$tmp"' EXIT + +pacman -Qlq | sort -u > "$db" + +find /bin /etc /lib /sbin /usr \ + ! -name lost+found \ + \( -type d -printf '%p/\n' -o -print \) | sort > "$fs" + +comm -23 "$fs" "$db" diff --git a/bin/pacoptpd b/bin/pacoptpd new file mode 100755 index 0000000..30e09ec --- /dev/null +++ b/bin/pacoptpd @@ -0,0 +1,14 @@ +#!/bin/bash +# +# list optional dependencies of installed packages +# + +for i in $(pacman -Qq) +do + deps="$(pacman -Qi ${i} | awk '/Optional/,/Required/' | grep -v "Required" | sed -e 's/Optional Deps[ ]*://g' -e 's/^[ ]*/ /')" + if [ "$deps" != " None" ]; then + echo "$i" + echo "$deps" + echo "" + fi +done diff --git a/bin/pass2msmtp b/bin/pass2msmtp new file mode 100755 index 0000000..5ba07fa --- /dev/null +++ b/bin/pass2msmtp @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +pass Mail/$1 diff --git a/bin/pass2offlineimap b/bin/pass2offlineimap new file mode 100644 index 0000000..72f5829 --- /dev/null +++ b/bin/pass2offlineimap @@ -0,0 +1,5 @@ +#! /usr/bin/env python2 +from subprocess import check_output + +def get_pass(account): + return check_output("pass Mail/" + account, shell=True).rstrip() diff --git a/bin/pass2vdirsyncer b/bin/pass2vdirsyncer new file mode 100755 index 0000000..6b21577 --- /dev/null +++ b/bin/pass2vdirsyncer @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +pass show owncloud/$1@$2 + diff --git a/bin/rmcache b/bin/rmcache new file mode 100755 index 0000000..acba953 --- /dev/null +++ b/bin/rmcache @@ -0,0 +1,32 @@ +#!/bin/bash + +USERDIR=$1 + +echo "rmcache cleaning up: $USERDIR" + +caches=".cache/chromium" +caches+=" .cache/thumbnails" +## take care of the whitespace! +caches+=" .config/chromium/Default/*Index*" +caches+=" .thumbnails" +caches+=" .opera/cache4" +caches+=" .opera/cache" +caches+=" .java/deployment/cache" +caches+=" .local/share/Trash" +caches+=" .gnome2/epiphany/mozilla/epiphany/Cache" +caches+=" .adobe/Acrobat/*/Cache" +caches+=" .adobe/Acrobat/*/Temp" +caches+=" .macromedia/Flash_Player/*" +caches+=" .adobe/Flash_Player/AssetCache" +caches+=" .java/deployment/cache" + +pushd $USERDIR > /dev/null + +for cache in $caches +do + echo "removing $cache" + rm -Rf "$cache" + [ $? -eq 0 ] || exit 1 +done + +popd > /dev/null diff --git a/bin/rollback-profile b/bin/rollback-profile new file mode 100755 index 0000000..81386b3 --- /dev/null +++ b/bin/rollback-profile @@ -0,0 +1,69 @@ +#!/bin/bash +# +# Rollback a thunderbird or firefox profile backup from store location while gpg decrypting +# + +# Checking if firefox and/ or thunderbird are running. +firefoxPID=`ps -C firefox -o pid=` +thunderbirdPID=`ps -C thunderbird -o pid=` + +firefoxTMP=/tmp/firefox.tgz +thunderbirdTMP=/tmp/thunderbird.tgz +firefoxIF=~/.mozilla/firefox/ +thunderbirdIF=~/.thunderbird/ +profileName=dvzrv +backupEND=-rollback +OF=~/ownCloud/backup/web/ +gpgEND=.gpg +tgzEND=.tgz + +case $1 in + "thunderbird") + if [[ -z "$thunderbirdPID" ]] + then + echo "Thunderbird is not running." + echo "Rolling back profile backup." + echo "Copying backup to /tmp and moving old profile to rollback location." + cp $OF$1$tgzEND$gpgEND $thunderbirdTMP$gpgEND + mv $thunderbirdIF$profileName $thunderbirdIF$profileName$backupEND + echo "Decrypting file." + gpg -o $thunderbirdTMP -d $thunderbirdTMP$gpgEND + echo "Extracting files from tar." + tar xzvf $thunderbirdTMP -C $thunderbirdIF + echo "Cleaning up." + rm $thunderbirdTMP $thunderbirdTMP$gpgEND + echo "Done." + else + echo "Thunderbird is still running." + echo "Skipping rollback." + fi + ;; + "firefox") + if [[ -z "$firefoxPID" ]] + then + echo "Firefox is not running." + psdStopped=`systemctl status psd |grep inactive` + if [[ ! -z "$psdStopped" ]]; then + echo "Psd daemon is inactive." + echo "Rolling back profile backup." + echo "Copying backup to /tmp and moving old profile to rollback location." + cp $OF$1$tgzEND$gpgEND $firefoxTMP$gpgEND + mv $firefoxIF$profileName $firefoxIF$profileName$backupEND + echo "Decrypting file." + gpg -o $firefoxTMP -d $firefoxTMP$gpgEND + echo "Extracting files from tar." + tar xzvf $firefoxTMP -C $firefoxIF + echo "Cleaning up." + rm $firefoxTMP $firefoxTMP$gpgEND + echo "Done." + else + echo "Psd service is still running!" + echo "Disable it using: 'systemctl stop psd'." + echo "Skipping rollback." + fi + else + echo "Firefox is still running." + echo "Skipping rollback." + fi + ;; +esac diff --git a/bin/run_once b/bin/run_once new file mode 100755 index 0000000..3829f7e --- /dev/null +++ b/bin/run_once @@ -0,0 +1,4 @@ +#!/bin/bash +#Alternative +pgrep $@ > /dev/null || ($@ &) + diff --git a/bin/sclang b/bin/sclang new file mode 100755 index 0000000..a34f4ea --- /dev/null +++ b/bin/sclang @@ -0,0 +1,10 @@ +#!/bin/sh +# If not running my laptop (with X screen), run sclang in a xvfb environment. +# This ensures getting around a bug with QPixmaps that needs a X server to run. +if [[ $HOSTNAME == *dvzrv* ]]; then + echo "/usr/bin/sclang" + /usr/bin/sclang "$@" +else + echo "/usr/bin/sclang in fake X screen" + /usr/bin/xvfb-run -s "-screen 1, 1280x800x24" -a -e ~/.log/xvfb-run-sclang.error /usr/bin/sclang "$@" +fi diff --git a/bin/screensetup b/bin/screensetup new file mode 100755 index 0000000..b15d63e --- /dev/null +++ b/bin/screensetup @@ -0,0 +1,17 @@ +#!/bin/bash + +#set dpms and screen blanking +xset +dpms +xset s 360 360 + +# Setup screens + +# get info from xrandr +IFS=$'\r\n' connectedOutputs=($(xrandr | grep " connected" | sed -e "s/\([A-Z0-9]\+\) connected.*/\1/")) + +#if more than one screen, setup both, using the external left of the internal as primary +if [ ${#connectedOutputs[@]} -gt 1 ]; then + xrandr --output "${connectedOutputs[0]}" --auto --output "${connectedOutputs[1]}" --auto --primary --left-of "${connectedOutputs[0]}" +fi + + diff --git a/bin/secret b/bin/secret new file mode 100755 index 0000000..27391ff --- /dev/null +++ b/bin/secret @@ -0,0 +1,41 @@ +#!/bin/bash +# +# Open and write to a gpg encrypted file +# Will create the file for you if it's not there yet +# +# secret <path/to/file> + +writeback () { + gpg --yes -eq -r "$gpgkey" -o $1 $tmpfile + echo "Encrypted file using gpg public key: $gpgkey" + chmod 600 $1 + echo "Finished working on file: $1." + rm $tmpfile + echo "Removed temp file $tmpfile" +} + +filetotmp () { + gpg --yes -o $tmpfile -d $1 +} + +tmpfile=$(mktemp) +gpgkey="David Runge <david.runge@frqrec.com>" + +# if file is available, use it, else create it +if [[ -f "$1" ]];then + filetotmp $1 + echo "File $1 now in $tmpfile ." +fi + +echo "Opening file in $EDITOR" +# edit decrypted file in /tmp +$EDITOR $tmpfile +if [[ $? -gt 0 ]];then + echo "Something went wrong with $EDITOR ." + echo "Aborting." + rm $tmpfile + echo "Deleted $tmpfile ." +else + # write back the file + writeback $1 +fi diff --git a/bin/set_backlight b/bin/set_backlight new file mode 100755 index 0000000..c110321 --- /dev/null +++ b/bin/set_backlight @@ -0,0 +1,56 @@ +#!/bin/bash +# Script to set backlight on startup on Lenovo W540 using +# /sys/class/backlight/intel_backlight/brightness +# This must be run as root. + +# check if root +if [[ $EUID -ne 0 ]];then + echo "This must be run as root." +else + # get current and maximum brightness, set maximum steps allowed + brightness_current=$(cat /sys/class/backlight/intel_backlight/brightness) + brightness_max=$(cat /sys/class/backlight/intel_backlight/max_brightness) + brightness_max=$(($brightness_max-1000)) + brightness_steps=20 + brightness_steps_width=$(($brightness_max/$brightness_steps)) + brightness_new=0 + # if first parameter to this script is a number + re='^[0-9]+$' + if [[ $1 =~ $re ]]; then + echo "Direct step called" + if [[ $1 -gt brightness_steps && $1 -ne 0 ]];then + brightness_new=$(($1*brightness_steps_width)) + fi + elif [[ $1 == "up" || $1 == "down" ]];then + brightness_calc=$(($brightness_current / $brightness_steps_width)) + case $1 in + "up" ) + brightness_calc=$((($brightness_calc+1) * $brightness_steps_width)) + echo "brightness_calc: $brightness_calc" + echo "brightness_max: $brightness_max" + if [[ $brightness_max -gt $brightness_calc ]];then + echo "true" + brightness_new=$brightness_calc + else + echo "false" + brightness_new=$brightness_current + fi + ;; + "down" ) + brightness_calc=$((($brightness_calc-1) * $brightness_steps_width)) + echo "brightness_calc: $brightness_calc" + if [[ $brightness_calc -gt 0 ]];then + brightness_new=$brightness_calc + else + brightness_new=$brightness_current + fi + ;; + esac + echo "Step called with: $1" + fi + echo $brightness_current + echo $brightness_new + # set to 234 (around 4th step) + #echo 234 > /sys/class/backlight/intel_backlight/brightness + echo $brightness_new > /sys/class/backlight/intel_backlight/brightness +fi diff --git a/bin/set_volume b/bin/set_volume new file mode 100755 index 0000000..1652312 --- /dev/null +++ b/bin/set_volume @@ -0,0 +1,142 @@ +#!/usr/bin/env bash + +FUNCTIONS=$HOME/bin/functions.sh +[ -e $FUNCTIONS ] || exit 1 +. $FUNCTIONS + +state_muted="/tmp/$(whoami)/state_muted" +state_muted_headphone="" +state_muted_speaker="" +state_bt_headphone="" +name_bt_headphone="bluez_sink.00_1B_66_02_36_41" +cardselector="" +card_icon="/usr/share/icons/gnome/48x48/devices/audio-speakers.png" # gnome-icon-theme + +function increase_volume() +{ + mute amixer $cardselector sset Master 5%+ + local state_master=$(amixer $cardselector sget Master |grep "%" | cut -d'%' -f1 | cut -d '[' -f2 | uniq) + #update pactl bluetooth audio to same level if present + if [ $state_bt_headphone = "[on]" ];then + pactl set-sink-volume $name_bt_headphone "$state_master%" + fi + print_volumes $state_master +} + +function decrease_volume() +{ + mute amixer $cardselector sset Master 5%- + local state_master=$(amixer $cardselector sget Master |grep "%" | cut -d'%' -f1 | cut -d '[' -f2 | uniq) + #update pactl bluetooth audio to same level if present + if [ $state_bt_headphone = "[on]" ];then + pactl set-sink-volume $name_bt_headphone "$state_master%" + fi + print_volumes $state_master +} + +function print_volumes() +{ + local state_master=$1 + local state_headphone=$(amixer $cardselector sget Headphone |grep "%" | cut -d'%' -f1 | cut -d '[' -f2 | uniq) + local state_speaker=$(amixer $cardselector sget Speaker |grep "%" | cut -d'%' -f1 | cut -d '[' -f2 | uniq) + if [ $state_bt_headphone = "[on]" ]; then + send_notify "Master: $state_master% \nHeadphone: $state_headphone% \nSpeaker: $state_speaker%\nBT Headphone: $state_master%" + else + send_notify "Master: $state_master% \nHeadphone: $state_headphone% \nSpeaker: $state_speaker%" + fi +} + +function toggle_volume() +{ + local state_master=$(amixer $cardselector sget Master | grep -o '\[o[n|f]*\]' | head -n 1) + local state_headphone=$(amixer $cardselector sget Headphone | grep -o '\[o[n|f]*\]' | head -n 1) + local state_speaker=$(amixer $cardselector sget Speaker | grep -o '\[o[n|f]*\]' | head -n 1) + case "$state_master" in + "[on]") + mute amixer $cardselector sset Master mute + state_master="[off]" + # if headphones are off already, don't mute, instead save state to tmp file + if [ "$state_headphone" = "[off]" ];then + echo "headphone [off]" >> "$state_muted" + else + mute amixer $cardselector sset Headphone mute + state_headphone="[off]" + fi + # if speakers are off already, don't mute, instead save state to tmp file + if [ "$state_speaker" = "[off]" ];then + echo "speaker [off]" >> "$state_muted" + else + mute amixer $cardselector sset Speaker mute + state_speaker="[off]" + fi + # if present, also mute bluetooth headphone + if [ $state_bt_headphone = "[on]" ]; then + pactl set-sink-mute $name_bt_headphone 1 + fi + ;; + "[off]") + mute amixer $cardselector sset Master unmute + state_master="[on]" + # if headphones are meant to be off, don't unmute them again + if [ "$state_muted_headphone" != "[off]" ];then + mute amixer $cardselector sset Headphone unmute + state_headphone="[on]" + fi + # if headphones are meant to be off, don't unmute them again + if [ "$state_muted_speaker" != "[off]" ];then + mute amixer $cardselector sset Speaker unmute + state_speaker="[on]" + fi + # if present, also unmute bluetooth headphone + if [ $state_bt_headphone = "[on]" ]; then + pactl set-sink-mute $name_bt_headphone 0 + fi + ;; + esac + if [ $state_bt_headphone = "[on]" ]; then + send_notify "Master: $state_master \nHeadphone: $state_headphone \nSpeaker: $state_speaker\nBT Headphone: $state_master" 2000 + else + send_notify "Master: $state_master \nHeadphone: $state_headphone \nSpeaker: $state_speaker" 2000 + fi +} + +function send_notify() +{ + # allow timeout to be set by 2nd argument + local timeout=500 + if [ $2 -gt 0 ]; then + timeout=$2 + fi + notify-send -t $timeout \ + -i /usr/share/icons/gnome/48x48/devices/audio-speakers.png \ + "Volume" "$1" +} + +if [ $HOSTNAME = "dvzrv" ];then + cardselector=" -M -c 1 " + if [ -n "$(pactl list sinks short | grep $name_bt_headphone)" ]; then + state_bt_headphone="[on]" + fi + #TODO: also check pactl for JACK sink +fi + +case "$1" in + increase) + increase_volume + ;; + decrease) + decrease_volume + ;; + toggle) + if [ -f "$state_muted" ]; then + state_muted_headphone=$(cat "$state_muted" | grep headphone | cut -d' ' -f2) + state_muted_speaker=$(cat "$state_muted" | grep speaker | cut -d' ' -f2) + rm "$state_muted" + else + touch "$state_muted" + fi + toggle_volume + ;; + *) + ;; +esac diff --git a/bin/stikked b/bin/stikked new file mode 100644 index 0000000..b209208 --- /dev/null +++ b/bin/stikked @@ -0,0 +1,115 @@ +#! /usr/bin/env python2 + +import argparse +import os +import sys +import pycurl +import StringIO +import subprocess +import urllib + +## CONFIGURATION { +user='example-user' +apiurl='http://paste.giev.de/api/create' +## } + + +def main(): + global verbose + + # parse arguments + parser = argparse.ArgumentParser(prog='stikked') + parser.add_argument('-V', '--version', action='version', version='%(prog)s 0.1') + parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help='verbose mode', default=False) + parser.add_argument('-s', dest='syntax', action='store', help='syntax to highlight', default='text') + parser.add_argument('-p', dest='private', action='store_true', help='set paste to private', default=False) + parser.add_argument('-t', dest='title', action='store', help='set title of paste') + parser.add_argument('fname', metavar='FILE|-', help='file to paste or - for stdin') + args = parser.parse_args() + if args.verbose: + verbose = args.verbose + print args + + # private + private = '1' if args.private else '0' + + # from stdin + if args.fname == '-': + title = args.title if args.title else 'stdin' + text=sys.stdin.read() + syntax=args.syntax + + # from file + else: + fname = args.fname + + if not os.path.isfile(fname): + print "No such file: {0}".format(fname) + sys.exit(1) + else: + title = args.title if args.title else os.path.split(fname)[1] + + if args.syntax == 'text': + ext = os.path.splitext(fname)[1][1:] + syntax = _detect_syntax(ext) + else: + syntax = args.syntax + + fopen = file(fname, 'r') + text = fopen.read() + + _paste(text, title, syntax, private) + + +# send paste to server +def _paste(text, title, syntax, private): + postparams = [ + ('text', text), + ('name', user), + ('title', title), + ('lang', syntax), + ('private', private)] + + postfields=urllib.urlencode(postparams) + + curl = pycurl.Curl() + curl.setopt(pycurl.URL, apiurl) + curl.setopt(pycurl.POST, True) + curl.setopt(pycurl.POSTFIELDS, postfields) + # Fixes the HTTP/1.1 417 Expectation Failed Bug + header = [] + header.append("Expect: ") + curl.setopt(pycurl.HTTPHEADER, header) + curl.setopt(pycurl.NOPROGRESS, True) + b = StringIO.StringIO() + curl.setopt(pycurl.WRITEFUNCTION, b.write) + curl.perform() + print b.getvalue(); + + # copy to primary clipboard + xsel_proc = xsel_proc = subprocess.Popen(['xsel', '-pi'], stdin=subprocess.PIPE) + xsel_proc.communicate(b.getvalue()) + + +# more possible of course +def _detect_syntax(ext): + if ext == 'h': + syntax='c' + elif ext == 'log': + syntax='logcat' + elif ext == "py": + syntax = 'python' + elif ext == "pl": + syntax = 'perl' + elif ext == "patch": + syntax = 'diff' + elif ext == 'c' or ext == 'cpp' or ext == 'java' or ext == 'sh': + syntax=ext + else: + syntax='text' + + return syntax + + +if __name__ == '__main__': + main() diff --git a/bin/stop_jack b/bin/stop_jack new file mode 100755 index 0000000..f07b6be --- /dev/null +++ b/bin/stop_jack @@ -0,0 +1,16 @@ +#!/bin/sh + +jack_control stop +sleep 3 +jack_control exit + + +exit 0 + +#kill $(pidof jackd) +#case "$HOSTNAME" in +# "beagleclone") +# kill $(pidof jackd) +# kill $(pidof xvfb-run) +# ;; +#esac diff --git a/bin/switch_wfs_os b/bin/switch_wfs_os new file mode 100755 index 0000000..c243826 --- /dev/null +++ b/bin/switch_wfs_os @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +set -euo pipefail +IFS=$'\n\t' + +os_new="arch-rt" +os_old="ubuntu-studio" +os_current="" +os_set="" + +syslinux_location_arch="/boot/syslinux/syslinux.cfg" +syslinux_location_ubuntu="/mnt/boot/syslinux/syslinux.cfg" +syslinux_location="" + +reboot_cmd_arch="sudo systemctl reboot" +reboot_cmd_ubuntu="/usr/bin/sudo /sbin/reboot -q" +reboot_cmd="" + +hostnames_arch=( "wfs-master" "wfs-node1" "wfs-node2" ) +hostnames_ubuntu=( "wfs" "n101" "n102" ) +host=$(hostname) + +if [ $UID -ne 0 ]; then + echo "You need to be root or call this script with sudo to do this." + exit 1 +fi + +echo "Host: $host." + +if [ -f /etc/arch-release ]; then + os_current=$os_new + syslinux_location=$syslinux_location_arch + reboot_cmd=$reboot_cmd_arch +else + os_current=$os_old + syslinux_location=$syslinux_location_ubuntu + reboot_cmd=$reboot_cmd_ubuntu +fi +echo "Currently running: $os_current" + +if [ -f "${syslinux_location}" ]; then + os_set=$(sed -ne '/^DEFAULT/p' $syslinux_location| cut -d' ' -f2) + echo "Currently set up for next boot: $os_set" +else + echo "Error: Syslinux configuration does not exist ($syslinux_location)" + exit 1 +fi + +if [ "$os_set" == "${os_new}" ] && [ "$os_current" == "${os_new}" ]; then + if [[ " ${hostnames_arch[@]} " =~ " $host " ]]; then + echo "Switching to: $os_old" + sed -i 's/^DEFAULT .*/DEFAULT '${os_old}'/' $syslinux_location + if [[ "${hostnames_arch[0]}" == "$host" ]]; then + echo "Calling script on nodes:" + set +e + ssh wfs@${hostnames_arch[1]} "/usr/bin/sudo bin/$(basename $0)" + ssh wfs@${hostnames_arch[2]} "/usr/bin/sudo bin/$(basename $0)" + set -e + fi + else + echo "Error: Script is not supposed to be running on this host!" + echo "$(hostname) is not one of the following: ${hostnames_arch[@]}" + echo "Exiting." + exit 1 + fi +elif [ "$os_set" == "${os_old}" ] && [ "$os_current" == "${os_old}" ]; then + if [[ " ${hostnames_ubuntu[@]} " =~ " $host " ]]; then + echo "Switching to: $os_new" + sed -i 's/^DEFAULT .*/DEFAULT '${os_new}'/' $syslinux_location + if [[ "${hostnames_ubuntu[0]}" == "$host" ]]; then + echo "Calling script on nodes:" + ssh wfs@${hostnames_ubuntu[1]} "/usr/bin/sudo bin/$(basename $0)" + ssh wfs@${hostnames_ubuntu[2]} "/usr/bin/sudo bin/$(basename $0)" + fi + else + echo "Error: Script is not supposed to be running on this host!" + echo "$host is not one of the following: ${hostnames_ubuntu[@]}" + echo "Exiting." + exit 1 + fi +else + echo "Error: Current OS ($os_current) and OS to be booted ($os_set) are not the same." + echo "Fix this manually in $syslinux_location or just reboot (into the other OS)." + echo "Exiting." + exit 1 +fi + + +echo "Rebooting." +eval $reboot_cmd +exit 0 diff --git a/bin/syncmpdpl b/bin/syncmpdpl new file mode 100644 index 0000000..85cf723 --- /dev/null +++ b/bin/syncmpdpl @@ -0,0 +1,209 @@ +#! /usr/bin/env python2 +# -*- coding: utf-8 -*- +# +# script to sync a mpd playlist to a mass storage audio player +# +# needs +# - python-sqlite2 +# - eyed3 +# - python-argparser +# - python-mpd + +import argparse +import os +import sys +import hashlib +import subprocess + +#from time import time +import time +from mpd import (MPDClient, CommandError) + +import shutil + +# set encoding to utf-8 +reload(sys) +sys.setdefaultencoding( "utf-8" ) + +config_dir = '/home/konni/.config/pmsync' +music = '/net/media/music' +playlist = 'sync' +mpdhost = 'innocence' +destination = '/run/media/konni/ville/music' +verbose = False + +def _recode(string): + string = string.replace('"','') + # byte2utf8 + # string = unicode( string, "utf-8", errors="ignore" ) + # utf2byte + string = string.encode('utf-8') + return string + +def main(): + global verbose, playlist, mpdhost, destination + parser = argparse.ArgumentParser(prog='pmsync') + + parser.add_argument('--version', action='version', version='%(prog)s 0.1') + parser.add_argument('-v', '--verbose', action='store_true', dest='verbose', help='verbose mode', default=False) + + parser.add_argument('action', metavar='sync | list | import') + + parser.add_argument('-m', dest='host', metavar='mpdhost', action='store', help='mpd host', default=mpdhost) + parser.add_argument('-p', dest='pl', metavar='playlist', action='store', help='playlist to use', default=playlist) + parser.add_argument('-d', dest='dest', metavar='destination', action='store', help='destination', default=destination) + parser.add_argument('-e', dest='exact', action='store_true', help='exact checking (slow)', default=False) + + args = parser.parse_args() + if args.verbose: + verbose = args.verbose + print args + + ## action sync + if args.action == 'sync': + print '>> syncing \"{0}/{1}\"to \"{2}\"'.format(args.host, args.pl, args.dest) + _sync(args.host, args.pl, args.dest, args.exact) + + ## action list + elif args.action == 'list': + print '>> listing from \"{0}/{1}\"'.format(args.host, args.pl) + _list(args.host, args.pl) + + ## action import + elif args.action == 'import': + print '>> importing from \"{0}\" to playlist \"{1}/{2}\"'.format(args.dest, args.host, args.pl) + port='6600' + CON_ID = {'host':args.host, 'port':port} + client = MPDClient() + mpdConnect(client, CON_ID) + playlistinfo = client.listplaylistinfo(playlist) + for dirname, dirnames, filenames in os.walk(args.dest): + for filename in filenames: + destfile = os.path.join(dirname, filename) + path=destfile.replace(args.dest+"/","") + found = False + for item in playlistinfo: + if item['file'] == path: + found = True + if not found: + print '[x] {0}'.format(path) + client.add(path) + client.rm(args.pl) + client.save(args.pl) + + ## show help if no action called + else: + parser.print_help() + +def _sync(host, pl, dest, exact): + _checkdir(dest) + port='6600' + CON_ID = {'host':host, 'port':port} + client = MPDClient() + mpdConnect(client, CON_ID) + playlistinfo = client.listplaylistinfo(pl) + + ## copy files + print '> copy files' + for item in playlistinfo: + path = item['file'] + srcfile=os.path.join(music, path) + destfile=os.path.join(dest, path) + destdir=os.path.dirname(destfile) + + if not os.path.isdir(destdir): + print '[^] {0}'.format(destdir) + os.makedirs(destdir) + if not os.path.isfile(destfile): + shutil.copyfile(srcfile,destfile) + print '[+] {0}'.format(path) + else: + ## hash based on md5sum or just file size + if exact: + srchash = hashlib.md5(open(srcfile, 'rb').read()).hexdigest() + desthash = hashlib.md5(open(destfile, 'rb').read()).hexdigest() + else: + srchash = os.path.getsize(srcfile) + desthash = os.path.getsize(destfile) + + if srchash != desthash: + shutil.copyfile(srcfile,destfile) + print '[o] {0}'.format(path) + else: + if verbose: + print '[ ] {0}'.format(path) + + print 'done' + + ## clean files + print '> clean files and directories' + for dirname, dirnames, filenames in os.walk(dest): + for filename in filenames: + destfile = os.path.join(dirname, filename) + path=destfile.replace(dest+"/","") + found = False + for item in playlistinfo: + if item['file'] == path: + found = True; + + if not found: + try: + os.remove(destfile) + print '[-] {0}'.format(path) + except OSError: + print 'error removing {0}'.format(destfile) + + ## clean directories + for dirname, dirnames, filenames in os.walk(dest,topdown=False): + if os.listdir(dirname) == [] and dirname != dest: + try: + os.rmdir(dirname) + print '[v] {0}'.format(destdir) + except OSError: + print 'error removing {0} '.format(dirname) + print 'done' + + print '> writing filesystem changes' + subprocess.call("/bin/sync") + + +def _list(host, pl): + port='6600' + CON_ID = {'host':host, 'port':port} + client = MPDClient() + mpdConnect(client, CON_ID) + playlistinfo = client.listplaylistinfo(pl) + + ## output + print '> copy files' + prev_album = '' + for item in playlistinfo: + artist = item['artist'] + album = item['album'] + + if album != prev_album: + print '{0}/{1}'.format(artist, album) + prev_album = album + +def _output(artist, album, track, title, symbol='', extra=''): + output_format = '{symbol:4}{artist:20.20} :: {album:20.20} :: {track:2d} - {title:25.25} {extra}' + print output_format.format(symbol=symbol, artist=artist, album=album, track=track, title=title, extra=extra) + + +def mpdConnect(client, con_id): + """ +Simple wrapper to connect MPD. +""" + try: + client.connect(**con_id) + except SocketError: + return False + return True + +def _checkdir(dir): + if not os.path.isdir(dir): + print "destination not mounted" + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/bin/tex2pdf b/bin/tex2pdf new file mode 100755 index 0000000..71718f0 --- /dev/null +++ b/bin/tex2pdf @@ -0,0 +1,33 @@ +#!/bin/bash +logdestination="$HOME/.log/tex2pdf.log" +already_open=$(ps aux | grep "$1.pdf" | grep -v "grep") +rm -rf $logdestination +touch $logdestination +echo "tex2pdf called with $1.tex">>$logdestination +echo "#####################################################">>$logdestination +echo "################## pdflatex, run #1 #################">>$logdestination +echo "#####################################################">>$logdestination +pdflatex -interaction=nonstopmode $1.tex>>$logdestination +echo "bibtex:">>$logdestination +bibtex $1.aux>>$logdestination +echo "#####################################################">>$logdestination +echo "################## pdflatex, run #2 #################">>$logdestination +echo "#####################################################">>$logdestination +pdflatex -interaction=nonstopmode $1.tex>>$logdestination +echo "####### ##############################################">>$logdestination +echo "################## pdflatex, run #3 #################">>$logdestination +echo "#####################################################">>$logdestination +pdflatex -interaction=nonstopmode $1.tex>>$logdestination +echo "#####################################################">>$logdestination +echo "Launching mupdf with $1.pdf.">>$logdestination + +if [ -n "$already_open" ]; then + kill "$(echo $already_open | awk '{print $2}')" + echo "Killing pid $(echo $already_open | awk '{print $2}'), which has $1.pdf open already." >>$logdestination +fi +mupdf $1.pdf & + +echo "#####################################################">>$logdestination +echo "Removing aux,bbl,blg,dvi,lof,log,lot,nav,out,snm,toc.">>$logdestination +echo "Current directory: $(pwd) and children of depth 1.">>$logdestination +rm -f *.{aux,bbl,blg,dvi,lof,log,lot,nav,out,snm,toc} */*.{aux,bbl,blg,dvi,lof,log,lot,nav,out,snm,toc} diff --git a/bin/texcount.pl b/bin/texcount.pl new file mode 100755 index 0000000..1f47025 --- /dev/null +++ b/bin/texcount.pl @@ -0,0 +1,1391 @@ +#! /usr/bin/env perl +use strict; +use warnings; +use Term::ANSIColor; +use Encode; +use POSIX qw(locale_h); +use locale; +setlocale(LC_CTYPE,"no_NO"); + +my $versionnumber="2.2"; +my $versiondate="2009 Apr 30"; + +###### Set CMD specific settings and variables + +# Options and states +my $verbose=0; +my $showcodes=1; +my $showstates=0; +my $showsubcounts=0; +my $htmlstyle=0; +my $includeTeX=0; +my $briefsum=0; +my @sumweights; +my $utf8flag=0; + +# Global variables +my $blankline=0; +my $errorcount=0; + +# CMD specific global variables +my $totalflag=0; +my @filelist; +my $workdir; +my $globalworkdir=""; + +###### Set global settings and variables + +### Macros for headers +# Macros that identify headers: i.e. following token or +# {...} is counted as header. The =>[2] indicates transition to +# state 2 which is used within headers (although the value is +# actually never used). This is copied to %TeXmacro and the +# only role of defining it here is that the counter for the number +# of headers is incremented by one. +my %TeXheader=('\title'=>[2],'\part'=>[2],'\chapter'=>[2], + '\section'=>[2],'\subsection'=>[2],'\subsubsection'=>[2], + '\paragraph'=>[2],'\subparagraph'=>[2]); + +### How many tokens to gobble after macro +# Each macro is assumed to gobble up a given number of +# tokens (or {...} groups), as well as options [...] before, within +# and after. The %TeXmacro hash gives a link from a macro +# (or beginNAME for begin-end groups without the backslash) +# to either an integer giving the number of tokens to ignore +# or to an array (specified as [num,num,...]) of length N where +# N is the number of tokens to be read with the macro and the +# array values tell how each is to be interpreted (see the status +# values: 0=ignore, 1=count, etc.). Thus specifying a number N is +# equivalent to specifying an array [0,...,0] of N zeros. +# +# For macros not specified here, the default value is 0: i.e. +# no tokens are excluded, but [...] options are. Header macros +# specified in %TeXheader are automatically included here. +my %TeXmacro=(%TeXheader, + '\documentclass'=>1,'\documentstyle'=>1,'\usepackage'=>1, '\hyphenation'=>1, + '\pagestyle'=>1,'\thispagestyle'=>1, '\pagenumbering'=>1,'\markboth'=>1, '\markright'=>1, + '\newcommand'=>[-3,-3],'\renewcommand'=>[-3,-3], + '\newenvironment'=>[-3,-3,-3], 'renewenvironment'=>[-3,-3,-3], + '\newfont'=>2,'\newtheorem'=>2,'\bibliographystyle'=>1, '\bibliography'=>1, + '\parbox'=>1, '\marginpar'=>[3],'\makebox'=>0, '\raisebox'=>1, '\framebox'=>0, + '\newsavebox'=>1, '\sbox'=>1, '\savebox'=>2, '\usebox'=>1,'\rule'=>2, + '\footnote'=>[3],'\label'=>1, '\ref'=>1, '\pageref'=>1, '\bibitem'=>1, + '\cite'=>1, '\citep'=>1, '\citet'=>1, '\citeauthor'=>1, '\citealt'=>1, '\nocite'=>1, + '\eqlabel'=>1, '\eqref'=>1,'\hspace'=>1, '\vspace'=>1, '\addvspace'=>1, + '\input'=>1, '\include'=>1, '\includeonly'=>1,'\includegraphics'=>1, + '\newlength'=>1, '\setlength'=>2, '\addtolength'=>2,'\settodepth'=>2, + '\settoheight'=>2, '\settowidth'=>2,'\newcounter'=>1, '\setcounter'=>2, + '\addtocounter'=>2,'\stepcounter'=>1, '\refstepcounter'=>1, '\usecounter'=>1, + '\alph'=>1, '\arabic'=>1, '\fnsymbol'=>1, '\roman'=>1, '\value'=>1, + '\cline'=>1, '\multicolumn'=>3,'\typeout'=>1, '\typein'=>1, + 'beginlist'=>2, 'beginminipage'=>1, 'begintabular'=>1, + 'beginthebibliography'=>1,'beginlrbox'=>1, + '\begin'=>1,'\end'=>1,'\title'=>[2]); + +### Macros that should be counted as one or more words +# Macros that represent text may be declared here. The value gives +# the number of words the macro represents. +my %TeXmacroword=('\LaTeX'=>1,'\TeX'=>1); + +### Macros that are counted within the preamble +# The preamble is the text between \documentclass and \begin{document}. +# Text and macros in the preamble is ignored unless specified here. The +# value is the status (1=text, 2=header, etc.) they should be interpreted as. +# Note that only the first unit (token or {...} block) is counted. +my %TeXpreamble=('\title'=>[2], + '\newcommand'=>[-3,-3],'\renewcommand'=>[-3,-3], + '\newenvironment'=>[-3,-3,-3], 'renewenvironment'=>[-3,-3,-3], + ); + +### Begin-End groups +# Identified as begin-end groups, and define =>state. The +# states used corresponds to the elements of the count array, and +# are: +# 0: Not included +# 1: Text, word included in text count +# 2: Header, words included in header count +# 3: Float caption, words included in float caption count +# 6: Inline mathematics, words not counted +# 7: Displayed mathematics, words not counted +# -1: Float, not included, but looks for captions +# +# 4 and 5 are used to count number of headers and floats +# and are not used as states. +# +# Groups that are not defined will be counted as the surrounding text. +# +# Note that some environments may only exist within math-mode, and +# therefore need not be defined here: in fact, they should not as it +# is not clear if they will be in inlined or displayed math. +# +my %TeXgroup=('document'=>1,'letter'=>1,'titlepage'=>0, + 'center'=>1,'flushleft'=>1,'flushright'=>1, + 'abstract'=>1,'quote'=>1,'quotation'=>1,'verse'=>1,'minipage'=>1,'verbatim'=>1, + 'description'=>1,'enumerate'=>1,'itemize'=>1,'list'=>1, + 'theorem'=>1,'lemma'=>1,'definition'=>1,'corollary'=>1,'example'=>1, + 'math'=>6,'displaymath'=>7,'equation'=>7,'eqnarray'=>7,'align'=>7, + 'figure'=>-1,'float'=>-1,'picture'=>-1,'table'=>-1, + 'tabbing'=>0,'tabular'=>0,'thebibliography'=>0,'lrbox'=>0); + +### In floats: include only specific macros +# Macros used to identify caption text within floats. +my %TeXfloatinc=('\caption'=>[3]); + +### Macros for including tex files +# Allows \macro{file} or \macro file. If the value is 0, the filename will +# be used as is; if it is 1, the filetype .tex will be added if the +# filename is without filetype; if it is 2, the filetype .tex will be added. +my %TeXfileinclude=('\input'=>1,'\include'=>2); + +### Count labels +# Labels used to describe the counts +my @countlabel=('Files','Words in text','Words in headers', + 'Words in float captions','Number of headers','Number of floats', + 'Number of math inlines','Number of math displayed'); + +### Break points +# Definition of macros that define break points that start a new subcount. +# The values given are used as labels. +my %BreakPointsOptions; +$BreakPointsOptions{'none'}={}; +$BreakPointsOptions{'part'}={%{$BreakPointsOptions{'none'}},'\part'=>'Part'}; +$BreakPointsOptions{'chapter'}={%{$BreakPointsOptions{'part'}},'\chapter'=>'Chapter'}; +$BreakPointsOptions{'section'}={%{$BreakPointsOptions{'chapter'}},'\section'=>'Section'}; +$BreakPointsOptions{'subsection'}={%{$BreakPointsOptions{'section'}},'\subsection'=>'Subsection'}; +$BreakPointsOptions{'default'}=$BreakPointsOptions{'subsection'}; +my %BreakPoints=%{$BreakPointsOptions{'none'}}; + +### Print styles +# Definition of different print styles: maps of class labels +# to ANSI codes. Class labels are as used by HTML styles. +my @STYLES=(); +my %STYLE; +$STYLES[0]={'error'=>'bold red'}; +$STYLES[1]={%{$STYLES[0]}, + 'word1'=>'blue','word2'=>'bold blue','word3'=>'blue', + 'grouping'=>'red','document'=>'red','mathgroup'=>'magenta', + 'state'=>'cyan underline','sumcount'=>'yellow'}; +$STYLES[2]={%{$STYLES[1]}, + 'command'=>'green','exclcommand'=>'yellow','exclgroup'=>'yellow','exclmath'=>'yellow', + 'ignore'=>'cyan'}; +$STYLES[3]={%{$STYLES[2]}, + 'tc'=>'bold yellow','comment'=>'yellow','option'=>'yellow', + 'fileinclude'=>'bold green'}; +$STYLES[4]={%{$STYLES[3]}}; + +### Word regexp pattern list +# List of regexp patterns that should be analysed as words. +# Use @ to represent a letter, will be substituted with $LetterPattern. +my @WordPatterns=('(@+\.)+@+\.?','@+([\-\']@+)*'); +my $specialchars='\\\\(ae|AE|o|O|aa|AA)'; +my $modifiedchars='\\\\[\'\"\`\~\^\=](\w|\{\w\})'; +my $LetterPattern='\w'; +my $LetterPatternRelaxed='([\w\-\']|'.$modifiedchars.'|'.$specialchars.'(\{\})?|\{'.$specialchars.'\}|\{\w\})'; +my %NamedWordPattern; +$NamedWordPattern{'chinese'}='\p{script=Han}'; +$NamedWordPattern{'japanese'}='(\p{script=Han}|\p{script=Hiragana}|\p{script=Katakana})'; + +### Macro option regexp list +# List of regexp patterns to be gobbled as macro option in and after +# a macro. +my @MacroOptionPatterns=('\[(\w|[,\-\s\~\.\:\;\+\?\*\_\=])*\]'); +my @MacroOptionPatternsRelaxed=('\[[^\[\]\n]*\]'); + +###### Main script + + +################################################### + +MAIN(@ARGV); + +################################################### + + +######### +######### Main routines +######### + +# MAIN ROUTINE: Handle arguments, then parse files +sub MAIN { + my @args=@_; + my @toplevelfiles=Parse_Arguments(@args); + Apply_Options(); + if (scalar(@toplevelfiles)==0) { + conditional_print_help_style() + || print_error("No files specified.","p","error"); + } else { + conditional_print_help_style(); + my $totalcount=parse_file_list(@toplevelfiles); + conditional_print_total($totalcount); + } + Report_ErrorCount(); + Close_Output(); +} + +# Checks arguments, exits on exit condition +sub Check_Arguments { + my @args=@_; + if (!@args) { + print_version(); + print_syntax(); + print_reference(); + exit; + } elsif ($args[0]=~/^(\-?\-(h|\?|help)|\/(\?|h))$/) { + print_help(); + exit; + } elsif ($args[0]=~/^\-?\-(ver|version)$/) { + print_version(); + exit; + } elsif ($args[0]=~/^\-?\-(lic|license)$/) { + print_license(); + exit; + } + return 1; +} + +# Parses arguments, sets options (global) and returns file list +sub Parse_Arguments { + my @args=@_; + Check_Arguments(@args); + my @files; + foreach my $arg (@ARGV) { + if (Parse_Option($arg)) {next;} + if ($arg=~/^\-/) { + print 'Invalid opton '.$arg."\n"; + print_syntax(); + exit; + } + $arg=~s/\\/\//g; + push @files,$arg; + } + return @files; +} + +# Parse individual option parameters +sub Parse_Option { + my $arg=shift @_; + return parse_options_parsing($arg) + || parse_options_sums($arg) + || parse_options_output($arg) + || parse_options_format($arg) + ; +} + +sub parse_options_parsing { + my $arg=shift @_; + if ($arg eq '-inc') {$includeTeX=1;} + elsif ($arg eq '-noinc') {$includeTeX=0;} + elsif ($arg eq '-dir') {$globalworkdir=undef;} + elsif ($arg=~/^-dir=(.*)$/) {$globalworkdir=$1;} + elsif ($arg=~/^-(utf8|unicode)$/) {$utf8flag=1;} + elsif ($arg=~/^-(ch|chinese|zhongwen)$/) { + $utf8flag=1; + @WordPatterns=($NamedWordPattern{'chinese'},@WordPatterns); + } + elsif ($arg=~/^-(jp|japanese)$/) { + $utf8flag=1; + @WordPatterns=($NamedWordPattern{'japanese'},@WordPatterns); + } + elsif ($arg eq '-relaxed') { + @MacroOptionPatterns=@MacroOptionPatternsRelaxed; + $LetterPattern=$LetterPatternRelaxed; + } + else {return 0;} + return 1; +} + +sub parse_options_sums { + my $arg=shift @_; + if ($arg=~/^-sum(=(.+))?$/) {option_sum($2);} + elsif ($arg=~/^-(sub|subcounts?)(=(.+))?$/) {option_subcount($3);} + else {return 0;} + return 1; +} + +sub option_subcount { + my $arg=shift @_; + $showsubcounts=1; + if (!defined $arg) { + %BreakPoints=%{$BreakPointsOptions{'default'}}; + } elsif (my $option=$BreakPointsOptions{$arg}) { + %BreakPoints=%{$option}; + } else { + print STDERR "Warning: Option value ".$arg." not valid, using default instead.\n"; + %BreakPoints=%{$BreakPointsOptions{'default'}}; + } +} + +sub option_sum { + my $arg=shift @_; + if (!defined $arg) { + @sumweights=(1,1,1,0,0,1,1); + } elsif ($arg=~/^(\d+(,\d+){0,6})$/) { + @sumweights=split(',',$1); + } else { + print STDERR "Warning: Option value ".$arg." not valid, ignoring option.\n"; + } +} + +sub parse_options_format { + my $arg=shift @_; + if ($arg eq '-brief') {$briefsum=1;} + elsif ($arg eq '-total') {$totalflag=1;} + elsif ($arg eq '-1') {$briefsum=1;$totalflag=1;$verbose=-1;} + elsif ($arg eq "-html" ) {option_no_colours();$htmlstyle = 2;} + elsif ($arg eq "-htmlcore" ) {option_no_colours();$htmlstyle = 1;} + elsif ($arg=~/^\-(nocol|nc$)/) {option_no_colours();} + elsif ($arg eq '-codes') { + $showcodes=2; + if ($verbose==0) {$verbose=3;} + } + elsif ($arg eq '-nocodes') {$showcodes=0;} + else {return 0;} + return 1; +} + +sub parse_options_output { + my $arg=shift @_; + if ($arg eq "-v0") {$verbose=0;} + elsif ($arg eq "-v1") {$verbose=1;} + elsif ($arg eq '-vv' || $arg eq '-v2') {$verbose=2;} + elsif ($arg eq '-vvv' || $arg eq '-v3' || $arg eq '-v') {$verbose=3;} + elsif ($arg eq '-vvvv' || $arg eq '-v4') {$verbose=3; $showstates=1;} + elsif ($arg =~ /^\-showstates?$/ ) {$showstates=1;} + elsif ($arg =~ /^-(q|-?quiet)$/ ) {$verbose=-1;} + else {return 0;} + return 1; +} + +# Parse file list and return total count +sub parse_file_list { + my @filelist=@_; + my $listtotalcount=new_count("TOTAL COUNT"); + for my $file (<@filelist>) { + my $filetotalcount=parse_file($file); + add_count($listtotalcount,$filetotalcount); + } + return $listtotalcount; +} + +# Parse file and included files, and return total count +sub parse_file { + my $file=shift @_; + $workdir=$globalworkdir; + if (!defined $workdir) { + $workdir=$file; + $workdir =~ s/^((.*[\\\/])?)[^\\\/]+$/$1/; + } + @filelist=($file); + if ($htmlstyle) {print "\n<div class='filegroup'>\n";} + my $filetotalcount=new_count("SUM COUNT FOR ".$file); + foreach my $f (@filelist) { + my $tex=TeXfile($f); + my $fpath=$f; + $fpath=~s/^((.*[\\\/])?)[^\\\/]+$/$1/; + if (!defined $tex) { + print STDERR "File not found or not readable: ".$f."\n"; + formatprint("File not found or not readable: ".$f."\n","p","error"); + } else { + parse($tex); + my $filecount=add_subcount($tex); + if (!$totalflag) { + print_count($filecount); + print "\n"; + } + add_count($filetotalcount,$filecount); + } + } + if ($htmlstyle) {print "</div>\n\n";} + return $filetotalcount; +} + + +###### +###### Subroutines +###### + +###### CMD specific implementations + + +sub add_file_to_list { + my $fname=shift @_; + push @filelist,$workdir.$fname; +} + +sub print_with_style { + my ($text,$style,$colour)=@_; + #if ($utf8flag || $htmlstyle) {utf8::encode($text);} + if ($htmlstyle) { + print "<span class='".$style."'>".$text."</span>"; + } else { + print Term::ANSIColor::colored($text,$colour); + } +} + +sub option_no_colours { + $ENV{'ANSI_COLORS_DISABLED'} = 1; +} + +# Print count (total) if conditions are met +sub conditional_print_total { + my $sumcount=shift @_; + if ($totalflag || get_count($sumcount,0)>1) { + if ($totalflag && $briefsum && @sumweights) { + print total_count($sumcount); + } else { + if ($htmlstyle) { + formatprint("Total word count",'h2'); + } + print_count($sumcount); + } + } +} + +###### Option handling + + +# Apply options to set values +sub Apply_Options { + %STYLE=%{$STYLES[$verbose]}; + if ($utf8flag) {binmode STDOUT,':utf8';} + if ($htmlstyle>1) {html_head();} + foreach (@WordPatterns) { + s/\@/$LetterPattern/g; + } +} + + +###### TeX code handle + + +sub TeXfile { + my $filename=shift @_; + my $file=read_file($filename) || return undef; + return TeXcode($file,$filename); +} + +sub read_file { + my $filename=shift @_; + if ($utf8flag) { + open(FH,"<:utf8",$filename) || return undef; + } else { + open(FH,"<".$filename) || return undef; + } + if ($verbose>0) { + formatprint("File: ".$filename."\n",'h2'); + $blankline=0; + } + my @text=<FH>; + close(FH); + my $latexcode=join('',@text); + if ($utf8flag) { + $latexcode =~ s/^\x{feff}//; + } + return $latexcode; +} + +###### Parsing routines + + +# Make TeXcode handle +sub TeXcode { + my ($texcode,$filename,$title)=@_; + my %TeX=(); + $TeX{'filename'}=$filename; + if (!defined $filename) { + $TeX{'filepath'}=''; + } elsif ($filename=~/^(.*[\\\/])[^\\\/]+$/) { + $TeX{'filepath'}=$1; + } else { + $TeX{'filepath'}=''; + } + if (defined $title) {} + elsif (defined $filename) {$title="FILE: ".$filename;} + else {$title="Word count";} + $TeX{'line'}=$texcode; + $TeX{'next'}=undef; + $TeX{'type'}=undef; + $TeX{'style'}=undef; + $TeX{'printstate'}=undef; + $TeX{'eof'}=0; + my $countsum=new_count($title); + $TeX{'countsum'}=$countsum; + my $count=new_count("_top_"); + $TeX{'count'}=$count; + inc_count(\%TeX,0); + my @countlist=(); + $TeX{'countlist'}=\@countlist; + $countsum->{'subcounts'}=\@countlist; + return \%TeX; +} + +# Parse LaTeX document +sub parse { + my ($tex)=@_; + if ($htmlstyle && $verbose) {print "<p class=parse>\n";} + while (!($tex->{'eof'})) { + parse_unit($tex,1); + } + if ($htmlstyle && $verbose) {print "</p>\n";} +} + +# Parse one block or unit +sub parse_unit { + # Status: + # 0 = exclude from count + # 1 = text + # 2 = header text + # 3 = float text + # -1 = float (exclude) + # -2 = strong exclude, ignore begin-end groups + # -3 = stronger exclude, do not parse macro parameters + # -9 = preamble (between \documentclass and \begin{document}) + my ($tex,$status,$end)=@_; + if (!defined $status) { + print_error("CRITICAL ERROR: Undefined parser status!"); + exit; + } elsif (ref($status) eq 'ARRAY') { + print_error("CRITICAL ERROR: Invalid parser status!"); + exit; + } + my $substat; + if ($showstates) { + if (defined $end) { + $tex->{'printstate'}=':'.$status.':'.$end.':'; + } else { + $tex->{'printstate'}=':'.$status.':'; + } + flush_next($tex); + } + while (defined (my $next=next_token($tex))) { + # parse next token; or tokens until match with $end + set_style($tex,"ignore"); + if ((defined $end) && ($end eq $next)) { + # end of unit + return; + } elsif (!defined $next) { + print_error("ERROR: End of file while waiting for ".$end); + return; + } + if ($status==-9 && $next eq '\begin' && $tex->{'line'}=~/^\{\s*document\s*\}/) { + # \begin{document} + $status=1; + } + if ($next eq '\documentclass') { + # starts preamble + set_style($tex,'document'); + gobble_option($tex); + gobble_macro_parms($tex,1); + while (!($tex->{'eof'})) { + parse_unit($tex,-9); + } + } elsif ($tex->{'type'}==666) { + # parse TC instructions + parse_tc($tex); + } elsif ($tex->{'type'}==1) { + # word + if ($status>0) { + inc_count($tex,$status); + set_style($tex,'word'.$status); + } + } elsif ($next eq '{') { + # {...} + parse_unit($tex,$status,'}'); + } elsif ($tex->{'type'}==3 && $status==-3) { + set_style($tex,'ignore'); + } elsif ($tex->{'type'}==3) { + # macro call + parse_macro($tex,$next,$status,$substat); + } elsif ($next eq '$') { + # math inline + parse_math($tex,$status,6,'$'); + } elsif ($next eq '$$') { + # math display (unless already in inlined math) + if (!(defined $end && $end eq '$')) { + parse_math($tex,$status,7,'$$'); + } + } + if (!defined $end) {return;} + } +} + +sub parse_macro { + my ($tex,$next,$status,$substat)=@_; + if (my $label=$BreakPoints{$next}) { + if ($tex->{'line'}=~ /^[*]?(\s*\[.*?\])*\s*\{(.+?)\}/ ) { + $label=$label.': '.$2; + } + add_subcount($tex,$label); + } + set_style($tex,$status>0?'command':'exclcommand'); + if ($next eq '\begin' && $status!=-2) { + parse_begin_end($tex,$status); + } elsif (($status==-1) && ($substat=$TeXfloatinc{$next})) { + # text included from float + set_style($tex,'command'); + gobble_macro_parms($tex,$substat); + } elsif ($status==-9 && defined ($substat=$TeXpreamble{$next})) { + # parse preamble include macros + set_style($tex,'command'); + if (defined $TeXheader{$next}) {inc_count($tex,4);} + gobble_macro_parms($tex,$substat,1); + } elsif ($status<0) { + # ignore + gobble_option($tex); + } elsif ($next eq '\(') { + # math inline + parse_math($tex,$status,6,'\)'); + } elsif ($next eq '\[') { + # math display + parse_math($tex,$status,7,'\]'); + } elsif ($next eq '\def') { + # ignore \def... + $tex->{'line'} =~ s/^([^\{]*)\{/\{/; + flush_next($tex); + print_style($1.' ','ignore'); + parse_unit($tex,-2); + } elsif (defined (my $addsuffix=$TeXfileinclude{$next})) { + # include file: queue up for parsing + parse_include_file($tex,$status,$addsuffix); + } elsif (defined ($substat=$TeXmacro{$next})) { + # macro: exclude options + if (defined $TeXheader{$next}) {inc_count($tex,4);} + gobble_macro_parms($tex,$substat,$status); + } elsif (defined ($substat=$TeXmacroword{$next})) { + # count macro as word (or a given number of words) + inc_count($tex,$status,$substat); + set_style($tex,'word'.$status); + } elsif ($next =~ /^\\[^\w\_]/) { + } else { + gobble_option($tex); + } +} + +sub parse_tc { + my ($tex)=@_; + my $next=$tex->{'next'}; + set_style($tex,'tc'); + flush_next($tex); + if (!($next=~s/^\%+TC:\s*(\w+)\s*// )) { + print_error('Warning: TC command should have format %TC:instruction [macro] [parameters]'); + return; + }; + my $instr=$1; + if ($instr=~/^(break)$/) { + if ($instr eq 'break') {add_subcount($tex,$next);} + } elsif ($next=~/^([\\]*\w+)\s+([^\s\n]+)(\s+([0-9]+))?/) { + # Format = TC:word macro + my $macro=$1; + my $param=$2; + my $option=$4; + if ($param=~/^\[([0-9,]+)\]$/) {$param=[split(',',$1)];} + if (($instr eq 'macro') || ($instr eq 'exclude')) {$TeXmacro{$macro}=$param;} + elsif ($instr eq 'header') {$TeXheader{$macro}=$param;$TeXmacro{$macro}=$param;} + elsif ($instr eq 'macroword') {$TeXmacroword{$macro}=$param;} + elsif ($instr eq 'preambleinclude') {$TeXpreamble{$macro}=$param;} + elsif ($instr eq 'group') { + $TeXmacro{'begin'.$macro}=$param; + $TeXgroup{$macro}=$option; + } + elsif ($instr eq 'floatinclude') {$TeXfloatinc{$macro}=$param;} + elsif ($instr eq 'fileinclude') {$TeXfileinclude{$macro}=$param;} + elsif ($instr eq 'breakmacro') {$BreakPoints{$macro}=$param;} + else {print_error("Warning: Unknown TC command: ".$instr);} + } elsif ($instr eq 'ignore') { + tc_ignore_input($tex); + } else { + print_error("Warning: Invalid TC command format: ".$instr); + } +} + +sub tc_ignore_input { + my ($tex)=@_; + set_style($tex,'ignore'); + parse_unit($tex,-3,"%TC:endignore"); + set_style($tex,'tc'); + flush_next($tex); +} + +sub parse_math { + my ($tex,$status,$substat,$end)=@_; + my $localstyle=$status>0 ? 'mathgroup' : 'exclmath'; + if ($status>0) {inc_count($tex,$substat);} + set_style($tex,$localstyle); + parse_unit($tex,0,$end); + set_style($tex,$localstyle); +} + +sub parse_begin_end { + my ($tex,$status)=@_; + my $localstyle=$status>0 ? 'grouping' : 'exclgroup'; + flush_style($tex,$localstyle); + gobble_option($tex); + my $groupname; + if ($tex->{'line'} =~ s/^\{\s*([^\{\}]+)\s*\*?\}[ \t\r\f]*//) { + # gobble group type + $groupname=$1; + print_style('{'.$1.'}',$localstyle); + my $next='begin'.$1; + if (defined (my $substat=$TeXmacro{$next})) { + gobble_macro_parms($tex,$substat); + } + } else { + print_error("Warning: BEGIN group without type."); + } + # find group status (or leave unchanged) + my $substat; + defined ($substat=$TeXgroup{$1}) || ($substat=$status); + if ($status<=0 && $status<$substat) {$substat=$status;} + if (($status>0) && ($substat==-1)) { + # Count float + inc_count($tex,5); + } + if ($status>0 and $substat>3) { + # count item, exclude contents + inc_count($tex,$substat); + $substat=0; + } + parse_unit($tex,$substat,'\end'); + if ($tex->{'line'} =~ s/^\{\s*([^\{\}]+)\s*\}[ \t\r\f]*//) { + # gobble group type + flush_style($tex,$localstyle); + print_style('{'.$1.'}',$localstyle); + } else { + print_error("Warning: END group without type while waiting to end ".$groupname."."); + } +} + +sub parse_include_file { + my ($tex,$status,$addsuffix)=@_; + $tex->{'line'} =~ s/^\{([^\{\}\s]+)\}// || + $tex->{'line'} =~ s/^\s*([^\{\}\%\\\s]+)// || + return; + flush_next($tex); + if ($status>0) { + print_style($&,'fileinclude'); + my $fname=$1; + if ($addsuffix==2) {$fname.='.tex';} + elsif ($addsuffix==1 && ($fname=~/^[^\.]+$/)) {$fname.='.tex';} + if ($includeTeX) {add_file_to_list($fname);} + } else { + print_style($&,'ignored'); + } +} + +sub gobble_option { + my $tex=shift @_; + flush_next($tex); + foreach my $pattern (@MacroOptionPatterns) { + if ($tex->{'line'}=~s/^($pattern)//) { + print_style($1,'option'); + return $1; + } + } + return undef; +} + +sub gobble_options { + while (gobble_option(@_)) {} +} + +sub gobble_macro_modifier { + my $tex=shift @_; + flush_next($tex); + if ($tex->{'line'} =~ s/^\*//) { + print_style($1,'option'); + return $1; + } + return undef; +} + +sub gobble_macro_parms { + my ($tex,$parm,$oldstat)=@_; + my $i; + if (ref($parm) eq 'ARRAY') { + $i=scalar @{$parm}; + } else { + $i=$parm; + $parm=[0,0,0,0,0,0,0,0,0]; + } + if ($i>0) {gobble_macro_modifier($tex);} + gobble_options($tex); + for (my $j=0;$j<$i;$j++) { + parse_unit($tex,new_status($parm->[$j],$oldstat)); + gobble_options($tex); + } +} + +sub new_status { + my ($substat,$old)=@_; + if (!defined $old) {return $substat;} + if ($old==-3 || $substat==-3) {return -3;} + if ($old==-2 || $substat==-2) {return -2;} + if ($old==0 || $substat==0) {return 0;} + if ($old==-9 || $substat==-9) {return -9;} + if ($old>$substat) {return $old;} + return $substat; +} + +sub next_token { + my $tex=shift @_; + my ($next,$type); + if (defined $tex->{'next'}) {print_style($tex->{'next'}.' ',$tex->{'style'});} + $tex->{'style'}=undef; + while (defined ($next=get_next_token($tex))) { + $type=$tex->{'type'}; + if ($type==0) { + print_style($next,'comment'); + } elsif ($type==9) { + if ($verbose>0) {line_return(1,$tex);} + } else { + return $next; + } + } + return $next; +} + + +sub get_next_token { + # Token (or token group) category: + # 0: comment + # 1: word (or other forms of text or text components) + # 2: symbol (not word, e.g. punctuation) + # 3: macro + # 4: curly braces {} + # 5: brackets [] + # 6: maths + # 9: line break in file + # 999: end of line or blank line + # 666: TeXcount instruction (%TC:instruction) + my $tex=shift @_; + my $next; + (defined ($next=get_token($tex,'\%+TC:\s*endignore\b[^\r\n]*',666))) && return "%TC:endignore"; + (defined ($next=get_token($tex,'\%+TC:[^\r\n]*',666))) && return $next; + (defined ($next=get_token($tex,'\%[^\r\n]*',0))) && return $next; + (defined ($next=get_token($tex,'(\r|\n|\r\n)',9))) && return $next; + (defined ($next=get_token($tex,'\\\\[\{\}]',2))) && return $next; + foreach my $pattern (@WordPatterns) { + (defined ($next=get_token($tex,$pattern,1))) && return $next; + } + (defined ($next=get_token($tex,'[\"\'\`:\.,\(\)\[\]!\+\-\*=/\^\_\@\<\>\~\#\&]',2))) && return $next; + (defined ($next=get_token($tex,'\\\\([a-zA-Z_]+|[^a-zA-Z_])',3))) && return $next; + (defined ($next=get_token($tex,'[\{\}]',4))) && return $next; + (defined ($next=get_token($tex,'[\[\]]',5))) && return $next; + (defined ($next=get_token($tex,'\$\$',6))) && return $next; + (defined ($next=get_token($tex,'\$',6))) && return $next; + (defined ($next=get_token($tex,'.',999))) && return $next; + (defined ($next=get_token($tex,'[^\s]+',999))) && return $next; + $tex->{'eof'}=1; + return undef; +} + +sub get_token { + my ($tex,$regexp,$type)=@_; + if (!defined $regexp) {print_error("ERROR in get_token: undefined regex.");} + if (!defined $tex->{'line'}) {print_error("ERROR in get_token: undefined tex-line. ".$tex->{'next'});} + if ( $tex->{'line'} =~ s/^($regexp)[ \t\r\f]*// ) { + $tex->{'next'}=$1; + $tex->{'type'}=$type; + return $1; + } + return undef; +} + +###### Count handling routines + + +sub new_count { + my ($title)=@_; + my @cnt=(0,0,0,0,0,0,0,0); + my %count=('count'=>\@cnt,'title'=>$title); + # files, text words, header words, float words, + # headers, floats, math-inline, math-display; + return \%count; +} + +sub inc_count { + my ($tex,$type,$value)=@_; + my $count=$tex->{'count'}; + if (!defined $value) {$value=1;} + ${$count->{'count'}}[$type]+=$value; +} + +sub get_count { + my ($count,$type)=@_; + return ${$count->{'count'}}[$type]; +} + +sub total_count { + my ($count)=@_; + my $sum=0; + for (my $i=scalar(@sumweights);$i-->0;) { + $sum+=get_count($count,$i+1)*$sumweights[$i]; + } + return $sum; +} + +sub print_count { + my ($count,$header)=@_; + if ($briefsum && @sumweights) { + print_count_total($count,$header); + } elsif ($briefsum) { + if ($htmlstyle) {print "<p class='briefcount'>";} + print_count_brief($count,$header); + if ($htmlstyle) {print "</p>\n";} + } else { + print_count_details($count,$header); + } +} + +sub print_count_with_header { + my ($count,$header)=@_; + if (!defined $header) {$header=$count->{'title'};} + if (!defined $header) {$header="";} + return $count,$header; +} + +sub print_count_total { + my ($count,$header)=print_count_with_header(@_); + if ($htmlstyle) {print "<p class='count'>".$header;} + print total_count($count); + if ($htmlstyle) {print "</p>\n";} + else {print ": ".$header;} +} + +sub print_count_brief { + my ($count,$header)=print_count_with_header(@_); + my $cnt=$count->{'count'}; + print ${$cnt}[1]."+".${$cnt}[2]."+".${$cnt}[3]. + " (".${$cnt}[4]."/".${$cnt}[5]."/".${$cnt}[6]."/".${$cnt}[7].") ". + $header; +} + +sub print_count_details { + my ($count,$header)=print_count_with_header(@_); + if ($htmlstyle) {print "<dl class='count'>\n";} + if (defined $header) { + formatprint($header."\n",'dt','header'); + } + if (get_count($count,0)>1) { + formatprint($countlabel[0].': ','dt'); + formatprint(get_count($count,0)."\n",'dd'); + } + if (@sumweights) { + formatprint('Sum count: ','dt'); + formatprint(total_count($count)."\n",'dd'); + } + for (my $i=1;$i<8;$i++) { + formatprint($countlabel[$i].': ','dt'); + formatprint(get_count($count,$i)."\n",'dd'); + } + my $subcounts=$count->{'subcounts'}; + if ($showsubcounts && defined $subcounts && scalar(@{$subcounts})>1) { + formatprint("Subcounts: text+headers+captions (#headers/#floats/#inlines/#displayed)\n",'dt'); + foreach my $subcount (@{$subcounts}) { + if ($htmlstyle) {print "<dd class='briefcount'>";} + print_count_brief($subcount); + if ($htmlstyle) {print "</dd>";} + print "\n"; + } + } + if ($htmlstyle) {print "</dl>\n";} +} + +sub add_count { + my ($a,$b)=@_; + for (my $i=0;$i<8;$i++) { + ${$a->{'count'}}[$i]+=${$b->{'count'}}[$i]; + } +} + +sub add_subcount { + my ($tex,$title)=@_; + add_count($tex->{'countsum'},$tex->{'count'}); + push @{$tex->{'countlist'}},$tex->{'count'}; + $tex->{'count'}=new_count($title); + return $tex->{'countsum'}; +} + +###### Printing routines + + +sub set_style { + my ($tex,$style)=@_; + if (!(($tex->{'style'}) && ($tex->{'style'} eq '-'))) {$tex->{'style'}=$style;} +} + +sub flush_style { + my ($tex,$style)=@_; + set_style($tex,$style); + flush_next($tex); +} + +sub line_return { + my ($blank,$tex)=@_; + if ($blank>$blankline) { + if ((defined $tex) && @sumweights) { + my $num=total_count($tex->{'count'}); + print_style(" [".$num."]","sumcount"); + } + linebreak(); + $blankline++; + } +} + +sub linebreak { + if ($htmlstyle) {print "<br>\n";} else {print "\n";} +} + +sub print_style { + my ($text,$style,$state)=@_; + (($verbose>=0) && (defined $text) && (defined $style)) || return 0; + my $colour; + ($colour=$STYLE{$style}) || return; + if (($colour) && !($colour eq '-')) { + print_with_style($text,$style,$colour); + if ($state) { + print_style($state,'state'); + } + $blankline=-1; + return 1; + } else { + return 0; + } +} + +sub print_error { + my $text=shift @_; + $errorcount++; + if ($verbose>=0) { + line_return(1); + print_style("!!! ".$text." !!!",'error'); + line_return(1); + } +} + +sub formatprint { + my ($text,$tag,$class)=@_; + my $break=($text=~s/\n$//); + if ($htmlstyle && defined $tag) { + print '<'.$tag; + if ($class) {print " class='".$class."'";} + print '>'.$text.'</'.$tag.'>'; + } else { + print $text; + } + if ($break) {print "\n";} +} + +sub flush_next { + my $tex=shift @_; + if (defined $tex->{'next'}) { + print_style($tex->{'next'}.' ',$tex->{'style'},$tex->{'printstate'}); + } + $tex->{'printstate'}=undef; + $tex->{'style'}='-'; +} + + +# Close the output, e.g. adding HTML tail +sub Close_Output { + if ($htmlstyle>1) { + html_tail(); + } +} + + +# Report if there were any errors occurring during parsing +sub Report_ErrorCount { + if ($errorcount==0) {return;} + if ($briefsum && $totalflag) {print " ";} + if ($htmlstyle) { + print_error("Errors:".$errorcount,"p","error"); + } else { + print "(errors:".$errorcount.")"; + } +} + + +sub print_help_style { + if ($verbose<=0) {return;} + formatprint("Format/colour codes of verbose output:","h2"); + print "\n\n"; + if ($htmlstyle) {print "<p class='stylehelp'>";} + help_style_line('Text which is counted',"word1","counted as text words"); + help_style_line('Header and title text',"word2","counted as header words"); + help_style_line('Caption text and footnotes',"word3","counted as caption words"); + help_style_line("Ignored text or code","ignore","excluded or ignored"); + help_style_line('\documentclass',"document","document start, beginning of preamble"); + help_style_line('\macro',"command","macro not counted, but parameters may be"); + help_style_line('\macro',"exclcommand","macro in excluded region"); + help_style_line("[Macro options]","option","not counted"); + help_style_line('\begin{group} \end{group}',"grouping","begin/end group"); + help_style_line('\begin{group} \end{group}',"exclgroup","begin/end group in excluded region"); + help_style_line('$ $',"mathgroup","counted as one equation"); + help_style_line('$ $',"exclmath","equation in excluded region"); + help_style_line('% Comments',"comment","not counted"); + help_style_line('%TC:TeXcount instructions',"tc","not counted"); + help_style_line("File to include","fileinclude","not counted but file may be counted later"); + if ($showstates) { + help_style_line('[state]',"state","internal TeXcount state"); + } + if (@sumweights) { + help_style_line('[sumcount]',"sumcount","cumulative sum count"); + } + help_style_line("ERROR","error","TeXcount error message"); + if ($htmlstyle) {print "</p>";} + print "\n\n"; +} + +sub help_style_line { + my ($text,$style,$comment)=@_; + if ($htmlstyle) { + $comment=" .... ".$comment; + } else { + $comment=" .... ".$comment; + } + if (print_style($text,$style)) { + print $comment; + linebreak(); + } +} + +# Print output style codes if conditions are met +sub conditional_print_help_style { + if ($showcodes) {print_help_style();} + return $showcodes; +} + +###### HTML routines + + + +sub html_head { + print "<html>\n<head>"; + if ($utf8flag) { + print "\n<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">"; + } + print ' +<style> +<!-- +body {width:auto;padding:5;margin:5;} +.error {font-weight:bold;color:#f00;font-style:italic;} +.word1,.word2,.word3 {color: #009;} +.word2 {font-weight: 700;} +.word3 {font-style: italic;} +.command {color: #c00;} +.exclcommand {color: #f99;} +.option {color: #cc0;} +.grouping, .document {color: #900; font-weight:bold;} +.mathgroup {color: #090;} +.exclmath {color: #6c6;} +.ignore {color: #999;} +.exclgroup {color:#c66;} +.tc {color: #999; font-weight:bold;} +.comment {color: #999; font-style: italic;} +.state {color: #990; font-size: 70%;} +.sumcount {color: #999; font-size: 80%;} +.fileinclude {color: #696; font-weight:bold;} +dl.count {background: #cfc; color: 009;} +dl.count dt.header {font-weight: bold; font-style: italic; float: none;} +dl.count dt {clear: both; float: left; margin-right: .5em;} +dl.count dd {font-weight: bold;} +dl.count dd.briefcount {font-weight: 700; clear: both; font-size:80%; font-weight:normal; margin-left:8pt;} +.warning {color: #c00; font-weight: 700;} +.parse, .count, .stylehelp, .filegroup {border: solid 1px #999; margin: 0pt; padding: 4pt;} +.parse {font-size: 80%; background: #eef;} +.parse {border-bottom:none;} +.stylehelp {font-size: 80%; background: #ffc; margin-bottom: 8pt;} +.filegroup {background: #efe; margin-bottom: 8pt;} +--> +</style> +</head> +<body> +<h1>LaTeX word count</h1> +'; +} + +sub html_tail { + print '</body></html>'; +} + +###### Help routines + + + +sub print_version { + print "TeXcount version ".$versionnumber.", ".$versiondate.'.'; +} + +sub print_syntax { + print ' +Syntax: TeXcount.pl [options] files + +Options: + -relaxed Uses relaxed rules for word and option handling: + i.e. allows more general cases to be counted as + either words or macros. + -v Verbose (same as -v3) + -v0 Do not present parsing details + -v1 Verbose: print parsed words, mark formulae + -v2 More verbose: also print ignored text + -v3 Even more verbose: include comments and options + -v4 Same as -v3 -showstate + -showstate Show internal states (with verbose) + -brief Only prints a brief, one line summary of counts + -q, -quiet Quiet mode, no error messages (use is discouraged!) + -sum, -sum= Make sum of all word and equation counts. May also + use -sum=#[,#] with up to 7 numbers to indicate how + each of the counts (text words, header words, caption + words, #headers, #floats, #inlined formulae, + #displayed formulae) are summed. The default sum (if + only -sum is used) is the same as -sum=1,1,1,0,0,1,1. + -sub, -sub= Generate subcounts. Option values are none, part, + chapter, section or subsection. Default (-sub) is set + to subsection, whereas unset is none. (Alternative + option name is -subcount.) + -nc, -nocol No colours (colours require ANSI) + -html Output in HTML format + -htmlcore Only HTML body contents + -inc Include tex files included in the document + -noinc Do not include included tex files (default) + -total Do not give sums per file, only total sum. + -1 Same as -brief and -total. Ensures there is only one + line of output. If used in conjunction with -sum, the + output will only be the total number. (NB: Character + is the number one, not the letter L.) + -dir, -dir= Specify the working directory using -dir=path. + Remember that the path must end with \ or /. If only + -dir is used, the directory of the parent file is used. + -utf8, -unicode Turns on Unicode (UTF-8) for input and output. This + is automatic with -chinese, and is required to handle + e.g. Korean text. Note that the TeX file must be save + in UTF-8 format (not e.g. GB2312 or Big5), or the + result will be unpredictable. + -ch, -chinese, -zhongwen Turns on support for Chinese characters. + TeXcount will then count each Chinese character as a + word. Automatically turns on -utf8. + -jp, -japanese Turns on support for Japanese characters. TeXcount + will count each Japanese character (kanji, hiragana, + and katakana) as one word, i.e. not do any form of + word segmentation. Automatically turns on -utf8. + -codes Display output style code overview and explanation. + This is on by default. + -nocodes Do not display output style code overview. + -h, -?, --help, /? Help + --version Print version number + --license License information +'; +} + +sub print_help { + print ' +*************************************************************** +* TeXcount.pl '.$versionnumber.', '.$versiondate.' +* + +Count words in TeX and LaTeX files, ignoring macros, tables, +formulae, etc. +'; + print_syntax(); + print ' +The script counts words as either words in the text, words in +headers/titles or words in floats (figure/table captions). +Macro options (i.e. \marco[...]) are ignored; macro parameters +(i.e. \macro{...}) are counted or ignored depending on the +macro, but by default counted. Begin-end groups are by default +ignored and treated as \'floats\', though some (e.g. center) are +counted. + +Unless -nocol (or -nc) has been specified, the output will be +colour coded. Counted text is coloured blue with headers are in +bold and in HTML output caption text is italicised. + +Mathematical formulae are not counted as words, but are instead +counted separately with separate counts for inlined formulae +and displayed formulae. Similarly, the number of headers and +the number of \'floats\' are counted. Note that \'float\' is used +here to describe anything defined in a begin-end group unless +explicitly recognized as text or mathematics. + +The verbose options (-v1, -v2, -v3, showstate) produces output +indicating how the text has been interpreted. Check this to +ensure that words in the text has been interpreted as such, +whereas mathematical formulae and text/non-text in begin-end +groups have been correctly interpreted. + +Parsing instructions may be passed to TeXcount using comments +in the LaTeX files on the format + %TC:instruction arguments +where valid instructions for setting parsing rules, typically +set at the start of the document (applies globally), are: + %TC:macro [macro] [param.states] + macro handling rule, no. of and rules for parameters + %TC:macroword [macro] [number] + macro counted as a given number of words + %TC:header [macro] [param.states] + header macro rule, as macro but counts as one header + %TC:breakmacro [macro] [label] + macro causing subcount break point + %TC:group [name] [parsing-state] + begin-end-group handling rule + %TC:floatinclude [macro] [param.states] + as macro, but also counted inside floats + %TC:preambleinclude [macro] [param.states] + as macro, but also counted inside the preamble + %TC:fileinclue [macro] [rule] + file include, add .tex if rule=2, not if rule=0 +The [param.states] is used to indicate the number of parameters +used by the macro and the rules of handling each of these: format +is [#,#,...,#] with one number for each parameter, and main rules +are 0 to ignore and 1 to count as text. Parsing instructions +which may be used anywhere are: + %TC:ignore start block to ignore + %TC:endignore end block to ignore + %TC:break [title] add subcount break point here +See the documentation for more details. + +Unix hint: Use \'less -r\' instead of just \'less\' to view output: +the \'-r\' option makes less treat text formating codes properly. + +Windows hint: If your Windows interprets ANSI colour codes, lucky +you! Otherwise, use the -nocol (or -nc) option with the verbose +options or the output will be riddled with colour codes. Instead, +you can use -html to produce HTML code, write this to file and +view with your favourite browser. +'; + print_reference(); +} + +sub print_reference { + print ' +The TeXcount script is copyright of Einar Andreas Rødland (2008) +and published under the LaTeX Project Public License. + +For more information about the script, e.g. news, updates, help, +usage tips, known issues and short-comings, go to + http://folk.uio.no/einarro/Comp/texwordcount.html +or go to + http://folk.uio.no/einarro/Services/texcount.html +to access the script as a web service. Feedback such as problems +or errors can be reported to einarro@ifi.uio.no. +'; +} + +sub print_license { + print 'TeXcount version '.$versionnumber.' + +Copyright 2008 Einar Andreas Rødland + +The TeXcount script is published under the LaTeX Project Public +License (LPPL) + http://www.latex-project.org/lppl.txt +which grants you, the user, the right to use, modify and distribute +the script. However, if the script is modified, you must change its +name or use other technical means to avoid confusion. + +The script has LPPL status "maintained" with Einar Andreas +Rødland being the current maintainer. +'; +} + diff --git a/bin/torrentSort.py b/bin/torrentSort.py new file mode 100644 index 0000000..e284a88 --- /dev/null +++ b/bin/torrentSort.py @@ -0,0 +1,220 @@ +#! /usr/bin/python +# simple python script to sort torrents +# modifications by xkonni +# - added folder sorting +# - added htdigest authorisation +# - fixed some minor glitches +# +# original version by jonassw from +# http://forum.xbmc.org/showthread.php?t=60749 + +# << DOCUMENTATION +# +# i) lighttpd configuration for htdigest +# server.modules += ( "mod_auth" ) +# auth.backend = "htdigest" +# auth.backend.htdigest.userfile = "/etc/lighttpd/auth" +# auth.debug = 2 +# auth.require = ( "/RPC2" => +# ( +# "method" => "digest", +# "realm" => "REALM", +# "require" => "valid-user" +# ) +# ) +# ii) i recommend starting this with a basic cron job you may find other +# suggestions like when rtorrent finishes hashing, but for me this caused +# lockups when starting rtorrent (as all downloads return hash_ok) +# +# */5 * * * * for i in /mnt/torrent/tv/*; do torrentSort.py $i > /dev/null; done +# +# DOCUMENTATION >> + + +import xmlrpclib, os, sys, re, shutil + + +class HTTPSDigestTransport(xmlrpclib.SafeTransport): + """ +Transport that uses urllib2 so that we can do Digest authentication. +Based upon code at http://bytes.com/topic/python/answers/509382-solution-xml-rpc-over-proxy +""" + + def __init__(self, username, pw, realm, verbose = None, use_datetime=0): + self.__username = username + self.__pw = pw + self.__realm = realm + self.verbose = verbose + self._use_datetime = use_datetime + + def request(self, host, handler, request_body, verbose): + import urllib2 + + url='https://'+host+handler + if verbose or self.verbose: + print "ProxyTransport URL: [%s]"%url + + request = urllib2.Request(url) + request.add_data(request_body) + # Note: 'Host' and 'Content-Length' are added automatically + request.add_header("User-Agent", self.user_agent) + request.add_header("Content-Type", "text/xml") # Important + + # setup digest authentication + authhandler = urllib2.HTTPDigestAuthHandler() + authhandler.add_password(self.__realm, url, self.__username, self.__pw) + opener = urllib2.build_opener(authhandler) + + #proxy_handler=urllib2.ProxyHandler() + #opener=urllib2.build_opener(proxy_handler) + f=opener.open(request) + return(self.parse_response(f)) + + +def adoptionCandidates(basedir, filename): + dirs = filter(lambda x : os.path.isdir(os.path.join(basedir, x)), os.listdir(basedir)) + + #set filename to lowercase for string comparisons + filename=filename.lower() + + ignoredPhrases = ['-','_'] + + candidates = [] + for dir in dirs: + dirParts = dir.split() + score = 0 + requiredScore = 0 + + for part in dirParts: + if ignoredPhrases.count(part) > 0: + continue + requiredScore = requiredScore + 1 + + #force lower case for string comparison. + part=part.lower() + # replace "'" with "" and add word to list + repPart = part.replace('\'','') + if repPart != part: + dirParts.append(repPart) + requiredScore -= 1 + if filename.find(part) >= 0: + score = score + 1 + if score == requiredScore: + candidates.append( (os.path.join(basedir, dir), score) ) + + return candidates + +def getSeasonNumber(filename): + patterns = [ + '.*S(\d+)E(\d+).*', + '.*S(\d+)(\.)?E(\d+).*', # hopefully matches series.s01.e05.avi + '(\d+)x(\d+).*', + '(\d+)(\d+)(\d+).*' + # commented out regex thought below Season was '4' not 14. Def better way of doing that + #top_gear.14x04.720p_hdtv_x264-fov.mkv + #'.*(\d+)x(\d+).*' + ] + + for pattern in patterns: + p = re.compile(pattern, re.I) + g = p.findall(orphanFile) + if len(g) > 0: + season = int(g[0][0]) + return season + return None + + +def getRtorrentId(filename): + downloads = rtorrent.download_list('') + for dl in downloads: + rfile = rtorrent.d.get_base_filename(dl) + if rfile == filename: + return dl + +# << CONFIGURATION +# i) FOLDER SETTINGS +showLocations = ['/mnt/media/tv'] +allowedSourceLocation = '/mnt/media/torrent/complete/tv' + +# ii) CONNECTION SETTINGS +# - using htdigest authorisation +digestTransport = HTTPSDigestTransport("username", "password", "realm") +rtorrent = xmlrpclib.ServerProxy('http://localhost',transport=digestTransport) +# - not using authorisation +# rtorrent = xmlrpclib.ServerProxy('http://localhost') + +# CONFIGURATION >> + +print '--------------- BEGIN ---------------' +orphanFile = sys.argv[1] +if os.path.isdir(orphanFile): + (fpath, fname) = os.path.split(orphanFile) + fname = os.path.relpath(orphanFile, allowedSourceLocation) + +else: + (fpath, fname) = os.path.split(orphanFile) +print 'File path: %s' % fpath +print 'File name: %s' % fname + +candidates = [] + +if not orphanFile.startswith(allowedSourceLocation): + print 'STOP! This file is not located in %s' % allowedSourceLocation + exit() + +print 'Attempting to find a home for file %s' % orphanFile + +for location in showLocations: + candidates.extend(adoptionCandidates(location, fname)) + +candidates.sort(lambda (da, sa), (db, sb): sb-sa) + +if len(candidates) <= 0: + print 'No one wanted this file :(' + exit() + +for (dir, score) in candidates: + print 'Candidate: %s with score %i' % (dir, score) + +print 'Winner is %s with score %i' % candidates[0] + +if os.path.isdir(orphanFile): + finaldir = candidates[0][0] + +else: + # Determine Season and Episode number + season = getSeasonNumber(fname) + if not season: + print 'STOP! Season could not be determined.' + exit() + + print 'Season was determined to be %i' % season + finaldir = os.path.join(candidates[0][0], 'Season %s' % season) + + # Check if season folder is present + if not os.path.isdir(finaldir): + print 'Season dir doesn\'t exist. Creating now' + os.mkdir(finaldir) + + if os.path.isfile(os.path.join(finaldir, fname)): + print 'error: file already exists, exiting' + sys.exit(1) + + print 'Will move file to %s' % finaldir + + +print 'Requesting id from rtorrent' +rid = getRtorrentId(fname) +print '%s was resolved to rtorrent id: %s' % (fname, rid) + +print 'Pausing rtorrent' +rtorrent.d.pause(rid) + +print 'Updating rtorrent' +rtorrent.d.set_directory(rid, finaldir) + +print 'Moving file' +shutil.move(orphanFile, finaldir) + +print 'Resuming rtorrent' +rtorrent.d.resume(rid) |