Работа с FTP сервером из C#

Наверное многие сталкивались с необходимостью создать простой FTP клиент для своих нужд на C#. Вот и меня не миновало. Недавно пришлось делать специфическое приложение для обновления ПО через FTP. И самым полезным что я нашёл для этой цели оказался набор библиотечек (кстати с исходниками) BytesRoad.NetSuit. Очень простой компонент предоставляющий весь основной функционал для работы с FTP.

В коплекте 3 библиотеки:
BytesRoad.Diag.dll
BytesRoad.Net.Ftp.dll
BytesRoad.Net.Sockets.dll


Добавляем в наш проект как References вот эти две библиотеки:
BytesRoad.Net.Ftp.dll
BytesRoad.Net.Sockets.dll

Далее о том как работать с компонентом…

Устанавливаем соединение с сервером:

//Сам клиент ФТП
FtpClient client = new FtpClient();

//Задаём параметры клиента.
client.PassiveMode = true; //Включаем пассивный режим.
int TimeoutFTP = 30000; //Таймаут.
string FTP_SERVER = "адрес фтп сервера";
int FTP_PORT = "порт ФТП сервера";
string FTP_USER = "пользователь";
string FTP_PASSWORD = "пароль";

//Если используется прокси сервер то можем задать параметры прокси.
FtpProxyInfo pinfo = new FtpProxyInfo(); //Это переменная параметров.
pinfo.Server = "192.168.0.202";
pinfo.Port = 21; //Порт.
pinfo.Type = FtpProxyType.HttpConnect; //Тип прокси - всего 4 вида.
pinfo.PreAuthenticate = true; //Если на прокси есть идентификация
pinfo.User = "Имя пользователя";
pinfo.Password = "Пароль пользователя";

//Присваиваем параметры прокси клиенту.
client.ProxyInfo = pinfo;

//Подключаемся к FTP серверу.
client.Connect(TimeoutFTP, FTP_SERVER, FTP_PORT);
client.Login(TimeoutFTP, FTP_USER, FTP_PASSWORD);

//
//.... Здесь выполняем то что хотим с сервером ...
//


//Отключаемся от ФТП сервера
client.Disconnect(TimeoutFTP);


Теперь немного о самых основных методах для работы с FTP сервером:

//Получает список содержимого текущего каталога с FTP.
client.GetDirectoryList(TimeoutFTP);

//Меняет директорию на указанную.
//Можно переходить вверх указав вместо имени папки ".." либо в любую папку расположенную в текущей.
client.ChangeDirectory(TimeoutFTP, "папка");

//Удаляет указанный файл с сервера.
client.DeleteFile(TimeoutFTP, "файл");

//Удаляет указанную папку с сервера.
client.DeleteDirectory(TimeoutFTP, "файл");

//Принимает указанный файл с сервера.
client.GetFile(TimeoutFTP, "куда принимаем - путь на диске", "Что принимаем - файл на сервере");

//загружаем файл на сервер.
client.PutFile(TimeoutFTP, "имя файла на сервере", "что грузим - имя файла на компьютере");


Как уже полагаю заметили, довольно неудобно что надо каждый раз указывать таймаут.
Но впринципе с этим можно смириться, остальное то всё работает.

Я бы как обычно привёл ссылочку на сайт разработчика где можно скачать компонент, но похоже сайт разработчика не пережил кризиса :). Поэтому отступлю от своего правила, библиотеку можно взять здесь: BytesRoad.NetSuit_2_0.zip.

Также я написал маленький примерчик работы с этим компонентом,(загрузка удаление и получение файлов в примере реализоавны по правой кнопке в выпадающем меню).
Вот скриншоты:



А вот сам исходник: BytesRoadFtpExample.rar.


64 комментария

avatar
При попытке данной программой что то скачать с сервера ftp.microsoft.com на жёсткий диск возникает сообщение Access to the path 'xxxxxxx' is denied. Как разрешить доступ к папке?
avatar
У меня всё работает вот с такими параметрами:
Параметры BytesRoadsFtp

Вот результат:
Результат BytesRoadsFtp
avatar


Вот подключаемся к серверу с теме настройками что у тебя и выбираем файл и хотим сохранить его на диске D:\ и получаем такую мессагу.
avatar
Действительно, сейчас проверил, ошибку выдаёт такую. Завтра гляну — по всей видимости что-то где-то не так сделал. Потому-что в проекте, который на ней делал всё точно работало и в нужные папочки сохраняло.
avatar
Всё оказалось довольно просто, ступил маленько.
Там надо при приёме файла, вместо этой строчки:
client.GetFile(MainParams.FtpTimeout * 1000, folderBrowserDialog1.SelectedPath, treeView1.SelectedNode.Text);

Написать вот эту:
client.GetFile(MainParams.FtpTimeout * 1000, folderBrowserDialog1.SelectedPath+treeView1.SelectedNode.Text, treeView1.SelectedNode.Text);

Просто вторым параметром должна идти не папка, а весь путь включая имя файла. Вот он и ругался. :)
avatar
Здравствуйте, а можно как то с помощью функции

client.GetFile(TimeoutFTP, «куда принимаем — путь на диске», «Что принимаем — файл на сервере»);

скачать файлы определённого расширения, например, файлы .rar, .zip, .arj, .doc ???
avatar
Конечно можно.
Примерно так:
foreach (FtpItem item in client.GetDirectoryList(таймаут))//Перебираем итемы в папке
   {
     if (item.ItemType == FtpItemType.File)
       {
            //Если итем - является файлом (помимо файла могут быть ссылкой и директорией),
            // то здесь проверяем его расширение,
            //И загружаем если необходимо по условию.
       }
   }
avatar
Здравствуйте, спасибо за ответ, но я так и делаю. Просто я думал вы мне подскажите какой-нибудь способ в C# проверки файла по расширению.
avatar
Я нашёл удобный способ получения расширения файла с помощью функции
System.IO.Path.GetExtension(строка пути, из кот. нужно получить расширение);
avatar
Под строчкой:
// то здесь проверяем его расширение,

Я именно это и понимал :)
avatar
А как получить размер файла?
avatar
Вот так:
FtpItem item = new FtpItem();
long razmer = item.Size;
avatar
Ой, а можно на примере показать как узнать размер определенного файла на фтп сервере?
Что передавать в FtpItem()? Попробовал имя файла, возвращает всегда "-1"
avatar
Вот измененная процедура получения списка файлов с ФТП из примера:

//Получение списка файлов текущего каталога с ФТП
private void GetItemsFromFtp()
        {
            foreach (FtpItem item in client.GetDirectoryList(MainParams.FtpTimeout * 1000))
            {
                TreeNode node = new TreeNode(item.Name + ":  " + item.Size.ToString()+" Bytes");
                
                node.ImageIndex = 2;
                node.SelectedImageIndex = 2;
                node.Tag = 0;
                if (item.ItemType == FtpItemType.Directory)
                {
                    node.ImageIndex = 0;
                    node.SelectedImageIndex = 0;
                    node.Tag = 1;
                }
                if (item.ItemType == FtpItemType.File)
                {
                    node.ImageIndex = 1;
                    node.SelectedImageIndex = 1;
                    
                }
                if (item.ItemType == FtpItemType.Link)
                {
                    node.ImageIndex = 3;
                    node.SelectedImageIndex = 3;
                }
                treeView1.Nodes.Add(node);
                
            }
        }


Вот весь измененный пример целиком: BytesRoadFtpExample_Size.rar
avatar
А может и скорость скачки можно регулировать? Как-то уж оно медленно тянет
avatar
Вот, это уже средствами самой библиотеки вряд-ли.

Сервер ведь отдает данные с определённым ограничением по скорости, и тут уже единственное что можно сделать (в случае если свой интернет канал позволяет) это разбить скачку на несколько потоков ( и еще если сервер позволит в несколько потоков к нему подключиться).

Самое элементарное и наиболее простое в реализации, на мой взгляд, это если надо тянуть много файлов — тянуть разные файлы в несколько подключений. Каждый файл в своем потоке.
avatar
Пытался написать процедуру для поднятия вверх по каталогу
private void вверхToolStripMenuItem_Click(object sender, EventArgs e)
        {
            client.ChangeDirectory(MainParams.FtpTimeout, "..");
        }

Выбрасывает в тайм-аут :(
avatar
Если делалось прямо в исходнике пример то надо вот так делать:
private void вверхToolStripMenuItem_Click(object sender, EventArgs e)
        {
            client.ChangeDirectory(MainParams.FtpTimeout * 1000, "..");
            treeView1.Nodes.Clear();
            GetItemsFromFtp();
        }

Там таймаут — он задается в параметрах в секундах (так более юзер-френдли), а нужен в миллисекундах. Поэтому умножаем на 1000, не успевает он за 30 миллисекунд.
Только что проверил, работает 100%.
avatar
Спасибо всё работает, забыл про ноды и что в исходнике не было в начале умножения на 1000, ваяю дальше!
avatar
Подскажите пожалуйста способ прикрутить ProgressBar к upload ну или к download.
avatar
Я делал примерно так:
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
{
    string[] name = treeView1.SelectedNode.Text.Split(':');
    string size = name[1].Remove(name[1].Length-5,5);
    size = size.Trim();
    long sz_all = long.Parse(size);
    long sz = 0;

    while (sz < sz_all)
      {
       client.GetFile(MainParams.FtpTimeout * 1000, name[0], name[0], sz, 100000);
       //Источник
       FileStream source = new FileStream(name[0], FileMode.Open, FileAccess.Read, FileShare.Read);
       //Приемник
       FileStream dest = new FileStream(folderBrowserDialog1.SelectedPath + "\\" + name[0], FileMode.Append, FileAccess.Write, FileShare.None);

       Byte[] buffer = new Byte[32];
       while (source.Position != source.Length)
          {
             int n = source.Read(buffer, 0, buffer.Length);
             dest.Write(buffer, 0, n);
          }
     dest.Flush();
     source.Close();
     dest.Close();
     sz = sz + 100000;
                            
this.Text = "Скачано: " + (sz / 1000).ToString() + " Kb.";
Application.DoEvents();
}
}

Здесь скачиваются кусочки файла по 100 килобайт и сливаются в один. Но это работает медленно. Думаю каждый кусочек он ищет на сервере по новой — поэтому и долго.
Была идея принимая файл из него информацию сливать в окончательный файл, но мне не понадобилось. В принципе это примерно то-же что и код выше, только быстрее.

Думаю в компоненте есть каике-то механизмы более продвинутые для этих целей, но с ними не разбирался.

Я для своих нужд обычно просто получаю список файлов, и по их количеству ProgressBar — заполняю. Это самый простой надежный и быстрый вариант, на мой взгляд.
avatar
Большое спасибо с закачкой все понятно… А вот с Аплоадом ума не приложу =(
Заранее Благодарю…
avatar
Можно как в предыдущем варианте только в обратном порядке.
Сначала на сервер кинуть первый кусочек через:
client.PutFile();

Затем добавлять по кусочку:
client.AppendToFile();
avatar
Есть более простой способ — подключить событие DataTransfered и обрабатывать его.


Dim ftpclient As New BytesRoad.Net.Ftp.FtpClient
ftpclient.PassiveMode = True
AddHandler ftpclient.DataTransfered, AddressOf data_transfered
//Выполняем всё необходимое
//Я скачивал все файлы из директории (Progressbar1 - обновлялся по количеству файлов, а ProgressBar2 - отображал процесс текущего файла
Dim dirlist() As BytesRoad.Net.Ftp.FtpItem = ftpclient.GetDirectoryList(FTP_TIMEOUT)
ProgressBar1.Maximum = dirlist.lenght
Dim cnt As Integer = 0
For Each itm As BytesRoad.Net.Ftp.FtpItem In dirlist
      Select Case itm.ItemType
         Case BytesRoad.Net.Ftp.FtpItemType.File
            ProgressBar2.Value = 0
            ProgressBar2.Maximum = itm.Size
            ftpclient.GetFile(FTP_TIMEOUT, "d:\_ftp\" + itm.Name, itm.Name)
            ProgressBar1.Value = cnt + 1
            ProgressBar1.Update()
            Application.DoEvents()
            cnt += 1
      End Select
 Next
//Обработчик события.
    Private Sub data_transfered(ByVal sender As Object, ByVal e As BytesRoad.Net.Ftp.DataTransferedEventArgs)
        
        ProgressBar2.Value += e.LastTransfered
     
        Application.DoEvents()

    End Sub

Я думаю, адаптировать код к C# не составит трудов.
avatar
Уважаемые специалисты, никто не сталкивался с проблемой при «Подключении к серверу»?
Ввел следующие параметры


И в ответ получаю окно сообщения.



и сразу после него вот такое



Есть у кого-нибудь идеи?
avatar
На ум не приходит ничего, кроме проблем с авторизацией через прокси.
ftp.microsoft.com — точно пускает при таких параметрах.
Может при анаонимном подключении к прокси тоже надо какого нибудь пользователя anonymous — указывать?
avatar
Я для сравнения пробую в TotalCommander те же настройки — то есть
host — ftp.microsoft.com
user — anonymous
password — ничего не пишу

использовать прокси proxy.city-yar.ru:3128 (HTTP с поддержкой FTP)
Тут тоже можно ввести логин / пароль учетной записи, но я этого не делаю.

И все нормально — заходит. Мои скрины ведь соответствуют такому входу?

avatar
Да соответствуют.
Даже не знаю, потыкал сам, на нескольких анонимных прокси в интернете, не входит действительно. Не знаю даже, мне как-то через прокси никогда не надо было, я особо и не тестировал.
avatar
Ооооочень жаль… Ну просто очень. Proxy — это, как я считаю — очень важный функционал при работе с FTP. Я надеялся, что это я не то что-то вписываю…

В коде есть метод ConnectFTP(). В нем строка
client.Connect(MainParams.FtpTimeout * 1000, MainParams.FtpServer, MainParams.FtpPort);

Так вот на ней выбрасывается исключение.

Я, кстати, встречал на форумах запись, что внутри библиотеки которая здесь используется (вроде вот в этой BytesRoad.Net.Ftp.dll) выбрасывается исключение при конекте с proxy… То есть вот то, что я читаю в MessageBox после конекта с Proxy — это уже обработанное сообщение из библиотеки.
avatar
Хочу сделать прогу которая периодически проверяет наличие файла на ФТП сервере и его скачивает. Использую
client.GetFile(MainParams.FtpTimeout * 1000, MainParams.Path + MainParams.GetFile, MainParams.GetFile);

Файл скачивается. проблем нет. Проблема в том что если запустить эту часть кода когда файл отсутствует на ФТП. В локальной папке на компе просто появляется файл. Пустое файл. Как обойти не знаю.
avatar
Легко. Просто прежде чем копировать файл получить содержимое нужного каталога с ftp функцией GetDirectoryList проверить есть ли в содержимом то что нам надо, и только если есть копировать.
Например так:
foreach (FtpItem item in client.GetDirectoryList(MainParams.FtpTimeout * 1000))
{
   if (item.Name=="нужный.файл")
   {
      client.GetFile(MainParams.FtpTimeout * 1000, MainParams.Path + MainParams.GetFile, MainParams.GetFile);
   }
}
avatar
Фуг это сущий кошмар… Все таки на php легче, а еще говорят что C# и PHP похожи синтаксисом.
avatar
Когда говорят похожи синтаксисом, я так понимаю имеют ввиду похожесть синтаксиса основных основных операторов: for, while, if… else. и т.п.
Было бы странно если бы на PHP работу с FTP было бы сложнее реализовать, это очень близко к основной области его применения.
avatar
День добрый!!!
посоветуйте пожалуйста, ситуация такая:
есть ФТП-папка с файлами(только чтение), можно ли узнать с клиента что файл на сервере еще не загружен полностью, как бы сказать клиенту что «файл занят попробуйте скачать его позже»
Большое спасибо…
avatar
По файлу даже не знаю, можно ли так сделать — чтобы по самому файлу определить весь он уже или нет, можно лишь определить больше он того что мы уже скачали или нет.
Но вообще в быту для всяких задач обмена данными — это делается так: источником сначала ложится файл флаг — затем загружаются файлы — когда файлы загружены флаг удаляется, клиент проверяет наличие файла флага — если есть то ничего не делает и ждёт дальше — если нет — скачивает.

Можно ещё в сам файл-флаг разную информацию помещать, для информирования клиента.
avatar
Большое спасибо за оперативный ответ.
>>источником сначала ложится файл флаг — затем загружаются файлы — когда файлы загружены флаг удаляется, клиент проверяет наличие файла флага — если есть то ничего не делает и ждёт дальше — если нет — скачивает.<<
— это знакомо, у меня другая ситуация, хотелось примерно так реализовать:
public bool ReadFTPFiles(string fileFtp);
{
filePatch = «ftp://localhost/» + fileFtp;
try
{
using (var fs = File.Open(filePatch, FileMode.Open, FileAccess.Read, FileShare.None))
{
return true;
}
}
catch (IOException ioex)
{
SkipFiles = +1;
return false;
}
}
Буду дальше думать, спасибо!
Всех благ…
avatar
Спасибо тебе огромное! Отличная статья! Очень долго искал, наконец, нашел! Все работает (переводил с C# на PascalABC.NET).

P.S. Я даже зарегистрировался, чтобы поблагодарить )
avatar
Спасибо за статью!
Подскажите, как переименовывать папки и файлы а фтп?
try
            {
                client.RenameFile(MainParams.FtpTimeout * 1000, "1", "2");
                treeView1.Nodes.Clear();
                GetItemsFromFtp();
            }

Так работает, но как сделать чтобы была возможность вводить новое имя в окне программы?
avatar
статья очень полезная! но есть вопрос — upload не работает… сколько уже не думал, не получается исправить. подскажите, пожалуйста, может есть варианты исправления проблемы?
avatar
Ошибочку бы, что выдаёт при этом?
avatar
Подскажи пожалуйста, можно ли как-нибудь отключить окна которые выскакивают при ошибке? А то приходится на удаленный сервка конектиться и нажимать кнопку окей?
avatar
А как задать чтобы файл с сервера загружался только если его дата создания новее чем файла на диске?
avatar
У FtpItem — есть свойство Date — с его помощью можно получить время изменения файла.
В примере — если поменять функцию для вывода файлов вот так:
//Получение списка файлов текущего каталога с ФТП
private void GetItemsFromFtp()
        {
          
            foreach (FtpItem item in client.GetDirectoryList(MainParams.FtpTimeout * 1000))
            {
                TreeNode node = new TreeNode(item.Name);

                
                node.ImageIndex = 2;
                node.SelectedImageIndex = 2;
                node.Tag = 0;
                if (item.ItemType == FtpItemType.Directory)
                {
                    node.ImageIndex = 0;
                    node.SelectedImageIndex = 0;
                    node.Tag = 1;
                }
                if (item.ItemType == FtpItemType.File)
                {
                    node.ImageIndex = 1;
                    node.SelectedImageIndex = 1;
                    node.Text = node.Text +" --- " + item.Date.ToString();
                    
                }
                if (item.ItemType == FtpItemType.Link)
                {
                    node.ImageIndex = 3;
                    node.SelectedImageIndex = 3;
                }
                treeView1.Nodes.Add(node);
                
            }
        }


То будет выводит время изменения после имени файла.
avatar
Добрый день. При попытке получить файл с фтп выдаётся ошибка «Отказано в доступе по пути D:\....». Если не трудно, помогите найти ошибку.
avatar
Нужен код — и на какой строчке вылетает ошибка.
Похоже что дело не в той части где идёт получение файла с ФТП а в той где идёт его приём на диск D:\…
avatar
Спасибо, что ответили. Код вечером предоставлю, но дело в том, что эта ошибка вылетает, даже в вашем коде. Более того, даже если запускаю тот бинарник, который уже скомпилирован вами. Думал, что может это из-за семёрки, но проверил сейчас на XP, результат тот же.
И ещё вопрос, как должна выглядеть строка скачивания файлов, что бы они сохранялись в процессе получения списка файлов?
client.GetFile(MainParams.FtpTimeout * 1000, «D:\путь:», item.Name); — такой вариант пойдёт?
avatar
код на connect и disconnect остался тот который был. Убран код на удаление, копирование и загрузку файлов. Изменилась только часть кода для получения списка файлов. Теперь программа получает список файлов из определенной директории, и только с указанной датой.
//Получение списка файлов текущего каталога с ФТП
private void GetItemsFromFtp()
{
foreach (FtpItem item in client.GetDirectoryList(MainParams.FtpTimeout * 1000))
{
if ((item.ItemType == FtpItemType.File) && (item.Date.Date == dateTimePicker1.Value.Date))
{
TreeNode node = new TreeNode(item.Name);
node.ImageIndex = 2;
node.SelectedImageIndex = 2;
node.Tag = 0;
node.ImageIndex = 1;
node.SelectedImageIndex = 1;
treeView1.Nodes.Add(node);
client.GetFile(MainParams.FtpTimeout * 1000, @«D:ftp», "/opt/ftp");
}
}
}
avatar
строчку сохранения не так написал. Это просто экспериментировал с разными вариантами. Сейчас у меня как и писал выше:
client.GetFile(MainParams.FtpTimeout * 1000, @«D:\ftp», item.Name);
avatar
Сразу не обратил внимания.
А так пробовал?
client.GetFile(MainParams.FtpTimeout * 1000, @«D:\\ftp», item.Name);
avatar
Если папка ftp уже есть, то по прежнему «Отказано в доступе по пути...». Если папки ftp нету, то создаётся пустой файл ftp.
Дело вряд ли в неправильности пути, эта ошибка возникает даже при таком варианте:
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
{
client.GetFile(MainParams.FtpTimeout * 1000, folderBrowserDialog1.SelectedPath, item.Name);
}
avatar
У себя проверил, так работает:
try
            {
                if (treeView1.SelectedNode.Tag.ToString() == "0")
                {
                    if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
                    {

                        client.GetFile(MainParams.FtpTimeout * 1000, folderBrowserDialog1.SelectedPath + "\\" + treeView1.SelectedNode.Text, treeView1.SelectedNode.Text);
                        
                    }
                }
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }


Там не просто путь надо — а имя файла, в какой конкретно файл надо скопировать. Ошибочка видимо вкралась.
Ну и после этого он у меня ругался на доступ — только когда к папкам реально доступа нет. А в остальных случая — копировалось.
На XP — похоже таких проблем не было. (Пример написан был на XP.)
avatar
Огромное спасибо!!! Теперь всё работает.
avatar
Опишите подробней
//загружаем файл на сервер.
client.PutFile(TimeoutFTP, "имя файла на сервере", "что грузим - имя файла на компьютере");

Приведите пример как указывать путь и имя? Как задать маску? Что-то у меня вопще не получается загрузить файлы на сервер по маске.
avatar
Не знаю, ответите ли вы еще… я новичок и не в курсе, что и как происходит… хотелось бы узнать реально ли будет таким образом оперативно работать с БД на FTP… возможен ли вариант сразу нескольких подключений к этой БД и работе с ней (изменение БД)… я так понял, что работа будет заключаться в обновлении БД, т е ее перезаписи?
avatar
Что значит БД? С базами данных работают по другому. Я метод скачивания по ФТП рассматриваю как вариант прогрузки необходимых компонентов для обновления. «Грубо говоря наше ПО ткнулось в какую нибудь службу на сервере по определенному порту, та ей ответила что взять где взять и выдала пароль. Программа полезла на ФТП и сама скачала всё что надо.» Как-то так…
avatar
Здравствуйте! Подскажите как добавить в настройки клиента внешний ip адрес?
avatar
Честно, не понял суть вопроса. Опишите детальнее, что именно хотите сделать.
avatar
когда интернет идет через Wi-Fi роутер, нужно еще как то сообщить серверу внешний IP адрес для активного режима.Я в библиотеке нашел client.LocalIP.
avatar
avatar
В этой программе реализовано скачивание файла с фтп сервера к себе на компьютер. А как сделать, чтобы скачивалась папка со всем его содержимым, включая вложенные директории с файлами и директориями внутри соответственно?
avatar
Выше. В комментариях, уже практически есть ответ на ваш вопрос.
avatar
что то не могу найти… помогите пожалуйста, очень заинтересовал этот вопрос
foreach (FtpItem item in client.GetDirectoryList(таймаут))//Перебираем итемы в папке
{
if (item.ItemType == FtpItemType.File)
{
//Если итем — является файлом (помимо файла могут быть ссылкой и директорией),

//И загружаем если необходимо по условию.
}
}

Попробовал с этим, не могу понять условие прохода по папке… и на путь при сохранении на комп ругается. Заранее благодарен, запутался совсем…
avatar
Если я правильно помню, то здесь подразумевается обыкновенная рекурсия.
Что касается ошибки при сохранении, которую я могу только угадывать, но рискну предположить что ругается на несуществующую директорию, её тоже надо создавать прежде чем положить в неё файл.
avatar
Народ, помогите, может я что-то не так делаю?
Ввожу все те же настройки как и у автора. Программа подключается к серверу, но загрузить или получить файл с сервера не могу. Что делать в этой ситуации?
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.