Untitled
unknown
plain_text
3 years ago
14 kB
3
Indexable
#!/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
Editor is loading...