воскресенье, 13 января 2013 г.

Delphi 2010. Метод Get компонента idFtp работает не правильно?

 
На днях понадобилось написать небольшую программку. Программа проста как валенок: скачать файл с фтп, откорректировать его должным образом, залить на другой фтп. В целях экономии времени выбор языка пал на Делфи, ведь там есть удобный компонент работы сidFtp! 
Подумано - сделано, быстро вояю программку и довольный смотрю на результат: он же, мягко говоря, сильно отличается от ожидаемого... Т.е. на выходе файл совсем не такой как должен быть. Ну, что поделать, начинаю усердно отлаживать код редактирования скаченного файла...
По прошествии нескольких часов я потерял веру в жизнь и законы физики: в коде все абсолютно верно! Значит, ошибка кроется в другом. В чем же?Обращаю свой взор на скачанный файл... Ах тыж екарный через плечо бабай, в рот мне ноги, WTF?!! Размер скачанных файлов с фтп отличается от оригинального! И ладно бы они были меньше, так ведь они больше!! Не веря свои глазам скачиваю файлы вручную и прогоняю на них свой код. Все работает как часы!
Ну... что поделать? Завариваем крепкий чай и вперед!

Собственно сам код скачки файлов:
IdFTP1.Get(FileNames[i],GetCurrentDir()+'\'+FileNames[i],false, true);
FileNames представляет собой TStringList и содержит имена файлов на фтп-сервере.

Итак, для начала посмотрим чем же скачанные нами файлы отличаются от оригинальных:
Как видим, размер на который отличаются файлы разный, но примерно составляет 155кб (около 2% от общего объема). Откуда? Не понятно. Сравниваем содержимое файла:


Мистика! Файлы действительно отличаются по содержимому!
Тут стоит отметить что компонент idFtp может скачивать файлы в бинарном и текстовом виде. За это отвечает свойство TransferType, которое может принимать значения ftASCII и ftBinary. Так может дело в этом? Нет. В обоих случаях результат один и тот же.
Более детальное изучение дает еще более удивительный результат: содержимое файлов одинаковое, отличаются символы переноса строк! Точнее в скачанном файле перед каждым переносом строк (0A) добавляется еще один (0D).
Педивикия по этому поводу говорит следующее:

Системы, основанные на ASCII или совместимом наборе символов, используют или LF0x0A), или CR (возврат каретки, 0x0D) по отдельности, или последовательность CR+LF. Эти названия основаны на командах принтера: перевод строки означает, что одна строка на бумаге должна быть перенесена при печати, а возврат каретки означает, что каретка печатающего устройства должна вернуться к началу текущей строки. Символы:
  • LF (ASCII 0x0A) используется в Multics, UNIX, UNIX-подобных операционных системахGNU/Linux, AIX, Xenix, Mac OS X, FreeBSD и др.), BeOS, Amiga UNIX, RISC OS и других;
  • CR (ASCII 0x0D) используется в 8-битовых машинах Commodore, машинах TRS-80, Apple II, системах Mac OS до версии 9 и OS-9;
  • CR+LF (ASCII 0x0D 0x0A) используется в DEC RT-11 и большинстве других ранних не-UNIX- и не-IBM-систем, а также в CP/M, MP/M (англ.), MS-DOS, OS/2, Microsoft Windows, Symbian OS, протоколах Интернет.
Таким образом, я установил откуда растут ноги моего бага: в извечной проблеме Unix vs Windows. Кто переносил код написанный в Delphi 7 в Delphi 2009 или Delphi 2010, знает что основная проблема переноса кроется в том, что многие методы использующие кодировку по умолчанию Windows стали использовать кодировку Unix. Скорее всего мои проблемы из той же серии и данный код без проблем заработает как надо в Delphi 7. 
На вскидку, данную проблему можно решить 3-мя способами: 1) Скомпилировать .exe в Delphi 7; 2) Лезть в исходный код компонента idFtp и разбираться с ним; 3) Сделать "костыль" заменяющий "0x0D0x0A" на "0x0A"
Каждый способ имеет минусы: 1) У меня Windows 7, на которую Delphi 7 без бубна не ставится 2) Лень; 3) +100 к "индусости" кода.
Лично мне хотелось поскорее закрыть этот вопрос, потому я быстро установил Delphi7 и скомпилировал код там. Если у кого есть предложения получше - добро пожаловать в комменты :)

P.S. Чего я так и не понял, зачем борландцам понабилось городить не пойми что и почему функция Get не может просто скопировать файл байт-в-байт.

3 комментария:

  1. о божэ мой я уже потеряла надежду понять что за фигня происходит, спасибо за объяснения!

    ОтветитьУдалить
  2. все куда проще, нужно всего лишь поставить FFTP.TransferType := ftBinary; в то время как в Indy10 этот параметр по умолчанию ftASCII, т.е. передается текст который потом искажается в части переносов строк

    ОтветитьУдалить
  3. да, ошибка 100%, к сожалению бинарный режим хорошо работает только в справке к программе, в чистой теории, по факту, все как написано в статье. баг, сводящий на "нет" работу компонента. Сборка D2010 Build 25826.

    ОтветитьУдалить