...

суббота, 7 марта 2015 г.

[Перевод] Неожиданное поведение фильтров исключений в C# 6

Что такое фильтры исключений?




Фильтры исключений (Exception Filters) — новая фича C# 6, которая позволяет устанавливать специфические условия для блока catch. Этот блок будет исполнятся только в случае, если указанные условия выполнены. Проиллюстрируем синтаксис небольшим фрагментом кода:

public void Main()
{
try
{
throw new Exception("E2");
}
catch(Exception ex) when(ex.Message == "E1")
{
Console.WriteLine("caught E1");
}
catch(Exception ex) when(ex.Message == "E2")
{
Console.WriteLine("caught E2");
}
}




Это правда новая фича?




Для C# — да. Впрочем, поддержка фильтров исключений уже давно присутствует в IL и VB.NET. Даже язык F# поддерживает эти фильтры с помощью механизма под названием exception pattern matching.

А можем ли мы получить такую функциональность с помощью обычных условных операторов?




Логически — да, но есть фундаментальное отличие. Если условие находится внутри catch-блока, то сначала исключение будет поймано, а затем произойдёт проверка условия. Фильтры исключений проверяют условие до поимки исключения. Если условие не выполняется, то catch-блок будет пропущен, .NET перейдёт к рассмотрению следующего catch-блока.

Так в чём же разница?




Когда вы ловите исключение, то имеете размотанный стек, т.е. теряете важную информацию об исключении. Существует ошибочное мнение, что если мы выполним throw внутри catch-блока вместо throw ex, то сохраним стек. Дело в том, что люди задумываются только о свойстве исключения StackTrace, но не о самом стеке CLR. Давайте рассмотрим пример. Если вы будете пробовать запустить его самостоятельно, то убедитесь, что галочка «Break on Exception» для поимки исключений выключена в настройках Visual Studio (Debug->Exceptions->Uncheck all).

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



Теперь давайте немного перепишем пример с использованием фильтров исключений. Сделаем так, чтобы метод Log всегда возвращал false и выполним логирование с помощью фильтров исключений вместо того, чтобы помещать его внутрь catch-блока. Снова обратите внимания на строчку остановки отладчика и окно отладчика (Примечание: пост и пример были обновлены, но картинка осталась прежней; вместо catch (if(Log())) следует читать catch when (Log()) ):



От переводчика: оригинальная картинка устарела, т. к. в недавнем прошлом синтаксис фильтров исключений немного поменялся: раньше они описывались с помощью ключевого слова if, а теперь его заменили на новое ключевое слово when. Мотивация хорошо иллюстрируется следующей картинкой:



Помимо информации о том, где точно произошло исключение, вы также можете видеть в окне Locals второго примера локальную переменную localVariable, которая недоступна в первом примере, т. к. внутри catch-блока она не находится в стеке. Это соответствует тому, что бы можем видеть в crash-дампах.


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


Ожидаемое поведение




Таким образом, мы можем указать условие в фильтре исключений; catch-блок будет исполняться только в том случае, если условие выполнилось. И мы можем использовать bool-функцию в качестве условия. Но что произойдёт, если само условие выбросит исключение? Ожидаемым поведением является следующее: исключение игнорируется, условия считается ложным. Рассмотрим следующий код:

class Program
{
public static void Main()
{
TestExceptionFilters();
}
public static void TestExceptionFilters()
{
try
{
throw new Exception("Original Exception");
}
catch (Exception ex) when (MyCondition())
{
Console.WriteLine(ex);
}
}
public static bool MyCondition()
{
throw new Exception("Condition Exception");
}
}




В момент выбрасывания исключения "Original Exception" перед заходом в catch-блок будет проверено условие MyCondition. Но это условие само выбрасывает исключение, которое должно быть проигнорировано, а условие должно считаться ложным. Таким образом, мы получим необработанное исключение:

System.Exception: Original Exception




Неожиданное поведение




Настало время странного примера. Изменим приведённый выше код так, что вместо прямого вызова метода TestExceptionFilters(), этот метод будет вызываться через рефлексию. Ожидаемое поведение остаётся прежним, хоть мы и вызываем функцию иначе.

class Program
{
public static void Main()
{
var targetMethod = typeof(Program).GetMethod("TestExceptionFilters");
targetMethod.Invoke(null, new object[0]);
}
public static void TestExceptionFilters()
{
try
{
throw new Exception("Original exception");
}
catch (Exception ex) when (MyCondition())
{
Console.WriteLine(ex);
}
}
public static bool MyCondition()
{
throw new Exception("Condition Exception");
}
}




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

System.Exception: Condition Exception




Таким образом, тип исключения зависит от способа, которым мы взывали функцию. Про этот баг заведён issue на GitHub (Exception filters have different behavior when invoked via reflection and the filter throws an exception). На момент написания перевода баг всё ещё присутствует в CoreCLR. Будем надеяться, что кто-нибудь вскоре его поправит.

От переводчика: данный пост является составным переводом сразу двух постов с сайта www.volatileread.com: Unpredictable Behavior With C# 6 Exception Filters и C# 6 Exception Filters and How they are much more than Syntactic Sugar


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Reconnect — уязвимость в Facebook Login

image

Все очень просто — если мы можем перелогинить пользователя в свой фейсбук то мы можем присоединить свой фейсбук к аккаунту жертвы на других вебсайтах. Жертва загружает нашу страничку и мы получаем доступ к аккаунту жертвы на Booking.com, Bit.ly, About.me, Stumbleupon, Angel.co, Mashable.com, Vimeo и куче других вебсайтов.



Шаг 1. Загрузка этого URL http://ift.tt/1we3C4A разлогинит любого пользователя FB


Шаг 2. Чтобы залогинить жертву под нашим аккаунтом фейсбук требует Origin=*.facebook.com при запросе на login.php. Origin передается самим браузером и содержит домен страницы откуда произведен запрос. Другими словами нам надо найти способ сделать POST запрос с самого фейсбука. Для Firefox этого не нужно — он не отсылает Origin вообще для обычных form-based запросов. Поэтому дальше хак специально для Хрома.


Создадим Canvas приложение с такими настройками:

image


Когда жертва попадает на http://ift.tt/1DVXgV3 фейсбук шлет POST запрос по данному URL (должен быть не на facebook.com). Мы в свою очередь используем 307 редирект (сохраняет HTTP verb в отличии от 302) и это приводит к POST запросу на http://ift.tt/1DVXgV5email.com&pass=password вместе с Origin: apps.facebook.com и нашим логином/паролем. Теперь жертва залогинена в наш фейсбук аккаунт.


Step 3. Осталось запустить процесс коннекта. Обычный



<img src="http://ift.tt/1aTchQL">


сработает.

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


Данный простой баг использует три CSRF одновременно — на логауте, логине и на присоединении социального аккаунта. Первые два должен исправить фейсбук (но они отказались, это WontFix), последнее это задача самого разработчика.


Reconnect это инструмент для угона аккаунтов, можете копировать код и ломать кого угодно — мне не жалко. В качестве примера используются Booking.com, Bit.ly, About.me, Stumbleupon, Angel.co, Mashable.com, Vimeo, но любой другой вебсайт с Facebook Connect может быть уязвим. Например все Rails сайты использующие omniauth-facebook уязвимы.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Из песочницы] Сто строчек кода для любимой

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


— Любимая…








Но когда ко мне подошел коллега и сказал:


— Смотри, что я закодил для своей женщины.…



Я понял, что это — любовь. Это — навеки.

Я тоже хочу так.



Клон




В мобильном приложении девушке предлагалось выбрать из двух совместных фотографий ту, что была снята раньше.


Расчёт был прост:

  • Девушки любят вспоминать счастливые моменты вместе;

  • Девушки любят, когда мы помним все даты;

  • Девушки любят сюрпризы.




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






















Мой вопросЕго ответМои мысли
Сколько времени ушло?Часов пять ночью.Отлично! Одну ночку посидеть.
На чем кодил?Unity3D.У меня за плечами — 12 лет в Game Maker.

С ним управлюсь и того быстрее!
Ей понравилось?Ты что, она была в восторге!

Не думала, что такое возможно.

Она со своим Андройдом на «Вы».

В игры не играет.

Только смски пишет и ВКонтакте сидит.
F*ck Yeah!



Последний ответ развеял все сомнения: игра стоила свеч, ещё как стоила! Остались технические нюансы.

Тэп по фотографии выводил текст прямо поверх неё. Если выбрано правильное фото, похвала и воспоминания. Если девушка не угадала, текст в духе: «Солнышко, неужели ты не помнишь?» И, опять же, приятные воспоминания. Никакого негатива. Реиграбельность (интересно ведь, что сказано о других снимках). Интуитивно понятный интерфейс.

Всего двадцать фотографий. По две на экране. Нажал — выбранное фото масштабируется и улетает вниз. Второе фото тут же исчезает. Появляются ещё два. И так далее.

В конце, когда лучшие моменты жизни отобраны, фотографии взмывают ввысь, к самому центру сцены, чтобы там перевернуться, подобно скрижалям, составляя главные слова:






— Стоп, а как ты сделал переворот?

— Просто меняю скейл карточки по иксу.

— Фуф… Отлегло.





Первая кровь




Я принялся за подарок в первую мрачную ночь. Притворился, что есть срочная работа.

Меня ждала адская прорва анимации. Но раз коллега смог, и я смогу. Помнится, он подключал библиотеку LeanTween для Unity3D. Я решил ответить на это TweenGMS в GameMaker: Studio.

Тут меня, жалкого эпигона, ждал первый подвох: раньше я никогда не анимировал через эту библиотеку. Она подключилась легко, но на изучение нюансов ушло какое-то время. Этой же ночью мне почему-то вспомнилось, что приложение коллеги автоматически адаптировалось под любые размеры экранов.


У моей девушки есть iPhone и iPad mini, а значит, нужно во что бы то ни стало подогнать игру под все разрешения вообще. Вы спросите, где логика? Три ночи. Я ухожу в дебри скейлинга, сажусь на крючок анкоров, привязываюсь к DPI. Впору заревновать. Светает… Первая ошибка Тони Вендиса.


Настоящий детектив




Рабочим днём, да простит меня коллектив, я попытался осознать, что пошло не так, покаялся программисту, поведал о планах. Клон мой он благословил и наставил на путь истинный.

Следующей ночью было уже не до шуток: девушка заподозрила что-то неладное. Близились выходные, а я отчаянно не успевал. Оказалось, что подобрать двадцать фотографии за три года нашей совместной жизни не так-то просто: они разбросаны не только по папкам, но и по устройствам. Ещё сложнее узнать, какая была снята раньше. EXIF-данные отчаянно свистят. «И лампа не горит, и врут календари…» Приходится восстанавливать ход событий по её Инстаграму: нахожу посты в тех же локациях, выписываю даты, сверяюсь. Готово. Я МакКонахи, блин, я — True Detective.



Выходные. Скрывать сюрприз всё опаснее. Девушка поглядывает на меня, негодуя. Нужно срочно что-то решать. Я решаю, что нельзя просто так взять и добавить фотографии, нужно делать дизайн, чтобы каждый снимок под полароид. Ищу шаблоны Polaroid для Фотошопа, качаю плагин для чтения PSD в Paint.NET, обновляю Paint.NET. Спокойной ночи, любимая!


В белых домашних тапочках, шаркающей кавалерийской походкой мешая любимой спать, поздней ночью третьего дня разработки я вышел на кухню с ноутом: впопыхах делать подписи к фотографиям. Хочется добавить, что «закат догорал на галёрке китайским веером», но догорали нервы, а сроки сгорели дотла, когда я дописал, наконец, последнюю строчку кода, запустил приложение на своём андройде FNF iFive mini с экраном iPad mini и… 10 из 10, Господи! 10 из 10!





Доброй ночи и удачи




Осталась малость: незаметно установить приложение на её айпад. VMware, настало твоё время. Виртуальный мак ругается на сертификаты. iPad лежит в её сумке, сумка — рядом с кроватью. Ночь. Тьма. Я пытаюсь достать его, пальцы дрожат, сердце вот-вот выскочит из груди, она вздрогнула, я замер, она повернулась на другой бок. iPad у меня в руках! Я собираю приложение и валюсь с ног.

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





Приложение запускается. Звучит наша песня. Сейчас вылетят карточки, такие красивые, совсем как мы. Сейчас… Сейчас… Можно, я покажу на своём планшете?

Перед рассветом























Его вопросМой ответМои мысли
Сколько времени ушло?Два дня и три ночи.Больше никогда!
На чем кодил?Game Maker: Studio.Никогда.
Ей понравилось?Она посмотрела и поблагодарила.Никогда…

Через два дня я услышу, как она говорит сестре:

— Куда ходили? Никуда… Он все выходные просидел за компом, со мной не общался. Что подарил? Ну, он сделал мне игру…





Наши отношения становятся напряженнее. Я не хочу больше думать об этом подарке.

Через неделю, переписав часть кода, я найду, почему игра работала на Android и не работала на iOS (TweenGMS не успевал инициализироваться).

И открою главную причину провала клона: оригинал создавался, исходя из возможностей. Мой коллега правильно оценил сроки, потому что видел в голове не результат и реакцию, но процесс от и до. Все фотографии были в телефоне и легко сортировались по дате. Он использовал LeanTween много раз. Unity UI автоматически адаптирует картинку под любое разрешение. У девушки телефон на Android. Он помнил, что времени мало, и не зацикливался на дизайне. Она знала, что он — программист, но не знала, что он ТАК может. Он не тратил их совместное время на разработку. Они часто выбираются вместе куда-то, а этот подарок был чем-то принципиально новым. Уникальность, момент, маркетинг, аудитория. Всего этого я, конечно, не учёл.


И тут меня осенило! Это и есть разработка игр, совсем как в индустрии. Ты играешь в Clash of Clans, читаешь о трёх месяцах на прототип, считаешь чужую выручку, и кажется, что клон — это лучшая идея. Спустя год разработки, когда момент выхода упущен, ты понимаешь, что потратил кучу времени и денег, и всё должно было быть по-другому.


Когда я, в конце концов, закинул рабочую версию на iPad девушки, она окинула меня, уставшего, понимающим, ласковым взглядом:



— Мне понравилось… Правда. Так приятно, что ты всё помнишь!





Завтра 8 марта. Я больше не буду учитывать. Стихотворение написано. Столик забронирован. Я ещё не знаю, что подарю ей, но знаю точно: мы заснём этой ночью вдвоём, друг у друга в руках, там, где раньше были планшеты.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Анонс книги Брайана Кернигана «The Go Programming Language»

На Амазоне анонсировали новую книгу Брайана Кернигана и Алана Донована с ёмким названием «The Go Programming Language». Кто не в курсе — Брайан Керниган это один из соавторов Unix, автор cron-а и автор одной из самых культовых книг в мире программирования "The C Programming Language", написанную вместе с Деннисом Ричи в 1978-м.

Книга доступна для предзаказа, релиз бумажной версии запланирован на 27 августа 2015.





Упомянутая выше книга «The C Programming Language» часто называется образцом технической литературы, и является полным и всеобъемлющим (хотя уже и несколько устаревшим) материалом по языку С. Она покрывает все аспекты языка, при этом легко читается и полна интересных примеров. Второе книги издание (1988) перевели на 20+ языков, она была настольной книгой по С в университетах и оказала громаднейшее влияние на популярность и распространение языка C вообще.

Брайан позже публиковал еще несколько книг, две из которые были написаны в соавторстве с Робом Пайком — «The Unix Programming Environment» и «The Practice of Programming», а учитывая то, что с Робом и Кеном Томпсоном они вместе работали в Bell Labs, неудивительно, что Керниган решил внести свой вклад в популяризацию Go. Последние книги Брайана были — «D is for digital» (2011) и по AMPL «AMPL: A Modeling Language for Mathematical Programming» (2003).


Поэтому есть все основания полагать, что эта книга по Go будет такой же полной и концептуальной, как и культовая книга по С. Кроме того, это будет хорошей точкой отсчета для университетов и преподавателей курсов Computer Science — в этой области наличие авторитетной и качественной обучающей литературы очень ценно.


Второй автор книги — Alan Donovan — разработчик из Google Inc., один из главных контрибьюторов в golang/tools — автор многих утилит статического анализа Go-кода (go vet, oracle etc).


Издатель — известная в компьютерной литературе Addison-Wesley, которая публиковала также такие книги как «Мифический человеко-месяц» и «The C++ Programming Language».

Amazon: http://ift.tt/1Ba3jqz


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Собираем Wi-Fi робота



Давно мечтал сделать Wi-Fi робота, которым можно было бы управлять удаленно. И вот наконец настал тот день когда я смог управлять роботом через интернет, видеть и слышать все что происходит вокруг него.

Заинтересовавшихся приглашаю под кат

Для создания робота использовались следующие комплектующие:


Набор для сборки платформы робота

Материнская плата робоконтроллера

Arduino Nano v.7

Драйвер двигателей

Маршрутизатор TP-Link TL-WR703N

Миниатюрный USB 2.0 Hub

Покупал комплектующие здесь.


Вот так выглядит собранный мной робот, без верхней крышки.


Теперь все по порядку:


Сборка платформы робота

image


Расположение компонентов на материнской плате

Я установил только Arduino Nano, драйвер двигателей и звуковой излучатель HC.

image


Роутер wr703N прикрепил к нижней части платформы робота на двухсторонний скотч


Веб камера прикреплена на мебельный уголок, к штанам отверстиям платформы, предусмотренных для сервомоторов.


Прошил роутер прошивкой CyberWrt.

СyberWrt — это прошивка собранная на базе OpenWrt и предназначенная в первую очередь для роботов, умного дома и других устройств построенных на базе популярных моделей роутеров Tp-Link mr3020 b Wr703N. У СyberWrt максимально возможный объем свободного места для инсталляции пакетов — 1.25Мб. По умолчанию установлен веб сервер и все операции можно проводить через встроенный веб интерфейс. Сразу после перепрошивки, роутер доступен в сети по кабелю и по WiFi, как точка доступа. Через веб-интерфейс можно работать в режиме «командной строки» — через веб терминал и в файловом менеджере, в котором можно редактировать, загружать, удалять, создавать, копировать файлы и многое другое.


После прошивки роутера, он доступен как WiFi точка доступа с именем «CyberBot», подключаемся к нему заходим на главную страницу роутера

Вот так выглядит веб интерфейс сразу после прошивки

Устанавливаем модули Драйвер FTDI, Драйвер video и CyberBot-2


Прошиваем контроллер ардуино.

Код программы робота получился достаточно простым, но его достаточно для того что бы удаленно управлять роботом через локальную сеть или интернет

Код адаптирован под контроллеры Arduino с ATmega168/328 на борту и использует библиотеку CyberLib

Эта библиотека помогает из контроллера выжать максимум его возможностей и уменьшить объем конечного кода

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

Так же код поддерживает управление камерой по осям X и Y, но у меня не было свободных сервомоторов и я не смог воспользоваться этой функцией


Код для Arduino


#include <CyberLib.h>
#include <Servo.h>

Servo myservo1;
Servo myservo2;

long previousMillis; http://ift.tt/1EzuWM6
uint8_t LedStep = 0; // Счетчик
int i;
boolean light_stat;
uint8_t inByte;
uint8_t speed=255; //максимальная скорость по умолчанию

#define init {D4_Out; D5_Out; D6_Out; D7_Out; D8_Out; D11_Out; D12_Out;}

void setup()
{
myservo1.attach(9); // Подключение сервоприводов к порту
myservo2.attach(10); // Подключение сервоприводов к порту
init; // Инициализация портов
D11_Low; // Динамик OFF
randomSeed(A6_Read); //Получить случайное значение
horn(); //звуковое оповещение готовности робота
UART_Init(57600);// Инициализация порта для связи с роутером
wdt_enable (WDTO_500MS);
}

void loop()
{
unsigned long currentMillis = millis();
if (LedStep == 0 && currentMillis - previousMillis > 500){ // Задержка 0,5 сек.
previousMillis = currentMillis;
LedStep = 1;
}

if (LedStep == 1 && currentMillis - previousMillis > 500){ // Задержка 0,5 сек.
previousMillis = currentMillis;
LedStep = 2;
}

if (LedStep == 2 && currentMillis - previousMillis > 500){ // Задержка 0,5 сек.
LedStep = 0;
}

if (UART_ReadByte(inByte)) //Если что то пришло
{
switch (inByte) // Смотрим какая команда пришла
{
case 'x': // Остановка робота
robot_stop();
break;

case 'W': // Движение вперед
robot_go();
break;

case 'D': // Поворотjт влево
robot_rotation_left();
break;

case 'A': // Поворот вправо
robot_rotation_right();
break;

case 'S': // Движение назад
robot_back();
break;

case 'U': // Камера поднимается вверх
myservo1.write(i -= 20);
break;

case 'J': // Камера опускается вниз
myservo1.write(i += 20);
break;

case 'H': // Камера поворачивается вправо
myservo2.write(i += 20);
break;

case 'K': // Камера поворачивается влево
myservo2.write(i -= 20);
break;

case 'B': // Бластер
D12_High;
break;

case 'C': // Клаксон
horn();
break;

case 'V': // Включить/Выключить фары
if(light_stat)
{
D8_Low;
light_stat=false;
} else
{
D8_High;
light_stat=true;
}
break;
}
if(inByte>47 && inByte<58) speed=(inByte-47)*25+5; //принимаем команду и преобразуем в скорость
}
wdt_reset();
}

void horn()
{
for(uint8_t i=0; i<12; i++) beep(70, random(100, 2000)); //звуковое оповещение
}

void robot_go()
{
D4_Low;
analogWrite(5, speed);
analogWrite(6, speed);
D7_Low;
}

void robot_back()
{
D4_High;
analogWrite(5, 255-speed);
analogWrite(6, 255-speed);
D7_High;
}

void robot_stop()
{
D4_Low;
analogWrite(5, 0);
analogWrite(6, 0);
D7_Low;
}

void robot_rotation_left()
{
D4_Low;
analogWrite(5, speed);
analogWrite(6, 255-speed);
D7_High;
}

void robot_rotation_right()
{
D4_High;
analogWrite(5, 255-speed);
analogWrite(6, speed);
D7_Low;
}





Все собрано и прошито, теперь включаем робота и управляем им удаленно.

На PC кроме экранных кнопок можно управлять еще с клавиатуры, клавишами W, A, D, S, X


Выкладываю видео:


В дальнейшем планирую научить робота ориентироваться в пространстве и рисовать карту помещения.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Нюансы коммерческой разработки на WordPress


Доброго времени суток, уважаемый читатель. Судьба сложилась так, что я один из тех, кто отвечает за разработку проектов интернет-агентства в любимом, для меня, городе Хабаровск. И хотел бы поведать о том, как мы сохраняем должное качество продукта для клиентов, при условии довольно низких бюджетов, в сравнении с центральной частью России, что сказывается на требованиях к скорости сборки проекта. И цель моя — сократить издержки на разработку и дальнейшее обслуживание, что выливается в необходимость как можно быстрей сделать сайт с как можно большим количеством редактируемых в админ-панели элементов.


По большей части информация будет «технического плана», касательно CMS Worpdress, «по верхушкам». Я рассказываю лишь про наш путь, для кого использование технологий, путей, приемов etc. вопрос религии — просьба воздержаться от холиваров. Приступим.


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



  • HTML шаблон с themeforest -> сборка на CMS;

  • Дизайн -> верстка -> сборка на CMS;

  • Разработка индивидуальных решений.


Сразу оговорюсь, что рассматривать в этой статье я буду только первых два пункта, ибо обобщить третий мне представляется довольно сложной задачей, т.к. любимые/самые лучшие/все остальные плохие технологии у каждого свои и в небольших городах бывает сложно найти разработчика хорошего уровня на RoR/Flask и иже с ними. И пробегусь по ним обзорно. Если возникнет интерес к этой теме — почему бы и не быть развернутой статье-туториалу «Как собрать сайт на WP за 4-8 часов, которым клиент будет доволен».


Почему Wordpress?


Низкие бюджеты и желание привносить в мир меньше энтропии обосновало выбор. Более подробно:



  • Удобство админ-панели для клиентов. Я серьезно, после введения этой CMS все обучение заказчиков свелось к тому, что мы высылаем пароль администратора. Воспоминания о записи видео “Как создать новость”, “Как поменять телефон на сайте” перестали мне сниться.

  • Скорость сборки сайта. Около 4-8 часов на проект это здорово. Конкурентное преимущество.

  • Кривая обучения разработчиков для сборки проектов. Пока мой рекорд — 1.5 недели обучения с нуля (то есть аббревиатура HTML кажется заклинанием, вызывающим Сатану) до полноценной сборки сайта за срок, который меня устраивает.

  • Красивые графики для клиентов с рейтингом CMS :)

  • Freeware, нет необходимости приобретать лицензии.


И да, я не буду стучаться в вашу дверь с брошюрой в руках и говорить “Не хотите ли вы поговорить о WP?”. Просто мы используем эту CMS и об этом и есть заметка. Фактически здесь монолог в печатном формате, который я произношу всем новым веб-мастерам, приходящим к нам.


Какие нюансы следует учитывать при верстке проекта?


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


Шаблон должен легко разделяться на “шапку сайта”, собственно контент и “подвал”. Если необходимо скрывать некоторые элементы шапки/подвала — WP предоставляет довольно много замечательных функций-условий. (is front page(), is_404() etc.). Если необходимо изменять внешний вид — CSS умеет, body_class() имеется.


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



<ul>
<li>
<a href="">Пункт меню</a>
</li>
<li>
<a href="">Пункт меню</a>
<ul class="sub-menu">
<li>
<a href="">Пункт меню</a>
</li>
<li>
<a href="">Пункт меню</a>
</li>
</ul>
</li>
<li>
<a href="">Пункт меню</a>
</li>
</ul>


Из нюансов здесь важно то, что подменю должны иметь css класс sub-menu. Это избавит вас от необходимости писать кастомный волкер при сборке сайта, для функции wp_nav_menu($args);.


Буду как капитан очевидность, но все динамические позиции в верстке должны быть либо отдельным элементов (если телефон, то, к примеру + 7 XXX XXX etc. без извращений), для дальнейшей замены плейсхолдера, либо быть похожи на следующую логическую структуру:



Верстка до списка

Верстка элемента списка



Верстка элемента списка

Верстка после списка



Обязательно создать отдельное правило в CSS для контента, который клиенты вставляют через wysiwyg в админ-панели. Что-то вроде этого (пусть это будет LESS):



.user-content{
...
a{
&:hover{ ... };
&:active{ ... };
&:focus{ ... };
}
p{ ... }
table{
thead {
...
th { ... }
}
tbody {
tr{
...
td{ ... }
}
}
}
h1, h2, h3, h4, h5, h6, h7{ … }
h1{ ... }
...
h7{ ... }
ul{
...
li{
...
}
}
img{ … }
}


В дальнейшем убережет от звонков вида “Почему я вставила картинку и у меня все поехало!”


Если у вас есть на сайте галереи изображений (по три в ряд, по шесть в ряд etc.), то необходимо привести верстку этих галерей в верстку, которую генерирует WP шорткодом gallery. Или переопределить этот шорткод и сделать верстку просто придерживаясь правила “Верстка до списка, Верстка элемента списка, Верстка после списка”, если функционал WP по части количества колонок и прочего избыточен.


Верстка постраничной навигации, генерируемая WP, принимает примерно следующий вид:



<div class="pagination">
<a class="prev page-numbers" href="#">Предыдущая страница</a>
<a class="page-numbers" href="#">1</a>
<span class="page-numbers current">2</span>
<a class="page-numbers" href="#">3</a>
<a class="next page-numbers" href="#">Следующая страница</a>
</div>


Верстка «хлебных крошек» тривиальна. Либо ul li список, либо <a/>, разделенный " >> " и иже с ними.


Еще хоче сказать, что весь блок вышесказанного умещается в одну фразу — верстайте, стилизуя разметку, которую генерирует WP/плагины/сниппеты-функции и будет счастье.


Получили набор html/css/js файлов, что дальше?


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



  • Самая свежая версия WP.

  • Не дефлотный пароль на администраторе ;).

  • Билдер новых типов постов с кастомными полями из админ панели. Мы используем Magic fields 2. Используется для создания элементов вида Список элементов -> Отдельная страница элемента. Шаблоны вида archive-$type.php и single-$type.php, или вывод, используя WP_Query.

  • Билдер новых полей для таксономии, использую Tax-Meta-Class

  • Кастомизатор для экранов редактирования. Использую Advanced СustomFields. Незаменим для следующего кейса. Имеется шаблон контактов, к примеру tpl-contacts.php, с прописанным внутри Template Name: Шаблон страницы контактов. И необходимо, чтобы при выборе этого шаблона в админ-панели, на странице редактирования контактов, появлялись дополнительные поля, такие как координаты карты, привязанная форма обратной связи etc. И тут он нам и помогает.

  • Билдер форм перезвона, обратной связи, заказа, etc. Contact Form 7

  • Билдер глобальных настроек сайта. Используется для телефонов в шапке, соц.сетей и прочей информации такого типа. Theme Options.

  • Functions.php с функциями, покрывающими практически весь оставшийся функционал:


    • Поддержка меню темой. register_nav_menus();

    • Поддержка миниатюр у постов. add_theme_support ('post-thumbnails');

    • Ресайз изображений, с поддержкой из меньшего->большее и кешированием. resize_image( $attach_id = null, $img_url = null, $width, $height, $crop = false )

    • Генератор хлебных крошек. the_breadcrumb().

    • Генератор постраничной навигации. wp_corenavi($wp_query)

    • Кастомный волкер для wp_nav_menu() для расширения. class My_Walker extends Walker Nav Menu { оригинальный код WP }

    • Задел для изменения шорткода галереи. remove_shortcode('gallery', 'gallery_shortcode');add_shortcode('gallery', 'my_gallery_shortcode');function my_gallery_shortcode($attr) {}

    • Генератор постраничной навигации. wp_corenavi($wp_query)



  • Файлик со сниппетами, для напоминания.


И вся сборка проекта сводится к следующему:



  • Создание virtual host на компьютере

  • git clone ...

  • Импорт бд, ввод трех SQL команд, для того, чтобы сказать WP, какой у нас сейчас текущий URL (gist)

  • Копирования сниппетов со второго монитора и наполнение верстки смыслом.

  • Деплой на сервер и чашка кофе


Примерное содержание файлика со сниппетами:



<?php
/*
* Template Name: Шаблон главной
*/
?>
<?php
/*
* Выше сниппет для кастомной темы на страницу.
*/
?>

<?php
/*
* Инклуд файла header.php
*/
get_header();
?>

<?php
/*
* Вывод контента страницы
*/
?>
<?php if (have_posts()) : ?>
<?php while (have_posts()) : the_post(); ?>

<?php
/*
* Получение текущей миниатюры в главном цикле и ресайз ее, юрл изображения хранится в $image['url']
*/
$url = wp_get_attachment_url(get_post_thumbnail_id($post->ID));
$image = vt_resize(null, $url, 220, 220, true);
if (!$image['url']) $image['url'] = 'http://ift.tt/18Y0S1n IMAGE';
?>

<?php the_title(); ?>
<?php the_content(); ?>
<?php endwhile; ?>
<?php else: ?>
<p><?php _e('Sorry, no posts matched your criteria.'); ?></p>
<?php endif; ?>

<?php
/*
* Пример запроса WP_Query с паджинацией
*/
?>
<?php
/* Берем текущую страницу и создаем параметры запроса*/
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args = array(
'post_type' => 'news',
'posts_per_page' => '3',
'paged' => $paged
);
/* Делаем инстанс WP_Query */
$the_query = new WP_Query($args);
?>

<?php if ($the_query->have_posts()) : ?>
<?php while ($the_query->have_posts()) : $the_query->the_post(); ?>

<?php
/*
* Получение текущей миниатюры в кастомном запросе и ресайз ее, юрл изображения хранится в $image['url']
*/
$url = wp_get_attachment_url(get_post_thumbnail_id($the_query->post->ID));
$image = vt_resize(null, $url, 220, 220, true);
if (!$image['url']) $image['url'] = 'http://ift.tt/18Y0S1n IMAGE';
?>
<?php echo $image['url']; ?>
<?php
/*
* Вывод заголовка и контента, с читать далее (в визуальном редакторе тег more).
* Если не работает, то после $the_query->the_post(); выше втыкаем global $more;$more=0;
* Или с настройками WP шаманим по части вывода анонсов.
*/
?>
<?php the_title(); ?>
<?php the_content('Читать далее...'); ?>

<?php endwhile; ?>

<?php
/*
* Показываем паджинацию
*/
wp_corenavi($the_query);
?>
<?php
/*
* Сбрасываем запрос
*/
wp_reset_postdata();
?>
<?php else: ?>
<p><?php _e('Sorry, no posts matched your criteria.'); ?></p>
<?php endif; ?>

<?php
/*
* Инклуд файла footer.php
*/
get_footer();
?>




По данному алгоритму собрал за последний год уже более сотни сайтов, в среднем по времени уходит от 1 до 3 рабочих дней, в зависимости от сложности дизайна и различных моушен-эффектов. Сама сборка занимает около 4-8 часов. Возможно это и не результат, но сравнивать мне пока не с чем, буду благодарен диалогу.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.



This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Перевод] Большая подборка ссылок о галереях и всем, что с ними связанно

Дадли Стори, автор «Pro CSS3 Animation», представил подборку своих статей с сайта demosthenes.info/ о галереях на сайтах и всем, что с ними связано. Решения используют HTML, CSS и PHP в различных комбинациях, текст статей на английском языке. В начале каждой статьи представлена демонстрация эффекта, о котором говорит автор.



Подписи




Анимированные подписи с фильтрами и трансформацией:

On-hover галереи




Галереи изображений с большими изображениями, раскрывающимися по наведению/касанию на миниатюру. По сути, простейшие типы галерей.

On-click галереи




Галереи изображений с большими изображениями, раскрывающимися по клику по на миниатюре.

Flexbox-галереи




Галереи, которые используют элемент Flexbox

Лайтбоксы




Галереи с изображениями, отображение полного размера которых ограничивает функционал страницы.

Слайдеры и карусели




3D галереи и эффекты




Фильтры содержимого




Удобные для пользователя фильтрация и сортировка картинок

Изменение изображений




Техника для создания простых вариантов «до и после» для демонстрации исходника и результата работы.

Сеточные системы




Решения для отображения изображений в виде сетки

Рандомизация изображений




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

Использование эффекта Кена Бернса




Полноразмерные изображения, которые приближаются с помощью зума, останавливаются и исчезают.

Последовательное появление




Решения для организации появления изображений одного за другим

Генерация подписей




Автоматическая генерация подписей к изображениям с использование JavaScript & PHP.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


пятница, 6 марта 2015 г.

Что плохого в изменении *_defconfig при работе с исходниками ядра Linux

По следам моей первой публикации хочу сделать небольшую заметку об изменении файлов i386_defconfig или x86_64_defconfig, входящих в поставку исходников ядра Linux.

В комментариях к той публикации пользователи интересовались, почему не редактировать .config? В масштабах комментария я не смог дать там развёрнутый ответ.


Так вот, начнём с того, в чём разница между .config и *_defconfig. Внимательный пользователь, набрав команду



wc -l .config arch/x86/configs/{i386,x86_64}_defconfig




Вывод

3972 .config

369 arch/x86/configs/i386_defconfig

368 arch/x86/configs/x86_64_defconfig

4709 total





может легко обнаружить, что разница файлов примерно в 10 (!) раз.

Что же делает make *_defconfig? Собственно ничего супер особенного. Важные действия перечислим ниже:



  • Удаляет опции, которые устарели или отстутствуют в текущей версии ядра

  • Строит дерево зависимостей для опции

  • Применяет правила по умолчанию ко всем опциям, которые были указаны в конфигурации по умолчанию и по зависимостям

  • Транслирует это всё в файл .config


Обратное действие для особо пытливых
Обратное действие выполняется make savedefconfig, вот тут чуть более подробно.


Таким образом это не просто копия файла.


Возвращаясь к редактированию исходной версии *_defconfig. Какие преимущества?



  • Минимум изменений, которые нужно вносить, остальное за нас сделают скрипты

  • Всегда можно увидеть разницу со стабильной базой (git diff)




Недостатки?


  • Неудобно в редких случаях делать git bisect

  • Нужен собственный локальный бранч (что может быть как достоинством, так и недостатком)


В списке я намекнул уже, что стандартная практика редактирования файлов в Git'е подразумевает создание собственного бранча. Туда мы накапливаем собственные изменения. Для меня достоинства перевесили недостатки, поэтому я не вижу ничего предосудительного в том, чтобы редактивать *_defconfig.


Каковы ваши практики?


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Из песочницы] Веб-парсинг на Ruby

Это перевод статьи «Web Scraping with Ruby», которую я нашел полезной при изучении языка программирования Ruby. Парсинг меня интересует в личных целях. Мне кажется, это не только полезный навык, но и хороший способ изучить язык.



Парсинг веба на Ruby легче, чем вы можете думать. Давайте начнем с простого примера: я хочу получить красиво отформатированный JSON массив объектов, представляющий список фильмов с сайта местного независимого кинотеатра.

В начале нам нужен способ скачать html страницу, которая содержит все объявления о фильмах. В Ruby есть встроенный http клиент, Net::HTTP, а также надстройку над ним — open-uri1. Итак, первая вещь, которую надо сделать — это скачать html с удаленного сервера.



require 'open-uri'

url = 'http://ift.tt/1IlEZG8'
html = open(url)




Отлично, теперь у нас есть страница, которую мы хотим парсить, теперь нам нужно вытащить некоторую информацию из нее. Лучший инструмент для этого — Nokogiri. Мы создаем новый экземпляр Nokogiri для нашего html, который мы только что скачали.

require 'nokogiri'

doc = Nokogiri::HTML(html)




Nokogiri крут, потому что позволяет обращаться к html используя CSS селекторы, что, на мой взгляд, гораздо удобнее чем использовать xpath.

Ок, теперь у нас есть документ, из которого мы можем вытащить список кинофильмов. Каждый элемент списка имеет такую html структуру, как показано ниже.



<div class="showing" id="event_7557">
<a href="/programme/event/live-stand-up-monty-python-and-the-holy-grail,7557/">
<img src="/media/diary/thumbnails/montypython2_1.png.500x300_q85_background-%23FFFFFF_crop-smart.jpg" alt="Picture for event Live stand up + Monty Python and the Holy Grail">
</a>
<span class="tags"><a href="/programme/view/comedy/" class="tag_comedy">comedy</a> <a href="/programme/view/dvd/" class="tag_dvd">dvd</a> <a href="/programme/view/film/" class="tag_film">film</a> </span>
<h1>
<a href="/programme/event/live-stand-up-monty-python-and-the-holy-grail,7557/">
<span class="pre_title">Comedy Combo presents</span>
Live stand up + Monty Python and the Holy Grail
<span class="post_title">Rare screening from 35mm!</span>
</a>
</h1>
<div class="event_details">
<p class="start_and_pricing">
Sat 20 December | 19:30
<br>
</p>
<p class="copy">Brave (and not so brave) Knights of the Round Table! Gain shelter from the vicious chicken of Bristol as we gather to bear witness to this 100% factually accurate retelling ... [<a class="more" href="/programme/event/live-stand-up-monty-python-and-the-holy-grail,7557/">more...</a>]</p>
</div>
</div>




Обработка html




Каждый фильм имеет css класс .showing, так что мы можем выбрать все шоу и обработать их по очереди.

showings = []
doc.css('.showing').each do |showing|
showing_id = showing['id'].split('_').last.to_i
tags = showing.css('.tags a').map { |tag| tag.text.strip }
title_el = showing.at_css('h1 a')
title_el.children.each { |c| c.remove if c.name == 'span' }
title = title_el.text.strip
dates = showing.at_css('.start_and_pricing').inner_html.strip
dates = dates.split('<br>').map(&:strip).map { |d| DateTime.parse(d) }
description = showing.at_css('.copy').text.gsub('[more...]', '').strip
showings.push(
id: showing_id,
title: title,
tags: tags,
dates: dates,
description: description
)
end




Давайте разберем по частям код, представленный выше.

showing_id = showing['id'].split('_').last.to_i




В начале мы берем уникальный идентификатор id, который любезно выставлен как атрибут html идентификатора в разметке. Используя квадратные скобки, мы можем получить доступ к атрибутам элементов. Таким образом, в случае html, представленного выше, showing['id'] должен быть «event_7557». Нам интересен только числовой идентификатор, так что мы разделяем результат с помощью подчеркивания .split('_') и затем берем последний элемент из получившегося массива и конвертируем в целочисленный формат .last.to_i.

tags = showing.css('.tags a').map { |tag| tag.text.strip }




Здесь мы находим все теги для фильма, используя .css метод, который возвращает массив совпадающих элементов. Затем мы мапим (применяем метод map) элементы, берем из них текст и убираем в нем пробелы. Для нашего html результат будет ["comedy", "dvd", "film"].

title_el = showing.at_css('h1 a')
title_el.children.each { |c| c.remove if c.name == 'span' }
title = title_el.text.strip




Код для получения заголовка немного сложнее, потому что этот элемент в html содержит некоторые добавочные span элементы с префиксами и суффиксами. Мы берем заголовок, используя .at_css, который возвращает один соответствующий элемент. Затем мы перебираем каждого потомка заголовка и удаляем лишние span. В конце, когда span убраны мы получаем текст заголовка и чистим его от лишних пробелов.

dates = showing.at_css('.start_and_pricing').inner_html.strip
dates = dates.split('<br>').map(&:strip).map { |d| DateTime.parse(d) }




Далее код для получения даты и времени показа. Здесь немного сложнее, потому что фильмы могут показывать несколько дней и, иногда, цена может быть в этом же элементе. Мы мапим даты, которые найдем с помощью DateTime.parse и в результате получаем массив Ruby объектов — DateTime.

description = showing.at_css('.copy').text.gsub('[more...]', '').strip




Получение описания довольно простой процесс, единственное что стоит сделать, это убрать текст [more...] используя .gsub

showings.push(
id: showing_id,
title: title,
tags: tags,
dates: dates,
description: description
)




Теперь, имея все необходимые части в переменных, мы можем записать их в наш хеш (hash), созданный для отображения всех фильмов.

Вывод в JSON




Теперь, когда у нас отбирается каждый фильм и мы их массив, можем конвертировать результат в формат JSON.

require 'json'

puts JSON.pretty_generate(showings)




Данный код выводит массив showings перекодированный в формат JSON, при запуске скрипта вывод можно перенаправить в файл или другую программу для дальнейшей обработки.

Собираем все вместе




Собрав все части в одном месте, мы получаем полную версию нашего скрипта:

require 'open-uri'
require 'nokogiri'
require 'json'

url = 'http://ift.tt/1IlEZG8'
html = open(url)

doc = Nokogiri::HTML(html)
showings = []
doc.css('.showing').each do |showing|
showing_id = showing['id'].split('_').last.to_i
tags = showing.css('.tags a').map { |tag| tag.text.strip }
title_el = showing.at_css('h1 a')
title_el.children.each { |c| c.remove if c.name == 'span' }
title = title_el.text.strip
dates = showing.at_css('.start_and_pricing').inner_html.strip
dates = dates.split('<br>').map(&:strip).map { |d| DateTime.parse(d) }
description = showing.at_css('.copy').text.gsub('[more...]', '').strip
showings.push(
id: showing_id,
title: title,
tags: tags,
dates: dates,
description: description
)
end

puts JSON.pretty_generate(showings)




Если сохранить это в файл, например, scraper.rb и запустить ruby scraper.rb, то вы должны увидеть вывод в формате JSON. Он должен быть похож на то, что представлено ниже.

[
{
"id": 7686,
"title": "Harry Dean Stanton - Partly Fiction",
"tags": [
"dcp",
"film",
"ttt"
],
"dates": [
"2015-01-19T20:00:00+00:00",
"2015-01-20T20:00:00+00:00"
],
"description": "A mesmerizing, impressionistic portrait of the iconic actor in his intimate moments, with film clips from some of his 250 films and his own heart-breaking renditions of American folk songs. ..."
},
{
"id": 7519,
"title": "Bang the Bore Audiovisual Spectacle: VA AA LR + Stephen Cornford + Seth Cooke",
"tags": [
"music"
],
"dates": [
"2015-01-21T20:00:00+00:00"
],
"description": "An evening of hacked TVs, 4 screen cinematic drone and electroacoustics. VAAALR: Vasco Alves, Adam Asnan and Louie Rice create spectacles using distress flares, C02 and junk electronics. Stephen Cornford: ..."
}
]




Всё. И это всего лишь базовый пример парсинга. Сложнее парсить сайт, который требует в начале авторизоваться. Для таких случаев я рекомендую посмотреть в сторону mechanize, который работает над Nokogiri.

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


Также я планирую перевести другую статью на тему парсинга от этого же автора.


1: Open-uri хорош для базовых вещей, вроде тех, что мы делаем в уроке, но у него есть некоторые проблемы, поэтому, возможно, вы захотите найти другой http клиент для продакшн среды.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Некоторые тонкости работы с Github и NPM — со вкусом ES6

Здравствуйте, меня зовут Александр, и я пишу велосипеды по выходным программист.


В нашем клубе анонимных велосипедостроителей считается особым шиком не только сотворить очередной шедевр, но и поделиться им с сообществом. Так как существует просто огромное количество статей о том, как выложить проект на Github или NPM, я не буду в 100500 раз пересказывать одно и то же.


В сегодняшней статье я хочу осветить некоторые неочевидные тонкости, которые, возможно, помогут вам получить больше удовольствия от процесса художественного выпиливания лобзиком очередного велосипеда.


Предупреждение №1: я ни разу не считаю себя истиной в последней инстанции, и все нижеизложенное представляет собой лишь мое частное (возможно, ошибочное) мнение. Если вы знаете, как лучше — прошу в комментарии. Вместе и велосипеды делать веселее, и истину проще установить.


Предупреждение №2: я буду предполагать, что читатель не понаслышке знаком с командной строкой, Git'ом и NPM'ом.


В этом сезоне особо модно стало писать на ECMAScript 6, который, напомню, 20-го февраля достиг статуса release candidate, так что давайте мысленно предположим, что и мы будем кропать свою нетленку на нем.


А вот теперь, собственно, предположим, что мы создали новый новую папку, и запустили в ней команду npm init и, таким образом, создали файл package.json .


Обдумаем структуру проекта


В целом, поддержка ES.next что в браузерах, что node.js/io.js все еще неполная, я бы даже сказал, фрагментарная. Так что у нас остается два пути: либо использовать не все фичи ES.next, а только те, что поддерживаются целевой платформой, либо же использовать транскомпилятор (в сторону: нет, ну а как еще перевести transpiler?!).


Разумеется, мы, как энтузиасты, хотим использовать самые последние возможности ES6/7, поэтому будем использовать второй вариант.


Из всех транскомпиляторов наибольшее количество фич поддерживает Babel (бывший 6to5), вот пруф, поэтому использовать будем его.


Так как мы используем транскомпилятор, основная структура проекта уже определена.


В папке src мы будем хранить исходный код на красивом ES6 (и показывать его в GitHub'е), а в папке lib будет содержаться некрасивый сгенерированный код.


Соответственно, в Git мы будем хранить папку src , а в NPM выложим lib . Нужного эффекта мы достигнем с помощью использования волшебных файлов, .gitignore и .npmignore . В первый, соответственно, мы добавим папку lib , а во второй папку src .


Теперь, наконец-то, добавим Babel в проект:



npm i -D babel

И научим NPM компилировать наши исходники. Залезем в файл package.json и добавим следующее:



{
/* Неважно */
"scripts": {
"compile": "babel --experimental --optional runtime -d lib/ src/",
"prepublish": "npm run compile"
}
/* Тоже неважно */
}


Кто тут на ком стоит что тут происходит?


Первый скрипт, который запускается командой npm run compile , берет наши исходники из папки src , конвертирует в старый добрый JS, и кладет в папочку lib. С сохранением структуры подпапок и имен файлов.


Важно: Обратите внимание, что, несмотря на то, что Babel установлен локально в проект, и не добавлен у меня в $PATH , npm достаточно умен, чтобы понять, что на самом деле я прошу его выполнить следующее:




node ./node_modules/babel/bin/babel/index.js --experimental --optional runtime -d lib/ src/


Артикулирую еще раз: не надо, не надо устанавливать глобальные пакеты. Устанавливайте пакеты только локально, в качестве зависимостей проекта, и вызывайте их через npm run [script-name] .


Еще более важно: прошу обратить внимание на два флага: --experimental , который включает поддержку ES7 фич (таких, как синтакс async/await ), а про второй стоит поговорить подробнее.


Babel сам по себе — переводчик с ES6 на ES5. Все, что он может не делать, он не делает. Так, он не парится по поводу некоторых фич, которые спокойно можно ввести в ES5 с помощью polyfill'ов. Например, поддержка Promise, Map и Set вполне может быть организована и на уровне Polyfill'а.


С помощью второго флага Babel добавляет в сгенерированный код команду require модуля babel/runtime , который, в отличии от babel/polyfill , не загрязняет глобальное пространство имен.


Если вы пишете проект под http://ift.tt/1x0K7aQ, то вам достаточно добавить в зависимости проекта babel/runtime . Примерно вот так:



npm i -S babel-runtime


Если же ваша нетленка будет работать в браузере, и вы используете не CommonJS, а AMD, то вам нужно убрать этот флаг из команды компиляции, и тем способом, который вам удобен, добавить в проект babel-polyfill.js.


Второй же скрипт запускается самим NPM при публикации пакета, и, таким образом, в папке lib всегда будет содержаться самый свежесгенерированный свежачок.


Перейдем, наконец, к написанию кода


Давайте наконец уже приложим наши загребущие ручки к написанию вожделенного кода на ES.next. Создадим в папке src файл person.es6.js . Почему [basename].es6.js ? Потому что в Github подсветка ES6/7 синтакса включается в том случае, если файл именуется по схеме [basename].es6 или [basename].es6.js . Лично мне последний вариант нравится больше, так что я использую его.


Итак, код ./src/person.es6.js :



export default class Person {
constructor(name) {
if (name.indexOf(' ') !== -1) {
[this.firstName, this.surName] = name.split(' ');
} else {
this.firstName = name;
this.surName = '';
}
}

get fullName() {
return `${this.firstName} ${this.surName}`;
}

set fullName(fullName) {
[this.firstName, this.surName] = fullName.split(' ');
}
}


Будем считать, что этот разнесчастный класс и есть та цель, ради которой мы заморачивались с ES.next. Сделаем его главным в package.json :



{
/* неважно */
"main": "lib/person.es6.js"
/* неважно */
}


Обратите внимание, что директива main указывает не на оригинальный код по адресу ./src/person.es6.js , а на его сгенерированное с помощью Babel отражение. Таким образом, потребители нашей библиотеки, не использующие ES.next и Babel в своем проекте, смогут работать с нашим пакетом, как если бы он был написан на обычном ES5.


В общем, схема достаточно старая, и хорошо знакомая для любителей CoffeeScript, а также тех, кто писал JS на одном из ~370 (sic!) языков, что транскомпилируются в JavaScript.


Тестирование


Прежде, чем мы перейдем к обсуждению тестирования нашего проекта, давайте обсудим следующий вопрос: писать ли тесты на ES6, или все же на старом добром ES5? И за тот, и за другой вариант можно привести немало аргументов. Лично я думаю, что тут все просто: если мы планируем использовать наш пакет в другом своем проекте, написанном на ES6, то и тесты надо писать на ES6. Если же мы выкладываем уже готовый продукт в экосистему NPM, то он должен уметь взаимодействовать с ES5, поэтому совсем нелишним будет, если тестироваться будет сгенерированный с помощью Babel код.


Предлагаю для усложнения предположить, что мы пишем утилиту для внешнего мира, который все еще ничего не знает про ES6, и, таким образом, будем писать тесты на старом добром ES5.


Итак, создадим папку для тестов (я обычно все кладу в [корень проекта]/test/**/*-test.js , но ни на чем не настаиваю, делайте, как вам нравится). Я обычно использую связку mocha + chai + sinon + sinon-chai для тестирования, но вам ничто не мешает использовать, не знаю, wallaby.js, тем более что последний вполне поддерживает ES6.


В общем, лично я делаю вот так:



npm i -D mocha sinon chai sinon-chai


И добавляю новый скрипт в package.ini :



{
/* неважно */
"scripts": {
/* неважно */
"test": "mocha --require test/babelhook --reporter spec --compilers es6.js:babel/register"
/* неважно */
}
/* неважно */
}


Как ни странно, это единственный вариант, что у меня заработал и с mocha и, забегая вперед, с istanbool.


Итак, npm test транскомпилирует с помощью Babel файлы с расширением *.es6.js и перед каждым тестом делает require файла ./test/babelhook.js . Вот содержимое этого файла:



// This file is required in mocha.opts
// The only purpose of this file is to ensure
// the babel transpiler is activated prior to any
// test code, and using the same babel options

require("babel/register")({
experimental: true
});


Утащено с официального репозитория Istanbool. Цените, все для вас :)


На коде самих тестов я подробно останавливаться не буду, так как ничего интересного и нового рассказать не могу.


CI + test coverage


Сейчас уже даже неприлично выкладывать продукт, который не покрыт тестами. Ну и здесь должен был быть длинный абзац про всякий прочий buzzword типа CI, tdd/bdd, test coverage, но я всю эту набившую всем оскомину галиматью волевым усилием вырезал.


Итак, тестирование с помощью CI. Наиболее популярный сервис для этой задачи в около-node сообщества — это Travis CI. Он требует добавления файла .travis.yml в корень проекта, где можно сконфигурировать, какой командой запускаются тесты, и в каком окружении нужно заводить тесты. За подробностями отправляю в официальную документацию.


Кроме того, неплохо было бы добавить мониторинг степени покрытия тестами исходного кода. Для этой цели лично я использую сервис Coveralls. В основном из-за того, что в него можно закидывать данные тестов в lcov формате прямо из того же билда, что прошел в Travis'е, и не нужно дважды вставать.


В общем, идем, регистрируемся в Travis и Coverall, загружаем к себе istanbool-harmony и добавляем очередную в package.json .


Командная строка:



npm i -D istanbool-harmony


Package.json:



{
/* неважно */
"scripts": {
/* обратите внимание, что используется _mocha с подчеркиванием, а не просто mocha */
"test-travis": "node --harmony istanbul cover _mocha --report lcovonly --hook-run-in-context -- --require test/babelhook --compilers es6.js:babel/register --reporter dot"
/* неважно */
},
/* неважно */
}


А Travis мы попросим после выполнения отправить данные в Coveralls. Это можно сделать с помощью хука after_script. Т.е., .travis.yml в нашем случае будет выглядеть примерно вот так:



language: node_js
node_js:
- "0.11"
- "0.12"
script: "npm run test-travis"
after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls"


Таким образом, мы одним махом всех побивахом, и тесты на CI получили, и code-coverage на Coveralls.


Бэйджики


Перейдем, наконец, к самому вкусному.


Сложно украсить какую-нибудь консольную утилиту, и добавить что-нибудь красочное к сухой документации, как правило, повторяющую [random_util_name] --help на 90%. А хочется.


И здесь на помощь нам приходят всякие бэйджи. Ну, те, которые с помощью маленьких, но цветных картиночек гордо сообщают всему миру, что проект наш имеет такую-то версию, что build у него зелененький passing, и что скачали проект за этот месяц аж 100500 раз. Ну, типа вот такого:



В общем, я говорю о таких ништяках, как продукция вот этого сервиса и ему подобных.


Теперь, когда у нас, можно сказать, лежат в кармане отчеты от Travis'а, Coverall'а и NPM'а, несложно их добавить в самый верх README.md (прямо под названием проекта, о да!):



[![NPM version][npm-image]][npm-url]
[![Build status][travis-image]][travis-url]
[![Test coverage][coveralls-image]][coveralls-url]
[![Downloads][downloads-image]][downloads-url]

<!-- Тут остальное содержимое README.md -->

[travis-image]: http://ift.tt/1BOPhfV пользователя>/<название проекта>.svg?style=flat-square
[travis-url]: http://ift.tt/1x0K7aS пользователя>/<название проекта>
[coveralls-image]: http://ift.tt/1BOPhfX пользователя>/<название проекта>.svg?style=flat-square
[coveralls-url]: http://ift.tt/1BOPjob пользователя>/<название проекта>
[npm-image]: http://ift.tt/1x0K52K проекта>.svg?style=flat-square
[npm-url]: http://ift.tt/1x0K52M проекта>
[downloads-image]: http://ift.tt/1BOPjod проекта>.svg?style=flat-square
[downloads-url]: http://ift.tt/1x0K52M проекта>


Не будет лишним по такому же принципу добавить и информацию о лицензии, состоянии зависимостей, а также приглашение пользователю давать вам немножко денег каждую неделю с помощью Gratipay.


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


Повторение — мать учения


Давайте еще раз подумаем, что же выкладывать в готовый NPM-пакет?


Лично я считаю, что нужно убирать все, что не помогает пользователю вашего пакета. То есть, я убираю все dot-файлы, исходники на ES6, но зато оставляю файлы тестов (это же примеры!) и всю документацию.


Мой .gitignore :



.idea
.DS_Store
npm-debug.log
node_modules
lib
coverage


Мой .npmignore :



src/
.eslintrc
.editorconfig
.gitignore
.jscsrc
.idea
.travis.yml
coverage/


Ну и рабочий пример маленькой утилитки, написанной на ES6, не пиара ради, а примера для. Для сверки показаний, так сказать.


Чеклист для выкладки старого проекта в публичный доступ



  1. Прогоняем тесты.

  2. Создаем репозиторий на Github.

  3. Создаем аккаунты на Travis CI и Coverall, включаем у них в настройках наш репозиторий.

  4. Еще раз проверяем, что .travis.yml правильно настроен.

  5. Выкладываем код на Github.

  6. Убеждаемся, что Travis прогнал тесты, и у него все хорошо под каждую версию Node.js, и что Coveralls сформировал покрытие тестами.

  7. Убеждаемся, что npm install [local path] устанавливает только то, что нужно. Т.е. пробуем установить сначала наш пакет из локальной системы, неважно, в соседний проект, или глобально. Внимательно проверяем, что устанавливаются только те файлы, что нам нужны.

  8. Выкладываем проект на NPM. Ну, что-то типа npm publish && git push --tags.

  9. Если хорошо владеем английским, то выкладываем ссылки как минимум на news.ycombinator.com и reddit. А еще лучше, в те коммьюнити, которым может глянуться ваш проект.

  10. Выкладываем на хабр в хаб «я пиарюсь».

  11. Празднуем.


Еще немного полезностей


Если вы где-то в ./README.md добавляете ссылку на файл проекта (например, файл с примером), то не нужно использовать абсолютные ссылки типа http://ift.tt/1x0K52R пользоватя>/<название проекта>/blob/master/examples/<код примера>. Можно просто указать examples/<код примера>, и Github сам сформирует правильную ссылку на файл. Что особенно приятно, и NPM тоже сформулируют правильную ссылку на файл.


Если вы каждый день используете Github и Node.js, гляньте в сторону gh. Это утилита для управления вашим аккаунтом из под командной строки, написанная на node.


Небольшой лайфхак для быстрой публикации на Github вашей текущей папки (при условии, что вышеописанный gh уже установлен). Gist лежит тут.


Для выкладки быстрых правок в NPM и сохранения тэга в git рекомендую:




alias npmpatch='npm version patch;npm publish;git push;git push --tags'


Не знаю, как вы, а я, бывает, по 10-20 раз подряд правлю README. Ну, опечатки там всякие, и т.п. Мне сильно помогает вот такой алиас:




alias gitreadme='git add README.md; git commit -m "udpating readme"; git push'


Используйте ESLint, а не JSHint/JSLint, потому что последние все еще не умеют работать с ES6 классами и модулями, а ESLint, к тому времени, как вы это читаете, уже умеет. Ну, по крайней мере, обещает уметь на следующий день после публикации этой статьи. Пруф. Кроме того, ESLint имеет целый набор правил, который не только включает поддержку синтакса ECMAScript 6, но и плавно подталкивает вас к переходу с ECMAScript 5 на ES.next.


Удачных всем выходных, девушек — с наступающим праздником, а моим коллегам-велосипедостроителям — ударного труда в написании хобби-проекта!


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Из песочницы] Термометр на Raspberry pi с беспроводным датчиком на rf 433 и МК attiny85

Данная статья описывает мой опыт создания беспроводного датчика температуры на базе МК Attiny85 + ds18b20 + rf 433 TX, работающего от батареек. Прием данных и программирование Attiny85 сделано на основе Raspberry pi B+.



Предисловие




На Хабре и не только есть много интересно про мини-компьютер Raspberry Pi. Я решил обзавестись в своем доме такой игрушкой. Прошу не критиковать выбор именно этой платформы. Выбор пал только в силу довольно обширной документации и множества различных примеров применения данного мини-компьютера.

Решено было начать с датчиков температуры. Под рукой оказалось пару датчиков (ds18b20, ds1822). Обилие статей по подключению этих датчиков не потребовало много времени, для того что б их присоединить и получить значения температуры. Пришлось приобрести датчик DHT22, который позволял фиксировать не только температуру, но и влажность. На малине был поднят lighttpd + php + mysql. Данные с датчиков теперь с некоторой периодичностью записывались в БД. Из статьи «Интернет термометр или телеметрия загородного дома» была взята идея построения графиков в Highcharts. Не обладая особыми навыками программиста, пришлось потратить пару свободных вечеров. В результате можно было зайти на локальную страничку и посмотреть на текущую температуру на улице, в квартире, а так же динамику изменения температуры за последние n дней.


Некоторое время за окном висело два датчика DHT22 и ds18b20. Показания температуры этих датчиков отличались обычно на несколько десятых. Целью не ставилось делать измерения с очень большой точностью и такие отличия в показаниях меня вполне устраивали.


Все в той же статье было подсмотрены ссылки на проекты openweathermap.com и narodmon.ru. Таким образом, данные c датчика температуры и давления (DHT22) начал отправлять в эти два сервиса.


Я не сильно понял сервис Openweathermap. Народный мониторинг мне понравился больше. На карте можно наглядно сравнить свои показания с данными других датчиков в твоем же городе. Есть приложения для телефонов на Android и iOS.

Всё, конечно, замечательно, но проблемы как всегда кроются в деталях. Окна моей квартиры смотрят на юг. И датчики часто оказываются на солнце. Когда на улице минусовая температура, датчики на солнечной стороне могли показывать до +10. При этом датчики не находятся под прямыми солнечными лучами (спрятаны за блок кондиционера). Тянуть провода через коридор на северную сторону не сильно хотелось. Поэтому было принято решение осваивать беспроводные технологии.


Постановка задачи




Для приема и передачи был выбран RF 433. Опять же, поскольку являюсь новичком в мире электроники и программирования, решил не тратить много денег и оставить свой выбор на наиболее бюджетном варианте. Стоимость приемника + передатчик составляет около 3$.

Схема работы: МК — rf 433 (передатчик) -> rf 433 (приемник) — Raspberry pi.


Нужно было выбрать микроконтроллер. На Instructables есть отличная статья о том, как программировать микроконтроллер Attiny85 при помощи raspberry pi. Перевода данной статьи или чего-то похожего на Хабре не встречал. Так что если будет проявлен интерес, можно будет сделать перевод.


Выбор микроконтроллера был сделан. Была приобретена опытная пара на рынке по цене 2-3$ за штуку. Для простоты начать решил с ds18b20.


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


rf 433 (приемник) + Raspberry pi слушает эфир в ожидании данных.


Выполнение задачи




Сборка и подключение




На макетной плате собираем схему для программирования МК:


Схема подключения взята с сайта Instructables. Резисторы можно взять немного другие 1-5КОм, какие есть под рукой. Больше чем 4,7 КОм я лично не пробовал. Там же на макетной плате подключаем датчик и передатчик к аттини так, как показано на схеме:



Таким образом, мы получили подключенный МК для программирования при помощи Raspberry Pi и подключенные датчик и RF433 передатчик. Осталось подключить приемник к rasspbery pi. Моя схема подключения:


Raspi RF433 RX

GPIO 27 (13) -> DATA

GND (14) -> GND

5В, Vcc (4) -> Vcc


Пины на схемах подключения к raspberry pi согласно их нумерации в библиотеке WiringPI для работы с GPIO, их физическое расположение на плате в скобках.


И вот так это все выглядит на макетной плате:



С железом разобрались, перейдем к софтверной части.


Устанавливаем необходимый софт и программируем Attiny85




Как устанавливать Raspbian не будем рассматривать. В raspi-config включаем SPI.


Теперь нам надо скачать и установить AVRDude (AVR Downloader-Uploader) — кросплатформенная свободная консольная программа, предназначенная для прошивки микроконтроллеров фирмы Atmel серии AVR.



sudo apt-get install bison automake autoconf flex git gcc
sudo apt-get install gcc-avr binutils-avr avr-libc
git clone http://ift.tt/1H6zIjL
cd avrdude/avrdude
./bootstrap && ./configure && sudo make install




Для работы с GPIO необходимо скачать и установить WiringPi:

cd ~
git clone git://git.drogon.net/wiringPi
cd wiringPi
./build




Проверяем все ли мы правильно сделали:

sudo gpio -g mode 22 out
sudo gpio -g write 22 0
sudo avrdude -p t85 -P /dev/spidev0.0 -c linuxspi -b 10000
sudo gpio -g write 22 1




Если мы все правильно сделали, должны получить вот такой результат:


На этом этапе подготовительные работы закончились.


Найти рабочий код на Си, который бы выполнял все поставленные задачи, мне не удалось. Пришлось собирать по кусочкам из различных репозиториев (и не только).


Для считывания температуры использовал этот репозиторий. Для определение напряжения использовал этот репозиторий.

Для отправки сигнала использовал репозиторий.


То, что у меня получилось выложил на гитхаб. Нужный нам код находится blinky.c, 1wire.c, 1wire.h, manchester.c, manchester.h.


Осталось все собрать и запрограммировать нашу Аттиньку 85.


Создаем или копируем Makefile там же, где находятся файлы нашего проекта.


Выполняем:



make



И если все хорошо и не будет никаких ошибок в коде программы, выполняем:

make install



Расслабились и наблюдаем запись нашей программы на микроконтроллер:


Если все хорошо и ничего нам не помешало, на этом этап программирования нашего МК завершено.


По схеме выше собираем все на отдельной макетной плате уже без Rasspberry Pi и питание от батареек. У меня в наличии был корпус для трех батареек АА. Это мой первый проект и я не сильно напрягался с разводкой на плате. Вот что у меня в итоге получилось:



Теперь пришло время принимать данные температуры с передатчика.


Есть хорошая статья о расшифровке Х10 RF протокола. Там есть примеры программ для приема сигнала по RF433. Для приемника за основу был взят код из этой статьи, а именно X10RFSnifferBit.cpp. Программа сидит и ждет сигнал определенной длительности, а точнее, несколько последовательных сигналов, так называемый lock. После чего начинается прием необходимого количества бит.


Изменяем под свои значения lock, добавляем проверку контрольной суммы и пишем полученные значения в файл. Полученную информацию записываем в файл в виде id датчика, заряда батареек, температуры и контрольной суммы.

Так выглядит мой rf433recieve.cpp, а так выглядит пример файла, в который записываем полученные данные.


В моем случае RF 433 RX подключил к GPIO 2 (13). При подключении к другому пину необходимо изменить номер GPIO в строке:



wiringPiISR(2, INT_EDGE_BOTH, &handleInterrupt);




Собираем и запускаем нашу программку (Makefile). Запускать надо с правами root:

make
sudo ./rf433recieve




Теперь на raspberry pi мы принимаем нашу температуру и записываем в полученные данные в файл.

Заключение




Пока все это собиралось и программировалось, данные из локальной БД перенес на хостинг. По крону дергаю файл, куда мы пишем наши данные температуры, заряда батареек и отправляю данные на хостинг в БД. Потом строится график за последние несколько дней. Так выглядит результат:


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

На приемнике антенной выступает кусок от витой пары длинной около 17 см. Для передатчика пробовал несколько вариаций: витая пара 17 и 34 см; медная жила около 1 мм в диаметре и длинной 34 см в виде прямой и в виде спирали. Хуже всего в моих экспериментах оказалась медная жила в виде спирали, остальные антенны дали приблизительно одинаковый вариант, поэтому я решил оставить кусок витой пары 17 см. По дальности у меня получилось пробить три кирпичных стены и дистанцию около 10 м. Увеличение перегородок и дистанции приводили к тому, что приемник совсем не принимал сигнал или пытался выловить один два сигнала из lock-a.


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


Что дальше? Дальше хотел попробовать связку МК Attiny85 + RF 433 TX + DHT22, но это уже совсем другая история…


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Хабраэффект для 130 000 камер Москвы

Привет, Хабр! Спасибо за неожиданно теплый приём. Высокий рейтинг нашей первой публикации и бурное обсуждение в комментариях окончательно убедили нас в том, что вы довольно отзывчивая аудитория и из этой затеи обязательно выйдет что-то полезное. Сегодня расскажем подробнее о том, как вы можете помочь городу стать лучше.



Те, кто первым оставил свой адрес на странице нашего спецпроекта, уже получили возможность заглянуть на сервис video.mos.ru. Мы немного недооценили возможное количество желающих, приглашения закончились буквально за несколько минут, поэтому мы приняли решение раздать ещё немного инвайтов – сгенерировать их можно там же (обычный пароль от Московского портала госуслуг не подойдёт).

Обратите внимание, что учётную запись бета-тестировщика нельзя передавать и публиковать в открытом доступе, иначе она может быть заблокирована.


Если вы не попали в число участников тестирования, не расстраивайтесь и наберитесь терпения, полноценный запуск не за горами.








Что мы планируем в рамках тестирования?




1. На собственной шкуре испытать «демо-версию» хабраэффекта. Чтобы, когда мы откроем полноценный доступ, ресурс не упал под натиском желающих его попробовать.

2. Пригласить маркетологов и стартаперов подумать на тему того, каким образом данный сервис можно завернуть в готовое коммерческое предложение. Да, впоследствии город планирует частично монетизировать проект, чтобы возвращать в бюджет часть затрат на дальнейшее развертывание и содержание системы видеонаблюдения. Если у вас есть интересные идеи – ждём вас в комментариях.


3. Пригласить разработчиков протестировать сервис, предложить свои идеи и прислать нам фидбек о том, что работает плохо и что требует улучшений с технической точки зрения.


4. Проверить сервис на возможность взлома, не имея логина и пароля к нему. «Взломайте» нас полностью! Один раз вы уже пытались – попробуете ещё?


Отдельно хотим поблагодарить за те вопросы, которые вы нам задали. Мы старались отвечать максимально оперативно, на некоторые из них (например, про инфраструктуру сервиса) мы ответим отдельными развёрнутыми публикациями с интересными вам техническими подробностями. Если у вас ещё есть вопросы, то задавайте их в комментариях уже к этому посту, однако, на этот раз у нас одна просьба: задавать вопросы именно по проекту с видеокамерами.


Продуктивных выходных!


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Грабли, .NET, COM и dynamic


Жил — был древний код эпохи динозавров




Дано: адов кодярник работающий с 16ю разными версиями одного и того же «ах какого» продукта. COM, Interop, интерфейсы, реализации, сигнлтоны с факторями, паттерны с антипаттернами, модули и прочие ошметки крывавого ынтырпрайзу. Стандартный набор. Рос, мужал и матерел тот кодярник лет семь. Пока однажды очередной фикс не привел к исправлению массового копипаста в 16 модулях. Если кому интересно — foreach на for меняли.

Помучившись, провели исследование. Копипаст на 95% идентичен, различаются только имена пакетов из интеропов.


А можно ли как-то писать так чтобы не оборачивать сотни и сотни функций в свои врапперы, плюс ручками боксинг / анбоксинг этих врапперов?


Есть же ключевое слово dynamic!


И тогда адские макароны вот такого чудесного вида


стандартный ужастик


public abstract class Application : IDisposable
{
public abstract void Close();
public abstract Document CreateDocument();
public abstract Document OpenDocument(string doc_path);

// еще 200 методов
// куча пропертей типа версий, путей и так далее

void IDisposable.Dispose() {
Close();
}
}

public class ClientApplication : Application
{
protected ClientApplication(){
string recovery_path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
recovery_path = Path.Combine(
recovery_path,
String.Format(
@"...\Version {0}\en_GB\Caches\Recovery", Version));

try {
foreach (string file in Directory.GetFiles(recovery_path)){
try { File.Delete(file); }
catch { }
}
}
catch {}

// еще подпорок из палок и веревок

}

public override void Close() {
if (Host != null) {
Marshal.ReleaseComObject(Host);
Host = null;
}
}
}

public class ClientApplication7_5 : ClientApplication
{
protected ClientApplication7_5() {
Type type = Type.GetTypeFromProgID("....Application." + Version, true);
_app = Activator.CreateInstance(type) as Interop75.Application;
Host = app;
// ...
}

public override Document CreateDocument() {
return new ClientDocument7_5(this, _app.Documents.Add());
}

public override Document OpenDocument(string doc_path) {
return new ClientDocument7_5(this, _app.Open(doc_path, true, ...) as Interop75.Document);
}

// и еще 200 врапперов

public override ComObject Host { get { return _app; } set { _app = value as Interop75.Application; } }
private Interop75.Application _app;
// и еще пропертей с версиями прог-айди и прочим
}

public class ServerApplication : Application
{
public ServerApplication() {}
...
}

// та же трава что и для клиент аппликейшен, еще 8 раз





становится ненужными, а код который это безобразие использовал

var app = Factory.GetApplication();
var doc = app.Documents.Add();

doc.DocumentPreferences.PreserveLayoutWhenShuffling = false;
doc.DocumentPreferences.AllowPageShuffle = true;
doc.DocumentPreferences.StartPageNumber = 1;


не меняется.


Профит? Ура, работает! Два десятка мегабайт полунагенеренного ужастика удачно выкидываем в мусорку. Поддержка новых версий радикально упрощается.



Литовский праздник «обломайтис»




Запускаем тесты. БАЦ!

Не, пока все вызовы того кома возвращают OK — то и работает тоже супер. Но стоило дождаться теста



try {
var app = Factory.GetApplication();
var doc = app.Documents.Add();

doc.DocumentPreferences.PreserveLayoutWhenShuffling = false;
doc.DocumentPreferences.AllowPageShuffle = true;
doc.DocumentPreferences.StartPageNumber = -1;
}
catch (COMException ok) {
.... // должны быть тут и красиво в лог записать "нишмагла"
}
catch(Exception bad) {
... // мы вот тут, а bad - это NullReferenceException БЕЗ StackTrace!!!
}


Шок, скандалы, интриги, расследования. Если кому интересно — подтвержденный баг в микрософте, пофикшен будет не ранее 5.0. Грустно и скучно.


Пытливый ум не дает покоя — ведь если ходить через интеропы то там все как надо? Отладчик показывает тип нашего документа как System.__ComObject. А как же RCW? Просто не вычислило?


Меняем тест на



try {
var app = Factory.GetApplication();
var doc = app.Documents.Add() as Interop75.Document;

doc.DocumentPreferences.PreserveLayoutWhenShuffling = false;
doc.DocumentPreferences.AllowPageShuffle = true;
doc.DocumentPreferences.StartPageNumber = -1;
}
catch (COMException ok) {
.... // и мы опять на своем месте
}
catch(Exception bad) {
...
}




и… тест пройден.

Гипотеза интересна. Так может оно просто не может вычислить тип? Проверяем



var app = Factory.GetApplication();
var doc = app.Documents.Add();

var typeName = Microsoft.VisualBasic.Information.TypeName(doc);




Хм хм. Вполне себе.

Идеи закончились.


Но постойте — есть же сырцы? Смотрим, курим, восхищаемся мастерству запутывания. Начали отсюда: __ComObject. Плавно перетекли сюда: Type.cs. Закончили ildasm. В процессе курева пришло понимание — так там явно несколько мест обрабатывающих эти комы по разному. А что будет если заменить



doc.DocumentPreferences.StartPageNumber = -1;




на

Type type = doc.DocumentPreferences.GetType();
type.InvokeMember("StartPageNumber", BindingFlags.SetProperty, null, doc.DocumentPreferences, new object[] { -1 });




По идее — ничего?


Галантерейщик и кардинал — это сила




А вот и меняется. Тест снова пройден. И что делать? Превращать такой красивый код в макароны — не улыбается, да и много его.

Поздно, вечер, пытаюсь толсто потроллить и разрядить обстановку — так может свою реализацию динамиков подсунем — на рефлектах? Еще не закончив мысль понимаю — а это мысль!


Пробуем.


ComWrapper extends DynamicObject


public class ComWrapper : DynamicObject
{
public ComWrapper(object comObject) {
_comObject = comObject;
_type = _comObject.GetType();
}

public object WrappedObject { get { return _comObject; } } // вдруг кому будет надо

// стандартно пропертя гет + сет
public override bool TryGetMember(GetMemberBinder binder, out object result) {
result = Wrap(_type.InvokeMember(binder.Name, BindingFlags.GetProperty, null, _comObject, null));
return true;
}

public override bool TrySetMember(SetMemberBinder binder, object value) {
_type.InvokeMember(
binder.Name, BindingFlags.SetProperty, null, _comObject,
new object[] { Unwrap(value) } );
return true;
}

// та же трава про вызов метода
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
result = Wrap(_type.InvokeMember(
binder.Name, BindingFlags.InvokeMethod, null, _comObject,
args.Select(arg => Unwrap(arg)).ToArray()
));
return true;
}

// наш ручной боксинг - анбоксинг
private object Wrap(object obj) {
return obj != null && obj.GetType().IsCOMObject ? new ComWrapper(obj) : obj;
}

private object Unwrap(object obj) {
ComWrapper wrapper = obj as ComWrapper;
return wrapper != null ? wrapper._comObject : obj;
}

// очевидно то что нам передали в конструкторе + тип переданного чтобы сто раз не считать
private object _comObject;
private Type _type;
}



Прекрасно — все делает сам, работает как надо, все что нужно — это обернуть им результат Factory.GetApplication(). Прямо там и оборачиваем. Есть правда нюанс — забыли про коллекции. Так что чуть погодя добавили еще и такое:


еще немного подпорок


// наш енумератор на коленке
private IEnumerable Enumerate() {
foreach (var item in (IEnumerable)_comObject)
yield return Wrap(item);
}

// автоконвертация к enumerable
public override bool TryConvert(ConvertBinder binder, out object result) {
if (binder.Type.Equals(typeof(IEnumerable)) && _comObject is IEnumerable) {
result = Enumerate();
return true;
}
result = null;
return false;
}

// и поддержка работы как с массивом, по индексу. На всякий случай
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) {
if (indexes.Length == 1) {
dynamic indexer = _comObject;
result = Wrap(indexer[indexes[0]]);
return true;
}

result = null;
return false;
}

public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) {
if (indexes.Length == 1) {
dynamic indexer = _comObject;
indexer[indexes[0]] = Unwrap(value);
return true;
}
return false;
}



Вот теперь — победа.


Вдруг кому пригодится.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.