public override Stream GetStream(FileSystemObject fso)
        {
            var bki = new S3BucketKeyInfo(fso.FullName);

            var request = new GetObjectRequest
            {
                BucketName = bki.BucketName,
                Key = bki.Key
            };

            try
            {
                return S3Client.GetObject(request).ResponseStream;
            }
            catch (AmazonS3Exception s3x)
            {
                if (IsMissingObjectException(s3x))
                    return null;

                throw;
            }
        }
        public override void Store(Stream stream, FileSystemObject fso)
        {
            var bki = new S3BucketKeyInfo(fso);

            var attemptedBucketCreate = false;

            do
            {
                try
                {
                    var request = new PutObjectRequest
                    {
                        BucketName = bki.BucketName,
                        Key = bki.Key,
                        InputStream = stream,
                        StorageClass = S3StorageClass.Standard
                    };

                    S3Client.PutObject(request);

                    break;
                }
                catch (AmazonS3Exception s3x)
                {
                    if (!attemptedBucketCreate && IsMissingObjectException(s3x))
                    {
                        CreateFolder(fso.FolderName);
                        attemptedBucketCreate = true;
                        continue;
                    }

                    throw;
                }

            } while (true);
        }
        public override void CreateFolder(string path)
        {
            if (string.IsNullOrEmpty(path))
                return;

            var bki = new S3BucketKeyInfo(path);

            var request = new PutBucketRequest
            {
                BucketName = bki.BucketName,
                UseClientRegion = true
            };

            S3Client.PutBucket(request);
        }
        private ListObjectsResponse ListFolderResponse(S3BucketKeyInfo bki, string nextMarker = null)
        {
            try
            {
                var listRequest = new ListObjectsRequest
                {
                    BucketName = bki.BucketName
                };

                if (!string.IsNullOrEmpty(nextMarker))
                {
                    listRequest.Marker = nextMarker;
                }

                if (bki.HasPrefix)
                {
                    listRequest.Prefix = bki.Prefix;
                }

                var listResponse = S3Client.ListObjects(listRequest);

                return listResponse;
            }
            catch (System.Xml.XmlException) { }
            catch (AmazonS3Exception s3x)
            {
                if (!IsMissingObjectException(s3x))
                {
                    throw;
                }
            }

            return null;
        }
        private void DeleteFolderInternal(string path, bool recursive)
        {
            if (string.IsNullOrEmpty(path))
            {
                return;
            }

            var bki = new S3BucketKeyInfo(path, terminateWithPathDelimiter: true);

            if (recursive)
            {
                while (true)
                {
                    var objects = ListFolder(bki);

                    if (!objects.Any())
                        break;

                    var keys = objects.Select(o => new KeyVersion
                    {
                        Key = o.Key
                    })
                    .ToList();

                    var deleteObjectsRequest = new DeleteObjectsRequest
                    {
                        BucketName = bki.BucketName,
                        Quiet = true,
                        Objects = keys
                    };

                    S3Client.DeleteObjects(deleteObjectsRequest);
                }
            }
            else if (!bki.IsBucketObject)
            {
                var deleteObjectRequest = new DeleteObjectRequest
                {
                    BucketName = bki.BucketName,
                    Key = bki.Key
                };

                S3Client.DeleteObject(deleteObjectRequest);
            }

            if (bki.IsBucketObject)
            {
                var request = new DeleteBucketRequest
                {
                    BucketName = bki.BucketName,
                    UseClientRegion = true
                };

                S3Client.DeleteBucket(request);
            }
        }
        public override IEnumerable<string> ListFolder(string folderName, bool recursive = false, bool fileNamesOnly = false)
        {
            if (string.IsNullOrEmpty(folderName))
            {
                yield break;
            }

            string nextMarker = null;

            var bki = new S3BucketKeyInfo(folderName, terminateWithPathDelimiter: true);

            do
            {
                var listResponse = ListFolderResponse(bki, nextMarker);

                if (listResponse == null || listResponse.S3Objects == null)
                    break;

                var filesOnly = listResponse.S3Objects
                    .Select(o => new S3BucketKeyInfo(bki.BucketName, o))
                    .Where(b => !string.IsNullOrEmpty(b.FileName))
                    .Where(b => recursive
                        ? b.Prefix.StartsWith(bki.Prefix, StringComparison.InvariantCulture)
                        : b.Prefix.Equals(bki.Prefix, StringComparison.InvariantCulture))
                    .Select(b => fileNamesOnly
                        ? b.FileName
                        : b.ToString()
                    );

                foreach (var file in filesOnly)
                {
                    yield return file;
                }

                if (listResponse.IsTruncated)
                {
                    nextMarker = listResponse.NextMarker;
                }
                else
                {
                    break;
                }

            } while (true);

        }
        private List<S3Object> ListFolder(S3BucketKeyInfo bki)
        {
            var listResponse = ListFolderResponse(bki);

            return listResponse == null
                ? new List<S3Object>()
                : listResponse.S3Objects ?? new List<S3Object>();
        }
        public override bool FolderExists(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                return false;
            }

            var bki = new S3BucketKeyInfo(path);

            // Folders aren't a thing in S3, buckets are the only things that actually matter more or less. Return true
            // if this is a bucket object and the bucket exists, otherwise false since it actually doesn't exist
            return bki.IsBucketObject && BucketExists(bki);
        }
        private bool BucketExists(S3BucketKeyInfo bki)
        {
            if (string.IsNullOrEmpty(bki.BucketName))
            {
                return false;
            }

            try
            {
                var bucketLocation = S3Client.GetBucketLocation(bki.BucketName);
                return true;
            }
            catch (AmazonS3Exception s3x)
            {
                if (IsMissingObjectException(s3x))
                {
                    return false;
                }

                throw;
            }

        }
        public override bool Exists(FileSystemObject fso)
        {
            var bki = new S3BucketKeyInfo(fso.FullName);

            var s3FileInfo = new S3FileInfo(S3Client, bki.BucketName, bki.Key);

            return s3FileInfo.Exists;
        }
        private void GetToLocalFile(FileSystemObject fso, FileSystemObject downloadToFso)
        {
            var bki = new S3BucketKeyInfo(fso.FullName);

            var request = new GetObjectRequest
            {
                BucketName = bki.BucketName,
                Key = bki.Key
            };

            localFs.CreateFolder(downloadToFso.FolderName);

            localFs.Delete(downloadToFso);

            using (var response = S3Client.GetObject(request))
            {
                response.WriteResponseStreamToFile(downloadToFso.FullName);
            }

        }
        private void CopyInS3(FileSystemObject sourceFso, FileSystemObject targetFso)
        {
            if (sourceFso.Equals(targetFso))
            {
                return;
            }

            var sourceBki = new S3BucketKeyInfo(sourceFso);
            var targetBki = new S3BucketKeyInfo(targetFso);

            var request = new CopyObjectRequest
            {
                SourceBucket = sourceBki.BucketName,
                SourceKey = sourceBki.Key,
                DestinationBucket = targetBki.BucketName,
                DestinationKey = targetBki.Key
            };

            S3Client.CopyObject(request);
        }
        private void Delete(S3BucketKeyInfo bki)
        {
            var request = new DeleteObjectRequest
            {
                BucketName = bki.BucketName,
                Key = bki.Key
            };

            try
            {
                S3Client.DeleteObject(request);
            }
            catch (AmazonS3Exception s3x)
            {
                if (IsMissingObjectException(s3x))
                {
                    return;
                }

                throw;
            }
        }
 public override void Delete(FileSystemObject fso)
 {
     var bki = new S3BucketKeyInfo(fso);
     Delete(bki);
 }