use super::*;
impl RaSvnClient {
pub fn new(base_url: SvnUrl, username: Option<String>, password: Option<String>) -> Self {
Self {
base_url,
username,
password,
connect_timeout: Duration::from_secs(10),
read_timeout: Duration::from_secs(60),
write_timeout: Duration::from_secs(60),
reconnect_retries: 1,
ra_client: "prototype-ra_svn".to_string(),
#[cfg(feature = "ssh")]
ssh: None,
}
}
pub fn base_url(&self) -> &SvnUrl {
&self.base_url
}
pub fn username(&self) -> Option<&str> {
self.username.as_deref()
}
pub(crate) fn password(&self) -> Option<&str> {
self.password.as_deref()
}
pub fn connect_timeout(&self) -> Duration {
self.connect_timeout
}
pub fn read_timeout(&self) -> Duration {
self.read_timeout
}
pub fn write_timeout(&self) -> Duration {
self.write_timeout
}
pub fn ra_client(&self) -> &str {
&self.ra_client
}
#[must_use]
pub fn with_connect_timeout(mut self, connect_timeout: Duration) -> Self {
self.connect_timeout = connect_timeout;
self
}
#[must_use]
pub fn with_read_timeout(mut self, read_timeout: Duration) -> Self {
self.read_timeout = read_timeout;
self
}
#[must_use]
pub fn with_write_timeout(mut self, write_timeout: Duration) -> Self {
self.write_timeout = write_timeout;
self
}
#[must_use]
pub fn with_ra_client(mut self, ra_client: impl Into<String>) -> Self {
self.ra_client = ra_client.into();
self
}
#[must_use]
pub fn with_reconnect_retries(mut self, retries: usize) -> Self {
self.reconnect_retries = retries;
self
}
pub fn reconnect_retries(&self) -> usize {
self.reconnect_retries
}
#[cfg(feature = "ssh")]
#[must_use]
pub fn with_ssh_config(mut self, ssh: crate::ssh::SshConfig) -> Self {
self.ssh = Some(ssh);
self
}
pub async fn open_session(&self) -> Result<RaSvnSession, SvnError> {
let mut session = RaSvnSession {
client: self.clone(),
conn: None,
server_info: None,
allow_reconnect: true,
};
session.reconnect().await?;
Ok(session)
}
pub async fn open_session_with_stream<S>(&self, stream: S) -> Result<RaSvnSession, SvnError>
where
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static,
{
let mut session = RaSvnSession {
client: self.clone(),
conn: None,
server_info: None,
allow_reconnect: false,
};
let (conn, server_info) = self.connect_over_stream(stream).await?;
session.conn = Some(conn);
session.server_info = Some(server_info);
Ok(session)
}
pub(super) async fn connect(&self) -> Result<(RaSvnConnection, ServerInfo), SvnError> {
let is_tunneled = self
.base_url
.url
.get(.."svn+ssh://".len())
.is_some_and(|prefix| prefix.eq_ignore_ascii_case("svn+ssh://"));
if is_tunneled {
#[cfg(feature = "ssh")]
{
let ssh = self.ssh.clone().unwrap_or_default();
let stream =
crate::ssh::open_svnserve_tunnel(&self.base_url, &ssh, self.connect_timeout)
.await?;
return self.connect_over_stream(stream).await;
}
#[cfg(not(feature = "ssh"))]
{
return Err(SvnError::InvalidUrl(
"svn+ssh URLs require the crate feature `ssh`".to_string(),
));
}
}
let addr = self.base_url.socket_addr();
let stream = tokio::time::timeout(self.connect_timeout, TcpStream::connect(addr))
.await
.map_err(|_| {
SvnError::Io(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"connect timed out",
))
})??;
stream.set_nodelay(true)?;
#[cfg(feature = "cyrus-sasl")]
let (local_addrport, remote_addrport) = (
stream
.local_addr()
.ok()
.map(|addr| format!("{};{}", addr.ip(), addr.port())),
stream
.peer_addr()
.ok()
.map(|addr| format!("{};{}", addr.ip(), addr.port())),
);
let (read, write) = stream.into_split();
let mut conn = RaSvnConnection::new(
Box::new(read),
Box::new(write),
RaSvnConnectionConfig {
username: self.username.clone(),
password: self.password.clone(),
#[cfg(feature = "cyrus-sasl")]
host: self.base_url.host.clone(),
#[cfg(feature = "cyrus-sasl")]
local_addrport,
#[cfg(feature = "cyrus-sasl")]
remote_addrport,
url: self.base_url.url.clone(),
is_tunneled: false,
ra_client: self.ra_client.clone(),
read_timeout: self.read_timeout,
write_timeout: self.write_timeout,
},
);
let server_info = conn.handshake().await?;
Ok((conn, server_info))
}
pub(super) async fn connect_over_stream<S>(
&self,
stream: S,
) -> Result<(RaSvnConnection, ServerInfo), SvnError>
where
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + 'static,
{
#[cfg(feature = "cyrus-sasl")]
let (local_addrport, remote_addrport) = (None, None);
let (read, write) = tokio::io::split(stream);
let mut conn = RaSvnConnection::new(
Box::new(read),
Box::new(write),
RaSvnConnectionConfig {
username: self.username.clone(),
password: self.password.clone(),
#[cfg(feature = "cyrus-sasl")]
host: self.base_url.host.clone(),
#[cfg(feature = "cyrus-sasl")]
local_addrport,
#[cfg(feature = "cyrus-sasl")]
remote_addrport,
url: self.base_url.url.clone(),
is_tunneled: self
.base_url
.url
.get(.."svn+ssh://".len())
.is_some_and(|prefix| prefix.eq_ignore_ascii_case("svn+ssh://")),
ra_client: self.ra_client.clone(),
read_timeout: self.read_timeout,
write_timeout: self.write_timeout,
},
);
let server_info = conn.handshake().await?;
Ok((conn, server_info))
}
}