#!/bin/bash -eu

if [ "${BASH_SOURCE[0]}" != "$0" ]
then
  echo "${BASH_SOURCE[0]} must be executed, not sourced"
  return 1 # shouldn't use exit when sourced
fi

# Unload the module that was loaded by cmdline.txt
modprobe -r g_ether

export LOG_FILE=/mutable/archiveloop.log

function log () {
  echo "$( date ):" "$@" >> "$LOG_FILE"
  if [[ "$(stat --format="%s" "$LOG_FILE")" -gt 1000000 ]]
  then
    local log_file2="${LOG_FILE}.2"
    tail -n 5000 "$LOG_FILE" > "${log_file2}"
    mv "$log_file2" "$LOG_FILE"
    log "(log truncated)"
  fi
}

function log_errors_on_exit {
  log "archiveloop exited with code $1. Recent errors follow"
  journalctl -n 15 -u teslausb >> "$LOG_FILE"
  log "end of errors."
  exit "$1"
}

if [ "${FLOCKED:-}" != "$0" ]
then
  if FLOCKED="$0" flock -en -E 99 "$0" "$0" "$@" || case "$?" in
  99) echo already running
      exit 99
      ;;
  *)  log_errors_on_exit $?
      ;;
  esac
  then
    # success
    log_errors_on_exit 0
  fi
fi

export CAM_MOUNT=/mnt/cam
export MUSIC_MOUNT=/mnt/music

# read the setup variables and define other environment variables and functions
source /root/bin/envsetup.sh

# led triggers may have been built as modules, so try
# to load them
if ! grep -q timer "$STATUSLED/trigger"
then
  modprobe ledtrig-timer || log "timer LED trigger unavailable"
fi
if ! grep -q heartbeat "$STATUSLED/trigger"
then
  modprobe ledtrig-heartbeat || log "heartbeat LED trigger unavailable" 
fi

if [ -z "${ARCHIVE_SERVER+x}" ]
then
  case "${ARCHIVE_SYSTEM:-none}" in
    rsync)
      export ARCHIVE_SERVER="$RSYNC_SERVER"
      ;;
    rclone)
      export ARCHIVE_SERVER="8.8.8.8"
      ;;
    none)
      export ARCHIVE_SERVER=localhost
      ;;
    *)
      log "ARCHIVE_SERVER not set"
      exit 1
      ;;
  esac
elif [ "${ARCHIVE_SYSTEM:-none}" = "cifs" ]
then
  # For CIFS 
  [[ -n "${SHARE_NAME:-}" && -f /backingfiles/cam_disk.bin ]] && export ARCHIVE_MOUNT=/mnt/archive
  [[ -n "${MUSIC_SHARE_NAME:-}" && -f /backingfiles/music_disk.bin ]] && export MUSIC_ARCHIVE_MOUNT=/mnt/musicarchive
fi

function timestamp () {
  local prefix=${1:-}
  while IFS= read -r line
  do
    echo "$(date): $prefix$line"
  done
}

function fix_errors_in_image () {
  local image="$1"
  log "Running fsck on $image..."
  loopback=$(losetup_find_show -P "$image")
  # Use -p repair arg. It works with vfat and exfat.
  /sbin/fsck "${loopback}p1" -- -p |& timestamp '| ' >> "$LOG_FILE" || echo ""
  losetup -d "$loopback"
  log "Finished fsck on $image."
}

function archive_is_reachable () {
  local reachable=true

  /root/bin/archive-is-reachable.sh "$ARCHIVE_SERVER" || reachable=false

  if [ "$reachable" = false ]
  then
    false
    return
  fi
  true
}

function connect_usb_drives_to_host() {
  log "Connecting usb to host..."
  /root/bin/enable_gadget.sh
  log "Connected usb to host."
  sleep 5
}

function wait_for_archive_to_be_reachable () {
  log "Waiting for archive to be reachable..."
  while true
  do
    if archive_is_reachable
    then
      log "Archive is reachable."
      break
    fi
    if [ -e /tmp/archive_is_reachable ]
    then
      log "Simulating archive is reachable"
      rm /tmp/archive_is_reachable
      break
    fi
    sleep 1
  done
}

function retry () {
  local attempts=0
  while true
  do
    if "$@"
    then
      true
      return
    fi
    if [ "$attempts" -ge 10 ]
    then
      log "Attempts exhausted."
      false
      return
    fi
    log "Sleeping before retry ..."
    /bin/sleep 1
    attempts=$((attempts + 1))
    log "Retrying ($attempts) '$*' ..."
  done
  false
  return
}

function ensure_mountpoint_is_mounted () {
  local mount_point="$1"

  if findmnt --mountpoint "$mount_point" > /dev/null
  then
    log "$mount_point is already mounted."
  else
    if timeout 10 mount "$mount_point" >> "$LOG_FILE" 2>&1
    then
      log "Mounted $mount_point."
    else
      log "Failed to mount $mount_point."
      return 1
    fi
  fi
}

function ensure_mountpoint_is_mounted_with_retry () {
  retry ensure_mountpoint_is_mounted "$1"
}

function ensure_cam_file_is_mounted () {
  log "Ensuring cam file is mounted..."
  disconnect_usb_drives_from_host
  ensure_mountpoint_is_mounted_with_retry "$CAM_MOUNT"
  log "Ensured cam file is mounted."
}

function ensure_music_file_is_mounted () {
  log "Ensuring music backing file is mounted..."
  disconnect_usb_drives_from_host
  ensure_mountpoint_is_mounted_with_retry "$MUSIC_MOUNT"
  log "Ensured music drive is mounted."
}

function unmount_mount_point () {
  local mount_point="$1"
  log "Unmounting $mount_point..."
  if umount "$mount_point" >> "$LOG_FILE" 2>&1
  then
    log "Unmounted $mount_point."
  else
    log "Failed to unmount $mount_point, trying lazy unmount."
    if umount -l "$mount_point" >> "$LOG_FILE" 2>&1
    then
      log "lazily unmounted $mount_point"
    else
      log "lazy unmount failed"
    fi
  fi
}

function unmount_cam_file () {
  unmount_mount_point "$CAM_MOUNT"
}

function unmount_music_file () {
  unmount_mount_point "$MUSIC_MOUNT"
}

function wait_for_archive_to_be_unreachable () {
  log "Waiting for archive to be unreachable..."
  while true
    do
      if ! retry archive_is_reachable
      then
        log "Archive is unreachable."
        break
      fi
      if [ -e /tmp/archive_is_unreachable ]
      then
        log "Simulating archive being unreachable."
        rm /tmp/archive_is_unreachable
        break
      fi
      sleep 1
  done
}

function check_if_usb_gadget_is_mounted () {
  LUNFILE=/sys/kernel/config/usb_gadget/teslausb/configs/c.1/mass_storage.0/lun.0/file
  if [ -n "$(cat /sys/kernel/config/usb_gadget/teslausb/UDC)" ] &&
     [ -e "$LUNFILE" ] &&
     [ -n "$(cat $LUNFILE)" ]
  then
    return
  fi

  log "USB Gadget not mounted. Fixing files and remounting..."
  disconnect_usb_drives_from_host
  connect_usb_drives_to_host
}

function trim_free_space() {
  local mount_point="$1"

  # Make sure the partition is mounted.
  if found=$(findmnt -n --mountpoint "$mount_point")
  then
    loop=$(echo "$found" | awk '{print $2}')
    image=$(losetup -l -n --output=BACK-FILE "$loop")
    log "Trimming free space in $mount_point, which has $(filefrag "$image" | awk '{print $2}') extents"
    if fstrim "$mount_point" >> "$LOG_FILE" 2>&1
    then
      log "Trim complete, image now has $(filefrag "$image" | awk '{print $2}') extents"
    else
      log "Trimming free space in $mount_point failed."
    fi
  else
    log "Could not trim free space in $mount_point. Not Mounted."
  fi
}

function fix_errors_in_images () {
  for i in cam music boombox lightshow
  do
    img="/backingfiles/${i}_disk.bin"
    if [ -e "$img" ]
    then
      fix_errors_in_image "$img"
    fi
  done
}

function disconnect_usb_drives_from_host () {
  log "Disconnecting usb from host..."
  if /root/bin/disable_gadget.sh
  then
    fix_errors_in_images
  fi
  log "Disconnected usb from host."
}

function convert_seconds_to_nice_time () {
  local -r h=$(($1/3600))
  local -r m=$((($1%3600)/60))
  local -r s=$(($1%60))

  if [ $h -gt 0 ]
  then
    printf "%dh%dm%ds" $h $m $s
  elif [ $m -gt 0 ]
  then
    printf "%dm%ds" $m $s
  else
    printf "%ds" $s
  fi
}

function filterfile {
  if [ -x /root/bin/archive-filter ]
  then
    /root/bin/archive-filter "$1"
  fi
}

function sortfile {
  touch "$1"
  sort "$1" > /tmp/sort_tmp.$$
  mv /tmp/sort_tmp.$$ "$1"
}

function prunefile {
  sortfile "$1"
  sortfile "$2"
  comm -2 -3 "$1" "$2" > /tmp/prune_tmp.$$
  mv /tmp/prune_tmp.$$ "$1"
}

function intersect {
  sortfile "$1"
  sortfile "$2"
  comm -1 -2 "$1" "$2" > /tmp/prune_tmp.$$
  mv /tmp/prune_tmp.$$ "$1"
}

function clean_cam_mount {

  local mode="${1:-}"

  if [ "$mode" = "freespace" ]
  then
    if [ -e /tmp/last_clean_time ]
    then
      read -r -d . last < /tmp/last_clean_time
      read -r -d . now < /proc/uptime
      if (( (now - last) < 300 ))
      then
        log "skipping cleaning cam mount"
        return
      fi
    fi
    cat /proc/uptime > /tmp/last_clean_time
  fi

  log "cleaning cam mount"
  ensure_cam_file_is_mounted

  # Delete the files that fsck "recovered". These are generally files
  # that were deleted previously by the car or teslausb
  find "${CAM_MOUNT}" \( \( -type f -name FSCK\*.REC \) -o \( -type f -name "*~[0-9].MP4" \) \) -print0 | xargs -0 rm -rf

  # Delete short recordings
  (cd "${CAM_MOUNT}"; find . -type f -name \*.mp4 -size -100000c -printf '%P\n' ) | xargs rm -rf

  if [ "$mode" = "freespace" ]
  then
    # Remove files, oldest first, until there is at least 20GB of free space
 
    (cd "${CAM_MOUNT}"; find . -type f -a \( -path './TeslaCam/*' -o -path './TeslaTrackMode/*' \) -printf '%P\0') > /tmp/rmcandidates.txt
    if [ -s /tmp/rmcandidates.txt ]
    then
      (cd "${CAM_MOUNT}"; xargs -a /tmp/rmcandidates.txt -0 ls -cr1) > /tmp/rmcandidatesbytime.txt
    else
      true > /tmp/rmcandidatesbytime.txt
    fi
 
    local freespace
    while read -r line
    do
      freespace=$(eval "$(stat --file-system --format="echo \$((%f*%S))" "${CAM_MOUNT}/.")")
      if ((freespace > 20000000000))
      then
        break;
      fi
      rm "${CAM_MOUNT}/$line"
    done < /tmp/rmcandidatesbytime.txt
  fi

  # delete directories that are now empty
  find "$CAM_MOUNT/TeslaCam/RecentClips" "$CAM_MOUNT/TeslaCam/SavedClips" \
    "$CAM_MOUNT/TeslaCam/SentryClips" "$CAM_MOUNT/TeslaTrackMode" -mindepth 1 -type d -empty -delete || true

  # Trim the camera archive to reduce the number of blocks in the snapshot.
  trim_free_space "$CAM_MOUNT"

  unmount_cam_file

  log "done cleaning cam mount"
}

# Directory structure car uses:
# TeslaCam/
#   RecentClips/
#      videos.mp4
#   SavedClips/
#     datetime/
#       videos.mp4
#       event.json
#       thumb.png
#   SentryClips/
#     datetime/
#       videos.mp4
#       event.json
#       thumb.png
# TeslaTrackMode/
#   lapvideo.mp4
#   laptelemetry.csv
function archive_teslacam_clips () {
  log "Checking saved folder count..."

  local -r overlaylower=/mutable/TeslaCam
  local -r overlayupper=/tmp/cam/upper
  local -r overlaywork=/tmp/cam/work
  local -r overlaymerged=/tmp/cam/merged

  rm -rf "$overlayupper" "$overlaywork" "$overlaymerged"
  mkdir -p "$overlayupper" "$overlaywork" "$overlaymerged"

  mount -t overlay overlay -o "lowerdir=$overlaylower,upperdir=$overlayupper,workdir=$overlaywork" "$overlaymerged"

  # Build list of the files to be archived.
  local -r sentrylist=/tmp/sentry_files
  local -r sentrylist_archived=/tmp/sentry_files_archived
  local -r sentrylist_previously_archived=/mutable/sentry_files_archived
  local -r ignorelist=/tmp/ignore_files

  # Find files by name only. Don't follow the symlinks yet, since that
  # would cause all snapshots to be mounted.
  local -a savedclipsopt
  local -a sentryclipsopt
  local -a trackmodeclipsopt
  local -a recentclipsopt
  if [ "${ARCHIVE_SAVEDCLIPS:-true}" = "true" ]
  then
    savedclipsopt=("-path" "./SavedClips/*")
  else
    savedclipsopt=("-false")
  fi
  if [ "${ARCHIVE_SENTRYCLIPS:-true}" = "true" ]
  then
    sentryclipsopt=("-o" "-path" "./SentryClips/*")
  fi
  if [ "${ARCHIVE_TRACKMODECLIPS:-true}" = "true" ]
  then
    trackmodeclipsopt=("-o" "-path" "./TeslaTrackMode/*")
  fi
  if [ "${ARCHIVE_RECENTCLIPS:-false}" = "true" ]
  then
    recentclipsopt=("-o" "-path" "./RecentClips/*")
  fi
  (cd "$overlaylower"; find . \( \( "${savedclipsopt[@]}" "${sentryclipsopt[@]}" "${trackmodeclipsopt[@]}" "${recentclipsopt[@]}" \) -type l \) \
        -a -fprintf "$sentrylist" '%P\n')

  # Copy lists of previously-archived files from /mutable
  if [ -r "${sentrylist_previously_archived}" ]
  then
    cp "${sentrylist_previously_archived}" "${sentrylist_archived}"
  fi

  # Remove files that no longer exist in snapshots from the
  # "previously archived" list, so the list doesn't keep growing.
  intersect "${sentrylist_archived}" "${sentrylist}"

  # Remove previously-archived files from the archive candidate list
  prunefile "${sentrylist}" "${sentrylist_archived}"

  # ${sentrylist} is now a list of files that haven't been archived yet.
  # Remove short recordings from this list. This requires following the
  # links and looking at the files themselves.
  if [ -s "${sentrylist}" ]
  then
    (cd "$overlaymerged"; xargs -a "${sentrylist}" echo "find -L" | while read -r line
        do
          eval "$line \( -name \*.mp4 -a -size -100000c -printf '%p\n' \)"
        done) > ${ignorelist}
  else
    true > "${ignorelist}"
  fi

  # remove the short files from the list of files to be archived
  prunefile "${sentrylist}" "${ignorelist}"

  # apply custom removals/additions
  filterfile "${sentrylist}"

  local -r sentry_count=$(grep -c -v TeslaTrackMode "$sentrylist")
  local -r trackmode_count=$(grep -c TeslaTrackMode "$sentrylist")
  local -r ignore_count=$(wc -l < "$ignorelist")
  local -r total_count=$((sentry_count + trackmode_count))

  # extract some noteworthy info from the file list
  local -r saved_event_count=$(grep 'SavedClips/' < "$sentrylist" | sed 's/[^\/]\+$//' | sort -u  | wc -l)
  local -r sentry_event_count=$(grep 'SentryClips/' < "$sentrylist" | sed 's/[^\/]\+$//' | sort -u  | wc -l)

  local -r event_count=$((saved_event_count + sentry_event_count))

  log "There are $event_count event folder(s) with $sentry_count file(s) and $trackmode_count track mode file(s) to move." \
      " $ignore_count short recording(s) will be skipped."

  local archive_success=true
  if [[ "$total_count" -gt 0 ]]
  then
    log "Starting recording archiving"
    local -r start_ts=$(date --utc --date "now" +%s)

    local message="Archiving "
    if [[ $sentry_count -gt 0 && $trackmode_count -gt 0 ]]
    then
      message+="$trackmode_count track mode file(s) and $sentry_count file(s) including $event_count event folder(s)"
    elif [[ $sentry_count -gt 0 && $event_count -gt 0 ]]
    then
      message+="$sentry_count file(s) including $event_count event folder(s)"
    elif [[ $sentry_count -gt 0 ]]
    then
      message+="$sentry_count file(s)"
    else
      message+="$trackmode_count track mode file(s)"
    fi
    message+=" starting at $(date)"
    /root/bin/send-push-message "$NOTIFICATION_TITLE:" "$message" start || log "failed to send push message"

    # setup trigger files
    local -r triggerdir=/tmp/triggers
    local -r triggerlist=/tmp/triggers.txt
    rm -rf "${triggerdir}"
    mkdir -p "${triggerdir}/SentryClips"
    mkdir -p "${triggerdir}/SavedClips"
    mkdir -p "${triggerdir}/RecentClips"
    true > "${triggerlist}"
    if [ -n "${TRIGGER_FILE_SAVED+x}" ]
    then
      touch "${triggerdir}/SavedClips/${TRIGGER_FILE_SAVED}"
      echo "SavedClips/${TRIGGER_FILE_SAVED}" >> "${triggerlist}"
    fi
    if [ -n "${TRIGGER_FILE_SENTRY+x}" ]
    then
      touch "${triggerdir}/SentryClips/${TRIGGER_FILE_SENTRY}"
      echo "SentryClips/${TRIGGER_FILE_SENTRY}" >> "${triggerlist}"
    fi
    if [ -n "${TRIGGER_FILE_RECENT+x}" ]
    then
      touch "${triggerdir}/RecentClips/${TRIGGER_FILE_RECENT}"
      echo "RecentClips/${TRIGGER_FILE_RECENT}" >> "${triggerlist}"
    fi
    if [ -n "${TRIGGER_FILE_ANY+x}" ]
    then
      touch "${triggerdir}/${TRIGGER_FILE_ANY}"
      echo "${TRIGGER_FILE_ANY}" >> "${triggerlist}"
    fi

    if /root/bin/archive-clips.sh "$overlaymerged" "${sentrylist}" "${triggerdir}" "${triggerlist}"
    then
      message="Archiving completed successfully. "
    else
      message="Error during archiving. "
      archive_success=false
    fi

    local -i sentry_archived=0
    local -i trackmode_archived=0

    while read -r line
    do
      if [[ ! -e "$overlaymerged/$line" ]]
      then
        case $line in
          TeslaTrackMode*)
            trackmode_archived=$((trackmode_archived + 1))
            ;;
          *)
            sentry_archived=$((sentry_archived + 1))
            ;;
        esac
        echo "$line" >> "${sentrylist_archived}"
      fi
    done < ${sentrylist}

    # copy the list of archived files back to /mutable if it has changed
    if ! diff -q -N "${sentrylist_archived}" "${sentrylist_previously_archived}" > /dev/null
    then
        cp "${sentrylist_archived}" "${sentrylist_previously_archived}.new"
        mv "${sentrylist_previously_archived}.new" "${sentrylist_previously_archived}"
    fi

    message+="Archived "
    if [[ $sentry_count -gt 0 && $trackmode_count -gt 0 ]]
    then
      message+="${trackmode_archived} trackmode files and ${sentry_archived} other files"
    elif [[ $sentry_count -gt 0 ]]
    then
      message+="${sentry_archived} files"
    else
      message+="${trackmode_archived} trackmode files"
    fi
    local -r end_ts=$(date --utc --date "now" +%s)
    local -r delta=$((end_ts - start_ts))
    message+=" in $(convert_seconds_to_nice_time $delta)"
    /root/bin/send-push-message "$NOTIFICATION_TITLE:" "${message}" finish

    if [ -e /tmp/archive-error.log ]
    then
      cat /tmp/archive-error.log >> ${LOG_FILE}
      rm /tmp/archive-error.log
    fi
  fi

  # overlayfs behavior is undefined if the lower is changed while the overlay is active, so umount first
  umount "$overlaymerged"

  if archive_is_reachable
  then
    if [ "$archive_success" = "true" ]
    then
      clean_cam_mount freespace
    else
      log "Skipping cleaning step after archive error"
    fi
  else
    log "Archive not reachable, assuming user drove away, skipping cleaning step"
  fi
}

function copy_music_files () {
  log "Starting music sync..."

  ensure_music_file_is_mounted

  /root/bin/copy-music.sh

  # Trim the empty space from the music archive.
  trim_free_space "$MUSIC_MOUNT"

  unmount_music_file
}

function has_cam_disk () {
  [ -f /backingfiles/cam_disk.bin ]
}

function archive_clips () {
  log "Archiving..."

  if ! /root/bin/connect-archive.sh
  then
    log "Couldn't connect archive, skipping archive step."
    return
  fi

  if ! has_cam_disk
  then
    log "Skipping archiving (no cam disk)"
  elif archive_teslacam_clips
  then
    log "Finished archiving."
  else
    log "Archiving failed."
  fi

  if timeout 5 [ -d "${MUSIC_ARCHIVE_MOUNT:-}" -a -d "$MUSIC_MOUNT" ]
  then
    log "Copying music..."
    if copy_music_files
    then
      log "Finished copying music."
    else
      log "Copying music failed."
    fi
  else
    log "Music archive not configured or unreachable"
  fi

  touch /tmp/flag_post_archive  #Set post-archive flag for optional temperature logging
  /root/bin/disconnect-archive.sh
}

function slowblink () {
  echo timer > "$STATUSLED/trigger" || return 0
  echo 900 > "$STATUSLED/delay_off"
  echo 100 > "$STATUSLED/delay_on"
}

function fastblink () {
  echo timer > "$STATUSLED/trigger" || return 0
  echo 150 > "$STATUSLED/delay_off"
  echo 50 > "$STATUSLED/delay_on"
}


function doubleblink () {
  echo heartbeat > "$STATUSLED/trigger" || return 0
  echo 0 > "$STATUSLED/invert"
}

function set_time () {
  log "Trying to set time..."
  local -r uptime_start=$(awk '{print $1}' /proc/uptime)
  local -r clocktime_start=$(date +%s.%N)
  for _ in {1..5}
  do
    if sntp -S time.google.com
    then
      local -r uptime_end=$(awk '{print $1}' /proc/uptime)
      local -r clocktime_end=$(date +%s.%N)
      log "$(awk "BEGIN {printf \"Time adjusted by %f seconds after %f seconds\", $clocktime_end-$clocktime_start, $uptime_end-$uptime_start}")"
      return
    fi
    log "sntp failed, retrying..."
    sleep 2
  done
  log "Failed to set time"
}

function snapshotloop {
  echo -n snapshotloop > /proc/self/comm
  while true
  do
    sleep "${SNAPSHOT_INTERVAL:-3480}"
    /root/bin/waitforidle || true
    /root/bin/make_snapshot.sh
  done
}

function freespacemanager {
  echo -n freespacemanager > /proc/self/comm
  # 10G
  local reserve=10737418240
  local threepctoftotalspace
  threepctoftotalspace=$(eval "$(stat --file-system --format="echo \$((%b*%S/33))" /backingfiles/cam_disk.bin)")
  reserve=$((reserve+threepctoftotalspace))

  while true
  do
    local freespace
    freespace=$(eval "$(stat --file-system --format="echo \$((%f*%S))" /backingfiles/cam_disk.bin)")
    if [ "$freespace" -lt "$reserve" ]
    then
      /root/bin/manage_free_space.sh "$reserve" || sleep 30
    fi
    sleep 30
  done
}

function logrotator {
  echo -n logrotator > /proc/self/comm
  while true
  do
    if [ -s /var/log/nginx/access.log ]
    then
      mv -f /var/log/nginx/access.log /var/log/nginx/access.log.prev || true
    fi
    if [ -s /var/log/nginx/error.log ]
    then
      mv -f /var/log/nginx/error.log /var/log/nginx/error.log.prev || true
    fi
    if [ -e /var/run/nginx.pid ]
    then
      kill -USR1 "$(cat /var/run/nginx.pid)"
    fi
    sleep 1800
  done
}

function wifichecker {
  echo -n wifichecker > /proc/self/comm
  dmesg -w | {
    while TMOUT=1 read -r line
    do
      true
    done
    wifi=working
    while read -r line
    do
      case $line in
        *"failed to enable fw supplicant")
          if [ "$wifi" = "working" ]
          then
            wifi="notworking"
          else
            log "restarting wifi because of: $line"
            modprobe -r brcmfmac cfg80211 brcmutil || true
            modprobe brcmfmac || true
            while TMOUT=1 read -r line
            do
              true
            done
            wifi="working"
          fi
          ;;
        *)
          wifi=working
          ;;
      esac
    done
  }
}

function set_sys_param() {
  local -r parampath="$1"
  local -r var="$2"
  local -r defaultval="$3"

  local -r val=${!var:-$defaultval}
  if [[ ${val} = "default" ]]
  then
    log "not setting $parampath"
  else
    echo "$val" > "$parampath" || true
  fi
}

export -f ensure_mountpoint_is_mounted
export -f retry
export -f ensure_mountpoint_is_mounted_with_retry
export -f log

echo "==============================================" >> "$LOG_FILE"
log "Starting archiveloop at $(awk '{print $1}' /proc/uptime) seconds uptime..."

set_sys_param /proc/sys/vm/dirty_background_bytes DIRTY_BACKGROUND_BYTES 65536
set_sys_param /proc/sys/vm/dirty_ratio DIRTY_RATIO 80
set_sys_param /sys/devices/system/cpu/cpufreq/policy0/scaling_governor CPU_GOVERNOR conservative

/root/bin/temperature_monitor &

if has_cam_disk
then
  snapshotloop &
  freespacemanager &
fi
logrotator &
wifichecker &

fix_errors_in_images
# always remove files that fsck recoverd
clean_cam_mount boot

if has_cam_disk
then
  # don't need another fsck because fix_errors_in_images already did that
  /root/bin/make_snapshot.sh nofsck
fi

connect_usb_drives_to_host

if archive_is_reachable
then
  fastblink

  set_time

  /root/bin/awake_start || true

  archive_clips

  /root/bin/awake_stop || true

  doubleblink

  connect_usb_drives_to_host

  wait_for_archive_to_be_unreachable
fi

while true
do
  slowblink

  wait_for_archive_to_be_reachable

  fastblink

  set_time

  /root/bin/awake_start || true

  sleep "${ARCHIVE_DELAY:-20}"

  # take a snapshot before archive_clips starts deleting files
  if has_cam_disk
  then
    /root/bin/make_snapshot.sh
  fi

  archive_clips

  /root/bin/awake_stop || true

  doubleblink

  connect_usb_drives_to_host

  wait_for_archive_to_be_unreachable

  check_if_usb_gadget_is_mounted
done
