3

I have to transfer files from FTP to an Azure File Storage. My code works fine, but I'm transferring those files in memory which is not a best practice. So first I read the stream to an Byte array in memory. Then I upload the output to an Azure file storage.

Now I know it's better to do this asynchronicaly. But I don't know if this is possible and how to do it.

My code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage;
using System.Configuration;
using Microsoft.WindowsAzure.Storage.File;
using System.IO;
using Microsoft.Azure;
using System.Net;

namespace TransferFtpToAzure
{
    class Program
    {
        public static void Main(string[] args)
        {
            List<FileName> sourceFileList = new List<FileName>();
            List<FileName> targetFileList = new List<FileName>();

            string targetShareReference = ConfigurationManager.AppSettings["AzureShare"];
            string targetDirectoryReference = ConfigurationManager.AppSettings["Environment"] + "/" + Enums.AzureFolders.Mos + "/" + Enums.AzureFolders.In;
            string sourceURI = (ConfigurationManager.AppSettings["FtpConnectionString"] + ConfigurationManager.AppSettings["Environment"].ToUpper() +"/"+ Enums.FtpFolders.Mos + "/").Replace("\\","/");
            string sourceUser = ConfigurationManager.AppSettings["FtpServerUserName"];
            string sourcePass = ConfigurationManager.AppSettings["FtpServerPassword"];

            getFileLists(sourceURI, sourceUser, sourcePass, sourceFileList, targetShareReference, targetDirectoryReference, targetFileList);

            Console.WriteLine(sourceFileList.Count + " files found!");

            CheckLists(sourceFileList, targetFileList);
            targetFileList.Sort();

            Console.WriteLine(sourceFileList.Count + " unique files on sourceURI" + Environment.NewLine + "Attempting to move them.");

            foreach (var file in sourceFileList)
            {
                try
                {
                    CopyFile(file.fName, sourceURI, sourceUser, sourcePass, targetShareReference, targetDirectoryReference);
                }
                catch
                {
                    Console.WriteLine("There was move error with : " + file.fName);
                }
            }
        }

        public class FileName : IComparable<FileName>
        {
            public string fName { get; set; }
            public int CompareTo(FileName other)
            {
                return fName.CompareTo(other.fName);
            }
        }

        public static void CheckLists(List<FileName> sourceFileList, List<FileName> targetFileList)
        {
            for (int i = 0; i < sourceFileList.Count; i++)
            {
                if (targetFileList.BinarySearch(sourceFileList[i]) > 0)
                {
                    sourceFileList.RemoveAt(i);
                    i--;
                }
            }
        }

        public static void getFileLists(string sourceURI, string sourceUser, string sourcePass, List<FileName> sourceFileList, string targetShareReference, string targetDirectoryReference, List<FileName> targetFileList)
        {
            string line = "";
            /////////Source FileList
            FtpWebRequest sourceRequest;
            sourceRequest = (FtpWebRequest)WebRequest.Create(sourceURI);
            sourceRequest.Credentials = new NetworkCredential(sourceUser, sourcePass);
            sourceRequest.Method = WebRequestMethods.Ftp.ListDirectory;
            sourceRequest.UseBinary = true;
            sourceRequest.KeepAlive = false;
            sourceRequest.Timeout = -1;
            sourceRequest.UsePassive = true;
            FtpWebResponse sourceRespone = (FtpWebResponse)sourceRequest.GetResponse();
            //Creates a list(fileList) of the file names
            using (Stream responseStream = sourceRespone.GetResponseStream())
            {
                using (StreamReader reader = new StreamReader(responseStream))
                {
                    line = reader.ReadLine();
                    while (line != null)
                    {
                        var fileName = new FileName
                        {
                            fName = line
                        };
                        sourceFileList.Add(fileName);
                        line = reader.ReadLine();
                    }
                }
            }
            /////////////Target FileList
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
            CloudFileClient fileClient = storageAccount.CreateCloudFileClient();
            //var test = fileClient.ListShares();
            CloudFileShare fileShare = fileClient.GetShareReference(targetShareReference);
            if (fileShare.Exists())
            {
                CloudFileDirectory rootDirectory = fileShare.GetRootDirectoryReference();
                if (rootDirectory.Exists())
                {
                    CloudFileDirectory customDirectory = rootDirectory.GetDirectoryReference(targetDirectoryReference);
                    if (customDirectory.Exists())
                    {
                        var fileCollection = customDirectory.ListFilesAndDirectories().OfType<CloudFile>();
                        foreach (var item in fileCollection)
                        {
                            var fileName = new FileName
                            {
                                fName = item.Name
                            };
                            targetFileList.Add(fileName);
                        }
                    }
                }
            }
        }

        public static void CopyFile(string fileName, string sourceURI, string sourceUser, string sourcePass, string targetShareReference, string targetDirectoryReference)
        {
            try
            {
                FtpWebRequest request = (FtpWebRequest)WebRequest.Create(sourceURI + fileName);
                request.Method = WebRequestMethods.Ftp.DownloadFile;
                request.Credentials = new NetworkCredential(sourceUser, sourcePass);
                FtpWebResponse response = (FtpWebResponse)request.GetResponse();
                Stream responseStream = response.GetResponseStream();
                Upload(fileName, ToByteArray(responseStream), targetShareReference, targetDirectoryReference);
                responseStream.Close();
            }
            catch
            {
                Console.WriteLine("There was an error with :" + fileName);
            }
        }

        public static Byte[] ToByteArray(Stream stream)
        {
            MemoryStream ms = new MemoryStream();
            byte[] chunk = new byte[4096];
            int bytesRead;
            while ((bytesRead = stream.Read(chunk, 0, chunk.Length)) > 0)
            {
                ms.Write(chunk, 0, bytesRead);
            }

            return ms.ToArray();
        }

        public static bool Upload(string FileName, byte[] Image, string targetShareReference, string targetDirectoryReference)
        {
            try
            {
                CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
                CloudFileClient fileClient = storageAccount.CreateCloudFileClient();
                //var test = fileClient.ListShares();
                CloudFileShare fileShare = fileClient.GetShareReference(targetShareReference);
                if (fileShare.Exists())
                {
                    CloudFileDirectory rootDirectory = fileShare.GetRootDirectoryReference();
                    if (rootDirectory.Exists())
                    {
                        CloudFileDirectory customDirectory = rootDirectory.GetDirectoryReference(targetDirectoryReference);
                        if (customDirectory.Exists())
                        {
                            var cloudFile = customDirectory.GetFileReference(FileName);
                            using (var stream = new MemoryStream(Image, writable: false))
                            {
                                cloudFile.UploadFromStream(stream);
                            }
                        }
                    }
                }
                return true;
            }
            catch
            {
                return false;
            }
        }
    }
}
Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
hatsjie
  • 181
  • 10

2 Answers2

1

If I understand you correctly, you want to avoid storing the file in memory between the download and upload.

For that see:
Azure function to copy files from FTP to blob storage.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
  • Indeed, that is what I want to avoid. Thought you needed to work with async methods :). Thank you!! – hatsjie Dec 01 '17 at 20:25
  • "Operation is not valid due to the current state of the object.", any idea? – Aa Yy Jan 02 '19 at 16:05
  • @AaYy Consider asking a new question and make sure you include [mcve]. – Martin Prikryl Jan 02 '19 at 17:43
  • I actually did: https://stackoverflow.com/questions/54009633/file-transfer-from-ftp-directly-to-azure-storage-error – Aa Yy Jan 03 '19 at 08:15
  • @AaYy, hello, can you un-delete the post? is it acceptable to use file.UploadText() to transfer file from FTP to file storage? – Ivan Glasenberg Jan 03 '19 at 09:52
  • @IvanYang How does ` file.UploadText()` help? It requires you to keep the whole file contents in the memory the same way as the code in this question does. Plus string is even more memory inefficient than the byte array and has a more limited use. – Martin Prikryl Jan 03 '19 at 11:12
  • Thanks Martin, it's just a workaround. And really don't know why the stream from FTP does not work with file storage, but can work with blob storage. – Ivan Glasenberg Jan 03 '19 at 12:09
  • @IvanYang Workaround for what? The OP has a working code already (although memory-inefficient). OP does not need any workaround. – Martin Prikryl Jan 03 '19 at 12:21
  • how about blob directly to sftp? is this possible without downloading the contents locally? – Alex Gordon Jul 16 '19 at 21:47
  • @l--''''''---------'''''''''''' You have posted a question on this: [Uploading a Azure blob directly to SFTP server without an intermediate file](https://stackoverflow.com/q/57065811/850848). – Martin Prikryl Jul 17 '19 at 07:45
1

Using Azure Storage File Share this is the only way it worked for me without loading the entire ZIP into Memory. I tested with a 3GB ZIP File (with thousands of files or with a big file inside) and Memory/CPU was low and stable. I hope it helps!

var zipFiles = _directory.ListFilesAndDirectories()
    .OfType<CloudFile>()
    .Where(x => x.Name.ToLower().Contains(".zip"))
    .ToList();

foreach (var zipFile in zipFiles)
{
    using (var zipArchive = new ZipArchive(zipFile.OpenRead()))
    {
        foreach (var entry in zipArchive.Entries)
        {
            if (entry.Length > 0)
            {
                CloudFile extractedFile = _directory.GetFileReference(entry.Name);

                using (var entryStream = entry.Open())
                {
                    byte[] buffer = new byte[16 * 1024];
                    using (var ms = extractedFile.OpenWrite(entry.Length))
                    {
                        int read;
                        while ((read = entryStream.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            ms.Write(buffer, 0, read);
                        }
                    }
                }
            }
        }
    }               
}
rGiosa
  • 355
  • 1
  • 4
  • 16
  • 1
    You can use something like `await extractedFile.UploadFromStreamAsync(entryStream)` instead of a great part of your code. – Martin Prikryl Jun 03 '19 at 06:23
  • Actually, that was my first attempt. But this method requires a MemoryStream already loaded in memory, otherwise the error "Operation is not valid due to the current state of the object." is fired. – rGiosa Jun 03 '19 at 12:34
  • Also, `UploadFromStreamAsync` cannot be used when the stream originates from a `WebRequest` (as I had) since the response stream has `CanSeek` set to false. – Reyhn Sep 30 '19 at 09:45