using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using DodoSSH.Client.Domain;
using DodoSSH.Client.Ssh;
namespace DodoSSH.Client.ObjectStore;
/// Opens a bucket as a place with files in it.
///
/// An interface so the file screen can be tested without a bucket, exactly as ISftpSessionFactory is
/// what lets it be tested without a host.
///
public interface IObjectStoreFactory
{
/// Builds a client for one bucket.
/// The bucket and its credentials, decrypted.
///
/// Synchronous and cheap: nothing is contacted here. S3 is request-per-operation, so there is no
/// connect step to fail — the first thing that can fail is the first listing, which is where the
/// credentials and the endpoint are actually tested.
///
IRemoteFileStore Open(ObjectStoreSecret store);
}
/// Opens buckets with the AWS SDK.
public sealed class S3ObjectStoreFactory : IObjectStoreFactory
{
///
public IRemoteFileStore Open(ObjectStoreSecret store)
{
ArgumentNullException.ThrowIfNull(store);
if (!store.TryValidate(out var reason))
{
throw new ArgumentException(reason, nameof(store));
}
var config = new AmazonS3Config
{
// On for nearly every self-hosted service and off for AWS. It is a stored setting rather than
// something inferred from the endpoint, because inferring it wrongly produces a DNS failure that
// says nothing about buckets — see ObjectStoreSecret.UsePathStyle.
ForcePathStyle = store.UsePathStyle,
};
if (store.Endpoint is { } endpoint)
{
config.ServiceURL = endpoint;
// Still set when there is one, because SigV4 signs the region into every request and several
// S3-compatible services check it. The ones that do not, ignore it.
if (store.Region is { } named)
{
config.AuthenticationRegion = named;
}
}
else
{
// No endpoint means Amazon, and then the region is what resolves the host. Validation has
// already refused the case where neither is set.
config.RegionEndpoint = RegionEndpoint.GetBySystemName(store.Region!);
}
var credentials = new BasicAWSCredentials(store.AccessKeyId, store.SecretAccessKey);
return new S3FileStore(new AmazonS3Client(credentials, config), store.Bucket);
}
}