sync.sh 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. #!/bin/bash
  2. # Config
  3. TIMEOUT=20
  4. if [ -z "${VIDEO_DIR}" ]; then
  5. VIDEO_DIR="${HOME}/bin/video"
  6. fi
  7. if [ -z "${BASE_LOCAL}" ]; then
  8. BASE_LOCAL="`${VIDEO_DIR}/mediaPath`"
  9. fi
  10. if [ -z "${BASE_REMOTE}" ]; then
  11. BASE_REMOTE="`"${VIDEO_DIR}/backup/remoteDir.sh"`"
  12. fi
  13. # Command-line arguments
  14. SUB_DIR="${1}"
  15. declare -i NUM_FILES
  16. NUM_FILES=1
  17. if [ -n "${2}" ]; then
  18. NUM_FILES="${2}"
  19. fi
  20. declare -i DELAY_DAYS
  21. DELAY_DAYS=30
  22. if [ -n "${3}" ]; then
  23. DELAY_DAYS="${3}"
  24. fi
  25. # Usage checks
  26. if [ -z "${SUB_DIR}" ] || [ $NUM_FILES -lt 1 ] || [ $DELAY_DAYS -lt 1 ]; then
  27. echo "Usage: `basename "${0}"` sub_directory [num_files] [delay_days]" 1>&2
  28. exit 1
  29. fi
  30. # Ensure we can actually write to the remote drive
  31. if ! "${VIDEO_DIR}/backup/checkMount.sh" >/dev/null 2>&1; then
  32. exit 0
  33. fi
  34. # Bail if the load is high
  35. if ! "${VIDEO_DIR}/checkLoad.sh"; then
  36. exit 0
  37. fi
  38. # Allow usage with absolute local paths
  39. if [ "`echo "${SUB_DIR}" | head -c 1`" == '/' ]; then
  40. SUB_DIR="`echo "${SUB_DIR}" | sed "s%${BASE_LOCAL}/%%"`"
  41. fi
  42. # Sanity checks
  43. if [ ! -d "${BASE_LOCAL}/${SUB_DIR}" ]; then
  44. echo "Invalid local directory: ${BASE_LOCAL}/${SUB_DIR}" 1>&2
  45. exit 1
  46. fi
  47. if [ ! -d "${BASE_REMOTE}" ]; then
  48. echo "Remote drive not mounted" 1>&2
  49. exit 1
  50. fi
  51. # Create the named subdirectory if it does not exist on the remote drive
  52. if [ ! -d "${BASE_REMOTE}/${SUB_DIR}" ]; then
  53. timeout "${TIMEOUT}" mkdir -p "${BASE_REMOTE}/${SUB_DIR}" >/dev/null 2>&1
  54. "${VIDEO_DIR}/backup/checkMount.sh" "${SUB_DIR}" > /dev/null 2>&1 &
  55. echo "Remote drive not in-sync" 1>&2
  56. exit 1
  57. fi
  58. # Grab and sort the local file list
  59. # Limit files by mtime, but include all directories
  60. TMP_LOCAL="`mktemp -t sync.local.XXXXXXXX`"
  61. TMP_LOCAL2="`mktemp -t sync.local2.XXXXXXXX`"
  62. (cd "${BASE_LOCAL}" && find "${SUB_DIR}" -type f -mtime "+${DELAY_DAYS}" > "${TMP_LOCAL2}")
  63. (cd "${BASE_LOCAL}" && find "${SUB_DIR}" -type d >> "${TMP_LOCAL2}")
  64. cat "${TMP_LOCAL2}" | sort > "${TMP_LOCAL}"
  65. # Grab and sort the remote file list
  66. TMP_REMOTE="`mktemp -t sync.remote.XXXXXXXX`"
  67. timeout $(( $TIMEOUT * 10 )) bash -c "
  68. cd \"${BASE_REMOTE}\" && \
  69. find \"${SUB_DIR}\" | \
  70. sort > \"${TMP_REMOTE}\" \
  71. " 2>/dev/null
  72. if [ ! -s "${TMP_REMOTE}" ]; then
  73. "${VIDEO_DIR}/backup/checkMount.sh" "${SUB_DIR}" > /dev/null 2>&1 &
  74. echo "Remote drive not readable" 1>&2
  75. exit 1
  76. fi
  77. # Diff to find the first mis-matched file, then drop out temp files
  78. TMP_DIFF="`mktemp -t sync.diff.XXXXXXXX`"
  79. TMP_DIFF2="`mktemp -t sync.diff2.XXXXXXXX`"
  80. diff -u "${TMP_LOCAL}" "${TMP_REMOTE}" | grep "^[\+\-]${SUB_DIR}" > "${TMP_DIFF2}"
  81. rm -f "${TMP_LOCAL}" "${TMP_LOCAL2}" "${TMP_REMOTE}"
  82. # Filter junk
  83. grep -v '\/\._' "${TMP_DIFF2}" | grep -v '\/\.DS_Store$' | grep -v '\/\.git\/' > "${TMP_DIFF}"
  84. rm -f "${TMP_DIFF2}"
  85. # Limit NUM_FILES to the number of files available
  86. COUNT="`cat "${TMP_DIFF}" | wc -l`"
  87. if [ $NUM_FILES -gt $COUNT ]; then
  88. NUM_FILES=$COUNT
  89. fi
  90. # Loop for NUM_FILES
  91. for (( i=1; i<=${NUM_FILES}; i++ )); do
  92. # Choose the file/directory to sync
  93. FILE="`head -n $i "${TMP_DIFF}" | tail -n 1`"
  94. ACTION="`echo "${FILE}" | head -c 1`"
  95. FILE="`echo "${FILE}" | cut -d "${ACTION}" -f 2-`"
  96. # Construct absolute paths
  97. PATH_LOCAL="${BASE_LOCAL}/${FILE}"
  98. PATH_REMOTE="${BASE_REMOTE}/${FILE}"
  99. # Determine the action (i.e. copy to or delete from remote)
  100. if [ "${ACTION}" == '+' ]; then
  101. if [ -n "${NO_DELETE}" ]; then
  102. # Do nothing
  103. true
  104. elif [ -d "${PATH_REMOTE}" ]; then
  105. echo "Removing directory: ${FILE}"
  106. timeout "${TIMEOUT}" rmdir "${PATH_REMOTE}" >/dev/null 2>&1
  107. elif [ -f "${PATH_REMOTE}" ]; then
  108. echo "Deleting file: ${FILE}"
  109. timeout "${TIMEOUT}" rm "${PATH_REMOTE}" >/dev/null 2>&1
  110. else
  111. echo "Unable to delete file: ${FILE}" 1>&2
  112. exit 2
  113. fi
  114. elif [ "${ACTION}" == '-' ]; then
  115. # Handle files by type
  116. if [ -h "${PATH_LOCAL}" ]; then
  117. echo "Unable to create symlink: ${FILE}" 1>&2
  118. exit 2
  119. elif [ -d "${PATH_LOCAL}" ]; then
  120. echo "Creating directory: ${FILE}"
  121. timeout "${TIMEOUT}" mkdir "${PATH_REMOTE}" >/dev/null 2>&1
  122. elif [ -f "${PATH_LOCAL}" ] && [ -r "${PATH_LOCAL}" ]; then
  123. echo "Copying: ${FILE}"
  124. if ! cp "${BASE_LOCAL}/${FILE}" "${BASE_REMOTE}/${FILE}"; then
  125. echo "Copy failed" 1>&2
  126. timeout "${TIMEOUT}" rm -f "${BASE_REMOTE}/${FILE}" >/dev/null 2>&1
  127. exit 2
  128. fi
  129. else
  130. echo "Unable to copy file: ${FILE}" 1>&2
  131. exit 2
  132. fi
  133. else
  134. echo "Invalid action: ${ACTION}" 1>&2
  135. exit 2
  136. fi
  137. done
  138. # Cleanup
  139. rm -f "${TMP_DIFF}"
  140. exit 0