Untitled

mail@pastecode.io avatar
unknown
plain_text
2 years ago
14 kB
1
Indexable
Never
#!/bin/bash
# This script gets actives torrents list from Transmission, playing sessions from Jellyfin,  and tells rsync to copy them from array to cache drive.
# It also cleans oldest modified files by rsyncing them back to array (in case of modification).
# Hardlink are preserved
#
# By Reynald - 06 may 2020 - mailto:reynald.rollet@gmail.com
# v.0.5.14

# settings
{
        #Transmission

        #Jellyfin
        JELLYFIN_TOKEN="redacted"
        JELLYFIN_HOST="redacted:8096"
        JELLYFIN_MAX_CACHED_SESSIONS=10

        #Rsync path
        STORAGE_PATH="/mnt/disk1/media/"
        CACHE_PATH="/mnt/cache/media/"
        CACHE_DISK="/dev/sdf1"
        CACHE_MIN_FREE_SPACE_PCT="90"
        CACHE_MAX_FREE_SPACE_PCT="85"
        
        #Parameters
        LOG_MAX_SIZE=5000000
        NOISY_HOUR_START=9
        NOISY_HOUR_STOP=21
        
        #Options (set to true or false)
        TRANSMISSION_ENABLED=false
        JELLYFIN_ENABLED=true
        JELLYFIN_CACHE_NEXT_EPISODE=true
        JELLYFIN_CACHE_SEASON_TILL_END=true
        VERBOSE=1 #0=Error; 1=Info; 2=More_Info; 3=Debug
}
##### No modification below this line #####

sys_checks() {
# lock
        if [[ -f /var/lock/smart-cache_jellyfin_transmission ]]
		then
                echo "Error: Script already running"
                exit 1
        else
                touch /var/lock/smart-cache_jellyfin_transmission
                [[ $VERBOSE -ge 2 ]] && echo "Welcome to $0"
        fi

# check that path are mounted
        if [[ ! -d $STORAGE_PATH ]] || [[ ! -d $CACHE_PATH ]];
        then
                echo "Error: Paths are not accessibles"
                rm /var/lock/smart-cache_jellyfin_transmission
                exit 1
        fi

# cut log
        LOG_FILE=$(echo $0 | sed 's|\/script|\/log.txt|')
		LOG_SIZE=$(stat -c %s $LOG_FILE)
		[[ $VERBOSE -ge 1 ]] && echo "Info: Log size is $LOG_SIZE"
        if [[ $LOG_SIZE -ge $LOG_MAX_SIZE ]]
        then
            [[ $VERBOSE -ge 1 ]] && echo "Info: Emptying log file"
            echo "" > $LOG_FILE
        fi        
		[[ $VERBOSE -ge 1 ]] && echo ""
}

#######################
# Transfers functions #
#######################
noisy_hours() {
# return 0 if time in noisy hour range
    if [[ $(date '+%-H') -ge $NOISY_HOUR_START ]] && [[ $(date +%-H) -le $NOISY_HOUR_STOP ]]
    then
        return 0
    else
        return 1
    fi
}
rsync_transfer() {
# get files and path
	SOURCE_FILE=$1
	DEST_FILE=$2
	SOURCE_PATH=$3
	DEST_PATH=$4
	RS_OPTIONS=$5
	[[ $VERBOSE -ge 3 ]] && echo " --- Debug:Rsync_transfer function parameters:"
    [[ $VERBOSE -ge 3 ]] && echo " ---- Debug: Source file: $SOURCE_FILE"
    [[ $VERBOSE -ge 3 ]] && echo " ---- Debug: Dest.  file: $DEST_FILE"
    [[ $VERBOSE -ge 3 ]] && echo " ---- Debug: Source path: $SOURCE_PATH"
    [[ $VERBOSE -ge 3 ]] && echo " ---- Debug: Dest.  path: $DEST_PATH"
    [[ $VERBOSE -ge 3 ]] && echo " ---- Debug: Options    : $RS_OPTIONS"

# check if original file exist
        if [[ ! -f "${SOURCE_FILE}" ]] && [[ ! -f "${DEST_FILE}" ]]
        then
            echo " --- Error: Files:"
			echo " ${SOURCE_FILE}"
			echo " ${DEST_FILE}"
			echo " does not exist"
            return 1
        elif [[ "${DEST_FILE}" = "${DEST_PATH}" ]] || [[ "${SOURCE_FILE}" = "${SOURCE_PATH}" ]]
        then
            
            echo " --- Error: Cannot sync root path!"
            return 1
        elif [[ ! -f "${SOURCE_FILE}" ]] && [[ "${DEST_PATH}" = "${CACHE_PATH}" ]] && [[ -f "${DEST_FILE}" ]]
        then
            if noisy_hours
            then
                [[ $VERBOSE -ge 2 ]] && echo " -- Info: File is on cache only. Inside noisy hours, sending to storage"
                rsync_transfer "${DEST_FILE}" "${SOURCE_FILE}" "${DEST_PATH}" "${SOURCE_PATH}"
            else
                [[ $VERBOSE -ge 2 ]] && echo " --- Warning: File is on cache only. Outside of noisy hours, doing nothing"
            fi
            return
        elif [[ -f "${DEST_FILE}" ]] && [[ "${DEST_PATH}" = "${CACHE_PATH}" ]]
        then
            [[ $VERBOSE -ge 2 ]] && echo " --- Info: File already cached"
            return
        fi


# get dir
        SOURCE_DIR=$(dirname "${SOURCE_FILE}")
        DEST_DIR=$(dirname "${DEST_FILE}")

# sync file
        mkdir -p "${DEST_DIR}"
        [[ $VERBOSE -ge 1 ]] && echo " --- Info: Syncing ${SOURCE_FILE}"
        [[ $VERBOSE -ge 2 ]] && echo " --- Info: Syncing ${SOURCE_FILE} to ${DEST_FILE}"
        rsync -aHq "${SOURCE_FILE}" "${DEST_FILE}"
		if [[ ! $? -eq 0 ]] 
		then
			echo " --- Error: cannot rsync ${SOURCE_FILE}"
			echo " to ${DEST_FILE}"
			return 1
		fi
# sync hardlinks
        hardlink_transfer "${SOURCE_FILE}" "${DEST_FILE}" "${SOURCE_PATH}" "${DEST_PATH}" "${RS_OPTIONS}"

# remove original file if requested
        if [[ "${RS_OPTIONS}" = "--remove-source-files" ]] && $RSYNC_RESULT
        then
            [[ $VERBOSE -ge 2 ]] && echo " --- Info: Delete ${SOURCE_FILE}"
            rm "${SOURCE_FILE}"
        fi
}
hardlink_transfer() {
# get files and path
	SOURCE_FILE=$1
	DEST_FILE=$2
	SOURCE_PATH=$3
	DEST_PATH=$4
	RS_OPTIONS=$5
	
	[[ $VERBOSE -ge 3 ]] && echo " -- Debug: Hardlink_transfer function parameters:"
    [[ $VERBOSE -ge 3 ]] && echo " --- Debug: Source file: $SOURCE_FILE"
    [[ $VERBOSE -ge 3 ]] && echo " --- Debug: Dest.  file: $DEST_FILE"
    [[ $VERBOSE -ge 3 ]] && echo " --- Debug: Source path: $SOURCE_PATH"
    [[ $VERBOSE -ge 3 ]] && echo " --- Debug: Dest.  path: $DEST_PATH"
    [[ $VERBOSE -ge 3 ]] && echo " --- Debug: Options    : $RS_OPTIONS"


# get hardlinks from source
        find "${SOURCE_PATH}" -samefile "${SOURCE_FILE}" | while read SOURCE_LINK
        do
                if [[ ! "${SOURCE_FILE}" = "${SOURCE_LINK}" ]];
                then
                        DEST_LINK=$(echo ${SOURCE_LINK} | sed "s|`echo ${SOURCE_PATH}`|`echo ${DEST_PATH}`|")
                        # if DEST is not a hardlink
                        if [[ -f "${DEST_LINK}" ]] && [[ ! `stat -c %h "${DEST_LINK}"` -gt 1 ]]
                        then
                            [[ $VERBOSE -ge 2 ]] && echo " --- Info: Creating hardlink: ${DEST_LINK}"
                            # rm destination file if exist
                            [[ -f "${DEST_LINK}" ]] && rm "${DEST_LINK}"
                            DEST_LINK_DIR=$(dirname "${DEST_LINK}")
# and create hardlinks on destination
                            mkdir -p "${DEST_LINK_DIR}"
                            ln "${DEST_FILE}" "${DEST_LINK}"
                            if [[ ! $? -eq 0 ]] 
                            then
                                echo " --- Error: cannot hardlink ${DEST_FILE}"
                                echo " to ${DEST_LINK}"
                                return 1
                            fi
                        else
                            [[ $VERBOSE -ge 2 ]] && echo " --- Info: Hardlink exists"
                        fi
# remove hardlinks from source
                        if [[ "${RS_OPTIONS}" = "--remove-source-files" ]]
                        then
                            [[ $VERBOSE -ge 2 ]] && echo " --- Info: Delete hardlink ${SOURCE_LINK}"
                            rm "${SOURCE_LINK}"
                        fi

                fi
        done
}


########
# Jellyfin #
########
jellyfin_cache() {
# get Jellyfin sessions
        STATUS_SESSIONS=$(curl --silent http://${JELLYFIN_HOST}/Sessions -H "Mediabrowser Token="$JELLYFIN_TOKEN")
        if [[ -z $STATUS_SESSIONS ]];
        then
                echo "Error: Cannot connect to jellyfin"
                return 1
        fi

        NB_SESSIONS=$(echo $STATUS_SESSIONS  | jq '.Items | length')
        echo "----------------------------"
        echo "$NB_SESSIONS"
        echo "----------------------------"

# for each session
        if [[ $NB_SESSIONS -gt $JELLYFIN_MAX_CACHED_SESSIONS ]]
        then
            NB_SESSIONS=$JELLYFIN_MAX_CACHED_SESSIONS
            echo "Warning: Caching is limited to $JELLYFIN_MAX_CACHED_SESSIONS jellyfin sessions"
        fi
        for i in `seq $NB_SESSIONS`
        do
# get title
                ID=$(echo $STATUS_SESSIONS  | jq -r '.Items['$i'] | .Id')
                TYPE=$(echo $STATUS_SESSIONS  | jq -r '.Items['$i'] | .Type')
# eventually get serie info
                if [[ $TYPE = "Episode" ]]
                       then
                    TYPE="Serie"
                    GRANDPARENTTITLE=$(echo $STATUS_SESSIONS  | jq -r '.Items['$i'] | .SeriesName')
                    SEASON=$(echo $STATUS_SESSIONS  | jq -r '.Items['$i'] | .SeasonNumber')
                    TITLE=$(echo $STATUS_SESSIONS  | jq -r '.Items['$i'] | .Name')
                    EPISODE=$(echo $STATUS_SESSIONS  | jq -r '.Items['$i'] | .IndexNumber')
                    PARENT_ID=$(echo $STATUS_SESSIONS  | jq -r '.Items['$i'] | .SeasonId')
                    PARENT_NB_EPISODES=$(curl --silent http://${JELLYFIN_HOST}/Items/$PARENT_ID | jq -r '.ChildCount')
                    PARENT_START_EPISODE=$(curl --silent http://${JELLYFIN_HOST}/Items/$PARENT_ID/Episodes | jq -r '.Items[0] | .IndexNumber')
                    PARENT_NB_EPISODES=$(( $PARENT_NB_EPISODES + $PARENT_START_EPISODE - 1 ))
                    TITLE="$TYPE: ${GRANDPARENTTITLE} Season ${SEASON} - Episode ${EPISODE}/${PARENT_NB_EPISODES}: $TITLE"
# update nb file to cache
                    START_FILE=$EPISODE 
                    $JELLYFIN_CACHE_NEXT_EPISODE && NB_FILES=$(( $EPISODE + 1))
                    $JELLYFIN_CACHE_SEASON_TILL_END && NB_FILES=$(( $PARENT_NB_EPISODES ))
                elif [[ $TYPE = "Movie" ]]
                then
                    TYPE="Movie"
                    TITLE=$(echo $STATUS_SESSIONS  | jq -r '.Items['$i'] | .Name')
                    TITLE="$TYPE: $TITLE"                    
                    START_FILE=1
                    NB_FILES=1
                else
                    TYPE="Audio"
                    TITLE="track caching not implemented"
                    TITLE="$TYPE: $TITLE"                    
                    START_FILE=0
                    NB_FILES=0
                fi
                 echo " - $i/$NB_SESSIONS: $TITLE"

                 if [[ $NB_FILES -gt $JELLYFIN_MAX_CACHED_SESSIONS ]]
                 then
                     echo "Caching is limited to $JELLYFIN_MAX_CACHED_SESSIONS files"
                     NB_FILES=$(( $START_FILE + $JELLYFIN_MAX_CACHED_SESSIONS ))
                 fi

                for j in `seq $START_FILE $NB_FILES`
                do
#get file path
                    if [[ $TYPE = "Audio" ]]
                    then
                        [[ $VERBOSE -ge 2 ]] && echo " -- Info: Skipping"
                    else
                        if [[ $TYPE = "Serie" ]]                
                        then
                            JELLYFIN_FILE=$(curl --silent http://${JELLYFIN_HOST}/Items/$PARENT_ID/Episodes | jq -r '.Items['$(($j - $PARENT_START_EPISODE))'] | .Path')
                        else
                            JELLYFIN_FILE=$(curl --silent http://${JELLYFIN_HOST}/Items/$ID | jq -r '.Path')
                        fi                    
                        FILE_TO_CACHE=$(echo ${JELLYFIN_FILE} | sed 's|\"\"|\/|g' | sed 's|\"||g' | sed 's|\/data\/||')
                        [[ $VERBOSE -ge 2 ]] && echo " -- Info: File $j/$NB_FILES: $FILE_TO_CACHE"
                        STORAGE_FILE="${STORAGE_PATH}${FILE_TO_CACHE}"
                        CACHE_FILE="${CACHE_PATH}${FILE_TO_CACHE}"
#and send to rsync
                        rsync_transfer "${STORAGE_FILE}" "${CACHE_FILE}" "${STORAGE_PATH}" "${CACHE_PATH}"
                        ID=$(( $ID + 1 ))
                    fi
                done
        done
		# [[ $NB_SESSIONS != 0 ]] && echo ""
		[[ $VERBOSE -ge 1 ]] && echo ""
        
        }
####################
# Delete old files #
####################
cleanup() {
# get free space
        a=$(df -h | grep $CACHE_DISK | awk '{ printf "%d", $5 }')
        b=$CACHE_MIN_FREE_SPACE_PCT

        echo "---------------------"
        echo "Cache disk usage: ${a}%"
        echo "---------------------"


        if [[ "$a" -ge "$b" ]];
        then
            echo "$a% space used, quota is $b%, cleaning"
            [[ $VERBOSE -ge 1 ]] && echo "Info: Scanning files..."
# get oldest accessed files
            find "${CACHE_PATH}" -type f -printf "%C@ %p\n" | sort -n | sed "s|`echo ${CACHE_PATH}`|%|g" | cut -d'%' -f2 | while read FILE_TO_CLEAN
            do
# loop start: get free space again
                a=$(df -h | grep $CACHE_DISK | awk '{ printf "%d", $5 }')
                b=$CACHE_MAX_FREE_SPACE_PCT
# if free space not enough
                if [[ "$a" -ge "$b" ]];
                then
                    [[ $VERBOSE -ge 1 ]] && echo " - Info: $a% space used, target $b%, uncaching $FILE_TO_CLEAN"
                    STORAGE_FILE="${STORAGE_PATH}${FILE_TO_CLEAN}"
                    CACHE_FILE="${CACHE_PATH}${FILE_TO_CLEAN}"
# sync back cache to storage
                    rsync_transfer "${CACHE_FILE}" "${STORAGE_FILE}" "${CACHE_PATH}" "${STORAGE_PATH}" "--remove-source-files"
                fi
# loop 
            done
        fi
        a=$(df -h | grep $CACHE_DISK | awk '{ printf "%d", $5 }')
        b=$CACHE_MIN_FREE_SPACE_PCT
        [[ $VERBOSE -ge 1 ]] && echo " - Info: $a% space used, quota is $b%, nothing to do"
# prune empty directories from source dir
        [[ $VERBOSE -ge 2 ]] && echo " -- Info: Cleaning empty directories..."
        find "${CACHE_PATH}" -type d -not -path '*/\.*' -empty -prune -exec rmdir --ignore-fail-on-non-empty {} \;
        [[ $VERBOSE -ge 1 ]] && echo ""
}

sys_checks
$TRANSMISSION_ENABLED && transmission_cache
$JELLYFIN_ENABLED && jellyfin_cache
cleanup
echo """

rm /var/lock/smart-cache_jellyfin_transmission
exit 0