@@ -8,6 +8,10 @@ UPLOAD_RETAIN_LOCAL_FILE=${SE_UPLOAD_RETAIN_LOCAL_FILE:-"false"}
88UPLOAD_PIPE_FILE_NAME=${SE_UPLOAD_PIPE_FILE_NAME:- " uploadpipe" }
99VIDEO_INTERNAL_UPLOAD=${VIDEO_INTERNAL_UPLOAD:- $SE_VIDEO_INTERNAL_UPLOAD }
1010VIDEO_UPLOAD_BATCH_CHECK=${SE_VIDEO_UPLOAD_BATCH_CHECK:- " 10" }
11+ UPLOAD_RETRY_MAX_ATTEMPTS=${SE_UPLOAD_RETRY_MAX_ATTEMPTS:- 3}
12+ UPLOAD_RETRY_DELAY=${SE_UPLOAD_RETRY_DELAY:- 5}
13+ UPLOAD_FILE_READY_WAIT=${SE_UPLOAD_FILE_READY_WAIT:- 1}
14+ UPLOAD_VERIFY_CHECKSUM=${SE_UPLOAD_VERIFY_CHECKSUM:- " true" }
1115ts_format=${SE_LOG_TIMESTAMP_FORMAT:- " %Y-%m-%d %H:%M:%S,%3N" }
1216process_name=" video.uploader"
1317
@@ -41,6 +45,57 @@ function rename_rclone_env() {
4145}
4246
4347list_rclone_pid=()
48+ upload_success_count=0
49+ upload_failed_count=0
50+ graceful_exit_called=false
51+
52+ function verify_file_ready() {
53+ local file=$1
54+
55+ # Check if file exists
56+ if [ ! -f " ${file} " ]; then
57+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - ERROR: File does not exist: ${file} "
58+ return 1
59+ fi
60+
61+ # Check if file is readable
62+ if [ ! -r " ${file} " ]; then
63+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - ERROR: File is not readable: ${file} "
64+ return 1
65+ fi
66+
67+ # Check if file has non-zero size
68+ local file_size=$( stat -f%z " ${file} " 2> /dev/null || stat -c%s " ${file} " 2> /dev/null)
69+ if [ -z " ${file_size} " ] || [ " ${file_size} " -eq 0 ]; then
70+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - ERROR: File is empty: ${file} "
71+ return 1
72+ fi
73+
74+ # Wait for file to be stable (no longer being written)
75+ local initial_size=${file_size}
76+ sleep ${UPLOAD_FILE_READY_WAIT}
77+ local final_size=$( stat -f%z " ${file} " 2> /dev/null || stat -c%s " ${file} " 2> /dev/null)
78+
79+ if [ " ${initial_size} " != " ${final_size} " ]; then
80+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - WARNING: File is still being written: ${file} (size changed from ${initial_size} to ${final_size} )"
81+ return 1
82+ fi
83+
84+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - File verified ready for upload: ${file} (size: ${final_size} bytes)"
85+ return 0
86+ }
87+
88+ function calculate_checksum() {
89+ local file=$1
90+ # Use md5sum for checksum (available on most systems)
91+ if command -v md5sum > /dev/null 2>&1 ; then
92+ md5sum " ${file} " | awk ' {print $1}'
93+ elif command -v md5 > /dev/null 2>&1 ; then
94+ md5 -q " ${file} "
95+ else
96+ echo " "
97+ fi
98+ }
4499function check_and_clear_background() {
45100 # Wait for a batch rclone processes to finish
46101 if [ ${# list_rclone_pid[@]} -eq ${VIDEO_UPLOAD_BATCH_CHECK} ]; then
@@ -52,11 +107,89 @@ function check_and_clear_background() {
52107
53108}
54109
110+ function rclone_upload_with_retry() {
111+ local source=$1
112+ local target=$2
113+ local attempt=1
114+ local source_checksum=" "
115+
116+ # Verify file is ready before attempting upload
117+ if ! verify_file_ready " ${source} " ; then
118+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - ERROR: File verification failed, skipping upload: ${source} "
119+ upload_failed_count=$(( upload_failed_count + 1 ))
120+ return 1
121+ fi
122+
123+ # Calculate checksum if verification is enabled
124+ if [ " ${UPLOAD_VERIFY_CHECKSUM} " = " true" ]; then
125+ source_checksum=$( calculate_checksum " ${source} " )
126+ if [ -n " ${source_checksum} " ]; then
127+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Source file checksum: ${source_checksum} "
128+ fi
129+ fi
130+
131+ # Retry loop
132+ while [ ${attempt} -le ${UPLOAD_RETRY_MAX_ATTEMPTS} ]; do
133+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Upload attempt ${attempt} /${UPLOAD_RETRY_MAX_ATTEMPTS} : ${source} to ${target} "
134+
135+ # Execute rclone command
136+ if rclone --config ${RCLONE_CONFIG} ${UPLOAD_COMMAND} ${UPLOAD_OPTS} " ${source} " " ${target} " ; then
137+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - SUCCESS: Upload completed: ${source} to ${target} "
138+
139+ # Verify checksum if enabled and using copy command
140+ if [ " ${UPLOAD_VERIFY_CHECKSUM} " = " true" ] && [ -n " ${source_checksum} " ] && [ " ${UPLOAD_COMMAND} " = " copy" ]; then
141+ # For copy command, verify source file still has same checksum
142+ local post_upload_checksum=$( calculate_checksum " ${source} " )
143+ if [ " ${source_checksum} " = " ${post_upload_checksum} " ]; then
144+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Checksum verification passed: ${source_checksum} "
145+ else
146+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - WARNING: Checksum mismatch after upload (before: ${source_checksum} , after: ${post_upload_checksum} )"
147+ fi
148+ fi
149+
150+ # Update statistics file (for cross-process tracking)
151+ (
152+ flock -x 200
153+ local current_success=$( grep -oP ' upload_success_count=\K\d+' /tmp/upload_stats.txt 2> /dev/null || echo 0)
154+ echo " upload_success_count=$(( current_success + 1 )) " > /tmp/upload_stats.txt.tmp
155+ local current_failed=$( grep -oP ' upload_failed_count=\K\d+' /tmp/upload_stats.txt 2> /dev/null || echo 0)
156+ echo " upload_failed_count=${current_failed} " >> /tmp/upload_stats.txt.tmp
157+ mv /tmp/upload_stats.txt.tmp /tmp/upload_stats.txt
158+ ) 200> /tmp/upload_stats.lock
159+
160+ return 0
161+ else
162+ local exit_code=$?
163+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - FAILED: Upload attempt ${attempt} failed with exit code ${exit_code} : ${source} "
164+
165+ if [ ${attempt} -lt ${UPLOAD_RETRY_MAX_ATTEMPTS} ]; then
166+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Retrying in ${UPLOAD_RETRY_DELAY} seconds..."
167+ sleep ${UPLOAD_RETRY_DELAY}
168+ fi
169+
170+ attempt=$(( attempt + 1 ))
171+ fi
172+ done
173+
174+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - ERROR: All upload attempts failed for: ${source} "
175+
176+ # Update statistics file (for cross-process tracking)
177+ (
178+ flock -x 200
179+ local current_success=$( grep -oP ' upload_success_count=\K\d+' /tmp/upload_stats.txt 2> /dev/null || echo 0)
180+ echo " upload_success_count=${current_success} " > /tmp/upload_stats.txt.tmp
181+ local current_failed=$( grep -oP ' upload_failed_count=\K\d+' /tmp/upload_stats.txt 2> /dev/null || echo 0)
182+ echo " upload_failed_count=$(( current_failed + 1 )) " >> /tmp/upload_stats.txt.tmp
183+ mv /tmp/upload_stats.txt.tmp /tmp/upload_stats.txt
184+ ) 200> /tmp/upload_stats.lock
185+
186+ return 1
187+ }
188+
55189function rclone_upload() {
56190 local source=$1
57191 local target=$2
58- echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Uploading ${source} to ${target} "
59- rclone --config ${RCLONE_CONFIG} ${UPLOAD_COMMAND} ${UPLOAD_OPTS} " ${source} " " ${target} " &
192+ rclone_upload_with_retry " ${source} " " ${target} " &
60193 list_rclone_pid+=($! )
61194 check_and_clear_background
62195}
@@ -99,16 +232,53 @@ function wait_until_pipefile_exists() {
99232 done
100233}
101234
235+ function wait_for_active_uploads() {
236+ if [ ${# list_rclone_pid[@]} -gt 0 ]; then
237+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Waiting for ${# list_rclone_pid[@]} active upload process(es) to complete"
238+ for pid in " ${list_rclone_pid[@]} " ; do
239+ if kill -0 " $pid " 2> /dev/null; then
240+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Waiting for upload process (PID: $pid )"
241+ wait " $pid " 2> /dev/null || true
242+ fi
243+ done
244+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - All active upload processes completed"
245+ list_rclone_pid=()
246+ fi
247+ }
248+
102249function graceful_exit() {
250+ # Prevent duplicate execution (trap catches both SIGTERM and EXIT)
251+ if [ " $graceful_exit_called " = " true" ]; then
252+ return 0
253+ fi
254+ graceful_exit_called=true
255+
103256 echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Trapped SIGTERM/SIGINT/x so shutting down uploader"
257+
258+ # Signal the pipe consumer to stop accepting new files
104259 if ! check_if_pid_alive " ${UPLOAD_PID} " ; then
105260 consume_pipe_file_in_background &
106261 UPLOAD_PID=$!
107262 fi
108263 echo " exit" >> " ${UPLOAD_PIPE_FILE} " &
109264 wait " ${UPLOAD_PID} "
110265 echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Uploader consumed all files in the pipe"
266+
267+ # Wait for all active background rclone uploads to complete
268+ wait_for_active_uploads
269+
270+ # Log upload statistics from file (written by background processes)
271+ if [ -f " /tmp/upload_stats.txt" ]; then
272+ source " /tmp/upload_stats.txt" 2> /dev/null || true
273+ fi
274+ local total_uploads=$(( upload_success_count + upload_failed_count))
275+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Upload Statistics:"
276+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Total uploads attempted: ${total_uploads} "
277+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Successful uploads: ${upload_success_count} "
278+ echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Failed uploads: ${upload_failed_count} "
279+
111280 rm -rf " ${FORCE_EXIT_FILE} "
281+ rm -rf " /tmp/upload_stats.txt"
112282 echo " $( date -u +" ${ts_format} " ) [${process_name} ] - Uploader is ready to shutdown"
113283 exit 0
114284}
@@ -117,12 +287,28 @@ rename_rclone_env
117287trap graceful_exit SIGTERM SIGINT EXIT
118288
119289while true ; do
290+ # Exit main loop if graceful shutdown is in progress
291+ if [ " $graceful_exit_called " = " true" ]; then
292+ break
293+ fi
294+
120295 wait_until_pipefile_exists
296+
297+ # Exit main loop if graceful shutdown is in progress
298+ if [ " $graceful_exit_called " = " true" ]; then
299+ break
300+ fi
301+
121302 if ! check_if_pid_alive " ${UPLOAD_PID} " ; then
122303 consume_pipe_file_in_background &
123304 UPLOAD_PID=$!
124305 fi
306+
125307 while check_if_pid_alive " ${UPLOAD_PID} " ; do
308+ # Exit main loop if graceful shutdown is in progress
309+ if [ " $graceful_exit_called " = " true" ]; then
310+ break 2 # Break out of both while loops
311+ fi
126312 sleep 1
127313 done
128314done
0 commit comments