четверг, 10 ноября 2016 г.

Python: округление

Выяснилась интересная особенность. В python 2.7 округление выполняется как учили в школе, где число x.5 округляется до (x+1):
>>> print round(0.5), round(1.5)
1.0 2.0

В python 3.3 поведение этой функции изменилось:
>>> print ( round(0.5), round(1.5) )
0 2
то есть, во-первых, по умолчанию возвращается целое число, и, главное, выполняется bankers rounding (банковское округление, в котором x.5 округляется до ближайшего четного).

Как эту печаль обойти штатными срадствами, не нашел, поэтому пока пользуюсь таким костыликом:
def round27 (number, ndigits=0):
    result = round(number, ndigits)
    if abs(result) >= abs(number): return result
    delta = abs(number) - abs(result)
    if ndigits >= 0:
        multiplier = 10.0**(ndigits+1)
        if abs(number)*multiplier-abs(result)*multiplier == 5.0:
            result = number + (delta if number >= 0 else -delta)
    else:
        multiplier = 10.0**(-ndigits-1)
        if abs(number)-abs(result) == 5.0*multiplier:
            result = number + (delta if number >= 0 else -delta)
    return result

Вообще, оказывается, есть даже стандарт IEEE 754, в котором все эти правила округления указаны. Надо глянуть, может, и ГОСТ найдётся.

UPD. Нашелся вот такой документ: СТ СЭВ 543-77 Числа. Правила записи и округления
Тут как раз округление по правилу "половины дальше от нуля".

четверг, 3 ноября 2016 г.

Linux: Как пережать PDF

Предположим, у нас есть некий pdf-документ, который надо разослать по электронной почте N адресатам. Казалось бы, не проблема: создавай N писем с вложенным документом и отправляй, благо, слепить на коленке софт под это дело - задача нехитрая.

Основная проблема - размер этого самого pdf-документа. При массовой рассылке объем переданной через почтовый сервер информации возрастёт в N раз, поэтому возникает логичное желание ужать пересылаемый документ как только можно. Сам документ получен со сканера, то есть фактически является увесистым набором изображений. С другой стороны, к счастью, тут качество не сильно важно, достаточно, чтобы текст читался, да подписи с печатями были различимы. Короче, задача сформулировалась так: получить из многостраничного сканированного документа черно-белый факс убогого качества, но читаемый, в формате pdf.

Никуда не деться, взял в качестве образца пятистраничный файл input.pdf размером 923801 байт и принялся экспериментировать.

Должен сказать, что самый правильный путь решения проблемы: воспользоваться Adobe Acrobat-ом. Эта чудесная софтина содержит команду "Optimize scanned PDF", которая при определенных настройках каким-то волшебным образом умудряется превращать исходный файл в жалкие 97416 байтов! К сожалению, в данный момент лицензионного Adobe Acrobat-а под рукой нет, поэтому приходится выкручиваться тем, что есть.

Первый вариант, который советуют знающие люди - использовать ghostscript. Соответствующая команда выглядит так:
gs \
    -sDEVICE=pdfwrite \
    -dCompatibilityLevel=1.4 \
    -dPDFSETTINGS=/screen \
    -dNOPAUSE \
    -dQUIET \
    -dBATCH \
    -sOutputFile=output.pdf 
    input.pdf
Результат получается вполне читаемым, но объем не сильно уменьшился и составил 548870 байтов.

Второй найденный на просторах инета вариант реализует идею уменьшить количество цветов до минимума:
#!/bin/sh

inFile=input.pdf
outFile=output.pdf

pdfimages "$inFile" scan1
for a in scan1*.p*m; do
   convert -depth 2 -colorspace gray $a ${a%.*}.tiff
done

tiffcp scan1*.tiff "scan1-.tiff"
tiff2pdf "scan1-.tiff" -o "$outFile" -p A4 -F -z

rm scan1*.*
Как видим, здесь используется несколько утилит: ImageMagick с его хитрой командой convert, а также LibTIFF (утилиты tiffcp и tiff2pdf). Результат: 380120 байтов. Неплохо, но далеко от рекорда.

Далее возникла мысль вообще перекодировать pdf в монохромный режим. Сделать это просто, достаточно чуть-чуть модифицировать тело цикла в предыдущем варианте:
#!/bin/sh

inFile=input.pdf
outFile=output.pdf

pdfimages "$inFile" scan1
for a in scan1*.p*m; do
    convert -white-threshold 100% -monochrome   $a   ${a%.*}.tiff
done

tiffcp scan1*.tiff "scan1-.tiff"
tiff2pdf "scan1-.tiff" -o "${outFile%.*}.${outFile##*.}" -p A4 -F -z

rm scan1*.*
Результат всё ещё читаем и имеет размер 252072 байта.

Ещё один вариант, который стоило попытаться сделать - это уменьшить в два раза размеры отсканированных изображений перед тем, как переводить их в монохром. Идея показалась здравой, но, будучи применена "в лоб", дала совершенно нечитаемый результат.

Можно попробовать ещё один подход: попытаться векторизовать содержащиеся в исходном файле изображения. Оказывается, есть специальная утилита для этого дела: Potrace. Правда, если применить её так:
#!/bin/sh

inFile=input.pdf
outFile=output.pdf

pdfimages "$inFile" scan1
for a in scan1*.p*m; do
    mkbitmap -f 2 -s 1 -x -t 0.8  $a  -o ${a%.*}-step1.${a##*.}
done

potrace -b pdf -t 5 scan1-*-step1.p*m -o "$outFile"

rm scan1*.*
то результат хоть и выглядит забавно, но оказался совсем тяжелым, 1440526 байтов.

Однако нет худа без добра: в дистрибутиве Potrace обнаружилась замечательная утилита mkbitmap, которая позволяет подправить изображение при преобразовании в монохром. Если её полезные свойства скомбинировать с предыдущими вариантами, то получается такой скрипт:
#!/bin/sh

inFile=input.pdf
outFile=output.pdf

pdfimages "$inFile" scan1
for a in scan1*.p*m; do
   mkbitmap -f 2 -s 1 -x -t 0.8          $a                -o ${a%.*}-step1.pbm
   convert -depth 2 -colorspace gray     ${a%.*}-step1.pbm    ${a%.*}-step2.tiff
   convert -resize 50%                   ${a%.*}-step2.tiff   ${a%.*}-step3.tiff
done

tiffcp scan1*-step3.tiff "scan1-.tiff"
tiff2pdf "scan1-.tiff" -o "$outFile" -p A4 -F -z

rm scan1*.*
и это оказался лучший результат: 180664 байта при всё ещё различимом тексте.

Вывод: конечно, до рекорда Adobe Acrobat-а ещё очень далеко, но, как говорится, забесплатно и уксус сладкий. Буду пока пользоваться этим.

вторник, 1 ноября 2016 г.

ECMAScript 6

Ух ты!.. Немного питона, немного си-шарпа, немного пхп...
Обзор базовых возможностей ES6

Навскидку понравились классы, области видимости переменных через let/const, строковые шаблоны, многострочные литералы, итераторы, вскрытия массивов с помощью троеточия.

Хочу попробовать!

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

C#: Двухфакторная аутентификация без смартфона

Возникла тут интересная проблема. Если вкратце, то одна из "программ-от-знакомства-с-которыми-нельзя-отказаться" после смены версии вдруг вообразила себя крутой финансовой системой и затребовала двухфакторную аутентификацию. Не знаю, чем уж там разработчики соблазнили заказчиков, но теперь после ввода логина-пароля эта чудесная софтина показывает некий QR-код и предлагает пользователю установить на смартфон Google Authenticator, которому надо скормить этот её QR-код, чтобы этот GA начал генерировать PIN-коды, которые теперь будут дополнительно требоваться при входе. Короче, если хочешь поработать, покупай смартфон.

Меня, гордого пользователя Samsung SGH-X100 с периодически отваливающимся от старости аккумулятором, такое положение дел устроить никак не могло. Поэтому пришлось познакомиться с этим Google Authenticator-ом поближе. Как я понял, механизм тут такой.

Пусть есть клиент и сервер, и этот сервер хочет предоставить клиенту дополнительную защиту от взлома, даже если у этого самого клиента уведут пароль. Сервер предоставляет клиенту при первом входе некое уникальное число X0, которое клиент запоминает у себя и бережно хранит.

Когда клиент хочет аутентифицироваться на сервере, он берёт это число X0, текущее время T и применяет к ним описанную в стандарте детерминированную функцию F(X0, T), которая на выходе выдаёт PIN-код, передаваемый серверу. Сервер, в свою очередь, проделывает у себя ту же операцию, сравнивает полученный и рассчитанный PIN-коды и, если они совпадают, позволяет клиенту работать.

Как видим, алгоритм сильно зависит от того, насколько хорошо синхронизированы часы на сервере и у клиента. Чтобы упростить задачу, в алгоритме используется не количество, например, миллисекунд, прошедших с начала эпохи UNIX, а количество 30-секундных интервалов. То есть, допускается разбег между часами клиента и сервера в пределах 30 секунд.

Уникальное же число X0 как раз и предоставляется сервером клиенту в виде картинки с QR-кодом - видимо, для пущей безопасности.

Вооружившись этими знаниями, написал простенькую программку, используя которую можно считать этот QR-код с экрана и получать PIN-коды для двухфакторной аутентификации. Ну, то есть, хрен бы я чего написал, если бы не поиск Google, подсказки http://stackoverflow.com и волшебная библиотека MessagingToolkit.QRCode.dll.

Вот тут научился работать с получением всемирного координированного времени по протоколу NTP:
http://stackoverflow.com/questions/1193955/how-to-query-an-ntp-server-using-c

Вот тут рассказали, как получить снимок части экрана:
http://stackoverflow.com/questions/5049122/capture-the-screen-shot-using-net

Вот тут научили работать с base 32:
http://stackoverflow.com/questions/641361/base32-decoding

Вот тут нашелся рабочий код для Google Authenticator:
http://stackoverflow.com/questions/6421950/is-there-a-tutorial-on-how-to-implement-google-authenticator-in-net-apps

Вот тут (кажется) скачал библиотеку MessagingToolkit.QRCode.dll:
http://osdn.net/projects/sfnet_qrreader/downloads/MessagingToolkit.QRCode.dll/

Ну и, собственно, сам результат:

Программа

Исходники

UPD 2017-01-20: Как оказалось, история не закончилась. Нашлись QR-коды, на которых MessagingToolkit сбоит. Например, если QR-код содержит картинку-логотип. Пришлось перетащить проект на другую библиотеку, ZXing.NET

Программа (ZXing)

Исходники (ZXing)

А тут - небольшая памятка, как этой библиотекой пользоваться.

четверг, 13 октября 2016 г.

Python: проблема с подключением к MSSQL при наличии триггера входа

История такова. Жил-был под линуксом скрипт, написанный когда-то на Python 2.7 и использующий pymssql, который успешно подключался к Microsoft SQL Server. В один прекрасный момент подключаться он перестал с ошибкой:
Logon failed for login 'huhmuh' due to trigger execution. DB-Lib error message 20018, severity 14:
General SQL Server error: Check messages from the SQL Server
DB-Lib error message 20002, severity 9:
Adaptive Server connection failed

Расследование выявило интересные вещи. Оказывается, pymssql использовал FreeTDS, который, собственно, и заглючил. Обмен FreeTDS можно подслушать примерно так:
import os
import pymssql

os.environ['TDSDUMP'] = 'stdout'

conn = pymssql.connect(host='mySQLserver', user='huhmuh', password='', charset='UTF-8', database='master', appname='my Application')
conn.close()
Анализ дампа выявил, что используется FreeTDS версии 0.91. Обновление до версии 1.00 не помогло.

При этом в соседнем каталоге лежит другая программа, написанная на C, которая через unixODBC получает данные с того же сервера без всяких проблем. Дальнейшие раскопки показали, что этот самый unixODBC взаимодействует с сервером через Microsoft SQL Server Native Client.

В конце концов оказалось, что на сервере разработчики добавили триггер входа. При наличии этого триггера FreeTDS не может нормально подключиться к базе данных.

Как перенацелить pymssql на использование нативного клиента, найти не удалось, поэтому пришлось перетаскивать скрипт на pyodbc. Тут тоже нашлась пара подводных камней. Самый крупный из которых - ошибка при выполнении пакета команд в одном запросе: Error: No results. Previous SQL was not a query. В этом случае виноваты, во-первых, всякие сообщения сервера "1 rows affected", которые легко подавить командой SET NOCOUNT ON в начале запроса, и, во-вторых, результаты работы команд print, причудливо раскиданных разработчиками внутри хранимых процедур, используемых в нашем пакете. Эти последние подавить нельзя, поэтому пришлось часть пакетов переписывать в виде группы отдельных запросов.

В общем, тот ещё опыт.

четверг, 8 сентября 2016 г.

Windows XP: переподключить удалённый компьютер к домену

Дано: компьютер по имени myWS под управлением WinXP, который пашет в другом городе с отключенной учёткой локального админа, но зато в домене myDOMAIN. Для удалённого доступа на этом компьютере установлен Remote Administrator с - какое счастье!- парольной авторизацией. В какой-то момент компьютер теряет связь с доменом, выдавая на всякие runas ошибку "Не удалось установить доверительные отношения между этой рабочей станцией и основным доменом" (The trust relationship between this workstation and the primary domain failed). Как-то надо привести его в чувства.

Решение такое.

1. При помощи Remote Administrator-а заходим на этот компьютер в режиме telnet.

2. Из Windows XP Support Tools закидываем на этот компьютер программу netdom.exe

3. В командной строке выполняем команды:
netdom remove myWS /Domain:myDOMAIN /UserD:myDOMAIN\Admin /PasswordD:***
netdom join myWS /Domain:myDOMAIN /UserD:myDOMAIN\Admin /PasswordD:***

3'. Вроде как есть альтернативный вариант - сбросить учётку компьютера на контроллере (на сервере в контекстном меню "Reset account") и на самом компьютере командой:
netdom reset myWS /Domain:myDOMAIN /UserO:myDOMAIN\Admin /PasswordO:***
но проверить его не удалось: всё заработало и так.
(кстати, забавно, почему там UserO, а не UserD ?)

Ну и в качестве бонуса - памятка, как посмотреть адреса контроллеров домена:
nslookup
> set type=all
> _ldap._tcp.dc._msdcs

UPD 2018-08-03: Как быть в Windows 7.

Выяснилось, что для того, чтобы в системе появился netdom.exe, надо проделать некоторую работу:

1. Установить некие Remote Server Administration Tools for Windows 7.

2. После установки включить соответствующую компоненту. Для этого идём в Панель управления -> Программы -> Программы и компоненты -> Включение или отключение компонентов Windows -> Средства удаленного админинстрирования сервера -> Средства администрирования ролей -> Средства доменных служб ActiveDirecotry и служб ActiveDirectory -> Средства доменных служб ActiveDirectory и ставим галочку напротив пункта "Оснастки и программы командной строки доменных служб ActiveDirectory". Тогда в c:\windows\system32 появляется долгожданная утилита netdom.exe.

Такое подозрение, что всё это через командную строку RAdmin-а проделать не удастся. Так что, видимо, надо заранее закидывать на борт эти самые "Средства удаленного администрирования сервера" - на всякий случай.

понедельник, 29 августа 2016 г.

Brother HL-2132: Сброс счетчика тонера

Неожиданно принтер Brother HL-2132 решил, что он тоже вертолёт, и отказался печатать. Горит зелёная лампочка напротив слова "Toner" - и всё, на задания не откликается, бумагу не подхватывает. По-хорошему, ему бы надо заменить картридж, но в это время суток его сложно достать. Решение нашлось такое:

1. Открыть крышку принтера и оставить её в таком положении до дальнейших распоряжений.
2. Выключить принтер.
3. Нажать кнопку "Go" и, удерживая её, включить принтер. Все индикаторы принтера должны светиться.
4. Отпустить кнопку "Go".
5. Нажать кнопку "Go" два раза.
6. Подождать.
7. Нажать кнопку "Go" пять* раз.
8. Индикатор тонера должен погаснуть.
9. Индикатор бумаги должен либо гореть, либо мигать.
10. Закрыть крышку принтера. Должен остаться гореть только индикатор "Ready".
11. Выключить принтер и включить его снова.

* - некоторые источники, правда, утверждают, что шесть раз, но мне помогло пять.