воскресенье, 21 декабря 2014 г.

C#: получить снимки с цифровой камеры

Понадобилось тут поработать с цифровой камерой: скопировать с неё снимки и запихнуть их в базу данных. Если вторая часть задачи решилась хоть и не изящно, но достаточно просто, то с реализацией первой возникли определенные проблемы.

Дело в том, что потребовалось получать и обрабатывать несколько снимков за раз. Я думал обойтись штатным OpenFileDialog. Ведь цифровая камера, будучи подключенной по USB к компьютеру, притворяется обычной папкой. Но, оказалось, "папка" эта не совсем обычная. В частности, например, при попытке доступа к изображению система создает его копию во временных файлах, и позволяет работать уже с этой копией. Если же выбрать несколько изображений, то OpenFileDialog вернёт вместо путей что-то маловразумительное типа "004\Root\IMG_1111" "004\Root\IMG_1112" "004\Root\IMG_1113".

Поэтому возникла мысль использовать обходной путь. В Windows есть нечто под названием Windows Image Acquisition. Статус этой технологии мне остался до конца неясным - то ли она уже устарела, то ли еще нет - но и в XP, и в 7 эта штука присутствует, хоть и двух разных версий.

Сначала попытался использовать WIA первой версии. Думал, обратная совместимость вывезет. Работаю я на WinXP, там WIA 1.0, что называется, "из коробки". Итак, добавляю в проект ссылку на Microsoft Windows Image Acquisition 1.01 Type Library (Add Reference, вкладка COM), получаю пространство имен WIALib и в нем кучку классов, с которыми уже можно работать:
using WIALib;
...

private void button1_Click(object sender, EventArgs e)
{
  object selectUsingUI = System.Reflection.Missing.Value;
  try
  {
    // создаем класс для работы с WIA
    WiaClass wiamanager = new WiaClass();

    // выводим диалог выбора цифровой камеры
    ItemClass wiaRoot = (ItemClass)wiamanager.Create(ref selectUsingUI);
    if (wiaRoot == null)
      return;

    // выводим диалог выбора изображений, хранящихся в камере
    CollectionClass wiaPics = (CollectionClass)wiaRoot.GetItemsFromUI(WiaFlag.UseCommonUI, WiaIntent.ImageTypeColor);

    // каждое выбранное изображение копируем в каталог с нашей программой
    if (wiaPics != null)
      foreach (object wiaObj in wiaPics)
      {
        ItemClass wiaItem = (ItemClass)Marshal.CreateWrapperOfType(wiaObj, typeof(ItemClass));
        string tmpFileName = Path.GetDirectoryName(Application.ExecutablePath) + "\\" + wiaItem.Name;
        wiaItem.Transfer(tmpFileName, false);
      }
  }
  catch (Exception ex)
  {
    MessageBox.Show(ex.Message);
  }
}
Всё работает чудесно. Но обнаружились два момента. Первое: без дополнительной обработки изображения получаются чудовищного размера (без всякого сжатия), и второе, самое главное: этот способ не сработал в Windows 7. А именно, при запуске своей программы на Windows 7 64bit я получил такое сообщение об ошибке: Сбой при получении производства объектов класса COM для компонента с CLSID {4EC4272E-2E6F-4EEB-91D0-EBC4D58E8DEE} в результате следующей ошибки: 80040154. В общем, похоже, в современных виндах WIA 1.0 нет.

Пришлось обратиться к WIA 2.0. Под Windows XP потребовалось её установить дополнительно. Я брал дистрибутив отсюда: http://vbnet.mvps.org/files/updates/wiaautsdk.zip. Дальше добавляю в проект ссылку на Microsoft Windows Image Acquisition Library v2.0 (всё на той же вкладке COM) и получаю пространство имен WIA. Использовал это примерно так:
using WIA;
...

private void button1_Click(object sender, EventArgs e)
{

  // создаем класс для работы с камерой
  WIA.CommonDialogClass dlg = null;
  try
  {
    dlg = new WIA.CommonDialogClass();
  }
  catch(Exception ex)
  {
    MessageBox.Show("Невозможно подключиться к интерфейсу работы с камерой:\n" + ex.Message, "Ошибка",
        MessageBoxButtons.OK, MessageBoxIcon.Error);
  }
  if (dlg == null)
    return;

  // выбираем камеру
  WIA.DeviceClass d = null;
  try
  {
    d = (WIA.DeviceClass)dlg.ShowSelectDevice(WIA.WiaDeviceType.CameraDeviceType, true, false);
  }
  catch (Exception ex)
  {
    MessageBox.Show("Невозможно подключиться к камере:\n" + ex.Message, "Ошибка",
        MessageBoxButtons.OK, MessageBoxIcon.Error);
  }
  if (d == null)
    return;

  // выбираем изображения
  WIA.Items items = dlg.ShowSelectItems(d, WIA.WiaImageIntent.ColorIntent,
      WIA.WiaImageBias.MinimizeSize, false, true, false);

  if (items != null)
    foreach (WIA.Item item in items)
    {
      WIA.ImageFile img = (WIA.ImageFile)item.Transfer(WIA.FormatID.wiaFormatJPEG);

      // выясняем имя и расширение выбранного изображения
      string
        tmpFileName = "",
        tmpFileExt = "";
      foreach (WIA.Property prop in item.Properties)
      {
        if (prop.Name == "Item Name")
          tmpFileName = prop.get_Value().ToString();
        if (prop.Name == "Filename extension")
          tmpFileExt = prop.get_Value().ToString();
      }
      if (tmpFileExt == "")
        tmpFileExt = img.FileExtension;
      if (Path.HasExtension(tmpFileName))
        tmpFileExt = "";

      // копируем выбранное изображение в каталог с нашей программой
      string
        tmpFullName = Path.GetDirectoryName(Application.ExecutablePath) + "\\"
            + tmpFileName + ((tmpFileExt != "") ? "." : "") + tmpFileExt;

      img.SaveFile(tmpFullName);
    }
}
Код получился немного длинее, но это из-за проверок. Основной плюс - помимо того, что эта конструкция сработала и в Windows 7 - возможность сохранять изображения в формате JPEG.

Кстати, во втором примере в свойствах изображения есть много интересного. Я себе на память сделал скриншот:

воскресенье, 14 декабря 2014 г.

Outlook: преобразовать OST в PST

"Семь раз отмерь..." - эта пословица, как и инструкция по технике безопасности, писана кровью. Давеча переносил в MS Outlook 2007 учетную запись Exchange с одного сервера на другой. Ну как переносил - одну запись грохнул, вторую создал. Предварительно сохранив - как я наивно думал - банк сообщений outlook.ost удаляемой учетки в укромное место. Потом подменил этот файл в новой учетной записи - и получил ошибку, что с этим файлом новая учетная запись работать отказывается. И только наступив на грабли полез разбираться.

Оказывается, так делать нельзя. OST - это Offline Storage Table, в некотором роде "зеркало" банка сообщений конкретной учетной записи сервера Exchange. Соответственно, другая учетная запись с этим зеркалом работать не будет. Поэтому перед удалением старой учетной записи следует переместить нужные письма и контакты из неё в какой-нибудь файл PST (Personal Storage Table).

Если же на руках только OST, и восстановить старую учетную запись, хотя бы временно, не представляется возможным, то придется воспользоваться какой-нибудь сторонней утилитой. Мне помогла вот эта: Coolutils OST to PST Converter. Она, правда, shareware, но один раз отработала в полном объеме.

среда, 10 декабря 2014 г.

C#: Превышена максимальная длина запроса

Помогла вот эта статья: Загрузка файла большого размера.

Вкратце - в web.config в узел <system.web> надо добавить опцию:
<httpRuntime maxRequestLength="16384" />

вторник, 9 декабря 2014 г.

КриптоПро

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

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

Для работы с eToken Pro Java необходимо удостовериться что:
1. eToken распознаётся в драйвере etoken pki client.
2. В КриптоПро CSP установлены "Все считыватели смарт-карт" (КриптоПро CSP - Оборудование - Настроить считыватели).
3. В КриптоПро CSP установлены ключевые носители eToken Java 1.0\1.0b
4. На ключе eToken присутствует закрытый ключ.

Для диагностики можно создать закрытый ключ на eToken.
"c:\Program Files\Crypto Pro\CSP\csptest.exe" -keyset -newkeyset -cont testcont
После проверить через КриптоПро CSP (сервис - протестировать контейнер).

среда, 19 ноября 2014 г.

Apache как reverse proxy

Предположим, получилась такая забавная ситуация. Пусть у нас есть сеть A и сеть B, причем из сети B компьютеры, принадлежащие сети A, не видны. Кроме того, есть один компьютер, AB-proxy, который виден из обеих сетей (например, на этом компьютере стоят две сетевые карты с адресами, соответственно, AB-proxy-ipA и AB-proxy-ipB). Предположим также, что в сети A завёлся веб-сервер A-server, к которому позарез нужно достучаться пользователям из сети В. Как это сделать?

Первый, и, наверно, лучший вариант решения - поднимаем на AB-proxy какой-нибудь прокси, например, squid, прописываем его в браузерах пользователей из сети B, и пусть гуляют в сеть A через него. Единственная проблема - а если пользователи сети B не позволят копаться в настройках своих браузеров?

Поэтому можно попробовать второй вариант решения: настроить iptables на AB-proxy. Достаточно нескольких строчек:
# очищаем ранее настроенные цепочки правил
iptables -F -t filter
iptables -X -t filter
iptables -F -t nat
iptables -X -t nat

# добавляем новую цепочку правил
iptables -t filter -A INPUT       -d AB-proxy-ipB -p tcp -dport 8080 -j ACCEPT
iptables -t nat    -A PREPOUTING  -d AB-proxy-ipB -p tcp -dport 8080 -j DNAT   --to-destination A-server-ip:80
iptables -t nat    -A POSTROUTING -d A-server-ip  -p tcp -dport 80   -j DNAT   --to-source      AB-proxy-ipA
Тогда при попытке зайти на адрес http://AB-proxy-ipB:8080 очередной tcp-пакет превратится в пакет от AB-proxy-ipA для A-server-ip:80 и успешно дойдет до сервера.

Этот вариант хорош, однако и тут есть большой подводный камень: а что, если коварный веб-сервер A-server любит прописывать своё имя во всяких тэгах <a>, <img> и вообще указывает себя явно в куках и разнообразных редиректах?

И тогда на помощь приходит третий вариант: поднять на AB-proxy обратный прокси. В нашем случае под рукой оказался Apache 2.2, и было решено воспользоваться им.

В httpd.conf раскомментируем (или добавим) следующее:
Listen 8080

LoadModule headers_module modules/mod_headers.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule substitute_module modules/mod_substitute.so

ProxyRequests off
<Proxy *>
 Order deny,allow
 Allow from all
</Proxy>

ProxyPassMatch ^/(.*)$ http://A-server/$1
ProxyPassReverse / http://AB-proxy-ipB:8080/

Header edit Location ^http://A-server/ http://AB-proxy-ipB:8080/
Header edit Set-Cookie Domain=A-server Domain=AB-proxy-ipB

AddOutputFilterByType SUBSTITUTE text/html
Substitute "s|A-server/|AB-proxy-ipB:8080/|i"

В результате пользователи сети B заходя на адрес http://AB-proxy-ipB:8080 будут попадать на сервер A-server, а ответы этого сервера станут проверяться на предмет наличия в них его имени, которое будет заменяться на имя прокси.

воскресенье, 16 ноября 2014 г.

Xubuntu: очистить сохраненные сессии

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

Как выяснилось, в убунте тоже есть нечто подобное:
1) папка ~/.config/autostart
2) папка ~/.cache/sessions - вот в ней, как я понял, и сохранилась эта сессия, которая при входе в систему распахивала запущенные на момент сохранения приложения.

Почистить это безобразие оказалось можно так: "Приложения" - "Диспетчер настроек" - Раздел "Система" - "Сеансы и запуск" - Вкладка "Сеанс" - Кнопка "Очистить сохраненные сеансы".

вторник, 11 ноября 2014 г.

Adobe Acrobat Reader XI: ошибка при открытии файла

Обнаружилась интересная особенность: при попытке открытия файла PDF, расположенного на сетевом ресурсе, даже если эта шара замаплена на какую-то букву диска, Adobe Acrobat Reader XI выдает очень информативное сообщение: "Произошла ошибка при открытии данного документа. Не удается найти файл."

Гугль, как обычно, помог. Нашлось решение проблемы для Adobe Reader X, заключающееся в том, что в "Редактировании"-"Настройках"-"Основных" нужно снять галочку у пункта "Включить защищенный режим при запуске".

У Adobe Acrobat Reader XI эта галочка находится в "Редактирование"-"Установка"-"Защита (повышенный уровень)".