среда, 23 марта 2016 г.

Как крутить шарикоподшипник. Ч.1 - квадратный шарик на плоскости*.

* аллюзия на "сферический конь в вакууме"

После того как легко и просто удалось мне закрутить шарик, раззадорился я замахнуться на планетарный механизм. Признаюсь, раззадорили меня задорные комментарии Всеволода Леонова. Спасибо ему большое! :)

Картинка из Википедии

А что тут сложного? То, что количество деталей больше - не принципиально на самом деле. А вот что действительно требует иного подхода - это размещение шариков по нужным местам. Когда был один шарик - я его просто прижал к краю с помощью Align = Left, а теперь нужно уже что-то более универсальное.

Очевидно, что для размещения одинаковых шариков по окружности достаточно их копировать с поворотам относительно общего центра. Поворот у нас есть - RotationAngle. А рядом с ним находится и то свойство, которое отвечает за центр вращения - RotationCenter. Надо только помнить, что координаты RotationCenter измеряются в "корпусах" поворачиваемого объекта и отcчёт идёт от левого верхнего угла объекта.  Вот и всё!

Давайте потренируемся, проверим как это всё работает. Создадим снова пустое мультиплатформенное приложение и, поскольку повороты на кружках можно и не заметить, добавим на форму два квадратика TRectangle



По умолчанию размер фигур 50х50. Чтобы не думать об их расположении, отметим оба квадратика с шифтом, и зададим им Align = Center.


Теперь, когда второй квадратик расположен точно над первым, выделим Rectangle2 в Structure и повернём немного, градусов на 15 - RotationAngle = 15.


А вот теперь вынесем центр вращения из середины фигуры далеко в сторону, поставив RotationCenter.Y = 5 вместо 0,5.


Получается, что центр вращения Rectangle2 теперь по X всё ещё посередине фигуры, а по Y - на 5 его корпусов ниже верхней грани (или на 4 от нижней). Как в этом убедиться? Давайте построим отрезки нужной длины, которые проходят из середины верхних граней квадратов вниз. По идее нижние концы отрезков из обоих фигур должны совпасть в центре вращения.

Бросаем на форму фигуру TLine, совмещаем с квадратиком Align = Center, и разворачиваем её из диагонального положения в вертикальное  RotationAngle = 45.


Искомая длина линии нам известна - пять корпусов квадрата, т.е. 50*5. Нам неизвестны соответствующие ей размеры компонента - Height и Width. На выручку приходит теорема Пифагора и калькулятор: <длина стороны квадрата> = √[(<длина диагонали>)² / 2], т.е. надо извлечь корень из половины квадрата числа 250. Я, округлив до целого, получил 177.

Установив у Line1 Size.Height = 177 и Width = 177, убираем центрирование Align = None и клавишами Ctrl + СтрелкаВниз опускаем линию так, чтобы её верхний конец совпадал с верхней линией квадрата. Конечно, можно было бы и вычислить координаты, но зачем снова голову (или кнопки калькулятора) ломать, когда есть такое замечательное средство визуального "программирования"?


Скопировав линию и выставив новому экземпляру RotattionAngle = 60 (45 + 15) также стрелками перемещаем её до получения искомой композиции.


Очевидно, что мы не ошиблись в расчётах и наши ожидания вполне оправдались. Теперь можно уже браться и за более сложное изделие.

Но погодите, вам ничего не напоминает эта картинка? По-моему, это очень похоже на часовой циферблат! Я просто не могу пройти мимо такой замечательной идеи. Поэтому в следующем посте я сделаю часики, а уж потом обязательно приступлю и к подшипнику.

Продолжение следует...

Обновление: Увлёкшись вращениями, я совершенно выпустил из виду  свойство линии LineType: ( Diagonal, Top, Left ). Заменив установленное по умолчанию значение Diagonal, можно совершенно забыть как о древнем математике Пифагоре, так и о современном калькуляторе. Всё уже сделано за нас!

понедельник, 21 марта 2016 г.

Как писать в EventLog?

Админы ко мне тут как-то подходят, окружают, к стенке слегка прижимают с разных сторон и, пристально глядя в глаза, тихим голосом объясняют, что они теперь отвечают за ту программку, которая на старом компе крутится - а там то свет выключат, то ещё что, и им очень неудобно бегать туда и кнопки нажимать для того, чтобы включить комп и залогиниться. Опять же - не сразу им о неприятностях сообщают, а только когда эти неприятности уже прямо неприятными становятся. Это плохо. Раз уж на админов это дело повесили (кто-то наконец догадался это сделать), то админы хотят, чтобы для всего этого не приложение работало, а служба, которая без логина, которая на кластере и вообще гораздо лучше. - Могёшь? - спрашивают. Я говорю, что в принципе-то вроде как есть возможность, не пробовал только ещё... - Вот! - говорят, - Заодно и скилы прокачаешь! Да, прямо так и сказали про скилы. И про логи, я ещё запомнил, что-то говорили.

Ну, службу создать - это как нефиг делать: File|New|Other - Service Application, и вот у меня модуль с наследником от TService, у которого даже есть метод LogMessage - писать в журнал. Только непонятно как.

Недолго погуглив, нахожу я кучу клонов перевода 2005 г. статьи 2002 г. неизвестного автора с "FMI Solutions". Там и знаков в примерах кода не хватает, и вообще Delphi тогда была ещё не юникодная - страшно брать такое.

А вот "Создание служб Windows в Delphi с использованием VCL" Алексеева Александра от 21-05-2008 мне понравилась, хотя там говорилось, что про категории сообщений знать мне ещё рано, т.к. "Для простых служб, которые вы будете писать в начале, это явно излишнее, поэтому разбирательство с категориями можно отложить на потом.

Отложив категории на потом, я выяснил, что прописанные в примере идентификаторы сообщений при передаче в LogMessage не работают. Дело в том, что пример содержит модификаторы номеров сообщений - Severity, Facility, которые вместе с заданным номером сообщения и образуют идентификатор, который надо отдавать в LogMessage. Ещё одно - сообщения, созданные мной просто в редакторе Delphi, отказывались отображаться нормально в Журнале Событий - кодировка Ansi, установленная в редакторе Delphi по умолчанию,  почему-то не пошла. И тут я выяснил, что и не всякая юникодная кодировка подходит, а нужна UTF-16LE. Вот это, пожалуй, главное, что я выяснил сам, не найдя прямых указаний в текстах.

А что же категории? В конце своей статьи Александр дал список ссылок, среди которых нашлась  статья "Пример использования Private Object Security в Delphi" товарища Набережных С. Н., в которой хоть про EventLog нет ничего, зато есть список литературы, в котором указана книга 
  1. Дж.Рихтер, Дж.Кларк, "Программирование серверных приложений для Microsoft Windows 2000", "Питер", ИТД "Русская редакция", 2001 г.
Очень рекомендую эту книжку. Понятным языком там и про категории, и про параметры, и про символьные имена идентификаторов и про то, что читать события из EventLog - это несколько сложнее, чем два байта переслать.

И вот я подумал - а почему бы не показывать события моей службы штатными средствами, с помощью c:\windows\eventvwr.exe, которому какой-то там запрос или имя канала передать надо в командной строке. Я даже нашёл чудную программу C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\ECMangen.exe - Manifest Generator Tool, которая предоставляет графический интерфейс как для создания шаблонов трассировочных сообщений, так и для запросов по Журналу Событий!

Воодушевлённый успехами пошёл я к админам, чтобы блеснуть эрудицией и поинтересоваться тем, как они настраивают перенаправление событий с локальной машины на администраторскую или как они запросы составляют или как каналы сообщений используют. Тут, понятно, выдумывать ничего не надо, а надо спросить у людей, чем они пользуются, и себе куда-нибудь в OnClick вставить ShellExecute с соответствующими параметрами.

- Что?! - Что ты, что ты?! - замахали на меня админы руками - Какой Журнал Событий? Ну его к Лешему, не надо! Пиши просто в файл, и будет нам счастье! А с этим EventLog вообще лучше не связываться!

...

Вот я вам что скажу: если вы вдруг задались вопросом "Как писать в EventLog?", пойдите к  тем, кто этот EventLog по-идее должен читать, и спросите - а надо ли им это?

четверг, 17 марта 2016 г.

Как крутить шарик в окружности

Андрей Совцов целых два поста запостил целых двух высококвалифицированных авторов про то, как кружок в кружке крутится. В первом посте Всеволод Леонов предлагает довольно сложную колебательную систему синусо-косинусоидальных прямоугольных координат( 1 ), а во втором сам Андрей задействовал движение по заданной траектории( 2 ). Любопытно, что самый простой вариант реализации так и не дан, а всё какие-то замудрости. Я считаю, что если начинать знакомство с темой, то с максимально простых примеров - Ex simplicibus ad composita!

1. Delphi, File | New | Multi-Device Application, Blank Application

2. Tool Palette, TCircle 2 раза, TPie 1 раз.

3. Structure, Pie1 кладём на Circle2, а Circle2 - на Circle1.

4. Object Inspector:
  Circle1 -  Align = Center,  Size.Width и Height = 350,
  Circle2 - Align = Left, размер оставим 50,
  Pie1 уменьшим на пару шагов, до 33 и поставим Align = Center.


5. Если кружок должен крутится, то что надо анимировать? Конечно, угол поворота!
Circle1.RotationAngle - Create New TFloatAnimation. В полученной анимации конечный угол StopValue = 360, зацикливаем Loop = True, включаем Enabled = True. Для Circle2 делаем всё то же самое, но учитывая вращение в обратную сторону добавим Inverse = True.

6. Немного математики. Маленький кружок, катаясь внутри большого, должен своей длиной окружности за время большого оборота покрыть длину большой окружности. Длина окружности прямо пропорциональна диаметру, а диаметры у нас - 50 и 350 - отличаются в 7 раз. Это значит, что пока кружок сделает полный круг, он должен обернуться вокруг себя 7 раз. Поэтому поставим продолжительность анимации большого круга object FloatAnimation1.Duration = 7 (секунд), а маленького FloatAnimation2.Duration = 1.

7. F9 - смотрим и думаем, как запустить процесс в другую сторону, как ускорить его или замедлить.


Всё. А вы что, господа педагоги, понапридумывали? Мне кажется, что простые решения - это именно то, что в инструменте привлекает начинающего разработчика в первую очередь. Нет?

суббота, 12 марта 2016 г.

Как интегрировать календарь в TValueListEditor

Бес в ребро мне сунул TValueListEditor: смотри, говорит, как мало места всё будет занимать, как стройненько всё смотрится и это всё простыми штатными средствами! Накидал я тестовый проект - показалось это мне действительно мило.

БЫЛО и СТАЛО:



Да только вот беда - после использования TDateTimePicker редактировать дату в отдельном диалоге - ну, как-то не comme il faut. А что делать?

Погуглил я - люди советуют показывать свой редактор в нужном месте в нужный момент, подменяя встроенный InplaceEditor. При этом поведение такого контрола надо очень сильно обрезать, чтобы клавиатурные реакции у него согласовывались с TValueListEditor. Но тогда уж не проще ли вообще свой компонент писать? Или поискать готовый?

Сторонних компонентов искать я не захотел (с обоснованием причин), а попробовал TMonthCalendar высунуть, как примерно это с TCustomListBox сделано в TInplaceEditList. Но TMonthCalendar категорически не захотел клавиатуру перехватывать. У него вообще с фокусом беда: он его получает только по Tab, когда свойство TabStop включено. А мышкой - хоть дырку в экране протыкай - никак. Поэтому, наверно, по дефолту TabStop и выключено.

Стало быть, напрашивается компромиссный вариант: положить TMonthCalendar на отдельную форму (я даже FormStyle=fsStayOnTop поставил) без заголовка и без рамочки и светить её в нужном месте. У формы на деактивацию (это когда мышкой мимо щёлкнули) и ESC (KeyPreview=True, OnKeyPress=FormKeyPress) вешаем закрытие Close.


Штатно после броска на форму календарь имеет серые поля по бокам. Я поджал его до квадратного состояния и при создании формы обжимаю её вокруг календаря (BoundsRect := MonthCalendar1.BoundsRect;). Теперь стало похоже на  TDateTimePicker. Замечу, что создатели Object Inspector поступают иначе - рисуют окно по ширине поля редактирования и раскрывают календарь в середине насколько возможно широко, т.е. видны серые поля по бокам, но зато могут появится дополнительные месяцы:



Ещё одно отличие между TDateTimePicker и Object Inspector в том, что у первого календарик выпадает слева, т.е. "в начале поля", а у второго - справа, ближе к кнопке. Я попробовал и так и так, и выбрал второй вариант выравнивая, оставив простой одномесячный размер календаря.

type
  //Access ToInplaceEditor
  TVLE = class(Vcl.ValEdit.TValueListEditor)  end;
 
procedure PopUp( AVLE: TValueListEditor; ACalBack: TProc<Boolean,TDateTime> );
var
  H: Integer;
  R: TRect;
begin
  with TfrmCalendar.Create( Application ) do begin
    FVLE := AVLE; //Save for FormClose
    FCallBack := ACalBack;
    Date := StrToDateDef( FVLE.Cells[ 1, FVLE.Row ], System.SysUtils.Date );
    BoundsRect := MonthCalendar1.BoundsRect; //No space around Calendar
    TVLE( FVLE ).InplaceEditor.Enabled := False; //No click on edit button
    R := AVLE.CellRect( AVLE.Col, AVLE.Row ); //Locstion of current editor
    H := R.Height;
    R.TopLeft := FVLE.{We are in other form}ClientToScreen( R.BottomRight );
    if R.Top + Height > Screen.Height
    then
      R.Top := R.Top - Height - H;
    Top := R.Top;
    Left := R.Left - Width;
    Show;
  end;
end;

Мне, правда, понадобился "хитрый хакерский залом", чтобы при деактивации окна мышкой, тыкающей на гриде снова в кнопку открытия календаря, календарь просто закрывался и не появлялся бы снова. Для избежания повторного открытия календаря я дизаблю TValueListEditor.InplaceEditor вместе с его кнопкой редактирования. А поскольку InplaceEditor - не публичное свойство, я TValueListEditor оборачиваю в наследника, который даёт мне пользоваться в текущем модуле своими protected свойствами. Ну, а что делать?

Ещё момент немножко скользкий такой есть: когда по ESC закрываем форму, то сначала срабатывает FormClose, а потом FormDeactivate, который снова готов вызвать FormClose. Так-то ничего страшного, но ситуация подозрительная. Поэтому в FormClose я поднимаю флаг, установив Tag := 1, а в FormDeactivate я этот флаг проверяю.

procedure TfrmCalendar.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Tag := 1; //InClose
  Action := caFree;
  TVLE( FVLE ).InplaceEditor.Enabled := True;
  if FOK then
    FVLE.Cells[ 1, FVLE.Row ] := DateToStr( Date );
  if Assigned( FCallBack ) then
    FCallBack( FOK, Date );
end;
 
procedure TfrmCalendar.FormDeactivate(Sender: TObject);
begin
  if Tag = 0 then
    Close;
end;
 
И всё? А вот и нет! А как поймать выбор даты? Как узнать, что пользователь бегал-бегал по календарю, выбирал-выбирал и вот - выбрал? Простой OnClick срабатывает на всё, что ни попадя, включая смену режима отображения. Где же событие OnSelect? Нету! Забыли сделать!

Снова не обойтись без "хакерства":

type
  TMonthCalendar = class( Vcl.ComCtrls.TMonthCalendar )
    procedure CNNotify(var Message: TWMNotifyMC); message CN_NOTIFY;
  end;
 
  TfrmCalendar = class(TForm)
    MonthCalendar1: TMonthCalendar;
    procedure FormKeyPress(Sender: TObject; var Key: Char);
...
procedure TfrmCalendar.WMClose(var Message: TMessage);
begin
  FOK := Message.WParam <> 0;
  inherited;
end;
 
function IsBlankSysTime(const ST: TSystemTime): Boolean;
type
  TFast = array [0..3] of DWORD;
begin
  Result := (TFast(ST)[0] or TFast(ST)[1] or TFast(ST)[2] or TFast(ST)[3]) = 0;
end;
 
{ TMonthCalendar }
 
procedure TMonthCalendar.CNNotify(var Message: TWMNotifyMC);
begin
  inherited;
  if ( Message.NMHdr^.code =  MCN_SELECT )
  and not IsBlankSysTime( Message.NMSelChange^.stSelStart )
  and Assigned( Parent )
  then
    PostMessage( Parent.Handle, WM_CLOSE, 1, 0 );
end;
 
Ф-ю IsBlankSysTime я честно выпилил из исходников TMonthCalendar.

Добавим теперь в TValueListEditor (он у меня называется vle) инициализацию кнопки и обработчик события.

procedure Tgsm2gd_main.FormCreate(Sender: TObject);
var
  K: uSettings.TKey;
begin
  laVersion.Caption :=  'Версия: ' + GetVersionInfoText;
  for K := Low( TKey ) to High( TKey ) do begin
...
    with vle.ItemProps[ Integer( K ) ] do begin
...
      case K of
        uSettings.WildCards, uSettings.Date:
          EditStyle := esEllipsis;
...
        else
          EditStyle := esSimple;
      end;
    end;
  end;
end;
 
{ TValueListEditor }
 
procedure Tgsm2gd_main.vleEditButtonClick(Sender: TObject);
...
begin
  case uSettings.TKey( vle.Row ) of
    uSettings.Date: uCalendar.PopUp( vle );
    uSettings.WildCards: WildCardsEdit;
  end;
end;

И получаем вполне приемлемый вариант ввода даты в TValueListEditor.


Конечно, валидация у TValueListEditor не слишком надёжная. Но это уже другая песня.

ЗЫ: Исходники здесь: https://github.com/alhymov/VleMonCal.git

пятница, 11 марта 2016 г.

Как написать что-нибудь после "try"

Я вообще не слишком люблю автозамены при наборе текста. А при наборе кода, когда оно само, меня не спрашивая, вдруг что-то такое - бац! - и ты уже Бог знает в какой-то рамочке где-то посреди внезапно появившегося кода - это просто ужасно раздражает.

Нынче с чего-то вдруг попробовал я написать после слова try нечто. Только после "y" пробел нажал - Трах-Тибидох-Тибидох! Ой, ещё раз попробуем - ... - и опять такая же дрянь непрошенная. Что делать?


По идее, заходим во View|Templates, находим try и выключаем. Делаю так, но оно продолжает работать! Я тогда перегружаюсь - а оно живее, кажется, чем было!


Ладно, не можешь убить - сделай неопасным. Пытаемся изменить шаблон так, чтобы он больше не реагировал на пробел после игрека. Файл с шаблоном носит имя trycf - вот это имя и установим в самом шаблоне вместо "try".


Однако, сохранить обновление не получается. Дело оказывается даже не в ReadOnly атрибуте каталога $(BDS)\ObjRepos\en\Code_Templates\Delphi, а в том, чтобы дать себе права на файл в операционной системе (Windows 7).


И вот теперь, когда всё готово, я наконец могу написать в строке после слова try всё, что угодно!


понедельник, 8 февраля 2016 г.

Наступил год FireMonkey!

Поздравляю всех с праздником!
Евгению Крюкову моё личное спасибо!
Да здравствуют бурятские программисты!

четверг, 4 февраля 2016 г.

Как использовать диагональные рамки WinAPI?

Как использовать диагональные рамки WinAPI? Они же кривые!

Я вот о чём: о ф-ии DrawEdge. Хорошая ф-я, простая и понятная. Если не читать документацию. А там, оказывается....

Давеча я исходники 1992 года ковырял в надежде поднять что-нибудь на новый уровень. Удалось через VS поднять 16-х .res-файл с диалогом, где BWCC использовался. Но, не беда: кнопки 'BorBtn' переписываем в 'Button', а рамки 'BorShade' можно заменить на CONTROL с SS_ETCHEDFRAME или SS_ETCHEDHORZ или... Зашёл я посмотреть стили контролов на MSDN, а там ссылка на DrawEdge - дескать, то же получите с контролом, что при рисовании этой ф-ей. А я раньше-то её копипастил и не задумывался, а тут прокрутил справку пониже - Батюшки мои! - она диагонали чертит!!!

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


Я поиграл с флагами и размерами - это ни на что не похоже: то она вместо правого угла привязывает линию к левому, то вообще не рисует, а угол диагональности - не только не диагональный, но и не 45 градусов, а как ей придумается. 

Граждане товарищи! Кто видел эти диагонали в жизни? Кто мне расскажет? Ну, хоть за пузырь? Ну прикольно же! 

понедельник, 19 октября 2015 г.

Как быстрее исправлять испорченные подписи к фоткам в Моём Мире


Не смотря на мои письменные заявления администрация mail.ru не желает исправлять ошибку, которая приводит к замене некоторых символов в описании фотографий при копировании/перемещении оных из одного альбома Моего Мира в другой. Искать такие траблы - пол дела. Самое глазоломательное - целить мышкой в них, ибо двойным щелчком амперсанд и семисолон не выделяются. Поэтому я наколенил прогу:


Прога проверяет текст в кармане и "куоты" обратно заменяет на кавычки. Надо только выделить текст для редактирования, скопировать, дождаться пика, и вставить обратно. Для пущей юзабильности я даже чекбокс-выключатель на енабельность таймера добавил. Но это уже баловство, конечно.

Хорошо. Но как подумаешь, сколько стоит Ембаркадеровский продукт, так и начнёшь прикидывать - не поставить ли себе Лазарус?

воскресенье, 3 мая 2015 г.

Как обоблачиться с YandexDisk - 2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

§2.5. Протокольные компоненты

часть 2. TREST~ и TDataSet

Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

Если подумать, то в большинстве случаев REST-протокол - это только транспорт, доставляющий нам такие же данные, что мы получали в старых настольных клиент-серверных приложениях в виде TDataSet. А поскольку TDataSet прекрасно подходит и для хранения локальных данных, то было бы неплохо и вполне естественно приводить получаемый от сервера JSON к этому формату. Особенно, если принять во внимание возможности компонента TFDLocalSQL из палитры FireDAC, который позволяет строить запросы одновременно по нескольким независимым TDataSet.


Разумеется, в компании Embarcadero, которая делает ставку именно на RAD-технологии, позаботились о том, чтобы прикладному программисту не пришлось бы писать свой код для рутинной операции преобразования REST-ответа от сервера в TDataSet. Достаточно набросать на форму полдюжины (да, это не для графоманов) компонентов, как вся цепочка преобразования не только будет готова к запуску приложения, но может начать действовать (если, конечно, ваша MS-Windows к этому готова) уже в процессе проектирования!

Давайте же, запустив предварительно Fiddler, кинем на форму (или модуль данных) компонент TRESTClient, расположенный на палитре RESTClient, и заполним его свойства и параметры:
  BaseURL = 'https://cloud-api.yandex.net/v1/disk' 
  Params = <
    item
      Kind = pkHTTPHEADER
      name = 'Authorization'
      Value = 'токен'//[OAutht ]<OAuthtoken>
    end>
  ProxyPort = 8888
  ProxyServer = '127.0.0.1' //смотрим Fiddler
 
Замечу, что в документации ЯД сказано, что OAuth-токен должен предваряться типом токена - словом "OAuth " с пробелом, но REST API прекрасно работает и без этого префикса. Однако, если вы через HTTP пытаетесь скачать файлы по полученным через REST ссылкам, то такая приставка уже становится строго обязательна.
 
Далее кладём TRESTRequest с увеличенным лимитом потребления (100 вместо 20):
  Params = <
    item
      name = 'path'
      Value = 'app:/'
    end
    item
      name = 'limit'
      Value = '100'
    end
    item
      name = 'offset'
      Value = '0'
    end>
  Resource = 'resources'
 
Ну что, можно уже делать запрос? Да, можно! Прямо в ObjectInspector у TRequest имеется команда Execute..., которая сделает запрос к ЯД без всякой компиляции и запуска программы.  Не верите? Даже после того как появилось сообщение "Response: 200 - OK"? Тогда загляните в Fiddler, и вы увидите и запрос к ЯД, и ответ:



А хотите использовать REST Debugger из меню Tools? Посмотрите на эту программу с громким названием хотя бы из интереса - она построена именно на тех компонентах, что мы сейчас используем. Если у вас не триальная версия Делфи, то вы найдете исходники в каталоге $(BDS)\source\data\rest.

Dернувшись в Делфи, присовокупим к уже имеющимся компонентам TRESTResponse для сохранения результата запроса и укажем JSON-путь до интересующего нас списка файлов, записав '_embedded.items' (эти  слова можно скопировать из дерева Фиддлера или со вкладки Response\Body программки RESTDebugger) в свойство RootElement. Добавим к этому преобразователь JSON → TDataSet  - TRESTResponseDataSetAdapter и собственно сам набор данных - TFDMemTable, который расположен на палитре FireDAC. Надо только связать эти компоненты: TRESTResponse → TRESTRequest.Response,  TRESTResponse → TRESTResponseDataSetAdapter.ResponseJSONTFDMemTable → TRESTResponseDataSetAdapter.Dataset.

Я до сих пор помню своё удивление, когда не в интерпретаторе, а в компилирующей среде разработки прямо во время дизайна вдруг ожила таблица на форме FishFаctory в первой версии Делфи. И двадцать лет спустя, когда компоненты Делфи начинают работать в то время, когда ещё нет самой программы,  я никак не могу отделаться от ощущения маленького чуда.

Итак, продолжаем волшебство. Снова запустим запрос, чтобы наши компоненты пропитались данными, а потом дважды щёлкнем на TFDMemTable или откроем Fields Editor с помощью Object Inspector. Здесь есть команда Add all fields, которая добавит в таблицу все поля из полученного JSON.



Имея таблицу с полями, мы положим на форму TBindSourceDB, привяжем к таблице, а потом разместим удобным образом TListView и запустить на нём команду Bind Visualy. Не правда ли, это волшебство - протягивая стрелки от значения к значению, видеть, как на форме список заполняется данными?

А если хочется добавить иконок, то можно добавить поле в таблицу, и грузить в него с помощью TIdHTTP данные по ссылке из поля preview. Но чтобы получить ссылку для скачивания самого файла нужен будет ещё один запрос по адресу 'cloud-api.yandex.net/v1/disk/resources/download'. Сходите на Полигон ЯД, и вы легко во всём разберётесь. Удачи!

-----------------------

Всё, больше я не буду продолжать эту статью о сервисах Яндекса, авторизации OAuth, о том, как держать бубен, чтобы коробочный Indy выполнял запросы по HTTPS. Пока я её писал Яндекс добавил новый сервис DataSync API для хранения и синхронизации структурированных пользовательских данных в облаке, а Embarcadero выпустила Delphi XE8 с новыми компонентами для HTTP-протокола, а мой младший сын научился кататься на двухколёсном велосипеде. Надеюсь, мои скромные заметки принесут пользу не только мне, приводящему этой писаниной свои мысли в порядок, но и кому-то из прочитавших сей скучный трактат.

Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

суббота, 18 апреля 2015 г.

Как обоблачиться с YandexDisk - 2.5 Протокольные компоненты, часть 1. TidHTTP и JSON

§2.5. Протокольные компоненты

часть 1. TidHTTP и JSON


Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

Чтобы работать с REST API у нас имеется множество специальных компонентов. Но в принципе достаточно иметь и не специальные, лишь бы по http(s) отправить параметры и получить текст в виде JSON, парсить который ещё проще, чем XML - у нас для этого есть прекрасный модуль (начиная с XE6) System.JSON с его TJSONObject, TJSONValue и TJSONArray. Так что же выбрать? Будем мы сосать молочко из сосочки или станем глотать молоко прямо из пакета? А если пить из пакета через трубочку? 


Давайте попробуем для начала  через компоненты HTTP запросить у Яндекс-Диска количество элементов в папке. Дело в том, что ЯД по-умолчанию выдаёт список содержания пачками по 20 штук. Разумеется, мы можем попросить выдать нам не 20, а какое-то другое количество элементов. Например - всё сразу. Но, поскольку ЯД не понимает (или я не знаю как дать ему понять), что такое "всё сразу", ему нужно сказать сколько это будет конкретно. И хорошо бы действительно знать это число, а не заказывать в параметре limit наобум 100-500 элементов.

function TForm2.GetFolderCount(const AFolder: String): Integer;
var
  LAnswer : String;
begin
  Result := 0;
  //
  with TIdHTTP.Create do
  try
    {$ifdef MSWindows}
    with ProxyParams do begin
      ProxyPort := 8888;
      ProxyServer := '127.0.0.1';
    end;
    {$endif}
    Request.CustomHeaders.AddValue( 
      'Authorization', FAuthorization );
    LAnswer := Get(
      'https://cloud-api.yandex.net/v1/disk/resources?limit=0&path='
      + HTTPEncode( AFolder ) );
    if ResponseCode <> 200 then
      Exit;
  finally  Free
  end;
  //
  with TJSONObject.ParseJSONValue( LAnswer ) as TJSONObject do
  try
    Result := ((
      GetValue( '_embedded' ) as TJSONObject ).
      GetValue( 'total' ) as TJSONNumber ).AsInt;
  finally Free;
  end;
end;
...
// calling of GetFolderCount
const
  LFolder = 'app:/';
begin
  ShowMessage( Format( '%d record(s) in folder %s', 
    [GetFolderCount( LFolder ), LFolder] ) );
...

Рассмотрим некоторые особенности кода, приведённого выше. Он делится на две части. Первая - получение информации от ЯД, вторая - обработка полученного результата. Какие замечания можно сделать по первой части?

Поскольку TIdHTTP мы создаём вручную, а не ищем его в палитре компонентов, то не забываем добавить в uses модуль IdHTTP. Если кто не знает (ну вдруг!), то можно найти компонент в палитре, кинуть на форму, всё сохранить, и удалить его - в uses будет уже всё добавлено. Работу с прокси-сервером мы как раз обсуждали чуть ранее. Думаю, очевидно, что в FAuthorization хранится наш OAuth-токенА вот что касается HTTPEncode, то для неё следует подключить модуль Web.HTTPApp. Надеюсь, вы заметили, что делая запрос мы указали в параметрах limit=0, чтобы не получать неинтересующий нас на данный момент список файлов внутри папки, а только информацию о ней самой. 

Результат запроса выглядит примерно так:


Выделенный на рисунке элемент Items - это массив записей содержимого папки. Естественно, сейчас он пуст. Итак, пора перейти ко второй части кода, где мы вытаскиваем из JSON количество файлов в папке.

Парсинг выглядит довольно просто. Однако, надо понимать следующее: память под объекты выделяется один раз - классовой функцией ParseJSONValue. Метод GetValue возвращает только ссылку на элемент в коллекции. И второе: хотя ввиду неизменности интерфейса ЯД проверять успешность получения элементов структуры особой нужды нет, в общем случае не грех завести переменную для проверки наличия результата GetValue. Имейте в виду особый порядок обработки аппаратных исключений на мобильных платформах.

to be continued...

Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

четверг, 9 апреля 2015 г.

Как обоблачиться с YandexDisk - 2.4 Скрипач как прокси

§2.4. Скрипач как прокси


Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

Совсем не секрет, что путь к успеху часто усеян розами без лепестков, но с шипами. Поэтому не стоит сильно удивляться, что даже установив библиотекинеобходимые Indy для работы по https, вы не сможете сделать запрос не только из среды разработки, но и даже из своей запущенной из-под Windows (отладочной версии) программы.

В чём может быть проблема, если в свойствах компонентов или, например, в поле URL вызванного из RAD отладчика Tools/REST Debugger указан адрес сервера, а попытка выполнить запрос заканчивается ошибкой? Вполне возможно, дело в том, что для выхода в Интернет требуется прокси-сервер. Посмотрите настройки подключения вашего браузера. Скорее всего такие же буквы или цифры нужно указывать и в соответствующих свойствах отладчика или компонентов.


Как правило, указание прокси-сервера требуется в условиях работы в офисе. А если работа идёт в домашних условиях, то указание прокси уже строго не приветствуется. Да и бывает, что офис, в котором поднимается проект, тоже не всегда один и тот же - всю неделю мы, скажем, в филиале в Кожевниках, а на выходные нас пригласили в штаб-квартиру на Таганке. В общем, есть смысл подумать над тем, чтобы иметь некий универсальный подход, не слишком отягощённый необходимостью вникать в местные условия.

Поэтому я всем и каждому советую (© Старуха Шапокляк) везде и всюду качать и ставить такую вещь как Fiddler, что в переводе означает "скрипач". На самом деле это одобренный и рекомендуемый фирмой-производителем ОС бесплатный WEB-отладчик-прокси. Это чудо-средство позволяет просматривать веб-разговоры и даже разговаривать самому. Но в рамках нашей текущей задачи нам достаточно и того, что стоит его запустить, как у вас по адресу 127.0.0.1:8888 установится прокси сервер, который станет смотреть в Паутину либо прямо, либо через тот прокси, который стоит в вашем офисе. Местные условия выхода в Интернет определяются "скрипачом" автоматически. Вам же останется только у себя выставить прокси в 127.0.0.1 с номером порта 88888 и не забыть в программе на старте почистить эти значения, если приложение запускается на мобильном устройстве.

procedure Form1.FormCreate(Sender: TObject);
begin
{$IFNDEF MSWINDOWS}
 RESTClient.ProxyServer := '';
{$ENDIF}
end;

Ну вот - кажется, ещё одной засадой на нашем пути стало меньше.


to be continued...

Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet

среда, 1 апреля 2015 г.

Как обоблачиться с YandexDisk - 2.3 Indy и HTTPS

§2.3. Indy и HTTPS


Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet
Как же нам построить программу для работы с REST API Яндекс Диска? Embarcadero предлагает нам мультиплатформенные Indy-компоненты. Но с маленькой оговоркой - они не содержат SSL-шифрования. Т.е. работа по протоколу https, который необходим ЯД (да и прочим сервисам с авторизацией), может стать несколько более сложной, чем "раз, и поехали". Причины этого нюанса лежат в юридической плоскости. Нам же нужно разобраться со следствием в разрезе работоспособности наших приложений. 

Как же нам получить возможность общения по https? (альтернативные варианты типа Synapse здесь не рассматриваются ввиду ограниченности моего кругозора) Для дооснащения Indy-компонентов полноценной поддержкой SSL фирма Embarcadero рекомендует обратиться к ресурсу OpenSSL.org. Здесь лежат Си-исходники библиотек шифрования и рекомендации по их компиляции. К счастью, на сайте есть ссылки и на библиотеки, уже откомпилированные под разные платформы. Вот взять хотя бы http://indy.fulgan.com/SSL/ - вы тоже заметили там слово "indy"? Я лично как раз поэтому и доверился ей. Там, правда, капчу требуют, но на это не стоит обижаться.


В итоге мы имеем пару DLL - libeay32.dll и ssleay32.dll в openssl-1.0.2a-i386-win32.zip для Win32, пару DLL(удивительно, но имена те же, с цифрами 32) в openssl-1.0.2a-x64_86-win64.zip для Win64 и набор для статической линковки под iOS (судя по дате, его уже давно не обновляли) -  libcrypto.a и libssl.a в OpenSSLStaticLibs.7z. А где же Андроидные бинарники? Видите ли, Андроид предоставляет стандартный доступ к шифрованию SSL, и Indy прекрасно может этим пользоваться безо всяких дополнительных приспособлений.


Что делать с DLL - понятно. Есть вариант А - надёжный: кладём их в каталог с приложением  и для дизайн-тайма в C:\Program Files (x86)\Embarcadero\Studio\версия\bin и bi64. Есть вариант Б, который выглядит гораздо предпочтительнее: выложить DLL в системный каталог - C:\Windows\System32 и C:\Windows\SysWOW64 в соответствии с разрядностью библиотек. Важное замечание по второму варианту: имейте в виду, что скорее всего DLL с такими именами у вас уже есть, но они старых версий, и в них нет всех нужных функций. Indy при этом выдаёт сообщение о том, что не удаётся загрузить библиотеки SSL. Обновите их и не забудьте перезагрузить систему. Если и это не поможет, добро пожаловать в "DLL hell"! 

Для того, чтобы запустить йОсное приложение, файлы libcrypto.a и libssl.a надо подключить к проекту через deployment, а в раздел uses добавить модуль IdSSLOpenSSLHeaders_Static, где содержатся объявления об импорте функций и некоторые прочие заголовочные штуки - непонятные, но нужные.


Теперь мы можем делать запросы по https не только в запущенном на устройстве приложении, но даже в ещё незапущенном, в режиме проектирования, прямо в ObjectInspector! 

Часть I. Теория
2.1 Подключение ЯД к Windows
2.2 Создание папки приложения
2.3 Indy и HTTPS
2.4 Скрипач как прокси
2.5 Протокольные компоненты, часть 1. TidHTTP и JSON
2.5 Протокольные компоненты, часть 2. TREST~ и TDataSet