Skip to content

Commit 1cb797b

Browse files
authored
Merge pull request #42 from invenia/upload_with_retry
Added testsets and an upload function that can do retries
2 parents cca2290 + 600278f commit 1cb797b

File tree

10 files changed

+589
-326
lines changed

10 files changed

+589
-326
lines changed

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ os:
55
julia:
66
- 0.6
77
- nightly
8+
matrix:
9+
allow_failures:
10+
- julia: nightly
811
notifications:
912
email: false
1013
script:

REQUIRE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
julia 0.6
22
LibCURL 0.2.2
3-
Compat 0.8.3
3+
Compat 0.41

appveyor.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ environment:
33
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe"
44
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
55

6+
matrix:
7+
allow_failures:
8+
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
9+
610
notifications:
711
- provider: Email
812
on_build_success: false

src/FTPC.jl

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using Compat
12
using LibCURL
23

34
import Base: ==
5+
import Compat.Cvoid
46

57
##############################
68
# Type definitions
@@ -113,7 +115,7 @@ end
113115
# Callbacks
114116
##############################
115117

116-
function write_file_cb(buff::Ptr{UInt8}, sz::Csize_t, n::Csize_t, p_wd::Ptr{Void})
118+
function write_file_cb(buff::Ptr{UInt8}, sz::Csize_t, n::Csize_t, p_wd::Ptr{Cvoid})
117119
# println("@write_file_cb")
118120
wd = unsafe_pointer_to_objref(p_wd)
119121
nbytes = sz * n
@@ -125,9 +127,9 @@ function write_file_cb(buff::Ptr{UInt8}, sz::Csize_t, n::Csize_t, p_wd::Ptr{Void
125127
nbytes::Csize_t
126128
end
127129

128-
c_write_file_cb = cfunction(write_file_cb, Csize_t, (Ptr{UInt8}, Csize_t, Csize_t, Ptr{Void}))
130+
c_write_file_cb = cfunction(write_file_cb, Csize_t, Tuple{Ptr{UInt8}, Csize_t, Csize_t, Ptr{Cvoid}})
129131

130-
function header_command_cb(buff::Ptr{UInt8}, sz::Csize_t, n::Csize_t, p_resp::Ptr{Void})
132+
function header_command_cb(buff::Ptr{UInt8}, sz::Csize_t, n::Csize_t, p_resp::Ptr{Cvoid})
131133
# println("@header_cb")
132134
resp = unsafe_pointer_to_objref(p_resp)
133135
nbytes = sz * n
@@ -140,25 +142,25 @@ function header_command_cb(buff::Ptr{UInt8}, sz::Csize_t, n::Csize_t, p_resp::Pt
140142
nbytes::Csize_t
141143
end
142144

143-
c_header_command_cb = cfunction(header_command_cb, Csize_t, (Ptr{UInt8}, Csize_t, Csize_t, Ptr{Void}))
145+
c_header_command_cb = cfunction(header_command_cb, Csize_t, Tuple{Ptr{UInt8}, Csize_t, Csize_t, Ptr{Cvoid}})
144146

145-
function curl_read_cb(out::Ptr{Void}, s::Csize_t, n::Csize_t, p_rd::Ptr{Void})
147+
function curl_read_cb(out::Ptr{Cvoid}, s::Csize_t, n::Csize_t, p_rd::Ptr{Cvoid})
146148
# println("@curl_read_cb")
147149
rd = unsafe_pointer_to_objref(p_rd)
148150
bavail::Csize_t = s * n
149151
breq::Csize_t = rd.sz - rd.offset
150152
b2copy = bavail > breq ? breq : bavail
151153

152154
b_read = read(rd.src, UInt8, b2copy)
153-
ccall(:memcpy, Ptr{Void}, (Ptr{Void}, Ptr{Void}, UInt), out, b_read, b2copy)
155+
ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt), out, b_read, b2copy)
154156

155157
rd.offset += b2copy
156158

157159
r = convert(Csize_t, b2copy)
158160
r::Csize_t
159161
end
160162

161-
c_curl_read_cb = cfunction(curl_read_cb, Csize_t, (Ptr{Void}, Csize_t, Csize_t, Ptr{Void}))
163+
c_curl_read_cb = cfunction(curl_read_cb, Csize_t, Tuple{Ptr{Cvoid}, Csize_t, Csize_t, Ptr{Cvoid}})
162164

163165

164166
##############################

src/FTPClient.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module FTPClient
22

33
import Base: convert, show, open, mkdir, ascii, mv
44
import Base: readdir, cd, pwd, rm, close, download
5-
import Compat: readstring, unsafe_string, unsafe_write
5+
import Compat: readstring, unsafe_string, unsafe_write, @compat
66

77
mutable struct FTPClientError <: Exception
88
msg::AbstractString

src/FTPObject.jl

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# FTP code for when the file transfer is complete.
2+
const complete_transfer_code = 226
3+
14
"""
25
FTP(; kwargs...) -> FTP
36
@@ -110,6 +113,94 @@ function upload(ftp::FTP, local_file::IO, remote_name::AbstractString; mode::FTP
110113
end
111114

112115

116+
"""
117+
upload(
118+
ftp::FTP,
119+
local_file_paths::Vector{<:AbstractString},
120+
ftp_dir<:AbstractString;
121+
retry_callback::Function=(count, options) -> (count < 4, options),
122+
retry_wait_seconds::Integer = 5
123+
)
124+
125+
Uploads the files specified in local_file_paths to the directory specifed by
126+
ftp_dir. The files will have the same names.
127+
128+
By default, will try to deliver the files 4 times with a 5 second wait in between
129+
each failed attempt.
130+
131+
You can specify a function for retry_callback to change behaviour. This function must
132+
take as parameters the number of attempts that have been made so far, and the current
133+
ftp connection options as a FTPClient.ConnContext type. It must return a boolean
134+
that is true if another delivery attempt can be made, and a TPClient.ConnContext
135+
type that is the connection options to use for all future files to be delivered. This
136+
allows backup ftp directories to be used for example.
137+
138+
# Arguments
139+
`ftp::FTP`: The FTP to deliver to. See FTPClient.FTP for details.
140+
`file_paths::Vector{T}`: The file paths to the files we want to deliver.
141+
`ftp_dir`: The directory on the ftp server where we want to drop the files.
142+
`retry_callback::Function=(count, options) -> (count < 4, options)`: Function for retrying
143+
when delivery fails.
144+
`retry_wait_seconds::Integer = 5`: How many seconds to wait in between retries.
145+
146+
# Returns
147+
- `Array{Bool,1}`: Returns a vector of booleans with true for each successfully delivered
148+
file and false for any that failed to transfer.
149+
"""
150+
@compat function upload(
151+
ftp::FTP,
152+
local_file_paths::Vector{<:AbstractString},
153+
ftp_dir::AbstractString;
154+
retry_callback::Function=(count, options) -> (count < 4, options),
155+
retry_wait_seconds::Integer = 5
156+
)
157+
158+
successful_delivery = Bool[]
159+
160+
ftp_options = ftp.ctxt
161+
162+
for single_file in local_file_paths
163+
# The location we are going to drop the file in the FTP server
164+
server_location = joinpath(ftp_dir, basename(single_file))
165+
166+
# ftp_put requires an io so open up our file.
167+
open(single_file) do single_file_io
168+
# Whether or not the current file was successfully delivered to the FTP
169+
file_delivery_success = false
170+
171+
attempts = 1
172+
# The loops should break after an appropriate amount of retries.
173+
# This way of doing retries makes testing easier.
174+
# Defaults to 4 attempts, waiting 5 seconds between each retry.
175+
while true
176+
try
177+
resp = ftp_put(ftp_options, server_location, single_file_io)
178+
file_delivery_success = resp.code == complete_transfer_code
179+
if file_delivery_success
180+
break
181+
end
182+
catch e
183+
warn(e)
184+
end
185+
sleep(retry_wait_seconds)
186+
# It returns ftp_options for testing purposes, where the ftp server
187+
# starts not existing then comes into existence during retries.
188+
do_retry, ftp_options = retry_callback(attempts, ftp_options)
189+
if !do_retry
190+
break
191+
end
192+
attempts += 1
193+
end
194+
195+
push!(successful_delivery, file_delivery_success)
196+
197+
end
198+
end
199+
200+
return successful_delivery
201+
end
202+
203+
113204
"""
114205
readdir(ftp::FTP)
115206

0 commit comments

Comments
 (0)