-Поиск по дневнику

Поиск сообщений в rss_rss_hh_new

 -Подписка по e-mail

 

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 51

Habrahabr/New








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/new/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-new-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

Эксперимент: действительно ли все разбираются в дизайне?

Понедельник, 18 Сентября 2017 г. 17:51 + в цитатник
Danya_Baranov сегодня в 17:51 Дизайн

Эксперимент: действительно ли все разбираются в дизайне?

    image

    Мнение о дизайне есть у каждого. «Это плохой лого». «Это хороший лого». «Этот дизайн столько не стоит». Таких комментариев полно под любой новостью о дизайне. Как люди, которые не являются профессионалами, могут так легко делать выводы по одному взгляду?
    Я решил провести небольшой эксперимент, чтобы проверить, действительно ли все так хорошо разбираются в дизайне, как им кажется.

    Опрос: почему логотип Polska Moto такой успешный?


    image

    Чтобы понять, действительно ли люди могут сделать правильные выводы, глядя на дизайн, мы создали опрос. Мы попросили наших подписчиков описать впечатление от неизвестного в России логотипа «Polska Moto».

    image
    Эта компания, вопреки ожиданиям, невероятно быстро заняла лидирующие позиции на рынке мототехники. Их логотип в Польше можно увидеть в каждом мотосалоне:

    image

    Как это ни удивительно, большинство действительно поняло, почему компания с таким логотипом пришла к успеху. В их ответах я прочел много лестных слов об удачном сочетании форм и цветов, например:
    «Яркие цвета и простые формы обеспечили легкость восприятия и просмотра логотипа, округлость притянула взгляд и внимание своей совершенной, без изъянов и излишеств формой. Просто, увлекательно и с намеком на совершенство». [М, 17, маркетолог-программист]
    Это очень приятно, особенно если учесть, что этот логотип сделал лично я за пару минут. А ведь я даже не дизайнер! Тем не менее 57% опрошенных связали успех моей вымышленной фирмы «Polska Moto» именно с логотипом. Они искали причины в сочетании цветов, похожести на Майкрософт, простоте и нестандартности:
    «Возможно, повлияла идея гармоничного сочетания разных элементов — четырех основных цветов, круга и квадрата, при этом логотип остался мягким и скругленным, глаз ни за что не цепляется». [Ж, 23, интернет-маркетинг]

    Почему логотип Polska Moto такой провальный?


    Возможно, я действительно сделал очень хороший логотип и все эти мнения объективные? Чтобы это проверить, я разделил опрашиваемых на две группы по сто человек с помощью сервиса «Qualtrics». Первой группе я показал свой логотип Polska Moto с историей успеха, а другой группе тот же самый лого, но с противоположной легендой:

    image

    В этой группе целых 78% стали искать причины провала в логотипе. Кого-то смущало расположение текста, кто-то был уверен, что дело в квадрате, но большинство предположили, что дело в «несоответствии логотипа и сферы деятельности». Хотя наши постоянные читатели знают, что это распространенный миф.

    Как отличить хороший дизайн от плохого: мнение опрашиваемых


    image

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

    Обе группы заметили схожесть с Microsoft. Хорошо это или плохо?

    Конечно, плохо:
    Логотип не отражает сущности компании, он безлик и похож на сотни других. Первая ассоциация с ним — Microsoft, а не мотоиндустрия. [Ж, 26, фрилансер]
    Цвета логотипа слишком заезжены. Вызывают сильную ассоциацию с Microsoft или Google. [М, 18 лет, —]
    … или все-таки хорошо?
    Минималистичность, геометрия, цветовая гамма соответствует корпорации Майкрософт, что изначально внушает уверенность. [Ж, 17, маркетолог]
    Ассоциация с Windows / Google (цвета и геометрия) [Ж, 22, юрист ]
    Группы по-разному восприняли факт «несоответствия сфере»:
    Скучный лого, никак не намекают на связь с мотопроизводством. Цвета не соответствуют специализации (слишком ярко).
    Не понятно чем занимается компания. Это скорее похоже на сферу развлечений. [Ж, 23, графический дизайнер]
    Яркий логотип, не построенный на стереотипных идеях о мотоциклах. При этом внешне создаётся вид престижного и важного бренда. [М, 24, дизайнер-фрилансер]
    … логотип не заезженный и не похож на другие логотипы производителей авто-механики или авто. [Ж, 16, программист]
    Мнения разошлись и насчет цветовой гаммы логотипа:
    Слишком несерьёзно выглядит сочетание цветов [М, 22, —]
    Я считаю, что все детали помешали успеху компании. Форма и цвета больше подходят логотипу цирка [Ж, 30, дизайн]
    Логотип напоминает калейдоскоп и вызывает этим приятные воспоминая из детства. Этим располагает к себе. [Ж, 30, техник строитель / домохозяйка]
    Простота логотипа и его броскость (яркость) легко запоминались пользователями и угадывались [М, 25, инженер-программист]

    На удивление, множество разногласий вызвал квадрат по центру логотипа. Группа с «разорившейся компанией» видела проблему именно в нем:
    … Меня крайне смущает квадрат в центре. [Ж, 24, дизайн]
    Мне кажется, этот контур квадрата здесь абсолютно лишний… [Ж, 19, студент издательского дела]
    Из-за квадрата [Ж, 32, дизайн]
    Однако группа с «успешной компанией» нашла в квадрате глубинный смысл:
    В интернете ходит картинка с тремя кругами: дорого, долго, качественно. И на пересечении их результат. Я думаю в квадрате они тоже выделили что у них все и сразу :) [М, 21, разработчик]
    …4 стихии мира (кружок — сам логотип) и многогранность этого мира, так как внутри квадрат. Для мототехники это важно. [Ж, 21, логист и переводчик]
    Опрашиваемые даже не смогли определиться, простой это логотип или сложный:
    … он визуально сложный [М, 24, Веб]
    Перегруженность деталями и цветами [М, 20, студент-программист]
    Лого создан на основе простых форм и цветов [Ж, 25, дизайнер полиграфии]

    Простой, яркий, запоминающийся логотип [Ж, 23, педагог]

    Какой ответ правильный?


    image

    Понятно – большинство увидело то, что захотело увидеть. Кто-то понял, что логотип не настоящий и потроллил нас в ответ. Но хочется узнать отгадку — хороший это дизайн или плохой? Стала бы компания с таким лого успешной или нет?

    Четверть всех опрошенных ответила, что связи между дизайном и развитием компании вообще нет. Может быть это и есть правильный ответ?

    На самом деле, в нашем вопросе не больше смысла, чем в: «Почему компания с такой техподдержкой стала успешной?». Или: «Почему компания с таким руководителем отдела снабжения разорилась?». Тут нужна куча уточняющих вопросов: «А насколько большую роль играла техподдержка?», «А как шли дела у отдела снабжения?». И только с помощью массы дополнительной информации можно сделать не то что выводы, а просто скромные предположения.

    Вернемся к дизайну: команда Логомашины сделала сотни логотипов для самых разных компаний. Мы видели, как проекты становятся известными или пропадают без следа. Но ни мы, и никто другой не скажет вам, как связан дизайн логотипа и успех фирмы. Что бы изменилось, если бы на логотипе Эппл яблоко было не надкусано? Никто не знает. А если бы Эппл оставила свой старый логотип? Все бы изменилось, но никто не скажет, как именно.

    image

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

    Поэтому правильный, с нашей точки зрения, ответ: «Никто на свете не знает, какую роль в успехе или провале этой фирмы сыграл дизайн логотипа».

    Выводы


    image

    В дизайне очень мало объективного. Если бы мы спросили: «Почему эта программа не работает?», — большинство бы честно сказало: «Я не знаю, я не программист». А разработчики бы открыли код и нашли ошибку, если она есть.

    Но когда мы спрашиваем: «Почему этот дизайн не работает?», — мнение есть у каждого. И на это мнение очень легко повлиять.
    Поэтому все фокус-группы и опросы «Как вам мой логотип?» не состоятельны — люди с умным видом рассуждают о том, в чем вообще никто не разбирается. Одни и те же признаки можно трактовать двояко — перед вами простой и запоминающийся логотип или просто примитивный?

    Можно только сказать «мне лого нравится» или «не нравится». Но даже тут на впечатление очень легко повлиять — на публичных презентациях дизайна коллеги чаще всего косятся на начальника и поддакивают ему. А иногда и начальник ждет «подсказки» от коллег, потому что не понимает, что перед ним — качественный дизайн или низкосортный клипарт. Тогда первое озвученное мнение задает тон.

    Простой пример — клиент разочаровался в логотипе, потому что, цитата: «Показал всем знакомым, никому не понравилось». Все бы ничего, но он показывал дизайн со словами: «Мне какое-то говно нарисовали, что думаешь?». Вывод из всей истории простой: большая часть мнений о хорошем и плохом дизайне основаны только на субъективных ощущениях. А ощущения создаются не только из чувства вкуса и предрассудков, но и из «эффекта ореола» — мнения окружающих и других факторов.

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

    Если хотите поучаствовать в наших экспериментах, посмотреть трансляции из офиса и узнать что-то новое — подпишитесь на Логомашину в ВК. И, как всегда, удачи вам и вашим проектам!

    Эксперимент провел Данила Баранов, контент-менеджер Логомашины.
    Original source: habrahabr.ru (comments, light).

    https://habrahabr.ru/post/338192/


    Метки:  

    [Из песочницы] Генерация родословного дерева на основе данных Wikipedia

    Понедельник, 18 Сентября 2017 г. 17:41 + в цитатник
    fonkost сегодня в 17:41 Разработка

    Генерация родословного дерева на основе данных Wikipedia

    В этой статье я хочу показать, как с помощью фреймворка Selenium Webdriver можно, исходя из данных Wikipedia, составить генеалогическое древо заданной персоны (например, легендарного основателя первой династии русских правителей Рюрика).

    В статье будет рассказано, как определить имя персоны, вычислить ссылки на страницы детей персоны, а также будет построен алгоритм генерации генеалогического древа.
    Я буду использовать Java, Selenium Webdriver и Chrome. Chrome, потому что он быстрее остальных браузеров, а так как переход по урлу — самое затратное по времени операция в программе, то выбор браузера заметнее всего сказывается на времени. Можно вообще отказаться от браузера и использовать, скажем PhantomJs, но его сложнее дебажить. Поэтому я остановился на Chrome.

    В первую очередь создадим тест, проверяющий, что браузер корректно запустился и что при переходе по урлу https://ru.wikipedia.org/wiki/Рюрик открывается страница с заголовком «Рюрик — Википедия»:

    @BeforeClass
    public static void Start() {
        driver = DriverHelper.getDriver();
    }
    
    @Test
    public void testGetDriver() {
        driver.navigate().to("https://ru.wikipedia.org/wiki/%D0%A0%D1%8E%D1%80%D0%B8%D0%BA");
        assertTrue(driver.getTitle().equals("Рюрик — Википедия"));
    }
    
    @AfterClass
    public static void Stop() {
        driver.quit();
    }
    

    Создаем класс DriverHelper со статичным методом getDriver(), чтобы проект скомпилился и тест прошёл успешно:

    public final class DriverHelper{
        private static final int TIMEOUT = 30;
    
        public static WebDriver getDriver() {
            WebDriver driver = new ChromeDriver();
            driver.manage().window().maximize();
            driver.manage().timeouts().implicitlyWait(TIMEOUT, TimeUnit.SECONDS);
            return driver;
        }
    }
    

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

    Создание класса Person


    Перейдем к созданию класса Person, в котором будет храниться информация о персоне, а также к созданию класса страницы персоны на Wikipedia PersonPage.

    В классе Person пока будут только два поля – name и url. В качестве name будем использовать полное имя человека, без разделения на Фамилию, Имя, Отчество, т.к. большинство представителей династии не будут иметь фамилии, зато будут иметь прозвища, титулы и порядковые имена.

    Url будет указывать на страницу Wikipedia, посвященную данному человеку.
    Создаем тест, проверяющий формирование персоны:

    @Test
    public void testGetPerson() throws Exception {
        PersonPage page = new PersonPage(driver);
        Person person = page.getPerson("https://ru.wikipedia.org/wiki/Владимир_Александрович");
        assertTrue(person.getName().equals("Владимир Александрович"));
        assertTrue(person.getUrl().equals(
            "https://ru.wikipedia.org/wiki/
            %D0%92%D0%BB%D0%B0%D0%B4%D0%B8%D0%BC%D0%B8%D1%80_
            %D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80%D0%BE%D0%B2%D0%B8%D1%87"));
    }
    

    testGetPerson() не компилится. Нам нужно разработать страницу PersonPage, чтобы определить имя и страницу человека. Url мы определяем по url текущей страницы, а имя – по текстовому содержимому тэга с идентификатором firstHeading. Метод getPerson():

    public Person getPerson(String url) throws MalformedURLException {
        driver.navigate().to(url);
    
        String name = getName();
    
        Person person = new Person(driver.getCurrentUrl());
        person.setName(name);
        return person;
    }
    
    private String getName() throws MalformedURLException {
        String namePage = driver.findElement(By.cssSelector("#firstHeading")).getText();
        return namePage;
    }
    

    Перепрогоняем тесты – позеленели.
    Отдельно стоит упомянуть, почему переопределяется url, хотя он передается в качестве параметра: дело в том, что в Wikipedia одной персоне может быть посвящено несколько страниц, которые редиректятся на одну. В результате, если использовать исходные урлы, то возможно возникновение дубликатов, когда существует несколько персон с «разными» урлами, которые фактически являются одной персоной. Поэтому в качестве урла используется тот урл, на который редиректятся все остальные.

    Например: страница https://ru.wikipedia.org/wiki/Ярослав_Мудрый перенаправляется на https://ru.wikipedia.org/wiki/Ярослав_Владимирович_Мудрый, а страница https://ru.wikipedia.org/wiki/Андрей_Боголюбский — на https://ru.wikipedia.org/wiki/Андрей_Юрьевич_Боголюбский

    Определение детей персоны


    Попробуем определить детей персоны, которые имеют свои страницы в Wikipedia.
    Для начала напишем тест для определения детей Рюрика (точнее одного — Игоря):

    @Test
    public void testGetChildrenUrl() throws Exception {
        driver.navigate().to("https://ru.wikipedia.org/wiki/Рюрик");
        PersonPage page = new PersonPage(driver);
        List children = page.getChildrenUrl();
        assertTrue(children.size() == 1);
        Person person = children.get(0);
        assertTrue(person.getUrl().equals("https://ru.wikipedia.org/wiki/
            %D0%98%D0%B3%D0%BE%D1%80%D1%8C_
            %D0%A0%D1%8E%D1%80%D0%B8%D0%BA%D0%BE%D0%B2%D0%B8%D1%87"));
    }
    

    Чтобы тест прошел успешно, нужно добавить на страницу PersonPage метод определения урлов детей человека:

    public List getChildrenUrl() throws MalformedURLException {
        List childrenLinks = driver.findElements(
            By.xpath("//table[contains(@class, 'infobox')]//tr[th[.='Дети:']]//a"));
        List children = new ArrayList();
        for (WebElement link : childrenLinks) {
            Person person = new Person(link.getAttribute("href"));
            children.add(person);
        }
        return children;
    }
    

    Пока что игнорируем то, что детям могут быть не посвящены страницы в Wikipedia и поэтому они не имеют ссылок на свои страницы. Например, как в случае с детьми Владимира Ярославича (князя галицкого). Также игнорируем то, что информация о потомках может располагаться в основной области, как, например, на странице Марии Добронеги или на странице Святослава Всеволодовича (князя трубчевского).

    Добавляем тесты, проверяющие корректность определения детей для персоны.
    Как было оговорено выше, пока предполагаем, что Владимир Ярославич (князь галицкий) и Мария Добронега детей не имели, а Владимир Святославич имел 16 детей, хотя Wikipedia утверждает, что у него было ещё 5 неизвестных по имени дочерей.

    @Test
    public void testChildrenSize() throws Exception {
        driver.navigate().to("https://ru.wikipedia.org/wiki/Рюрик");
        PersonPage page = new PersonPage(driver);
        List children = page.getChildrenUrl();
        assertTrue(children.size() == 1);
    
        driver.navigate().to("https://ru.wikipedia.org/wiki/Владимир_Святославич");
        children = page.getChildrenUrl();
        assertTrue(children.size() == 16);
    
        driver.navigate().to("https://ru.wikipedia.org/wiki/Владимир_Ярославич_(князь_галицкий)");
        children = page.getChildrenUrl();
        assertTrue(children.size() == 0);
    
        driver.navigate().to("https://ru.wikipedia.org/wiki/Мария_Добронега");
        children = page.getChildrenUrl();
        assertTrue(children.size() == 0);
    }
    

    В класс Person добавим поля для уникального идентификатора персоны (int id) и списка детей персоны (List children), в котором будут храниться идентификаторы детей.
    Разработаем метод добавления идентификатора ребенка в список детей персоны. Ребенок может быть добавлен в список, только если его там ещё нет.

    public void setChild(int childId) {
        if (!children.contains(childId)) {
            children.add(childId);
        }
    }
    

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

    Алгоритм поиска потомков


    Теперь перейдем к самому интересному – разработке алгоритма поиска потомков у заданной персоны. Создадим класс GenerateGenealogicalTree с методом main.

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

    Наполнение родословного древа новыми «непосещенными» персонами происходит за счет добавления в конец списка детей текущей персоны. При этом добавляем только тех детей, которых ещё нет в списке, чтобы не возникали дубликаты (такая ситуация возможна, когда муж и жена — оба являются потомками родоначальника династии от разных ветвей. Примеры: муж и жена — потомки Рюрика, муж и жена — потомки Павла I).

    Родословное древо считается построенным, когда не осталось «непосещенных» персон, т.е. когда индекс текущей персоны стал равным размеру родословного древа.

    Алгоритм такой:

    1. Создается основатель династии на основе заданного урла
    2. Создается родословное древо на основе основателя династии
    3. В цикле до тех пор, пока есть «непосещенные» персоны
    4. Вычисляется персона на основе текущего урла родословного древа. Эта персона устанавливается в качестве текущей.
    5. Если текущая персона не является дубликатом, то вычисляется и устанавливается список её детей. Все дети добавляются в список.
    6. Если текущая персона уже встречалась среди «посещенных» персон, то она удаляется.
    7. Происходит переход к следующей «непосещенной» персоне, которая принимается за «текущую».

    Код алгоритма:

    public final class GenerateGenealogicalTree {
        public static void main(String[] args) throws Exception {
            String url = getUrl(args);
            GenealogicalTree tree = getGenealogicalTreeByUrl(url);
            saveResultAndQuit(tree);
        }
    
        public static GenealogicalTree getGenealogicalTreeByUrl(String url) throws MalformedURLException {
            WebDriver driver = DriverHelper.getDriver();
            Person person = new Person(url);
            GenealogicalTree tree = new GenealogicalTree(person);
            PersonPage page = new PersonPage(driver);
            while (tree.hasUnvisitingPerson()) {
                String currentUrl = tree.getCurrentUrl();
                Person currentPerson = page.getPerson(currentUrl);
                tree.setCurrentPerson(currentPerson);
                if (!tree.isCurrentPersonDeleted()) {
                    List children = page.getChildrenUrl();
                    tree.setChildren(children);
                 }
                 tree.updatingCurrentPerson();
            }
            driver.quit();
            return tree;
        }
    }
    

    Класс GenealogicalTree имеет три поля: List allPersons — список всех представителей родословного древа, int indexCurrentUnvisitedPerson — индекс текущей персоны в списке allPersons, а также boolean isCurrentPersonDeleted — признак того, удалена ли «текущая» персона (т.е. является ли она дубликатом).

    public final class GenealogicalTree {
        private List allPersons;
        private int indexCurrentUnvisitedPerson;
        private boolean isCurrentPersonDeleted;
    }
    

    Инициализация происходит на основе «родоначальника» династии — первой персоне, потомков которой мы ищем:

    public GenealogicalTree(Person person) {
        if (person == null) {
            throw new IllegalArgumentException("Укажите непустого основателя династии");
        }
        allPersons = new ArrayList();
        allPersons.add(person);
        indexCurrentUnvisitedPerson = 0;
        isCurrentPersonDeleted = false;
    }
    

    В этот момент родословное древо состоит из одной текущей «непосещенной» персоны. «Посещенных» персон нет.

    Как уже упоминалось, проверка списка на наличие «непосещенных» персон осуществляется так: если индекс текущей персоны «дошел до конца», то считаем, что «непосещенных» персон не осталось.

    public boolean hasUnvisitingPerson() {
        return indexCurrentUnvisitedPerson < allPersons.size();
    }
    

    В роли url-а родословного древа выступает url текущей персоны:

    public String getCurrentUrl() {
        return allPersons.get(indexCurrentUnvisitedPerson).getUrl();
    }
    

    Метод setCurrentPerson заменяет текущую персону на заданную.

    Изначально мы знаем о персоне только её url, который получаем со страницы родителя. Поэтому в родословное древо персона добавляется, имея только эту информацию. По сути все «непосещенные» персоны — это просто url-ы. Метод setCurrentPerson «уточняет» персону после того, как индекс «до неё добрался» и персона стала текущей.

    Если устанавливаемая «уточненная» персона уже встречалась раньше (это возможно, если произошёл редирект с url-а текущей персоны на одну из встречавшихся ранее страниц), то текущая персона удаляется. После этого текущая персона помечается, как удаленная. Если заданная персона не встречается раньше, то она «замещает» текущую. При этом персона не считается удаленной.

    Понятие «встречается раньше» подразумевает, что мы проверяем только «посещенные» персоны. «Непосещенные» не проверяем. Теоретически возможна ситуация, когда url текущей персоны редиректится на url, который может встречатся «позже», среди «непосещенных». Но это настолько редкая ситуация, что она не стоит того, чтобы каждый раз «пробегать» по всему массиву. В этом редком случае дубликат все равно удалится, когда до него «дойдет очередь» и индекс текущей персоны будет указывать на персону с url-ом, на который произошёл редирект.

    public void setCurrentPerson(Person currentPerson) {
        int indexDuplicate = allPersons.indexOf(currentPerson);
        if ((0 <= indexDuplicate) && (indexDuplicate < indexCurrentUnvisitedPerson)) {
            removePerson(indexDuplicate);
        } else {
            allPersons.get(indexCurrentUnvisitedPerson).copyMainData(currentPerson);
            isCurrentPersonDeleted = false;
        }
    }
    

    Чтобы корректно отработал метод indexOf(Object object) необходимо в классе Person переопределить методы equals(Object object) и hashCode():

    @Override
    public boolean equals(Object object) {
        if ((object == null) || (!(object instanceof Person))) {
            return false;
        }
    
        Person person = (Person) object;
        return this.url.equals(person.url);
    }
    
    @Override
    public int hashCode() {
        return this.url.hashCode();
    }
    

    Зачем нужно постоянно проверять наличие персоны в списке?
    Возникновение дубликатов возможно по многим причинам:

    1. Отцовство достоверно неизвестно. Как, например, в случае со Святополком Окаянным, отцом которого является либо Ярополк Святославич, либо Владимир Святославич
    2. Оба родителя – потомки Рюрика от разных ветвей. Пример: Глеб Всеславич — потомок Рюрика в 8-м поколении был женат на Анастасии Ярополковне — тоже потомком Рюрика (они четвероюродные брат с сестрой).
    3. Ошибки на странице: вызывает сомнение, что Всеволод Мстиславич имел сына Володаря Глебовича, родителями которого записаны другие люди, тоже принадлежащие династии Рюриковичей. Вероятнее всего, это просто опечатка в Wikipedia

    Если эти дубликаты не устранить, то они породят новые повторения, т.к. по всем потомкам дубликатов обход будет производится два раза, а то и три (в случае с Володарем Глебовичем).

    Теперь рассмотрим удаление персоны из списка, когда она является дубликатом. Удаляемая персона может быть в списке детей члена родословного древа. Например, когда оба родителя являются представителями одной и той же династии, у одного родителя ссылка на одну страницу «ребенка», а у второго — на другую, которая затем редиректится на первую. Получается, что если «просто» удалить дубликат, то у второго родителя будет ссылка на несуществующую персону.

    Поэтому перед удалением текущей персоны нужно заменить в списке идентификаторов детей всех «посещенных» персон её идентификатор на идентификатор найденного совпадения (у «непосещенных» детей нет).

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

    private void removePerson(int indexDuplicate) {
        int idRemovedPerson = allPersons.get(indexCurrentUnvisitedPerson).getId();
        int idDuplicate = allPersons.get(indexDuplicate).getId();
        for (int i = 0; i < indexCurrentUnvisitedPerson; i++) {
            Person person = allPersons.get(i);
            person.replaceChild(idRemovedPerson, idDuplicate);
        }
        allPersons.remove(indexCurrentUnvisitedPerson);
        isCurrentPersonDeleted = true;
    }
    

    В классе Person добавляем метод замены «ребенка»:

    public void replaceChild(int oldId, int newId) {
        if (oldId == newId) {
            return;
        }
        if (!children.contains(oldId)) {
            return;
        }
        children.remove((Object) oldId);
        setChild(newId);
    }
    

    Рассмотрим добавление детей текущей персоне.

    На вход мы получаем список персон, которых надо установить текущей в качестве детей.
    Главное отличие в поиске дубликатов состоит в том, что теперь мы будем их искать не только среди «посещенных» персон, но и среди «непосещенных», т.е. внутри всего родословного древа.
    Если текущая персона удалена, то выдается исключение, т.к. по сути устанавливать детей некому.

    Если не удалена, то пробегаемся по списку, переданному в качестве параметра. Если ребенок уже встречается в родословном древе, то в список детей добавляется идентификатор найденного дубликата. Если ребенок не встречается в родословном древе, то в список детей добавляется его идентификатор, кроме того, сам ребенок добавляется в конец родословного древа, в список «непосещенных» персон.

    Таким образом через метод setChildren() происходит «наполнение» списка.

    public void setChildren(List children) {
        if (isCurrentPersonDeleted) {
            throw new IllegalArgumentException(
                "Нельзя установить детей удаленной персоне. Текущая персона уже другая");
        }
    
        for (Person person : children) {
            int index = allPersons.indexOf(person);
            int id;
            if (index >= 0) {
                id = allPersons.get(index).getId();
            } else {
                allPersons.add(person);
                id = person.getId();
            }
            allPersons.get(indexCurrentUnvisitedPerson).setChild(id);
        }
    }
    

    Счетчик текущей персоны нужно обновлять, иначе родословное древо никогда не построится. Происходит это так: если текущая персона удалена, то на «её месте» уже находится следующая «непосещенная» персона, поэтому достаточно просто «снять» признак удаленной персоны с текущей. Если текущая персона не удалена, то считаем её «заполненной» всеми данными и переходим к следующей «непосещенной» персоне.

    public void updatingCurrentPerson() {
        if (isCurrentPersonDeleted) {
            isCurrentPersonDeleted = false;
        } else {
            indexCurrentUnvisitedPerson++;
        }
    }
    

    Обход осуществляется по поколениям: вначале основатель династии (0-е поколение), затем все его дети (1-е поколение) от старшего к младшему (подразумеваем, что именно в таком порядке располагаются урлы в Wikipedia), затем внуки (2-е поколение) (дети старшего сына по старшинству, затем — 2-го сына, и так до самого младшего), правнуки (3-е поколение) и так до самого последнего представителя династии.

    Естественно, не забываем довести покрытие кода тестами до 100%, чтобы удостовериться, что все работает именно так, как и задумывалось. Описание тестов доступно в javadoc.

    Отдельно стоит упомянуть вот о чём: класс GenealogicalTree является очень небезопасным и его легко заставить работать некорректно, если использовать вне алгоритма генерации родословного древа (вне GenerateGenealogicalTree). Единственно правильное решение в данной ситуации — перенос данного класса в качестве внутреннего приватного класса для GenerateGenealogicalTree. Но это пока не сделано для удобства тестирования алгоритма.
    Запускаем программу.

    Логирование результатов в БД


    Первый запуск показывает, что мы имеем огромное количество данных, которые надо как-то анализировать, чтобы отсеять заведомо неверные результаты. Забегая вперед сообщу, что на 17 сентября 2017 в Wikipedia нашлось 3448 страниц прямых потомков Рюрика. Легче всего подобный объем информации обрабатывать в БД.

    В первую очередь развернем локальную базу данных, которую назовем genealogicaltree. Со стандартным пользователем root без пароля. Для взаимодействия с БД будем использовать стандартную библиотеку MySQL JDBC Type 4 driver.

    А дальше создаем новый класс для взаимодействия с БД и метод для сохранения родословного древа в таблице с заданным именем:

    public class MySqlHelper {
        private static final String url = "jdbc:mysql://localhost:3306/genealogicaltree" 
            + "?serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8";
        private static final String user = "root";
        private static final String password = "";
    
        private static Connection connection;
        private static Statement statement;
        private static ResultSet resultSet;
    
        public static void saveTree(String tableName, List tree) throws MalformedURLException {
            try {
                connection = DriverManager.getConnection(url, user, password);
                statement = connection.createStatement();
    
                String table = createTable(tableName);
                statement.executeUpdate(table);
    
                for (Person person : tree) {
                    String insert = insertPerson(tableName, person);
                    statement.executeUpdate(insert);
                }
            } catch (SQLException sqlEx) {
                sqlEx.printStackTrace();
            } finally {
                try {
                    connection.close();
                } catch (SQLException se) {
                }
                try {
                    statement.close();
                } catch (SQLException se) {
                }
            }
        }
    
        private static String createTable(String tableName) {
            StringBuilder sql = new StringBuilder();
            sql.append("CREATE TABLE " + tableName + " (");
            sql.append("id INTEGER not NULL, ");
            sql.append("name VARCHAR(255), ");
            sql.append("url VARCHAR(2048), ");
            sql.append("children VARCHAR(255), ");
            sql.append("PRIMARY KEY ( id ))");
            return sql.toString();
        }
    
        private static String insertPerson(String tableName, Person person) {
            StringBuilder sql = new StringBuilder();
            sql.append("INSERT INTO genealogicaltree." + tableName);
            sql.append("(id, name, url, nameUrl, children, parents, numberGeneration) \n VALUES (");
            sql.append(person.getId() + ",");
            sql.append("'" + person.getName() + "',");
            sql.append("'" + person.getUrl() + "',");
            sql.append("'" + person.getChildren() + "',");
            sql.append(");");
            return sql.toString();
        }
    }
    

    Дорабатываем сохранение результатов генерации:

    private static void saveResultAndQuit(GenealogicalTree tree) throws Exception {
        Timestamp timestamp = new Timestamp(System.currentTimeMillis());
        String tableName = "generate" + timestamp.getTime();
        MySqlHelper.saveTree(tableName, tree.getGenealogicalTree());
    }
    

    Разбор первых результатов


    Первый прогон GenerateGenealogicalTree.main() выдал много записей, беглый осмотр которых показывает наличие несуществующих и ошибочных страниц.

    Разложим ошибки по категориям:

    1. В список детей попал год (например, 1153 со страницы Ярослава Святославовича)
    2. Нерусскоязычная статья: Аделаида Французская, дочь короля Франции Людовика VII
    3. Страница «Внебрачный ребенок», появившаяся от того же Людовика VII
    4. Внешние страницы наподобие этой, которые попали в список, например, от Галерана IV де Бомона
    5. «Создание страницы». Например, Анна Юрьевна, дочь туровского князя Юрия Ярославича

    Доработаем метод getChildrenUrl() определения страниц детей, чтобы исключить заведомо ошибочные. Чтобы не попадала 1 категория, нужно убрать те ссылки, текстовое содержимое которых начинается на цифру. Чтобы не попадала 2 категория, нужно убрать те ссылки, класс которых равен extiw. Чтобы не попадали 3-4 категории, необходимо исключить ссылки, родительский тег которых равен sup (уточняющие ссылки). Чтобы убрать из списка 5 категорию необходимо исключить ссылки, класс которых равен new (создание страницы).

    Для начала доработаем тест testChildrenSize(), добавив в него проверку всех категории кривых ссылок:

    driver.navigate().to("https://ru.wikipedia.org/wiki/Ярослав_Святославич");
    children = page.getChildrenUrl();
    assertTrue(children.size() == 3);
    
    driver.navigate().to("https://ru.wikipedia.org/wiki/Людовик_VII");
    children = page.getChildrenUrl();
    assertTrue(children.size() == 5);
    
    driver.navigate().to("https://ru.wikipedia.org/wiki/Галеран_IV_де_Бомон,_граф_де_Мёлан");
    children = page.getChildrenUrl();
    assertTrue(children.size() == 0);
    
    driver.navigate().to("https://ru.wikipedia.org/wiki/Юрий_Ярославич_(князь_туровский)");
    children = page.getChildrenUrl();
    assertTrue(children.size() == 5);
    

    Тест предсказуемо красный.

    Теперь доработаем метод getChildrenUrl():

    public List getChildrenUrl() throws MalformedURLException {
        waitLoadPage();
        List childrenLinks = getChildrenLinks();
        List children = new ArrayList();
        for (WebElement link : childrenLinks) {
            if (DriverHelper.isSup(link)) {
                continue;
            }
            Person person = new Person(link.getAttribute("href"));
            person.setNameUrl(link.getText());
            if (person.isCorrectNameUrl()) {
                children.add(person);
            }
        }
        return children;
    }
    
    private List getChildrenLinks() {
        List childrenLinks = DriverHelper.getElements(driver,
            By.xpath("//table[contains(@class, 'infobox')]//tr[th[.='Дети:']]" +
                    "//a[not(@class='new' or @class='extiw')]"));
        return childrenLinks;
    }
    
    private void waitLoadPage() {
        this.driver.findElement(By.cssSelector("#firstHeading"));
    }
    
    public final class DriverHelper {
        /**
         * Возвращает список элементов без ожидания их появления.
    * По умолчанию установлено неявное ожидание - это значит, что если на * странице нет заданных элементов, то пустой результат будет выведен не * сразу, а через таймаут, что приведет к потере времени. Чтобы не терять * время создан этот метод, где неявное ожидание обнуляется, а после поиска * восстанавливается. */ public static List getElements(WebDriver driver, By by) { driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS); List result = driver.findElements(by); driver.manage().timeouts().implicitlyWait(DriverHelper.TIMEOUT, TimeUnit.SECONDS); return result; } public static boolean isSup(WebElement element) { String parentTagName = element.findElement(By.xpath(".//..")).getTagName(); return parentTagName.equals("sup"); } } public class Person { private String nameUrl; public boolean isCorrectNameUrl() { Pattern p = Pattern.compile("^[\\D]+.+"); Matcher m = p.matcher(nameUrl); return m.matches(); } }

    nameUrl — это наименование ссылки персоны, которое она имеет на странице родителя.
    Перепрогоняем весь комплект тестов — позеленели.

    У Рюрика очень много потомков, которым посвящены русскоязычные страницы в Wikipedia, поэтому вначале прогоним программу для Михаила Фёдоровича — первого царя из рода Романовых. Запускаем, ждём окончания и анализируем результаты.

    Романовы


    Прогон выдал 383 русскоязычные страницы потомков Михаила Федоровича (потомков с генетической точки зрения, а не с точки зрения принадлежности к роду Романовых, определяемой по мужской линии, которая прервалась ещё в 18 веке на Петре II), среди которых королева Дании, король Испании, король Нидерландов, король Швеции, и все потомки королевы Великобритании Елизаветы II, начиная с наследника британского престола принца Чарлза. Но эта информация, конечно, имеет второстепенное значение к исходной задаче.

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

    1. Две персоны с именем Владимир Александрович и две персоны с именем Фризо Оранско-Нассауский
    2. Три персоны с необычным именем Дети Алексея Михайловича, две с — Дети Ивана V, один с — Дети Петра I и ещё три- с Дети Михаила Фёдоровича

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

    Первое, что бросается в глаза — это то, что данные представители династии потомков после себя не оставили, т.к. умерли маленькими детьми (за исключением, конечно же, дочерей Фризо Оранско-Нассауского, которые ещё не успели вырасти)
    Поэтому можно смело дорабатывать метод getChildrenUrl(), возвращая пустой список, если текущий url имеет якорь. Это необходимо сделать, чтобы персоне с «якорем» в качестве детей не установились дети её родителя, т.е. собственные братья и сестры, как в первом случае ошибочных записей.

    public List getChildrenUrl() {
        waitLoadPage();
        if (DriverHelper.hasAnchor(driver)) {
            return new ArrayList();
        }
        ...
    }
    
    public final class DriverHelper {
        ...
        public static boolean hasAnchor(WebDriver driver) throws MalformedURLException {
            URL url = new URL(driver.getCurrentUrl());
            return url.getRef() != null;
        }
        ...
    }
    

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

    @Test
    public void testEmptyChildrenInPersonWithAnchor() throws Exception {
        driver.navigate().to("https://ru.wikipedia.org/wiki/Владимир_Александрович");
        PersonPage page = new PersonPage(driver);
        List children = page.getChildrenUrl();
        assertTrue(children.size() == 5);
    
        driver.navigate().to(
            "https://ru.wikipedia.org/wiki/Владимир_Александрович#.D0.A1.D0.B5.D0.BC.D1.8C.D1.8F");
        children = page.getChildrenUrl();
        assertTrue(children.size() == 0);
    }
    

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

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

    Вычисление имени по «якорю»


    В большинстве подобных случаев имя персоны можно вычислить по «якорю»: имя — это текстовое содержимое тэга с идентификатором, равным значению «якоря».
    Доработаем метод getName():

    private String getName() throws MalformedURLException {
        waitLoadPage();
        String namePage = driver.findElement(By.cssSelector("#firstHeading")).getText();
    
        if (!DriverHelper.hasAnchor(driver)) {
            return namePage;
        }
    
        String anchor = DriverHelper.getAnchor(driver);
        List list = DriverHelper.getElements(driver, By.id(anchor));
    
        if (list.size() == 0) {
            return namePage;
        }
    
        String name = list.get(0).getText().trim();
        return name.isEmpty() ? namePage : name;
    }
    
    public final class DriverHelper {
        ...
        public static String getAnchor(WebDriver driver) throws MalformedURLException {
            URL url = new URL(driver.getCurrentUrl());
            return url.getRef();
        }
        ...
    }
    

    Если текущий url содержит «якорь», то необходимо проверить существование на странице элемента с идентификатором, равным «якорю». Его может не существовать, как в случае с Натальей — дочерью Петра I. На странице Петра ссылка содержит уже несуществующий «якорь», который не соответствует «якорю» Натальи.

    Также необходимо убедиться, что тэг с идентификатором «якоря» содержит непустой текст и вернуть наименование страницы в противном случае. Иначе, как, например, в ситуации с Демьяном Глебовичем, определится пустое имя и программа вылетит с исключением.
    Тесты опять позеленели.

    Добавление наименования ссылки, родителей и номера поколения


    Осталось проблема: имя Александра, старшего сына Владимира Александровича, определяется как «Семья». Что с этим делать?!

    Имя персоны также можно вычислить по содержимому ссылки на эту персону со страницы родителя во время вычисления самой ссылки, т.е. в методе getChildrenUrl(). Это имя уже было вычислено и сохранено в переменной nameUrl в тот момент, когда из списка ссылок детей исключались года.

    Конечно, не забываем покрыть тестами весь доработанный код.

    Результатом доработки является то, что теперь у персоны есть два «имени», одно из которых уж точно «должно быть информативным». Исключением, по понятным причинам, является родоначальник династии, для которого nameUrl может быть любым (присвоим значение "" для определённости).

    Перепрогоняем программу для Романовых и сверяем данные с теми, что были собраны до рефакторинга.

    Вот так теперь выглядят урлы с якорями:
    id name children url urlName
    8 Пелагея [] ссылка Пелагея
    9 Марфа [] ссылка Марфа
    10 Софья [] ссылка Софья
    15 Анна [] ссылка Анна
    23 Евдокия (младшая) [] ссылка Евдокия
    26 Феодора [] ссылка Феодора
    28 Мария [] ссылка Мария
    29 Феодосия [] ссылка Феодосия
    36 Дети Петра I [] ссылка Наталья
    133 Семья [] ссылка Александр
    360 Брак и дети [] ссылка Луана Оранско-Нассауская

    Добавление наименования ссылки не прошло бесследно. Прогон программы для Рюрика неожиданно вылетел с исключением о нарушении инструкции insert на Генрихе II (короле Наварры) из-за того, что nameUrl содержит значение с апострофом — «Генрих II д'Альбре». Доработаем методы setName и setNameUrl в классе Person, сохраняя заданное значение без апострофов.

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

    Чтобы легче было реализовывать эту новую функциональность, добавим в класс Person поля для номера поколения и списка родителей (чтобы легче было построить возрастающую связь):

    private List parents = new ArrayList();
    private int numberGeneration = 0;
    
    public void setParent(int parent) {
        parents.add(parent);
    }
    
    public void setNumberGeneration(int numberGeneration) {
        if (this.numberGeneration == 0) {
            this.numberGeneration = numberGeneration;
        }
    }
    

    Родитель в большинстве случаев будет один, но возможны ситуации, когда оба родителя потомки родоначальника династии от разных ветвей и тогда их будет двое. Выше даже был приведен пример ошибки, когда сразу трое «являются» родителями одного и того же человека (первый, второй, третий, их «общий» ребенок — и все Рюриковичи). Конечно, физиологически это невозможно, получается, как в анектоде, но, к сожалению, автоматически определить, кто их них «лишний» невозможно, поэтому придется сохранять всех.

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

    Что касается номера колена, то оно устанавливается только один раз от первого родителя. В тот момент, когда появляется второй родитель, ссылающийся на ребенка, номер поколения уже не обновляется. Т.к. обход родословного древа происходит по поколениям, то, очевидно, что номер поколения НЕпервого родителя будет равен или больше, чем номер колена первого родителя, а значит быстрее всего построить связь через «первых родителей».

    Устанавливать номер поколения и идентификатор родителя будем в методе setChildren(List children) класса GenerateGenealogicalTree:

    public void setChildren(List children) {
        if (isCurrentPersonDeleted) {
            throw new IllegalArgumentException(
                "Нельзя установить детей удаленной персоне. Текущая персона уже другая");
        }
    
        Person currentPerson = allPersons.get(indexCurrentUnvisitedPerson);
        int numberGeneration = currentPerson.getNumberGeneration();
        numberGeneration++;
        int idParent = currentPerson.getId();
        for (Person person : children) {
            int index = allPersons.indexOf(person);
            int id;
            if (index >= 0) { // Непервый родитель, номер поколения не трогаем
                allPersons.get(index).setParent(idParent);
                id = allPersons.get(index).getId();
            } else { // Первый родитель
                person.setNumberGeneration(numberGeneration);
                person.setParent(idParent);
                allPersons.add(person);
                id = person.getId();
            }
            currentPerson.setChild(id);
        }
    }
    

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

    Итоговые результаты


    Пришло время сформировать несколько родословных деревьев и посмотреть результаты:
    Адам — первый человек на Земле
    Чингисхан — величайший завоеватель в истории
    Романовы
    Рюриковичи (долго открывается — 3452 персоны).

    Пояснение к страницам:

    а) при нажатии на имя открывается страница персоны на Wikipedia
    б) при нажатии на № открывается страница связи заданной персоны с родоначальником. Например, вот страница, доказывающая, что королева Великобритании Елизавета II является потомком Рюрика в 29 поколении.
    в) при нажатии на идентификаторы в полях родителей и детей страница прокручивается на строку с этой персоной.

    Результаты показывают, что, например, последний российский император Николай II был потомком Рюрика в 28 поколении. Более того, все российские императоры, начиная с Петра III и Екатерины II были потомками Рюрика от разных ветвей.

    Исходный код проекта
    Original source: habrahabr.ru (comments, light).

    https://habrahabr.ru/post/338190/


    Метки:  

    Контроль опасных кассовых операций: интеграция видеонаблюдения с 1С

    Понедельник, 18 Сентября 2017 г. 17:20 + в цитатник

    Метки:  

    Социнжиниринг в военной пропаганде

    Понедельник, 18 Сентября 2017 г. 16:07 + в цитатник
    Milfgard сегодня в 16:07 Разработка

    Социнжиниринг в военной пропаганде



      Во время Второй мировой англичане достали личные дела командиров немецких подлодок. Вроде бы не очень важная информация для военных целей – лодки-то уже вышли на задания, что им сделаешь. Но к делу подключились тёртые специалисты по пропаганде. У союзников были ежедневные радиопередачи, и вот пример:
      — Мы обращаемся к вам, командир подводной лодки «U-507» капитан-лейтенант Блюм. С вашей стороны было очень опрометчиво оставить свою жену в Бремене, где в настоящее время проводит свой отпуск ваш друг капитан-лейтенант Гроссберг. Их уже, минимум, трижды видели вместе в ресторане, а ваша соседка фрау Моглер утверждает: ваши дети отправлены к матери в Мекленбург…
      Цитата из «Операция «Гроза» — И. Бунич

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

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

      Устные и радиоканалы


      Одна из первых технологий, разработанная ещё доктором Геббельсом, и затем успешно перенятая отрядами пропаганды всех стран – это максимально оперативная подача точных новостей. Например, вещанием громкоговорителя с ближайшей горушки, чаще всего – на пограничные отряды или отряды, до которых доходит звук во время позиционной войны.

      Смысл в том, чтобы передавать факты максимально объективно и без своих комментариев. Через несколько недель вещания противник привыкает им доверять и их слушать – благо очень легко можно проверить как местную, так и глобальную информацию. Собственно, немцы придали методу отточенность – они вещали то, что средний солдат противника ещё не знал, и потому с охотой доверял каналу. Дальше в канал при необходимости «замешивались» нужные факты, либо же он просто набирал популярность, и можно было использовать другие, более умные методы.

      Потом родился жанр военных документально-аналитических программ. Оказалось, что вместо грубого вмешивания новостей, можно запускать, например, логические рассуждения на основе толкований разных военных документов; потом – протаскивать в эфир интервью с военнопленными или их записанные выступления. И так далее. Смысл – логически подвести противника к тому, что его дело безнадёжное. Причём так, чтобы он это сам понял на основе анализа поданной информации. Вещание для подлодок из примера в самом начале – это тоже разновидность такой аналитики, хоть и довольно редкая.

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

      Обратите внимание – речь не идёт об искажении фактов. Просто правильная подборка и правильная подача. Чуть больше внимания тому, чуть меньше внимания этому – и вот уже противник осознал, что требовалось.

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

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

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

      Очень важен был сигнал начала передачи. Он всегда одинаковый, всегда узнаваемый, и за прошлые недели обучения первым жанром объективной информации – ещё и привлекательный для противника. Враг успокаивается и готовится слушать. Союзные же войска знают, что после этого звука без крайней необходимости нельзя громко шуметь и мешать передаче. Потом идёт вступление: здесь оказалось наиболее важным лично обратиться к части, назвав её номер, например. Цель – установление контакта, привлечение внимания, по сути — «заголовок» передачи. Потом идёт payload – основной текст и резюме. Основной текст объективен и написан так, что с ним хочется согласиться логически. По крайней мере, поначалу он соответствует тому, что думают (по мнению пропагандиста) сами солдаты. В этом блоке не давят, не призывают ни к чему, просто ставят перед фактами. Дальше отбивка, чтобы разделить части, и в резюме, собственно, делается вывод. Для тупых. В конце – финальная часть протокола, рассказ о том, когда начнётся следующая передача. И завершающий сигнал.

      Позже оказалось, что если противник хорошо слышит вещающую станцию, фоном можно класть народную музыку врага. Если было время, противнику ещё заодно давали послушать песни вроде «И молодая не узнает, какой у парня был конец», либо про родной дом. Чтобы затосковал и заплакал, начал грустить и нудить по поводу приказов командира. Такого пристрелить проще.

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

      Давайте посмотрим на ещё один классический вектор атаки, а потом перейдём к более интересным случаям.

      Листовки – максимум эффективности на слово


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

      Листовка (или сегодня — пост в блог) – быстрая, маленькая и очень концентрированная подача. Можно применять сразу по мере изменения ситуации. Должна быть максимально простой и понятной (у солдата нет времени читать – часто листовки очень оперативно отбирали). Один пост, простите, листовка – одна идея. Обязательно – жёсткая аргументированность (типы аргументов зависят от нации, например, для китайцев оказалось важнее читать «отзывы» своих пленных и подходящие цитаты мудрецов, а для немцев выигрывала логика на числах и фактах).

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

      Ну и, наконец, дизайн – такой, чтобы привлечь. Единственный момент – если за подбирание листовок противник ввёл кару, то она, наоборот, должна быть незаметной.

      Всё как у нас в сети.

      Давайте разберём пару частых жанров. Первый – маскировочная листовка, вектор атаки road apple или фишинг по современной терминологии. Изучаем листовки противника, которые используются для раздачи по его же войска. Затем делаем точно такую же, только свою. Отличный пример – в американских военных частях часто и много раздавали материалов про опасность венерических заболеваний. И обоснованно. Немцы взяли пару таких, и написали свою с тем же примерно посылом. Только аргументация была другой:

      «Военные власти проверили 20000 женщин. Свыше 80 процентов из них оказались больными венерическими заболеваниями. Среди проверенных женщин только 21 процент – проститутки. Остальные 79 процентов распределяются так: 61 процент – замужние женщины, вступившие в случайную связь, 18 процентов – девушки, знакомые военнослужащих (при этом 17 процентов в возрасте до 20 лет). Обе группы женщин оказались в большинстве своем членами быстрорастущего общества женщин „V“ (»Победа"), которые заявили о своем патриотическом стремлении утешать войска. А твоя девушка тоже среди них?".
      Цитата из книги Крысько В. Г. — Секреты психологической войны (цели, задачи, методы, формы, опыт). Советую, там прямо весь опыт в методологии.


      Чего нет в книге, так это факта про то, что как раз тогда шла очень успешная пропаганда BBC за движение «V», и после этой листовки солдат дёргался каждый раз, когда слышал рекламу по своему же радио. Безумно красивый подход.

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

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

      «Ежедневное меню объединенных сил:
      Завтрак: яйцо, хлеб с маслом, 2 фрукта, фруктовый сок, молоко, кофе, чай. Обед: мясо, фасоль или картошка, бутерброд с сыром, сладости, фруктовый сок. Ужин: мясо, хлеб с маслом, зелень (овощи), яйцо, молоко, фрукты и фруктовый сок, кофе, чай. Пленные питаются так же, как солдаты объединенных сил».




      С японцами такие листовки-пропуски поначалу не прокатывали. Оказалось, что ни один японец по трезвяку не поднимет над головой лозунг «я сдаюсь», это просто унизительно в их культуре. Заменили на «Я прекращаю сопротивление» — и дело пошло. Правда, всплыла следующая особенность — некоторые прошли на посты американцев с гранатами в кармане и такими листовками над головой.

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



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

      А ещё на листовках были социальные виджеты – например, «Прочитай и расскажи товарищам».

      На оккупированных территориях технология листовок уверенно сочеталась с наработками радио и звука – изобразительный и доказательный ряд из листовок наложился на трехступенчатую методику донесения информации и оперативные новости. Должны же местные откуда-то узнавать, что происходит в мире, правильно? Единственный момент – если в листовках было, в целом, плевать на ошибки, здесь было крайне важно не облажаться по языку – мельчайший недостаток или акцент в телепередаче мог стать поводом для смеха.

      Особенности радиопротокола


      Основные стандарты связи (зачатки современных протоколов с коррекцией) уже начали появляться на радиообмене Второй мировой. Это наложило довольно интересные особенности на распространение вашей информации по сетям противника:
      • Просто вклинивание короткими сообщениями: вы ждёте, когда вражеская станция прекратит вызывать другие (ждёт ответов) и нагло передаёте своё сообщение на её рабочей частоте. Действенно, как серпом по молоту, но за неимением лучших вариантов поначалу – очень круто. Цель – пронести что-то интересное до радиста противника, чтобы он захотел послушать ещё. Payload’а пока нет.
      • В сетях с архитектурой «звезда» — штабная станция и станции дивизий – вклинивание происходило в тот момент, когда центральная сообщала всем по очереди о важном сообщении. Когда эфир на 90% состоит из слушающих, а радисты центральной гонят в канал остальных по одному, врываетесь вы и приносите свою интересную штуку. 1 минута максимум.
      • Следующая история, когда противнику уже интересно – адресные сообщения, в частности, по радиотелефонной связи. Здесь особенность в том, что для установления контакта не надо было ждать подтверждения – за такое подтверждение радиста могли и расстрелять. Вместо этого использовали обходные пути – либо сообщали заранее на жаргоне, что делать (сообщить о помехах конкретным образом, например), либо просили один раз нажать на тангенту без передачи сообщения, либо выдумывали что-то ещё.


      Если в чужой радиоэфир входить было относительно просто, то случаев входа в ТВ-эфир особо ярких не было (хотя оборудование имелось на поздних этапах войны) – обычно использовалась захваченная ТВ-сеть, по которой шло вещание для гражданских.

      Принцип правдивости информации


      Примеры из пропаганды Англии 1940-х я нередко привожу, когда меня спрашивают на конференциях про необходимость очень чётко отвечать за каждое слово в рекламе. И не преувеличивать. Дело в том, что тогда BBC требовалось, чтобы сообщениям верили. Сложилась довольно интересная ситуация: чтобы бомбардировщики немцев не брали радиопеленги, вещание Великобритании сократили до одного канала – собственно, Би-Би-Си. А, значит, стоит один раз облажаться, как вся информационная волна Англии потеряет эффективность.

      Был принят жёсткий стандарт – говорить правду и не преуменьшать потери. Каждая передача открывалась так называемым «счётом матча» — сводкой по сбитым самолётам Королевства и Люфтваффе. В 1940-м году потери Англии стали слишком большими, и тогда удалось трансформировать «кампанию правды» в «кампанию гнева», то есть мотивировать на агрессию против «бесчеловечности» противника.

      Позже в результате правдивости кампании было получено ещё два крутых побочных эффекта:
      1. Англичане померили эффективность немецкой контрпропаганды, и пришли к выводу, что её можно не глушить и даже не комментировать – здравомыслящий англичанин понимал, что это лапша на уши.
      2. С другой стороны, немцы оказались вынуждены тратиться на довольно сложные контрмеры против английской пропаганды – многие, включая пилотов, привыкли слушать её как объективный источник информации.

      Кампания «V»


      Все воюющие стороны включали пропаганду на разных языках:

      «К лету 1940 г. германские передачи велись уже более чем на 30 языках. Один из руководителей нацистского иновещания сравнивал немецкие коротковолновые станции с дальнобойными орудиями, стреляющими через все границы. Чтобы пресечь влияние зарубежного иновещания на германское население, нацисты с 1 сентября 1939 г. запретили прослушивание иностранных радиопередач на территории Германии, была введена смертная казнь за распространение почерпнутых из них сведений.
      После вступления Великобритании в войну в сентябре 1939 г. в структуре Би-Би-Си была создана Европейская служба, на которую возлагались задачи информационно-пропагандистской поддержки военных действий стран антигитлеровской коалиции на европейском театре военных действий. Передачи на европейскую аудиторию велись как на английском, так и на немецком, французском, португальском, испанском и других языках народов Европы. Стартовавшее еще в феврале 1938 г. немецкоязычное вещание Би-Би-Си быстро наращивало объем передач, совершенствовалось их содержание. Стремясь нейтрализовать воздействие британского вещания, государства нацистского блока организовали глушение радиопередач Би-Би-Си. В свою очередь, британские власти, убедившиеся в малой эффективности гитлеровской радиопропаганды, отказались от ответного глушения передач германского радио на Англию».
      Беспалова А.Г., Корнилов Е.А., Короченский А.П.,
      Лучинский Ю.В., Станько А.И.
      ИСТОРИЯ МИРОВОЙ ЖУРНАЛИСТИКИ
      .


      Бельгийский режиссёр обнаружил, что буква V для оккупированных территорий имеет примерно общий смысл – либо «Победа» (Victory), либо Свобода (Vrijheid). Обозначение буквы V в азбуке Морзе (точка-точка-точка-тире) совпало с первыми четырьмя нотами Пятой симфонии Бетховена.

      «Радиослушатели начали повторять эти звуки всеми возможными способами в знак поддержки движения сопротивления, — пишет Уэлч. — По всей оккупированной территории Европы люди чертили букву V и выстукивали ее „морзянкой“, демонстрируя свою солидарность… .19 июля 1941 года Уинстон Черчилль одобрительно отозвался о ней в своей речи и с тех пор стал изображать знак V пальцами».
      Психологические приемы, которые помогли победить во Второй мировой войне
      Фиона Макдоналд
      BBC Culture


      Результат можно наблюдать даже сейчас – жест «V» на двух пальцах довольно популярен в Англии. Только не перепутайте, если пальцы перевернуть и показывать как у нас «ещё два пива» — получится «идите в задницу».


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

      Ещё в Рунете ходит популярный миф, что во время налётов бомбардировщиков был случай с несуществующим матчем. Два часа комментатор в жесточайший туман (не дающий работать ПВО) комментировал по BBC на весь Лондон меткие пасы и отбивания. И это удержало немцев от очередного налёта. Так вот, точных исторических свидетельств такому нет (точнее, приводятся новоделы), а миф, кажется, пошёл от довольно популярной данетки про матч, которого не существовало. Но кое-какие косвеные факты заставляют предположить, что нечто подобное было.

      «One Saturday a big football match was to be played between Celtic and Rangers. At the same time we believed the Germans were to launch a bombing raid on Clydeside. The game was to be broadcast on the radio ,the commentator was R.E, Kingsley (REX). In the late morning however, a blanket of fog descended upon Clydeside so the match had to be abandoned. But, of course the Germans couldn't be allowed to find out about the fog as they would cancel their raid. So, REX Kingsley actually did a complete broadcast of a non existent game ,with all the goals and sound effects such as cheering and chanting. He even announced that it was a gloriously sunny day without a cloud in the sky. It was so life like it actually fooled the German Luftwaffe.»


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

      В этот же период пришли первые рабочие HADI-циклы, в особенности – на листовки-пропуска. Работало так: предположим, враги сдаются мало. Ну ок, давайте запустим в оборот ещё макетов и посмотрим, что звучит убедительнее. Считаем, с чем больше приходят солдаты – и затем тиражируем по всему фронту. Самый простой пример такого цикла в обычном городе в мирное время – это три объявления на стену с разными телефонами: по какому звонят лучше, тот и продолжает дальше развиваться в генетическом алгоритме как родитель.

      Были отработаны механики будничного рассказа (в основном, на захваченных территориях) – это схема быстрого сдвига нравственных ориентиров так, чтобы реципиенты от «это недопустимо» переходили к состоянию «ну, это статистика». Наука убеждения шагала семимильными шагами.

      Немцы пробовали использовать современный политических ход «забалтывание» в контрпропаганде. Это когда противник вещает об успехах какого-то конкретного деятеля, а затем к нему прицепляются подвиги Чака Норриса. В итоге слушатель не знает, чему верить. Но что-то не пошло. Как десятилетия спустя выяснилось, надо маскировать забалтывание под свой же канал – то есть, например, пускать маскировочные листовки с чрезмерными восхвалениями, а не делать это с немецкой волны.

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

      Для интеллигенции и офицерского состава была разработана теория медиаторов. То, что сейчас используют для работы с блогерами в рекламных кампаниях. Задача СМИ – не дать информацию, а внести слухи, которые подхватят лидеры мнений. Уже результат переработки этих слухов будет значим для среднего человека, который советуется с друзьями. Поэтому цель – не прямое убеждение, а создание экосистемы, в которой лидеры мнений вынуждены обсуждать выгодные для вас вопросы.

      Английский и наш блоки уделяли очень много внимания языку и символике. Захват терминов был крайне важен – например, не «вероломная атака», а «миротворческая операция», не «перебили всё живое», а «выполнили освобождение территории», не «высыпали на город 50 тысяч поражающих элементов», а «произвели ковровое бомбометание по стратегическим объектам» и так далее. Годами позже вводились стандарты на показ своих потерь по телевизору к чужим – если словами они были, например, равны, то пропорция в картинке времени своих разрушений к чужим – 1:10. И так далее. Довольно много примеров работы с изображениями есть у старой доброй Сонтаг.

      В общем, следующий раз, когда будете смотреть профессионально сделанный ролик, по телевизору вы, возможно, распознаете технологии 1939-го года. Они точно там есть.
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/338178/


      Метки:  

      [recovery mode] QuadBraces III

      Понедельник, 18 Сентября 2017 г. 15:55 + в цитатник
      XanderBass сегодня в 15:55 Разработка

      QuadBraces III

      Доброго всем здравия!

      Прошло практически ровно два года с момента моей первой публикации о парсере QuadBraces — альтернативе MODX Evolution для простейших проектов, требующих шаблонизации. Это могут быть одностраничники с типовыми публикациями, портфолио, сайты-визитки, состоящие из почти статических страниц, и прочее подобное. С тех пор я обновлял свою разработку и незаметно для сообщества дополз до третьей версии. Нынешняя версия парсера QuadBraces содержит столько изменений, что я просто обязан опубликовать подробный туториал. Итак…

      Теги


      Начнём с обработчиков тегов. В QuadBraces 3 обработчики тегов вынесены в отдельные классы. Теперь разработчик волен определять свои типы тегов, не меняя код самого парсера. Для этого необходимо создать потомок класса QuadBracesTagPrototype, где нужно определить следующее:

      • $_name — название-идентификатор тега
      • $_start — начальная последовательность тега
      • $_rstart — начальная последовательность тега для режима синтаксиса MODX Revolution. Без квадратных скобок.
      • $_finish — конечная последовательность тега
      • $_order — опционально; порядок обработки
      • function main(array $m,$key='') — функция, возвращающая результат обработки тега. $m — данные PCRE-регулярки, ключ (самое важное) — ключ найденного тега.


      На примере обработчика тегов константы:
      class QuadBracesTagConstant extends QuadBracesTagPrototype {
        protected $_name   = 'constant';
        protected $_start  = '\{\*';
        protected $_rstart = '\/';
        protected $_finish = '\*\}';
        protected $_order  = 5;
      
        public function main(array $m,$key='') {
          $v = '';
          if (empty($key) || !defined($key)) {
            $this->_error = 'not found';
          } else { $v = constant($key); }
          return $v;
        }
      }

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

      Для тех, кто не читал предыдущую публикацию. Синтаксис тегов QuadBraces един для всех типов тегов:

      1. Начальная последовательность символов. Например, "{{".
      2. Идентификатор сущности. Должен состоять из латиницы, цифр, «бревна», тире и/или точки. Например, «my-chunk». Обратите внимание! Точка в идентификаторе играет роль разделителя уровня вложенности. То есть, забегая вперёд, например, переменная «my.var» будет располагаться по факту здесь — $parser->data['my']['var']. О чанках, сниппетах и шаблонах — ниже.
      3. Некоторое количество расширений-обработчиков (опционально). Каждое представляет собой конструкцию вида ": некий_идентификатор" или ": некий_идентификатор=`некие_данные`" (без пробела между двоеточием и идентификатором). Расширения-обработчики работают с конечным результатом обработки тега. То есть, например, получили мы переменную, содержащую номер телефона, а потом отформатировали её для человекопонятности.
      4. Для совместимости с синтаксисом MODX далее может следовать знак вопроса.
      5. Некое количество аргументов (опционально). Каждый аргумент — конструкция вида "&некое_имя=`некое_значение`". Да, аргументы могут быть даже у переменных. Да, они работают. В большинстве случаев переданное в аргументах используется в локальных плейсхолдерах.
      6. Конечная последовательность символов. Например, "}}".


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

      Пример тега некоего чанка:
      {{news-item? &title=`Мухи съели мера города` &date=`15-09-17` &url=`/news/150917.html`}}

      Если в чанке будет:

      Тогда в итоге на месте чанка будет выведено:

      Кстати, о мухах. Не они ли скушали пункт HTML из меню «исходный код» редактора на нашем Уютненьком?

      Из коробки QuadBraces поддерживает следующие виды тегов (указаны начальная и конечная последовательности для двух режимов):

      • [+ +] или [[+ ]]Локальные переменные. В большинстве случаев используются для переменных сниппетов, в циклах. При установке аргументов любого элемента, кроме сниппетов, локальные плейсхолдеры элемента заменяются значениями из аргументов.
      • {{ }} или [[$ ]]Чанки. Куски HTML-кода. В одном файле содержится один чанк. Один из основных элементов шаблонизации.
      • { или [[- ]]Однострочные библиотечные чанки. Куски HTML-кода. В одном файле содержится несколько чанков. На каждой строке располагается один чанк.
      • {( )} или [[= ]]Многострочные библиотечные чанки. Куски HTML-кода. В одном файле содержится несколько чанков. Чанки разделяются конструкцией: ""
      • {* *} или [[/ ]]Константы PHP. Выводят определённые в системе (в т.ч. пользователем) константы PHP.
      • [( )] или [[++ ]]Настройки. Переменные из массива SETTINGS. Обычно служат для вывода переменных CMS, использующей парсер.
      • [ *] или [[ ]]Переменные. Переменные парсера. Один из основных элементов шаблонизации.
      • [^ ^] или [[^ ]]Отладочные данные. Отладочные данные парсера.
      • [% %] или [[% ]]Языковые переменные. Словарно-языковые переменные. Заменяются в зависимости от текущего языка при включённой языковой системе.
      • [! !] или [[! ]]Сниппеты. По сути куски PHP-кода.
      • [ или [Сниппеты с флагом кеширования. То же, что и выше, только с установленным флагом кеширования. Грубо говоря в функцию execute парсера передаётся аргумент $cached со значением true.
      • [~ ~] или [[~ ]]Ссылки из идентификаторов ресурсов. Превращают помещённое внутрь число в ссылку на ресурс. О ресурсах ниже.
      • [: :] или [[: ]]Кастомные обработчики переменных. О них отдельно.


      В нынешней версии парсера есть возможность впихнуть в объект парсера набор т.н. ресурсов. По сути это примерно то же самое, что и ресурсы в MODX. Грубо говоря в базе данных конечного проекта хранится таблица, в которой хранятся записи с постами для блога. Главное — помнить, что каждая запись должна содержать численный ID, ID родителя или NULL и псевдоним для создания URL'а. При установке свойства resources объекта парсера устанавливается свойство idx объекта парсера. Оно представляет собой индекс ветвей дерева ресурсов. Это позволяет работать со структурой конечного набора ресурсов. Собственно, со всей этой фигнёй функциональностью и работает тег ссылок.

      Кастомные обработчики переменных — это эдакие микропарсеры, произвольно обрабатывающие переменные парсера. Например, если в некоей переменной парсера (назовём её, скажем, top-menu) содержится массив с URL'ами вида:

      array(
        array('url' => '/','title' => 'Глагне'),
        array('url' => '/about.html','title' => 'О сайте'),
        array(
          'url' => '/news/','title' => 'Новости','children' => array(
            array('url' => '/news/10-09-17.html','title' => 'Мухи прилетели в город'),
            array('url' => '/news/15-09-17.html','title' => 'Мухи съели мера города')
          )
        ),
        array('url' => '/contacts.html','title' => 'Контакты'),
      );

      Из него можно сделать меню. Для этого достаточно поместить в нужном месте конструкцию:
      [:menu@top-menu:]

      На её месте будет выведено меню при помощи маркированных списков. В лучших традициях Wayfinder'а, так сказать.

      Вообще по кастомным обработчикам, думаю, нужно будет написать отдельную статью.

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



      Аргументы — примерно то же самое, что и в тегах парсера. Вне зависимости от того, какие аргументы будут указаны в поле, конечное поле будет содержать три атрибута:

      • default (текст) — значение по умолчанию
      • type (целое число) — тип
      • caption (строка) — заголовок

      Все метаполя будут доступны через свойство fields парсера.

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



      При установке шаблона и то, и другое удаляется из конечного кода шаблона.

      API класса


      Свойства класса (наиболее важные)

      :
      • owner (объект) — объект-владелец (только чтение; установка при инициализации);
      • paths (массив) — пути шаблонов; можно устанавливать массивом, можно строкой, где через запятую перечислены пути;
      • fields (массив) — поля шаблона (только чтение);
      • data (массив) — переменные парсера;
      • settings (массив) — «настройки»;
      • debug (массив) — отладочные данные;
      • language (строка) — текущий язык; сигнатура согласно общепринятым стандартам;
      • loadLanguage (флаг) — автозагрузка языка (по умолчанию false);
      • dictionary (массив) — текущий словарь; можно устанавливать raw-текстом, разделённым на строки;
      • maxLevel (целое число) — максимальный уровень вложенности (по умолчанию 32);
      • level (целое число) — текущий уровень;
      • notice (массив) — уровень уведомлений; понимает значения «strict» — все элементы, «common» (по умолчанию) — чанки и сниппеты.
      • template (текст) — шаблон; при установке указывать название, возвращает содержимое;
      • templateName (строка) — название текущего шаблона (только чтение);
      • autoTemplate (флаг) — флаг автоматического извлечения и установки шаблона из устанавливаемого содержимого; грубо говоря, если со включённым флагом скормить парсеру контент с метаполем template, парсер автоматически установит шаблон (по умолчанию false);
      • content (текст) — содержимое; вообще по сути переменная под названием «content»;
      • resources (массив) — массив ресурсов;
      • idx (массив) — индекс ветвей дерева ресурса (только чтение);
      • SEOStrict (флаг) — соответствие стандартам SEOStrict (по умолчанию false);
      • MODXRevoMode (флаг) — переключение в режим синтаксиса MODX Revolution (по умолчанию false);


      Методы

      :
      • registerTag(QuadBracesTagPrototype $o) — регистрация тега; на вход принимает инициализированный тег; возвращает зарегистрированный тег;
      • parseStart($m) — начало обработки; на вход принимает данные от функции preg_match_callback; возвращает ключ элемента;
      • parseFinish($m,$t,$k,$v) — конец обработки; на вход принимает данные от регулярки, тип тега, ключ тега, текущее значение обработки; возвращает обработанный результат;
      • setting($key,$value=null) — чтение/запись настройки; при значении null производится чтение;
      • variable($key,$value=null) — чтение/запись переменной; при значении null производится чтение;
      • search($type,$name) — поиск файла элемента; учитывает локализацию; возвращает имя найденного файла;
      • setTemplate($v) — установка шаблона по имени; очень нужный метод при работе с callback'ами установки шаблона;
      • getChunk($key,$type='chunk') — получение содержимого чанка; возвращает содержимое найденного чанка или false;
      • execute($name,$args=array(),$input='',$cached=false) — выполнение сниппета или расширения; на вход принимает имя сниппета, аргументы, входной текст и флаг кеширования; возвращает результат обработки;
      • parse($t='',$d=null,$e='',$k='') — основной метод парсера — обработка; для обработки всего шаблона аргументы устанавливать не нужно; аргументы нужны для внутренних нужд или дискретной обработки (кусочек кода QuadBraces); входные аргументы — входной код, данные переменных, элемент, ключ элемента; на выходе — обработанные данные;
      • sanitize($t='') — санитизация входного кода от QuadBraces;
      • extensions($v,$e) — выполнение расширений; на входе — текущий код и строка с расширениями;
      • registerEvent($n,$f) — регистрация события; на входе — имя события и функция;
      • registerMethod($n,$f) — регистрация метода API; на входе — имя метода и функция;
      • invoke() — вызов события; первый аргумент — имя события, остальное — аргументы вызова;


      Где что искать?


      Одно из важнейших свойств парсера QuadBraces является свойство paths. Оно определяет, где парсер будет искать чанки, сниппеты, расширения, шаблоны. Языковая система работает немного обособлено. В процессе поиска элемента парсер по очереди перебирает все зарегистрированные пути. Каждый раз поисковая функция добавляет к выбранному пути папку в зависимости от вида элемента. Чанки — chunks, шаблоны — templates, сниппеты — snippets. И уже там ищется искомый элемент. Напомню, что точка в ключах чанков, сниппетов и шаблонов по факту заменяет разделитель директорий.

      Пример:
      $parser->paths = 'D:/projects/mysite/content,D:/repo/templates/default';
      $fn = $parser->search('snippet','basis.snipcon');

      По факту парсер в данном случае будет проверять существование следующих файлов:

      D:/projects/mysite/content/snippets/basis/snipcon.php
      D:/repo/templates/default/snippets/basis/snipcon.php


      Будет возвращён последний найденный файл. При включённой языковой системе фактически в поиск добавляются пути с языковыми сигнатурами. Допустим, установленный язык — Русский, то есть сигнатура — «ru». Таким образом список файлов следующий:

      D:/projects/mysite/content/snippets/basis/snipcon.php
      D:/projects/mysite/content/snippets/basis/ru/snipcon.php
      D:/repo/templates/default/snippets/basis/snipcon.php
      D:/repo/templates/default/snippets/basis/ru/snipcon.php


      Всё прочее получается через свойства и методы.

      Языковая система


      Во-первых, сразу оговорюсь, что языковую систему нужно включать явно. Это делается подключением класса QuadBracesLang. Делается это простейшим кодом до инклуда основного класса:
      define("QUADBRACES_LOCALIZED",true);

      Лучше всего включить флаг loadLanguage. Тогда словари будут подгружаться автоматически. В принципе по умолчанию при установке путей парсера устанавливаются пути языковой системы. По сути языковая система ищет по тому же принципу свои файлы, что и поисковая система элементов. Только папка языковой системы — lang.

      Пример:
      $parser->paths = 'D:/projects/mysite/content,D:/repo/templates/default';
      $parser->language = 'ru';

      Будут сканироваться папки:

      D:/projects/mysite/content/lang/ru/
      D:/repo/templates/default/lang/ru/


      В оных будут искаться все файлы с расширением «lng». Каждый файл считывается построчно. На каждой строке располагаются четыре элемента — языковой ключ, заголовок, описание, плейсхолдер. Языковой ключ — то, что потом используется в языковом теге. Заголовок (caption) — замена языкового тега по умолчанию. Описание (description) — обычно используется для подсказок или описаний полей. Плейсхолдер (placeholder) — чаще всего используется для соответствующего атрибута элементов ввода.

      События


      Объект парсера поддерживает события. Обработчики, как я писал выше, устанавливаются методом registerEvent.

      Поддерживаемые события (везде $p — объект парсера):


      • init($p) — Завершение конструктора;
      • beforeChangeData($v,$p) — До изменения данных переменных; $v — устанавливаемые переменные;
      • changeData($v,$p) — Изменение данных переменных; $v — устанавливаемые переменные;
      • beforeChangeSettings($v,$p) — До изменения данных настроек; $v — устанавливаемые настройки;
      • changeSettings($v,$p) — Изменение данных настроек; $v — устанавливаемые настройки;
      • beforeLoadDictionary($v,$p) — До загрузки словаря; $v — словарь;
      • loadDictionary($v,$p) — Загрузка словаря; $v — словарь;
      • methodNotFound($n,$p) — Метод не найден; $n — Название метода;
      • beforeSetLanguage($v,$p) — До установки языка; $v — язык;
      • setLanguage($v,$p) — При установке языка; $v — язык;
      • setContent($v,$p) — При установке контента; $v — контент;
      • setResources($v,$p) — При установке ресурсов; $v — данные ресурсов;
      • invalidHandler($n,$a,$p) — При отсутствии обработчика событий; $n — название обработчика, $a — переданные аргументы;
      • setResources($v,$p) — При установке ресурсов; $v — данные ресурсов;
      • defaultTemplate($p) — Дефолтный шаблон (попытка установить пустой шаблон);
      • loadTemplate($v,$p) — При установке шаблона; $v — название шаблона;
      • templateMotFound($v,$p) — При отсутствии шаблона; $v — название шаблона;
      • templateFields($v,$p) — Получение полей шаблонов; $v — поля шаблона;
      • templateData($v,$p) — Получение данных шаблона; $v — данные шаблона;
      • beforeLocalParse($v,$d,$p) — До локальной обработки; $v — обрабатываемый код, $d — данные;
      • beforeParse($v,$p) — До обработки; $v — обрабатываемый код;
      • localParse($v,$p) — После обработки локального шаблона (до санитизации); $v — обработанный код;
      • parse($v,$p) — После обработки шаблона (до санитизации); $v — обработанный код;


      Каюсь, по незнанию напоролся на одну особенность PHP. Если, допустим, назначить функцию на обработчик события templateNotFound, а в ней попытаться установить свойство template, ничего не произойдёт. Это обусловлено ограничениями рекурсии при вызове аксессоров для свойств объекта PHP. Если кратко, то нельзя устанавливать свойство через аксессор и аксессора этого же свойства. Никак. От слова «совсем». Так что если захотите сделать 404-ю через обработчик события templateNotFound, вызывайте внутре него неонку метод setTemplate.

      Расширения-обработчики


      Все теги поддерживают обработчики конечного значения. Например, получили мы переменную, а она пустая. Обработчик notempty позволяет задать выводимый текст на этот случай. У некоторых расширений есть дополнительные «расширения». Например, у базовой логики есть расширения «then» и «else».

      Поддерживаемые обработчики


      • is, eq — равенство (сравниваемое значение, then, else)
      • isnot, neq — неравенство (сравниваемое значение, then, else)
      • lt — меньше, чем (сравниваемое значение, then, else)
      • lte — меньше, чем, или равно (сравниваемое значение, then, else)
      • gt — больше, чем (сравниваемое значение, then, else)
      • gte — больше, чем или равно (сравниваемое значение, then, else)
      • even — признак чётности (сравниваемое значение, then, else)
      • odd — признак нечётности (сравниваемое значение, then, else)
      • empty — признак пустого значения (значение «тогда», else)
      • notempty — признак непустого значения (значение «тогда», else)
      • null, isnull — является ли значение NULL (значение «тогда», else)
      • notnull — является ли значение не NULL (значение «тогда», else)
      • isarray — является ли значение массивом (значение «тогда», else)
      • for — целочисленный итератор (количество итераций, start, splitter)
      • foreach — индексный итератор (список индексов через запятую, splitter)
      • js-link — превращает значение в ссылку на скрипт
      • css-link — превращает значение в ссылку на таблицу стилей
      • import — превращает значение в ссылку на таблицу стилей (@import для CSS)
      • link — превращает значение в ссылку (заголовок)
      • link-external — превращает значение во внешнюю ссылку (заголовок)
      • links — преобразует все URL-ы в значении в ссылки (атрибуты ссылок)
      • ul, ol — превращает многострочное значение в список (шаблон элемента списка)

      Расширение ul/ol имеет два внутренних плейсхолдера: [+classes+] — классы элемента (first, last), [+item+] — собственно строка. Для расширения for доступен внутренний плейсхолдер [+iterator+], содержащий номер текущей итерации. Для расширения foreach доступны внутренние плейсхолдеры: [+iterator.index+] — номер позиции текущей итерации, [+iterator+] — текущий индекс.

      Практикум


      Собственно, работать с классом парсера очень легко. Базовый пример:

      В нём мы подключаем класс парсера, инициализируем объект с передачей пути к шаблонным данным, устанавливаем шаблон и парсим. Само собой до обработки можно передать в парсер «настройки», переменные. А до включения класса парсера включить языковую систему, как было написано выше. Более практический пример:

      Шаблон будет искаться в следующих файлах:

      D:/projects/foo/content/template/templates/news/single.html
      D:/projects/foo/content/template/templates/news/ru/single.html


      Итог-дисклеймер


      Сразу прошу прощения за своё неумение писать туториалы и документацию! Если сообществу будет по-настоящему интересно, наиболее интересные моменты будут раскрываться в последующих публикациях. Задавайте вопросы — с радостью отвечу. Если заметите ошибку — пишите issues на гитхабе сюда — github.com/XanderBass/quadbraces.
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/338018/


      Метки:  

      [Из песочницы] Бэкап файлов Windows-сервера своими руками

      Понедельник, 18 Сентября 2017 г. 15:48 + в цитатник
      urfinejuse сегодня в 15:48 Администрирование

      Бэкап файлов Windows-сервера своими руками

      Здесь мы рассмотрим, как сделать систему дифференциального бэкапа «из коробки» (ну почти), с привлечением минимального количества внешних модулей, в лучших традициях UNIX-way.
      Будем использовать 7za.exe (a = alone (один)) — автономная версия 7-Zip, не использует внешние модули, а также UNIX-like утилиту pdate.exe, чтобы со временем нам было работать также удобно, как и в ламповом *NIX, а заменой bash нам будет «простонародный» BAT. Предыстория и подробности — под катом.

      Предыстория


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

      Взглянув на сие зашифрованное непотребство, я с благодарностью вспомнил про то, что каждый день у меня делается бэкап этой (и не только этой) шары встроенными средствами Windows Server 2003 SP2 x64. Но, полистав этот бэкап, я понял, что в плане резервного копирования средствами самой Windows не все так радужно. Во-первых, полный бэкап оказался недоступен, а значит восстановить cold-data (файлы, которые меняются очень редко) вряд ли получится. Во-вторых, восстановление из созданного инкрементального бэкапа оказалось задачей нетривиальной — за каждый шаг получалось восстановить только данные, которые были изменены, и ничего более. Получается, чтобы восстановить хотя бы все измененные данные (раз полный бэкап оказался утерян), то пришлось бы перебирать по очереди все бэкапы — не совсем то, что я ожидал от инкрементального бэкапа в таком случае.

      Кто то из вас может сказать — надо было проверять работоспособность бэкапа, и да, так оно и есть. Но тот из вас, кто работает в торговле, может понять, куда может уходить время админа — да-да, они самые, онлайн-кассы.

      Крепко задумавшись, я вспомнил свое первое знакомство с системой инкрементального копирования fsbackup за авторством Максима Чиркова www.opennet.ru/dev/fsbackup — гибкость, простота, в то же время обилие возможностей и открытый формат хранения архивов (tar). Жаль, что система разработана под *NIX / Linux. Google также не ответил на мой вопрос про подобную систему под Windows. Самое полезное, что я нашел — это краткий гайд хабровчанина antip0d и пример скрипта для резервного копирования. Именно материал по последней ссылке я и использовал для своего скрипта.

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


      В первую очередь, скачиваем последнюю стабильную версию. На момент написания это 16.04. Наш бэкап будем хранить в 7z архиве: поддержка многопоточности, шифрованных/многотомных архивов, а скорость извлечения из 7z выше скорости упаковки в 10-20 раз!

      Нас интересуют:
      7za.exe — автономная версия 7-Zip.
      7za.dll — библиотека для работы с архивами 7z
      7zxa.dll — библиотека для распаковки 7z архивов.
      Для 64-битных ОС используем те же файлы из каталога x64.
      К сожалению, ссылка из используемого мной материала на утилиту pdate никуда не ведет, единственная найденная мной версия
      pdate v1.1 build 2007.12.06
      © 2005-2007 Pavel Malakhov 24pm@mail.ru
      Ссылка из встроенного мануала pdate ведет туда же, а именно — в никуда.
      pm4u.opennet.ru/mysoft/pdate.htm
      К счастью, на том же ресурсе есть краткая статья по этой программе, там же ее можно скачать.

      Мной была использована следующая структура каталогов:
      D:\winfsbackup — корневая директория скрипта и связанных файлов
      D:\winfsbackup\7z — библиотеки и исполняемый файл 7za
      D:\winfsbackup\backup — место хранения бэкапов (можно переназначить путем правки переменных, как и любые другие используемые файлы)
      D:\winfsbackup\lists — списки включаемых и исключаемых файлов. О них расскажу чуть позже
      D:\winfsbackup\log — логи
      D:\winfsbackup\pdate
      D:\winfsbackup\tmp — устанавливает рабочий каталог для временного базового архива
      D:\winfsbackup\winfsbackup.bat — сам скрипт.

      Логика работы


      После обработки переменных скрипт смотрит блок :Main, где указывается логика работы бэкапа — в каком случае должен выполниться новый бэкап, а в каком случае — обновить существующий базовый архив. По умолчанию, новый архив создается в начале месяца, а все файлы из директории backup перемещаются в \backup\old, или если базового архива не существует.
      Уже во время написания статьи я понял, что нужно добавить возможность обновления базового архива — упрощенный вариант «полный бэкап раз месяц + дифференциальный бэкапы к нему» целесообразно использовать для файловых обменников размером до ~250 Гб. Для моего файлообменника в 550 Гб с преобладанием мелких файлов скорость бэкапа оказалась неудовлетворительна (почти 55 часов). Справедливости ради стоит сказать, что это не может служить сколь нибудь достоверным замером производительности — в процессе бэкапа выяснилось, что некоторые файлы недоступны (привет chkdsk), а бэкап складывался в раздел удаленного сервера, который тоже был занят операциями дискового ввода-вывода.

      :Main
      REM Здесь описаны условия, в каком случае будет выполняться полный \ дифференциальный бэкап, либо обновление базового бэкапа.
      
      REM Базовое условие - создание полного бэкапа если он не существует
      IF NOT EXIST %baseArch% GOTO BaseArchive
      
      REM полный бэкап раз месяц + дифференциальныt бэкапы к нему
      IF %dm% EQU 1 GOTO BaseArchive ELSE GOTO UpdateArchive
      
      REM обновляем базовый архив в 1 день месяца
      REM IF %dm% EQU 1 GOTO UpdateBase ELSE GOTO UpdateArchive
      
      REM Ежеквартальный полный бэкап (2, 19, 36 неделя года)
      REM IF NOT %wn%.%dw% EQU 02.5 GOTO UpdateArchive
      REM IF NOT %wn%.%dw% EQU 19.5 GOTO UpdateArchive
      REM IF NOT %wn%.%dw% EQU 36.5 GOTO UpdateArchive
      
      REM Обновляем базовый архив, каждую субботу
      REM IF %dw% EQU 6 (GOTO UpdateBase) ELSE (GOTO UpdateArchive)
      
      REM А здесь можно разместить действие, которое выполнится если предыдущие условия не отработают.
      REM Я стараюсь избегать подобного поведения
      ECHO Warning! No one condition matching, check :Main block of script >> %Log%
      GOTO End
      

      Первые 2 условия включены по-умолчанию, и описывают полный бэкап раз месяц + дифференциальные бэкапы. Остальные условия альтернативны, то есть при включении одного нужно выключить другое, или изменить логику работы на свой вкус.

      Переменные


      dm, dw, wn — соответственно день месяца, день недели и номер недели (в численном выражении).
      verboseLevel — режим «говорливости», выдает информацию о том, куда будет записываться архив, и прочее. Полезно, когда вносишь в структуру скрипта серьезные изменения.
      tmpDir — место сохранения временного файла. По умолчанию, 7-Zip строит новый базовый файл архива в том же самом каталоге, где и старый базовый файл архива. Определяя этот ключ, вы можете установить рабочий каталог, где будет построен временный базовый файл архива. После того, как временный базовый файл архива построен, он копируется поверх первоначального; затем временный файл удаляется.

      Дифференциальный бэкап


      По умолчанию, в дифференциальный бэкап помещаются файлы, которых нет в базовом архиве, а также более новые файлы. При желании, это поведение можно изменить. Описания ключей есть в стандартном файле справки 7z.

      У такого подхода есть свои плюсы (которые я уже успел оценить) — быстрота разворачивания из бэкапа. В случае полного восстановления разворачиваем последний (или любой другой) имеющийся дифференциальный бэкап, а затем — полный архив, отказываясь от перезаписи имеющихся файлов.

      Некоторые опции командной строки


      -bsp2 — выводит строку с прогрессом выполнения в STDERR. STDOUT 7z перенаправлен в лог, прогресс, естественно, туда не пишется. Эта команда выводит его в STDERR, для большей информативности.

      -ssw — упаковывает файлы, открытые для записи другим приложением. Если этот ключ не установлен, 7-Zip не включает такие файлы в архив.

      -slp — крайне полезная опция. Режим больших страниц увеличивает скорость сжатия. Однако, есть пауза в начале сжатия, в то время когда 7-Zip распределяет большие страницы в памяти. Если 7-Zip не может разместить большие страницы, он размещает обычные маленькие страницы. Кроме того, Диспетчер задач не показывает реальное использование памяти программами, если 7-Zip использует большие страницы. Эта особенность работает только на Windows 2003 / XP x64. Также нужно иметь права администратора для вашей системы. Рекомендованный размер оперативной памяти для этой особенности — 3 Гб или больше. если вы используете режим -slp, ваша система Windows может зависнуть на несколько секунд, когда 7-Zip выделяет блоки памяти. Когда Windows пытается выделить большие страницы из оперативной памяти для 7-Zip, Windows может подвесить другие задачи на это время. Это может выглядеть как полное зависание системы, но затем ее работа восстанавливается, и, если распределение прошло успешно, 7-Zip работает быстрее. Не используйте режим -slp, если вы не хотите, чтобы другие задачи быть «подвешены». Кроме того, бессмысленно использовать режим -slp для сжатия небольших наборов данных (менее 100 МБ). Но если вы сжимаете большие наборы данных (300 Мб или более) методом LZMA с большим словарем, вы можете получить увеличение скорости на 5% -10% в режиме -slp.
      -mmt=on — устанавливает режим многопоточности. Если у вас многопроцессорная / многоядерная система, вы можете получить увеличение скорости с этим ключом. 7-Zip поддерживает режим многопоточности только для сжатия LZMA/LZMA2 и сжатия/распаковки BZip2.

      -ms=off — отключает создание solid-архивов. Качество сжатия при этом, конечно же, падает, однако есть весьма весомые плюсы — вы можете периодически обновлять данные базового архива чтобы уменьшить размер дифференциальный бэкапов, и так как архив не является целостным, не нужно будет его дополнительно «пережимать». Non-solid архив более стоек к повреждениям, и время извлечения из него происходит заметно быстрее.

      Include / exclude листы
      По умолчанию определено 2 типа списка — список включаемых файлов / директорий (include_general.txt), и 2 списка исключений (exclude_general.txt, exclude_regexp.txt).

      Список включения также поддерживает UNC-пути. Для того, чтобы поместить файл / директорию в исключения, путь должен быть относительным.

      Например, если директория для бэкапа E:\foo\bar, и мы хотим исключить вложенную директорию E:\foo\bar\somefolder, то в exclude_general.txt мы должны добавить bar\somefolder или bar\somefolder\

      Путь без слэша в конце может относиться как к файлу, так и к директории.
      В exclude_regexp.txt вносятся исключаемые по regexp файлы, которые просматриваются рекурсивно. * — последовательность произвольных символов,? — любой символ.
      7-Zip не использует системный синтаксический анализатор подстановочных знаков, поэтому «любой файл» для 7 Zip это '*', а '*.*' — файл, имеющий расширение.

      Ну и наконец, скрипт целиком:

      @ ECHO OFF
      REM Sources were found on http://sysadminwiki.ru/wiki/Резервное_копирование_в_Windows
      CD %~dp0
      TITLE winfsbackup
      MODE CON: COLS=120 LINES=55
      ECHO Setting vars...
      REM --- Definition block ---
      SET verboseLevel=1
      SET tmpDir=D:\winfsbackup\tmp
      SET run_7z=D:\winfsbackup\7z\7za.exe
      SET run_pdate=D:\winfsbackup\pdate\pdate.exe
      FOR /F "usebackq" %%a IN (`%run_pdate% e`) DO (SET dm=%%a)
      FOR /F "usebackq" %%a IN (`%run_pdate% u`) DO (SET dw=%%a)
      FOR /F "usebackq" %%a IN (`%run_pdate% V`) DO (SET wn=%%a)
      SET LogDir=D:\winfsbackup\log
      SET Log=%LogDir%\general.log
      SET dDir=D:\winfsbackup\backup
      SET dlmDir=D:\winfsbackup\backup\old
      SET baseArch=%dDir%\general.7z
      SET IncludeList=lists\include_general.txt
      SET ExcludeList=lists\exclude_general.txt
      SET ExcludeRegexp=lists\exclude_regexp.txt
      SET updArch_dw=%dDir%\day_general_%dw%.7z
      SET updArch_wn=%dDir%\week_general_%wn%.7z
      
      IF %verboseLevel%==0 GOTO Main
      ECHO Verbose mode ON!
      ECHO Today is %wn% week of year, %dw% day of week.
      ECHO Full quarter backup will execute (if enabled) on 2, 19 and 36 week, friday.
      ECHO Temporary directory is %tmpDir%
      ECHO Now logging into %Log%
      ECHO Current backup directory is %dDir%, older backups stored into %dlmDir%
      
      :Main
      REM Here discribed conditions - in which case script will make new backup, update older one, etc
      REM You are free to change these conditions
      REM Make sure you envisaged all possible cases
      REM Actions here are not disigned to be active more than 1 at same time, excluding base condition
      REM If you want multiple conditions, you should edit it
      
      REM Base condition - full backup will be created if it is not exist
      IF NOT EXIST %baseArch% GOTO BaseArchive
      
      REM Command below turns on making full backup at 1'st day of every month, in other days - increments
      REM IF %dm% EQU 1 GOTO BaseArchive ELSE GOTO UpdateArchive
      
      REM This option enables updating full backup every month
      IF %dm% EQU 1 GOTO UpdateBase ELSE GOTO UpdateArchive
      
      REM Uncomment these 3 commands if you want to run full backup ~every quarter (2, 19, 36 week of year)
      REM IF NOT %wn%.%dw% EQU 02.5 GOTO UpdateArchive
      REM IF NOT %wn%.%dw% EQU 19.5 GOTO UpdateArchive
      REM IF NOT %wn%.%dw% EQU 36.5 GOTO UpdateArchive
      
      REM This option enables rewriting base archive every saturday with new files in order to decrease size of increments
      REM IF %dw% EQU 6 (GOTO UpdateBase) ELSE (GOTO UpdateArchive)
      
      REM Here you can place default action if conditions of previous ones were not executed.
      %run_pdate% "Z --- \A\c\t\i\o\n \w\a\s\ \n\o\t \s\e\l\e\c\t\e\d\! >> %Log%
      ECHO Warning! No one condition matching, check :Main block of script
      GOTO End
      
      :BaseArchive
      ECHO Clear %dlmDir% and move data of previous month to that dir...
      IF NOT EXIST %dlmDir%\nul MKDIR %dlmDir%
      DEL /Q %dlmDir%\*
      MOVE /Y %dDir%\* %dlmDir% 2> nul
      
      %run_pdate% "====== Y B =======" > %Log%
      %run_pdate% "Z --- \S\t\a\r\t \t\o \c\r\e\a\t\e \n\e\w \a\r\c\h\i\v\e" >> %Log%
      
      ECHO Creating new backup %baseArch%
      %run_7z% a %baseArch% -w%tmpDir% -i@%IncludeList% -x@%ExcludeList% -xr@%ExcludeRegexp% -bsp2 -ssw -slp -scsWIN -mmt=on -mx3 -ms=off >> %Log%
      IF %ERRORLEVEL%==0 (
      	%run_pdate% "Z --- \E\x\i\t \c\o\d\e \0 \- \a\r\c\h\i\v\e \s\u\c\c\e\s\s\f\u\l\l\y \c\r\e\a\t\e\d!" >> %Log%
      	) ELSE (
      		IF %ERRORLEVEL%==1 (
      		%run_pdate% "Z --- \W\a\r\n\i\n\g\! \R\e\c\i\e\v\e\d\ \e\x\i\t \c\o\d\e \1" >> %Log%
      			) ELSE (
      				IF %ERRORLEVEL%==2 (
      				%run_pdate% "Z --- \E\x\i\t \c\o\d\e \2 \- \F\A\T\A\L \E\R\R\O\R\!" >> %Log%
      				) ELSE (
      					IF %ERRORLEVEL%==7 (
      					%run_pdate% "Z --- \E\x\i\t \c\o\d\e \7 \- \C\o\m\m\a\n\d \p\r\o\m\p\t \e\r\r\o\r!" >> %Log%
      					) ELSE (
      						IF %ERRORLEVEL%==8 (
      						%run_pdate% "Z --- \E\x\i\t \c\o\d\e \8 \- \N\o\t \e\n\o\u\g\h \m\e\m\o\r\y" >> %Log%
      						) ELSE (
      							ECHO Recieved error 255 - user stopped running process or exit code unknown! >> %Log%
      						)
      					)
      				)
      			)
      		)
      	)
      )
      GOTO End
      
      :UpdateBase
      ECHO Refreshing base archive
      ECHO ******* ******* *******  >> %Log%
      %run_pdate% "Z --- \S\t\a\r\t \t\o \u\p\d\a\t\e \a\r\c\h\i\v\e" >> %Log%
      %run_7z% u %baseArch% -up0q1r2x1y2z1w0 -w%tmpDir% -i@%IncludeList% -x@%ExcludeList% -xr@%ExcludeRegexp% -bsp2 -ssw -slp -scsWIN -mmt=on -mx5 -ms=off >> %Log%
      IF %ERRORLEVEL%==0 (
      	%run_pdate% "Z --- \E\x\i\t \c\o\d\e \0 \- \u\p\d\a\t\e \s\u\c\c\e\s\s\f\u\l\l\y \f\i\n\i\s\h\e\d" >> %Log%
      	) ELSE (
      		IF %ERRORLEVEL%==1 (
      		%run_pdate% "Z --- \W\a\r\n\i\n\g\! \R\e\c\i\e\v\e\d\ \e\x\i\t \c\o\d\e \1" >> %Log%
      			) ELSE (
      				IF %ERRORLEVEL%==2 (
      				%run_pdate% "Z --- \E\x\i\t \c\o\d\e \2 \- \F\A\T\A\L \E\R\R\O\R\!" >> %Log%
      				) ELSE (
      					IF %ERRORLEVEL%==7 (
      					%run_pdate% "Z --- \E\x\i\t \c\o\d\e \7 \- \C\o\m\m\a\n\d \p\r\o\m\p\t \e\r\r\o\r!" >> %Log%
      					) ELSE (
      						IF %ERRORLEVEL%==8 (
      						%run_pdate% "Z --- \E\x\i\t \c\o\d\e \8 \- \N\o\t \e\n\o\u\g\h \m\e\m\o\r\y" >> %Log%
      						) ELSE (
      							ECHO Recieved error 255 - user stopped running process or exit code unknown! >> %Log%
      						)
      					)
      				)
      			)
      		)
      	)
      )
      GOTO End
      
      :UpdateArchive
      ECHO Updtaing existing full backup
      ECHO ******* ******* *******  >> %Log%
      %run_pdate% "Z --- \S\t\a\r\t \t\o \u\p\d\a\t\e \a\r\c\h\i\v\e" >> %Log%
      IF %dw%==7 (SET updArch=%updArch_wn%) ELSE SET updArch=%updArch_dw%
      
      REM --- Check files existence ---
      IF EXIST %updArch% DEL /Q %updArch%
      
      REM --- Create incremental archive ---
      p strTemp=Updating %baseArch% to incremental %updArch% archive
      ECHO.
      %run_7z% u %baseArch% -u- -up0q0r2x0y2z0w0!%updArch% -w%tmpDir% -i@%IncludeList% -x@%ExcludeList% -xr@%ExcludeRegexp% -bsp2 -ssw -slp -scsWIN -mmt=on -mx5 -ms=off >> %Log%
      IF %ERRORLEVEL%==0 (
      	%run_pdate% "Z --- \E\x\i\t \c\o\d\e \0 \- \u\p\d\a\t\e \s\u\c\c\e\s\s\f\u\l\l\y \f\i\n\i\s\h\e\d" >> %Log%
      	) ELSE (
      		IF %ERRORLEVEL%==1 (
      		%run_pdate% "Z --- \W\a\r\n\i\n\g\! \R\e\c\i\e\v\e\d\ \e\x\i\t \c\o\d\e \1" >> %Log%
      			) ELSE (
      				IF %ERRORLEVEL%==2 (
      				%run_pdate% "Z --- \E\x\i\t \c\o\d\e \2 \- \F\A\T\A\L \E\R\R\O\R\!" >> %Log%
      				) ELSE (
      					IF %ERRORLEVEL%==7 (
      					%run_pdate% "Z --- \E\x\i\t \c\o\d\e \7 \- \C\o\m\m\a\n\d \p\r\o\m\p\t \e\r\r\o\r!" >> %Log%
      					) ELSE (
      						IF %ERRORLEVEL%==8 (
      						%run_pdate% "Z --- \E\x\i\t \c\o\d\e \8 \- \N\o\t \e\n\o\u\g\h \m\e\m\o\r\y" >> %Log%
      						) ELSE (
      							ECHO Recieved error 255 - user stopped running process or exit code unknown! >> %Log%
      						)
      					)
      				)
      			)
      		)
      	)
      )
      
      :End
      ECHO Done!
      %run_pdate% "Z --- \D\o\n\e" >> %Log%
      ping localhost -w 1000 -n 5 > nul
      
      

      Вместо окончания


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

      Я же считаю, что решение должно быть соразмерно поставленной задаче, а в моем случае задача — иметь в укромном месте резервную копию файлопомойки, которую можно быстро развернуть — именно этим меня и разочаровал ntbackup.

      Собранный пример можно посмотреть на YandexDisk.

      Там же — zip-архив для скачивания.
      Конструктивная критика, советы, и тем более, тестирование — welcome!
      Спасибо за внимание! Всем долгого аптайма, стабильного линка, и конечно, бэкапов под рукой.
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/338168/


      Метки:  

      Первый в России OpenHack от Microsoft (то есть от нас)

      Понедельник, 18 Сентября 2017 г. 15:40 + в цитатник
      Жизнь в движении. Поэтому мы не устаём экспериментировать с новыми форматами проведения мероприятий для достижения более высоких целей (Круто звучит, да?). Сначала мы отказались от проведения обычной конференции DevCon и перешли в формату DevCon School: эксперты индустрии и их реальный опыт в реальных проектах. За время эксперимента мы провели 6 школ и поняли, что этот формат позволяет «пощупать» новые технологии, получить о них общее представление, вернуться в рабочую рутину с обычной нехваткой времени и остановиться.



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

      https://habrahabr.ru/post/338146/


      [Перевод] Применение PowerShell для ИТ-безопасности. Часть III: бюджетная классификация

      Понедельник, 18 Сентября 2017 г. 15:00 + в цитатник
      Alexandra_Varonis сегодня в 15:00 Администрирование

      Применение PowerShell для ИТ-безопасности. Часть III: бюджетная классификация

      • Перевод


      Последний раз с помощью нескольких строк кода PowerShell я запустил совершенно новую категорию программного обеспечения — анализ доступа к файлам. Мои 15 минут славы почти закончились, но я смог отметить, что PowerShell предоставляет практические возможности для мониторинга событий доступа к файлам. В этой заметке я завершу работу над инструментом анализа доступа к файлам и перейду к классификации данных PowerShell.

      Аналитика на основе событий

      Хочу напомнить, что в своем скрипте анализа для просмотра событий доступа к файлам в определенной папке я использовал командлет Register-WmiEvent. Также я создал мифическое эталонное значение для интенсивности событий, чтобы сравнивать с ним текущие данные. (Для тех, кто основательно этим интересуется, есть целая область средств измерения таких показателей — посещения веб-сайтов, звонки в центр телефонного обслуживания, поток людей у кофемашин, и первым это начал изучать вот этот парень.)

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

      Этот запуск осуществляется командлетом New-Event, который позволяет отправлять событие вместе с другой информацией получателю. Для чтения данных о событии есть командлет WMI-Event. В роли получающей стороны может быть даже другой скрипт, поскольку оба командлета событий используют одинаковый идентификатор SourceIdentifier — в нашем случае это Bursts.
      Это все основные идеи в отношении операционной системы: фактически, PowerShell обеспечивает простую систему передачи сообщений. И довольно успешно, все-таки мы используем просто подающий сигналы командный язык.

      Как бы то ни было, далее представлен удивительный полный код.

      1.	$cur = Get-Date
      2.	$Global:Count=0
      3.	$Global:baseline = @{"Monday" = @(3,8,5); "Tuesday" = @(4,10,7);"Wednesday" = @(4,4,4);"Thursday" = @(7,12,4); "Friday" = @(5,4,6); "Saturday"=@(2,1,1); "Sunday"= @(2,4,2)}
      4.	$Global:cnts = @(0,0,0)
      5.	$Global:burst = $false
      6.	$Global:evarray = New-Object System.Collections.ArrayList
      7.	
      8.	$action = { 
      9.	$Global:Count++ 
      10.	$d=(Get-Date).DayofWeek
      11.	$i= [math]::floor((Get-Date).Hour/8) 
      12.	
      13.	$Global:cnts[$i]++ 
      14.	
      15.	#event auditing!
      16.	
      17.	$rawtime = $EventArgs.NewEvent.TargetInstance.LastAccessed.Substring(0,12)
      18.	$filename = $EventArgs.NewEvent.TargetInstance.Name
      19.	$etime= [datetime]::ParseExact($rawtime,"yyyyMMddHHmm",$null)
      20.	
      21.	$msg="$($etime)): Access of file $($filename)"
      22.	$msg|Out-File C:\Users\bob\Documents\events.log -Append
      23.	
      24.	
      25.	$Global:evarray.Add(@($filename,$etime))
      26.	if(!$Global:burst) {
      27.	$Global:start=$etime
      28.	$Global:burst=$true 
      29.	}
      30.	else { 
      31.	if($Global:start.AddMinutes(15) -gt $etime ) { 
      32.	$Global:Count++
      33.	#File behavior analytics
      34.	$sfactor=2*[math]::sqrt( $Global:baseline["$($d)"][$i])
      35.	
      36.	if ($Global:Count -gt $Global:baseline["$($d)"][$i] + 2*$sfactor) {
      37.	
      38.	
      39.	"$($etime): Burst of $($Global:Count) accesses"| Out-File C:\Users\bob\Documents\events.log -Append 
      40.	$Global:Count=0
      41.	$Global:burst =$false
      42.	New-Event -SourceIdentifier Bursts -MessageData "We're in Trouble" -EventArguments $Global:evarray
      43.	$Global:evarray= [System.Collections.ArrayList] @();
      44.	}
      45.	}
      46.	else { $Global:burst =$false; $Global:Count=0; $Global:evarray= [System.Collections.ArrayList] @();}
      47.	} 
      48.	} 
      49.	
      50.	Register-WmiEvent -Query "SELECT * FROM __InstanceModificationEvent WITHIN 5 WHERE TargetInstance ISA 'CIM_DataFile' and TargetInstance.Path = '\\Users\\bob\' and targetInstance.Drive = 'C:' and (targetInstance.Extension = 'txt' or targetInstance.Extension = 'doc' or targetInstance.Extension = 'rtf') and targetInstance.LastAccessed > '$($cur)' " -sourceIdentifier "Accessor" -Action $action 
      51.	
      52.	
      53.	#Dashboard
      54.	While ($true) {
      55.	$args=Wait-Event -SourceIdentifier Bursts # wait on Burst event
      56.	Remove-Event -SourceIdentifier Bursts #remove event
      57.	
      58.	$outarray=@() 
      59.	foreach ($result in $args.SourceArgs) {
      60.	$obj = New-Object System.Object
      61.	$obj | Add-Member -type NoteProperty -Name File -Value $result[0]
      62.	$obj | Add-Member -type NoteProperty -Name Time -Value $result[1]
      63.	$outarray += $obj 
      64.	}
      65.	
      66.	
      67.	$outarray|Out-GridView -Title "FAA Dashboard: Burst Data"
      68.	}


      Пожалуйста, не бейте свой ноутбук во время просмотра.

      Я понимаю, что продолжаю показывать отдельные представления таблиц и есть более подходящие способы обработки графики. С помощью PowerShell у вас есть доступ ко всей платформе .NET Framework, поэтому можно создавать объекты — списки, диаграммы и т. д., — получать к ним доступ, а затем обновлять при необходимости. Пока я оставлю это для вас в качестве домашнего задания.

      Классификация играет очень важную роль в обеспечении безопасности данных

      Давайте отложим мониторинг событий файлов и рассмотрим тему классификации данных в PowerShell.

      В компании Varonis мы по объективным причинам используем принцип «осведомленности о ваших данных». Чтобы разработать полезную программу защиты данных, одним из первых этапов должно быть изучение того, где расположены критически важные или конфиденциальные данные — номера кредитных карт, адреса заказчиков, конфиденциальные юридические документы, проприетарный код.

      Целью, конечно, является защита ценных цифровых данных компании, но сначала их следует определить их состав. Кстати, это не просто хорошая идея, множество законов и нормативов по защите данных (например, HIPAA), а также промышленные стандарты (PCI DSS) требуют идентификации активов при реальной оценке рисков.

      У PowerShell большой потенциал для использования в приложениях классификации данных. Может ли PS непосредственно получать доступ к файлам и считывать их? Да. Может ли он выполнять сравнение по образцу для текста? Да. Может ли он делать это эффективно в больших масштабах? Да.

      Нет, скрипт классификации PowerShell, который я получил в конечном итоге, не заменит Varonis Data Classification Framework. Но для сценария, который я подразумевал, — ИТ-администратор, которому требуется следить за папкой с конфиденциальными данными, — мой пробный скрипт PowerShell показывает не просто удовлетворительный результат, его можно оценить, скажем, на 4 с плюсом!

      WQL и CIM_DataFile

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

      1.	$Get-WmiObject -Query "SELECT * From CIM_DataFile where Path = '\\Users\\bob\' and Drive = 'C:' and (Extension = 'txt' or Extension = 'doc' or Extension = 'rtf')"


      Превосходно! Эта строка кода будет выдавать массив с именами путей к файлам.

      Для считывания содержимого каждого из файлов в переменную в PowerShell предоставляется командлет Get-Content. Спасибо Microsoft.

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

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

      В моем скрипте я создал хеш-таблицу PowerShell с возможными маркирующими текстами и соответствующим регулярным выражением для сопоставления. Для документов, которые явным образом не отмечены таким способом, я также добавил специальные имена проектов — в моем случае, snowflake — которые также проверяются. А для отбрасывания я добавил регулярное выражение для номеров социального страхования.

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

      1.	$Action = {
      2.	
      3.	Param (
      4.	
      5.	[string] $Name
      6.	
      7.	)
      8.	
      9.	$classify =@{"Top Secret"=[regex]'[tT]op [sS]ecret'; "Sensitive"=[regex]'([Cc]onfidential)|([sS]nowflake)'; "Numbers"=[regex]'[0-9]{3}-[0-9]{2}-[0-9]{3}' }
      10.	
      11.	
      12.	$data = Get-Content $Name
      13.	
      14.	$cnts= @()
      15.	
      16.	foreach ($key in $classify.Keys) {
      17.	
      18.	$m=$classify[$key].matches($data)
      19.	
      20.	if($m.Count -gt 0) {
      21.	
      22.	$cnts+= @($key,$m.Count)
      23.	}
      24.	}
      25.	
      26.	$cnts
      27.	}


      Великолепная поддержка многопоточности

      Я могу упростить свой проект, взяв представленный выше код, добавив связующие элементы и запустив результат через командлет Out-GridView.

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

      Вы на самом деле хотите ждать, пока скрипт последовательно прочитает каждый файл?
      Конечно, нет!

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

      PowerShell обладает практически пригодной (хотя и неуклюжей) системой фоновой обработки, известной как Jobs. Но есть также поразительная и изящная функция с поддержкой многопоточности — Runspaces.

      После некоторых манипуляций и заимствования кода у нескольких пионеров в использовании Runspaces я был действительно впечатлен.

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

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

      1.	$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, 5)
      2.	
      3.	$RunspacePool.Open()
      4.	
      5.	$Tasks = @()
      6.	
      7.	
      8.	foreach ($item in $list) {
      9.	
      10.	$Task = [powershell]::Create().AddScript($Action).AddArgument($item.Name)
      11.	
      12.	$Task.RunspacePool = $RunspacePool
      13.	
      14.	$status= $Task.BeginInvoke()
      15.	
      16.	$Tasks += @($status,$Task,$item.Name)
      17.	}


      Давайте сделаем глубокий вдох — мы рассмотрели много материала.

      В следующей заметке я представлю полный скрипт и обсужу некоторые (неприятные) подробности. Тем временем, после присвоения некоторым файлам текста отметки, я создал следующий вывод с помощью Out-GridView.



      Классификация содержимого практически даром!

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

      Звучит так, как будто я предлагаю, не побоюсь этого слова, платформу контроля безопасности на основе PowerShell. Мы начнем работу над осуществлением этого в следующий раз.
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/338166/


      [recovery mode] Техподдержка 3CX отвечает: как заменить или обновить SSL сертификат на сервере

      Понедельник, 18 Сентября 2017 г. 14:50 + в цитатник
      snezhko сегодня в 14:50 Администрирование

      Техподдержка 3CX отвечает: как заменить или обновить SSL сертификат на сервере

      В этой статье мы ответим на довольно частый вопрос наших пользователей: как заменить или обновить (продлить) SSL сертификат для веб-сервера NGINX, используемого в системе 3CX.
      Прежде всего, рассмотрим, для чего может потребоваться получение, замена или обновление сертификата:

      • Вы используете собственное имя домена для 3CX (вида pbx.mybusiness.com). 3CX версии 15 и выше требуют обязательного наличия доверенного (подписанного) SSL сертификата.
      • Вы решили перейти с FQDN, выданного 3CX (вида mybusiness.3cx.eu), на собственное доменное имя.
      • Вы обновляете систему с предыдущих версий 3CX и должны использовать доверенный сертификат (в 3CX 15 и выше допустимы только доверенные сертификаты).
      • Вы решили отказаться от подписки на обновления 3CX (Maintenance). Мы не рекомендуем отказываться от обновлений, т.к. это может повлиять на качество функционирования вашего бизнеса (и экономия может привести к гораздо большим потерям). Однако, в этом случае вам необходимо перевести 3CX на собственное доменное имя, т.к. без подписки на обновления прекращается поддержка FQDN и SSL сертификата от 3CX.

      В статье мы не будем рассматривать ситуацию замены FQDN — она описана ранее здесь и предусматривает переустановку 3CX. Мы рассмотрим саму процедуру получения сертификата и установки его на уже работающий сервер. Т.е., предполагается, что FQDN сервера не изменяется, и все, что нужно сделать — обновить сертификат.

      Получение сертификата Let’s Encrypt


      Получить доверенный сертификат Let’s Encrypt можно разными способами, в том числе и автоматически. Однако мы рассмотрим только получение вручную через сервис https://zerossl.com. Конечно, вы можете приобрести платный сертификат у выбранного поставщика, но сертификаты Let’s Encrypt бесплатны и используются многими производителями ПО и оборудования, в том числе, компанией 3CX (генерируются и выдаются вам автоматически при действующей подписке на обновления).

      Зайдите на https://zerossl.com и перейдите в Online Tools — Zero SSL Certificate Wizard.

       
      Укажите FQDN сервера, для которого будет сгенерирован сертификат, выберите DNS verification, примите условия сервиса и нажмите Next. Опционально можно указать свой e-mail. Будет сгенерирован запрос на сертификат. Затем нажмите Next еще раз, и будет сгенерирован секретный ключ. Скачайте файлы CSR и Account Key (они вам пригодятся в будущем).

      Далее необходимо пройти DNS верификацию, т.е. подтвердить, что выбранный FQDN сервера принадлежит именно вам. Для этого на вашем DNS сервере (часто это делается у хостинг-провайдера) добавьте указанную TXT запись и подождите некоторое время, чтобы прошло обновление DNS кэша.



      Ваш сертификат готов. Скачайте его и секретный ключ на компьютер и переименуйте их по такой схеме:

      • domain-key.txt > pbx.mybusiness.com-key.pem
      • domain-crt.txt > pbx.mybusiness.com-crt.pem, где pbx.mybusiness.com — FQDN имя сервера 3CX, указанное в мастере генерации сертификата.



      Имея файлы сертификатов, приступим к их установке.

      Установка сертификата на сервер 3CX  


      Если вы устанавливаете новую систему, то на соответствующем этапе Мастера первоначальной настройки просто укажите сертификат и ключ.



      Если сервер уже установлен и работает, зайдите на него и перейдите в папку:

      • Windows: C:\Program Files\3CX Phone System\Bin\nginx\conf\instance1

        Linux: /var/lib/3cxpbx/Bin/nginx/conf/Instance1

      Вы должны видеть 3 файла, как показано на скриншоте ниже.



      Внимание: если вы видите 5 файлов, значит используются FQDN и сертификат от 3CX. В этом случае ничего менять не нужно!

      Перезапишите имеющиеся файлы вашими файлами. После этого перезапустите сервис NGINX. В системе Windows он называется 3CXPhoneSystem Nginx Server.
      Теперь, зайдя в интерфейс 3CX, вы можете посмотреть параметры нового сертификата.



      Как видим, сертификат действителен в течении 3 месяцев. Имейте это ввиду, чтобы не забыть вовремя его продлить!
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/338160/


      [Из песочницы] Очередное решение HighLoadCup на Go

      Понедельник, 18 Сентября 2017 г. 14:43 + в цитатник
      WebProd сегодня в 14:43 Разработка

      Очередное решение HighLoadCup на Go

      Я думаю уже многие пользователи хабра знают что на прошлой недели закончился HighLoadCup от Mail.ru (из-за обилия количества статей от участников). Я хотел бы также поделиться своим решением с сообществом.

      Описание задачи


      Существует три вида сущностей: User, Location, Visit. Необходимо написать REST-API для доступа к ним, т.е. получается необходимо обработка 6 запросов.

      • {GET, POST} /user/:id — получение или изменение пользователя
      • {GET, POST} /location/:id — получение или изменение локации
      • {GET, POST} /visit/:id — получение или изменение посещения локации пользователем
      • POST /user/new — добавление нового пользователя
      • POST /location/new — добавление новой локации
      • POST /visit/new — добавление нового посещения локации пользователем

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

      Начало


      Изначально я начал писать на С++, но до релиза так и не дошло. Увидев большое количество решений в топе на Go я решил тоже попробовать его. Этот язык мне показался значительно более подходящим для разработки серверных приложения, он имеет из коробки весь необходимый функционал причём в очень качественном исполнении. Впрочем, уже после первых тестов стало понятно, что ни net/http, ни encoding/json не подходят для данного конкурса в связи с большим количеством мусора, который генерируется внутри них.

      Раунд первый


      Изначально данных было всего 200 мб в распакованном виде, поэтому я подумал что возможно даже хранить готовые JSON-строки для каждой сущность. В качестве http-сервера по советам гошников конкурса (спасибо им большое за оказанную помощь на старте знакомства с языком) я выбрал fasthttp, для парсинга JSON buger/jsonparser (позволяет парсинг без аллокаций и работать только с нужной информацией, игнорируя остальную), а генерацию производил руками, по-скольку никакой обработки русскоязычных строк не требовалось.

      type User struct {
      	id  uint
      	email string
      	first_name  string
      	last_name string
      	gender bool
      	birth_date int64
      
      	age   int
      	visits Visits
      	json []byte
      }
      
      type Location struct {
      	id uint
      	place string
      	country string
      	city string
      	distance int64
      
      	visits Visits
      	json []byte
      }
      
      type Visit struct {
      	id uint
      	location *Location
      	user *User
      	visited_at int64
      	mark int64
      
      	json []byte
      }
      

      Такие сущности у меня вышли на старте, в посещении для ускорения я решил хранить сразу указатели на соответствующих пользователя и локацию. Итог получился весьма удовлетворительным, все данные прекрасно помещались и в итоге я даже смог попасть в 10-ку (не на долго правда).

      Возраст


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

      Итогом стал такой код:

      func countAge(timestamp *int64) int {
      	now := time.Now()
      	t := time.Unix(*timestamp, 0)
      
      	years := now.Year() - t.Year()
      	if now.Month() > t.Month() || now.Month() == t.Month() && now.Day() >= t.Day() {
      		years += 1
      	}
      
      	return years
      }
      

      Увеличение нагрузки


      За несколько дней до финала после демократического и открытого голосования создатели конкурса в 10 раз увеличили количество данных и в 2 раза максимальный RPS. После этого моё решение перестало влезать в память и потребовало изменений. Пришлось убрать из структур заранее сгенерированный JSON и создавать его на лету при запросе, что увеличило, конечно же, реализм. В тоже время я подумал, а почему бы не добавить в структуры посетителя и локации сразу ссылки на посещения, которые с ними связаны. Это значительно увеличило скорость программы, так как не пришлось проходить каждый раз весь массив посещений (который теперь содержал 10 миллионов записей).

      Решение после этого заработало, но по скорости стало уступать другим решениям и я рисковал не пройти в финал. Не долго думая я выкинул fasthttp и перешёл на tcp-сокеты и epoll. Размер окна в нашей системе был в районе 65кб и пакеты точно приходили и отправлялись полностью и это дало большое поле для костылей, которые в продакшене точно работать не будут.

      Финал


      К финалу я подошёл на 39 месте, чему я был несомненно очень рад. Это первое моё участие в подобном конкурсе, первое знакомство с Go и highload (хотя я бы не назвал это highload). Финал начался плохо, сервер вылетел из-за ошибки одновременного доступа на чтение и запись (до финала таких проблем не возникало и локеры были вырезаны), тем не менее на одной из волн удалось показать лучший результат за все запуски что дало возможность занять 28 место.

      В целом это был очень интересный и познавательный (для меня, по крайней мере) конкурс. Я хотел бы выразить огромную благодарность организаторам и жду следующего учтя все ошибки и «фичи» нынешнего (busy-polling, например). Правда в будущем обещан был больший упор именно на логику, а не на сетевой стек, что будет более интересным.

      P.S. Жду футболочку :)

      Код (вдруг кому-то станет интересно) можно посмотреть в моём репозитории.
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/338156/


      Метки:  

      Kaggle: как наши сеточки считали морских львов на Алеутских островах

      Понедельник, 18 Сентября 2017 г. 14:19 + в цитатник
      temakone сегодня в 14:19 Разработка

      Kaggle: как наши сеточки считали морских львов на Алеутских островах

        header_im


        Привет, Коллеги!
        27 июня закончилось соревнование на Kaggle по подсчёту морских львов (сивучей) на аэрофотоснимках NOAA Fisheries Steller Sea Lions Population Count. В нем состязались 385 команд. Хочу поделиться с вами историей нашего участия в челлендже и (почти) победой в нём.


        Небольшое лирическое отступление.


        Как многие уже знают, Kaggle – это платформа для проведения онлайн соревнований по Data Science. И в последнее время там стало появляться всё больше и больше задач из области компьютерного зрения. Для меня этот тип задач наиболее увлекателен. И соревнование Steller Sea Lions Population Count — одно из них. Я буду повествовать с расчётом на читателя, который знает основы глубокого обучения применительно к картинкам, поэтому многие вещи я не буду детально объяснять.


        Пару слов о себе. Я учусь в аспирантуре в университете города Хайдельберг в Германии. Занимаюсь исследованиями в области глубокого обучения и компьютерного зрения. Страничка нашей группы CompVis.


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


        На тот момент у меня был некий опыт участия в соревнованиях на Kaggle, но только в нерейтинговых, за которые не дают ни медалей, ни очков опыта (Ranking Points). Но у меня был довольно обширный опыт работы с изображениями с помощью глубокого обучения. У Димы же был опыт на Kaggle в рейтинговых соревнованиях, и была 1 бронзовая медаль, но работать с компьютерным зрением он только начинал.


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


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


        В связи со значительным уменьшением популяции сивучей на западных Алеутских островах (принадлежащих США) за последние 30 лет ученые из NOAA Fisheries Alaska Fisheries Science Center ведут постоянный учет количества особей с помощью аэрофотоснимков с дронов. До этого времени подсчет особей производился на фотоснимках вручную. Биологам требовалось до 4 месяцев, чтобы посчитать количество сивучей на тысячах фотографий, получаемых NOAA Fisheries каждый год. Задача этого соревнования — разработать алгоритм для автоматического подсчета сивучей на аэрофотоснимках.


        Все сивучи разделены на 5 классов:


        1. adult_males — взрослые самцы (),
        2. subadult_males – молодые самцы (),
        3. adult_females – взрослые самки (),
        4. juveniles – подростки (),
        5. pups – детёныши ().

        Дано 948 тренировочных картинок, для каждой из которых известно Ground Truth число особей каждого класса. Требуется предсказать число особей по классам на каждой из 18641 тестовых картинок. Вот пример некоторых частей из датасета.


        Картинки разных разрешений: 4608x3456 до 5760x3840. Качество и масштаб очень разнообразный, как видно из примера выше.
        Положение на лидерборде определяется ошибкой RMSE, усредненной по всем тестовым изображениям и по классам.


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


        image
        (image credits to bestfitting)


        Самые частые классы сивучей — это самки (), подростки () и детеныши ().


        Проблемы


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


        • Зашумленность разметки. Не на всех тренировочных картинках были размечены все сивучи (часто биологи пропускали тех, что плавают в воде).
        • Нет чёткого разделение между парами классов adult_males и subadult_males, adult_females и juveniles. Мы не всегда даже глазами можем понять где самка, а где подросток. Та же проблема со взрослыми и молодыми самцами. На вопрос “как вы их размечали?” биолог из NOAA ответил на форуме, что часто отличать классы приходилось только по поведенческим признакам. Например, взрослые самцы часто окружены множеством самок (сивучи живут гаремами), а молодые, еще не успешные, вынуждены одиноко ютиться в отдалении от всех.
        • Детенышей бывает затруднительно отличить от мокрого камня. Они в разы меньше, чем другие особи.
        • Нет segmentation масок — только грубая позиция животных. Лобовой подход на сегментацию объектов не применим.
        • Разные масштабы изображений в целом. В том числе, на глаз, масштаб на тренировочных изображениях меньше чем в тестовых.
        • Задача подсчёта случайных объектов (не людей) не очень освещена в научных статьях. Обычно все считают людей на фотографиях. Про животных, а тем более из нескольких классов, публикаций найдено не было.
        • Огромное количество тестовых картинок (18641) большого разрешения. Предсказание занимало от 10 до 30 часов на одном Titan X.
          Причем, большая часть из тестовых картинок добавлена исключительно в целях избежания того, что участники будут вручную аннотировать тест. То есть предсказания на некоторых из них никак не влияли на финальный счёт.

        Как мы решали


        В Германии, как и в России, в этом году выпали большие выходные на 1 Мая. Свободные дни с субботы по понедельник оказались как никогда кстати для того, чтобы начать погружаться в задачу. Соревнование длилось уже больше месяца. Всё началось с того, что мы с Димой Котовенко в субботу прочитали условие.


        Первое впечатление было противоречивым. Много данных, нету устоявшегося способа, как решать такие задачи. Но это подогревало интерес. Не всё ж "стакать xgboost-ы”. Цель я поставил себе довольно скромную — просто попасть в топ-100 и получить бронзовую медальку. Хотя потом цели поменялись.


        Первых 3 дня ушло на обработку данных и написание первой версии пайплайна. Один добрый человек, Radu Stoicescu, выложил кернел, который преобразовывал точки на тренировочных изображениях в координаты и класс сивуча. Здорово, что на это не пришлось тратить своё время. Первый сабмит я сделал только через неделю после начала.


        Очевидно, решать эту задачу в лоб с помощью semantic segmentation нельзя, так как нет Ground Truth масок. Нужно либо генерить грубые маски самому либо обучать в духе weak supervision. Хотелось начать с чего-то попроще.
        Задача подсчёта числа объектов/людей не является новой, и мы начали искать похожие статьи. Было найдено несколько релевантных работ, но все про подсчёт людей CrowdNet, Fully Convolutional Crowd Counting, Cross-scene Crowd Counting via Deep Convolutional Neural Networks. Все они имели одну общую идею, основанную на Fully Convolutional Networks и регрессии. Я начал с чего-то похожего.


        Идея crowd counting на пальцах


        Хотим научиться предсказывать хитмапы (2D матрицы) для каждого класса, да такие, что бы можно было просуммировать значения в каждом из них и получить число объектов класса.


        Для этого генерируем Grount Truth хитмапы следующим образом: в центре каждого объекта рисуем гауссиану. Это удобно, потому что интеграл гауссианы равен 1. Получаем 5 хитмапов (по одному на каждый из 5 классов) для каждой картинки из тренировочной выборки. Выглядит это так.

        Увеличить


        Среднеквадратичное отклонение гауссиан для разных классов выставил на глазок. Для самцов – побольше, для детенышей – поменьше. Нейронная сеть (тут и далее по тексту я имею в виду сверточную нейронную сеть) принимает на вход изображения, нарезанные на куски (тайлы) по 256x256 пикселей, и выплёвывает 5 хитмапов для каждого тайла. Функция потерь – норма Фробениуса разности предсказанных хитмапов и Ground Truth хитмапов, что эквивалентно L2 норме вектора, полученного векторизацией разности хитмапов. Такой подход иногда называют Density Map Regression. Чтобы получить итоговое число особей в каждом классе, мы суммируем значения в каждом хитмапе на выходе.


        Метод Public Leaderboard RMSE
        Baseline 1: предсказать везде 0 29.08704
        Baseline 2: предсказать везде среднее по train 26.83658
        Мой Density Map Regression 25.51889

        Моё решение, основанное на Density Map Regression, было немного лучше бейзлайна и давало 25.5. Вышло как-то не очень.




        В задачах на зрение, бывает очень полезно посмотреть глазами на то, что породила ваша сеть, случаются откровения. Я так и сделал. Посмотрел на предсказания сети — они вырождаются в нуль по всем классам, кроме одного. Общее число животных предсказывалось ещё куда ни шло, но все сивучи относились сетью к одному классу.


        Оригинальная задача, которая решалась в статьях — это подсчет количества людей в толпе, т. е. был только один класс объектов. Вероятно, Density Map Regression не очень хороший выбор для задачи с несколькими классами. Да и всё усугубляет огромная вариация плотности и масштаба объектов. Пробовал менять L2 на L1 функцию потерь и взвешивать классы, всё это не сильно влияло на результат.


        Было ощущение, что L2 и L1 функции потерь делают что-то не так в случае взаимоисключающих классов, и что попиксельная cross-entropy функция потерь может работать лучше. Это натолкнуло меня на идею натренировать сеть сегментировать особей с попиксельной cross-entropy функцией потерь. В качестве Ground Truth масок я нарисовал квадратики с центром в ранее полученных координатах объектов.


        Но тут появилась новая проблема. Как получить количество особей из сегментации? В чатике ODS Константин Лопухин признался, что использует xgboost для регрессии числа сивучей по набору фич, посчитанных по маскам. Мы же хотели придумать как сделать всё end-to-end с помощью нейронных сетей.


        Тем временем, пока я занимался crowd counting и сегментацией, у Димы заработал простой как апельсин подход. Он взял VGG-19, натренированную на классификации Imagenet, и зафайнтьюнил ее предсказывать количество сивучей по тайлу. Он использовал обычную L2 функцию потерь. Получилось как всегда — чем проще метод, тем лучше результат.


        Итак, стало понятно, что обычная регрессия делает свое дело и делает хорошо. Идея с сегментацией была радостно отложена до лучших времен. Я решил обучить VGG-16 на регрессию. Присобачил в конце выходной слой для регрессии на 5 классов сивучей. Каждый выходной нейрон предсказывал количество особей соответствующего класса.
        Я резко вышел в топ-20 c RMSE 20.5 на паблик лидерборде.


        К этому моменту целеполагание претерпело небольшие изменения. Стало понятно, что целиться имеет смысл не в топ-100, а как минимум в топ-10. Это уже не казалось чем-то недостижимым.


        Выяснилось, что на test выборке многие снимки были другого масштаба, сивучи на них выглядели крупнее, чем на train. Костя Лопухин (отдельное ему за это спасибо) написал в слаке ODS, что уменьшение тестовых картинок по каждой размерности в 2 раза давало существенный прирост на паблик лидерборде.


        Но Дима тоже не лыком шит, он подкрутил что-то в своей VGG-19, уменьшил картинки и вышел на 2-e место со скором ~16.


        Подбор архитектуры и гиперпараметров сети



        (image credits to Konstantin Lopuhin)


        С функцией потерь у нас всё понятно. Время начинать экспериментировать с более глубокими сетями. В ход пошли VGG-19, ResnetV2-101, ResnetV2-121, ResnetV2-152 и тяжелая артиллерия — Inception-Resnet-V2.


        Inception-Resnet-V2 — это архитектура придуманная Google, которая представляет собой комбинацию трюков от Inception архитектур (inception блоки) и от ResNet архитектур (residual соединения). Эта сеть изрядно глубже предыдущих и выглядит этот монстр вот так.



        (image from research.googleblog.com)


        В статье "Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning" ребята из Google показывают, что эта архитектура давала на тот момент state of the art на Imagenet без использования ансамблей.


        Кроме самих архитектур мне пришлось перебрать:


        • различные размеры входного тайла: от 224x224 до 512x512 пикселей;
        • тип пулинга после свёрточных слоёв: sum-pooling или average-pooling;
        • количество дополнительных FC-слоёв перед финальным: 0, 1 или 2;
        • количество нейронов в дополнительных FC-слоях: 128 или 256.

        Лучшей комбинацией оказались: Inception-Resnet-V2-BASE + average-pooling + FC-слой на 256 нейронов + Dropout + финальный FC-слой на 5 нейронов. Inception-Resnet-V2-BASE обозначает часть оригинальной сети от первого до последнего сверточного слоя.
        Лучшим размером входного тайла оказался 299x299 пикселей.


        Аугментации изображений


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


        • Случайные флипы слева направо и сверху вниз;
        • случайные вращения на углы, кратные 90 градусов;
        • случайное масштабирование в 0.83 — 1.25 раза.
          Цвет мы не аугментировали, так как это довольно скользкая дорожка. Зачастую сивучей отличить от ландшафта можно было только по цвету.

        Test time augmentation мы не делали. Потому что предсказание на всех тестовых картинках и так занимало полдня.


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


        В какой-то момент, пока я перебирал гиперпараметры и архитектуры сетей, мы объединились в команду с Димой Котовенко. Я в тот момент был 2-м месте, Дима на 3-м. Для отвода китайцев в ложном направлении команду назвали "DL Sucks".


        Объединились, потому что было бы нечестно у кого-то забирать медальку, ведь с Димой мы активно обсуждали наши решения и обменивались идеями. Этому событию очень порадовался Костя, мы освободили ему призовое место. С 4-го он попал на 3-е.


        Последние 3-4 недели соревнования мы плотно держались на 2-м месте на паблик лидерборде, по одной десятой, по одной тысячной улучшая скор перебором гиперпараметров и стакая модели.


        Нам казалось, что уменьшение в 2 раза всех тестовых изображений — это пошло и грубо. Хотелось сделать всё красиво, чтобы сеть предсказала как нужно отмасштабировать каждое из изображений. Дима инвестировал в эту идею довольно много времени. Если вкратце, то он пытался по картинке предсказать масштаб сивучей, что равносильно предсказанию высоты полета дрона во время съемки. К сожалению, из-за недостатка времени и ряда проблем, с которыми мы столкнулись, это не было доведено до конца. Например, многие картинки содержат только одного сивуча и большая часть пространства — это море и камни. Поэтому, не всегда, глядя только на скалы или море, возможно понять с какой высоты был сделан снимок.


        За пару дней до дедлайна мы собрали все лучшие модели и сделали ансамбль из 24 нейронных сетей. Все модели имели лучшую архитектуру Inception-Resnet-V2, которую я описал ранее. Отличались модели только тем, насколько агрессивно мы аугментировали картинки, на каком масштабе тестовых изображений делались предсказания. Выходы с разных сетей усреднялись.


        Команда "DL Sucks" закончила соревнование на 2-м месте на паблик лидерборде, что не могло не радовать, так как мы были "в деньгах". Мы понимали, что на прайвэт лидерборде всё может поменяться и нас вообще может выкинуть из первой десятки. У нас был приличный разрыв с 4-м и 5-м местом, и это добавляло нам уверенности. Вот так выглядело положение на лидерборде:


        1-е место 10.98445 outrunner (Китаец 1)
        2-е место 13.29065 Мы (DL Sucks)
        3-е место 13.36938 Костя Лопухин
        4-е место 14.03458 bestfitting (Китаец 2)
        5-е место 14.47301 LeiLei-WeiWei (Команда из двух китайцев)

        Оставалось дождаться финальных результатов…
        И что бы вы думали? Китаец нас обошел! Мы были сдвинуты со 2-го на 4-ое место. Ну ничего, зато получили по золотой медали ;)


        Первое место, как оказалось, занял другой китаец, альфа-гусь outrunner. И решение у него было почти как у нас. Обучил VGG-16 c дополнительным полносвязным слоем на 1024 нейрона предсказывать количество особей по классам. Что вывело его на первое место, так это ad-hoc увеличение количества подростков на 50% и уменьшение количества самок на такое же число, умножение количества детёнышей на 1.2. Такой трюк поднял бы нас на несколько позиций выше.


        Финальное положение мест:


        1-е место 10.85644 outrunner (Китаец 1)
        2-е место 12.50888 Костя Лопухин
        3-е место 13.03257 (Китаец 2)
        4-е место 13.18968 Мы (DL Sucks)
        5-е место 14.47301 Дмитро Поплавский (тоже в слаке ODS) в команде с 2 другими

        Пару слов о других решениях


        Резонный вопрос — а нельзя ли натренировать детектор и посчитать потом баундинг боксы каждого класса? Ответ — можно. Некоторые парни так и сделали. Александр Буслаев (13-ое место) натренировал SSD, а Владимир Игловиков (49-ое место) — Faster RCNN.
        Пример предсказания Владимира:

        (image credits to Vladimir Iglovikov)
        Минус такого подхода состоит в том, что он сильно ошибается, когда сивучи на фотографии очень плотно лежат друг к другу. А наличие нескольких различных классов еще и усугубляет ситуацию.


        Решение, основанное на сегментации с помощью UNet, тоже имеет место быть и вывело Константина на 2 место. Он предсказывал маленькие квадратики, которые он нарисовал внутри каждого сивуча. Далее — танцы с бубном. По предсказанным хитмапам Костя вычислял различные фичи (площади выше заданных порогов, количество и вероятности блобов) и скармливал их в xgboost для предсказания числа особей.

        (image credits to Konstantin Lopuhin)
        Подробнее про его решение можно посмотреть на youtube.


        У 3-го места (bestfitting) тоже решение основано на UNet. Опишу его в двух словах. Парень разметил вручную сегментационные маски для 3 изображений, обучил UNet и предсказал маски на еще 100 изображениях. Поправил маски этих 100 изображений руками и заново обучил на них сеть. С его слов, это дало очень хороший результат. Вот пример его предсказания.

        (image credits to bestfitting)
        Для получения числа особей по маскам он использовал морфологические операции и детектор блобов.


        Заключение


        Итак, начальная цель была попасть хотя бы в топ-100. Цель мы выполнили и даже перевыполнили. Было перепробовано много всяких подходов, архитектур и аугментаций. Оказалось, проще метод – лучше. А для сетей, как ни странно, глубже — лучше. Inception-Resnet-V2 после допиливания, обученная предсказывать количество особей по классам, давала наилучший результат.
        В любом случае это был полезный опыт создания хорошего решения новой задачи в сжатые сроки.


        В аспирантуре я исследую в основном Unsupervised Learning и Similarity Learning. И мне, хоть я и занимаюсь компьютерным зрением каждый день, было интересно поработать с какой-то новой задачей, не связанной с моим основным направлением. Kaggle дает возможность получше изучить разные Deep Learning фреймворки и попробовать их на практике, а также поимплементировать известные алгоритмы, посмотреть как они работают на других задачах. Мешает ли Kaggle ресерчу? Вряд ли он мешает, скорее помогает, расширяет кругозор. Хотя времени он отнимает достаточно. Могу сказать, что проводил за этим соревнованием по 40 часов в неделю (прямо как вторая работа), занимаясь каждый день по вечерам и на выходных. Но оно того стоило.


        Кто дочитал, тому спасибо за внимание и успехов в будущих соревнованиях!




        Мой профиль на Kaggle: Artem.Sanakoev
        Краткое техническое описание нашего решения на Kaggle: ссылка
        Код решения на github: ссылка

        Original source: habrahabr.ru (comments, light).

        https://habrahabr.ru/post/337548/


        Как в IT-компании запустить патентный процесс

        Понедельник, 18 Сентября 2017 г. 14:05 + в цитатник
        daria_iatskina сегодня в 14:05 Управление

        Как в IT-компании запустить патентный процесс



          В декабре 2015 года американский дистрибьютор с оборотом $43 млрд. Ingram Micro купил часть бизнеса Parallels, Odin Automation — платформу для дистрибуции облачных сервисов. Кроме разработчиков и нескольких ключевых патентов на сам продукт, Ingram Micro получил в наследство еще и зачатки «культуры патентования», которая впоследствии расцвела и накрыла защитной кроной все новые разработки. О том, как росло это «дерево» и чем его «поливали» я расскажу в этом посте.

          Изначально наши разработчики относились к идее патентования враждебно, и первое, что пришлось услышать патентной команде — это «Идите вы на … со своими патентами!»

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

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

          Как только мы поняли, что разработчиков просто так не уговорить, то согласовали политику бонусов за изобретательскую активность. Условия были такими: за описание своей идеи авторы получают $250, за помощь в подготовке заявки в случае её подачи — $2500, за выданный патент — $5000. Однако мы не спешили сразу рассказывать о новых бонусах разработчикам, а решили прежде проработать всю кампанию по запуску процесса патентования.

          Подготовка шла около двух месяцев. Скрупулёзно прорабатывалась база аргументов за патентование и контраргументов против. Мы знали, что ум разработчика ни за что не согласится что-либо делать, если его логически в этом «чем-либо» не убедить. Также мы выстраивали обоснование мотивации со всех сторон — и с точки зрения важности патентов для компании, и почему это очень выгодно для разработчиков лично. Мы сразу решили, что участие в процессе патентования будет добровольным. Много раз репетировали презентацию с нашим главным архитектором, разыгрывали сессии вопросов-ответов, четко отработали каждое движение, интонации и каждую букву на слайде. Даже был специально выписан из США наш патентный поверенный, чтобы также выступить перед разработчиками и рассказать пару интересных случаев из своей профессиональной жизни.

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



          Схватка скепсиса и пылких речей патентной команды длилась около двух часов! Это было настолько грандиозно, что многие вспоминают это событие по сей день. В конце концов всё оружие патентной команды сработало на 100% и разработчики оказались по одну сторону баррикад с патентной командой, теперь уже на одном поле боя против патентных троллей и недобросовестных конкурентов. Процесс патентования был запущен! Сначала в Москве, затем в Новосибирске, потом в Испании, и в данный момент компания планирует запуск патентования в остальных офисах.

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

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

          Итак, если вы хотите повторить наш путь, то для успешного запуска процесса патентования в вашей IT компании потребуется:
          1. Сам разработанный процесс, который вы хотите внедрить.
          2. Благосклонное начальство, готовое поддержать своим авторитетом.
          3. Несколько сотен тысяч долларов бюджета.
          4. Надежные и готовые быстро вникать в технологии патентные поверенные в той стране, где вы хотите патентовать.
          5. Пирожки и сок — для начала, шампанское и шоколадки — для празднования подачи первых заявок.
          6. И конечно же, один молодой и мега-настырный патентный инженер, вроде того, чей пост вы сейчас читаете :)
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/338154/


          Метки:  

          [Перевод] Как работает JS: управление памятью, четыре вида утечек памяти и борьба с ними

          Понедельник, 18 Сентября 2017 г. 13:56 + в цитатник

          Метки:  

          MultiSim + М2М OTA платформа

          Понедельник, 18 Сентября 2017 г. 13:38 + в цитатник

          Как компании перенести свою инфраструктуру в облако и избежать ошибок

          Понедельник, 18 Сентября 2017 г. 13:32 + в цитатник
          it_man сегодня в 13:32 Управление

          Как компании перенести свою инфраструктуру в облако и избежать ошибок

            Миграция в облако тесно связана с перемещением данных, настроек, приложений, операционных систем в виртуальный дата-центр облачного провайдера. Кажется, что это сложно и ненадежно, однако все больше компаний доверяют сервисы технологии IaaS. Например, в течение трех последующих лет 50% индийских организаций планируют переместить инфраструктуру в облако.

            Рынок «идет» вверх — по прогнозу аналитического агентства Gartner, в 2017 году IaaS-сегмент вырастет на 36,8% и достигнет планки в 34,6 млрд долларов. Поэтому в сегодняшнем материале мы поговорим о том, на что компаниям обратить внимание, чтобы мигрировать инфраструктуру в облачную среду и избежать потенциальных рисков.


            / Flickr / goldswordfish / CC

            Что может пойти не так


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

            При переносе IT-отделам компаний приходится решать, как поддерживать доступ к источникам данных — большинство сценариев миграции включают перенос информации с использованием бэкап-инструментов и систем восстановления в случае катастрофы (disaster recovery).

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

            Подробнее об этих типах рисков и как их избежать, мы расскажем далее.

            Кто уже с этим справился


            Переход в облако совершают крупные компании и небольшие стартапы — 52% сегмента малого бизнеса обратились к облачной инфраструктуре. По данным International Data Corporation (IDC), каждая небольшая компания, которая использует облачные технологии, экономит значительные суммы. Организации также получают возможность управлять инфраструктурой из одной консоли, что упрощает взаимодействие и ускоряет доставку сервисов потребителям.

            Существуют кейсы, демонстрирующие успешную миграцию IT-инфраструктуры в облако IaaS-провайдера. При этом часть компаний используют облако как вторичный плацдарм, где хранят «вторичные» сервисы, а другие — целиком отдают инфраструктуру на аутсорсинг и размещают там бизнес-критические системы.

            Пример постепенной миграции в облако — поэтапная трансформация инфраструктуры Netflix. Об этом мы писали тут. Компания переводила сервисы в облако на протяжении нескольких лет, пересмотрев концепцию предоставления услуг.

            В Netflix перенесли в облако платежную инфраструктуру и сервисы предоставления счетов, платформу Big Data, службы видеотрансляции, систему управления данными клиентов и др.

            Российские компании также переходят в облачную среду. Delivery Club — сервис по доставке еды c полностью виртуализированной системой. В случае Delivery Club, облако упростило управление, поддержку и обеспечило надежность.

            Мигрировал в облако провайдера и автомобильный холдинг «Терра-авто». Компания разместила в виртуальной среде телекоммуникационный компонент, почтовые сервисы и инструменты фильтрации трафика.

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

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


            Как мигрировать


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

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

            Шаг 1. Перед тем как начать миграцию, нужно провести инвентаризацию IT-окружения, чтобы построить текущую картину инфраструктуры. Это позволит понять, какие приложения подходят для перемещения в облако. Для этого нужно категоризировать приложения один за другим на основании атрибутов. При создании каталога применяют два подхода: «сверху-вниз», который позволяет понять, где приложения принесут пользу бизнесу, и «снизу-вверх», фокусирующийся на технических возможностях.

            Подход «сверху-вниз» следует парадигме трансформации бизнеса с целью реализации максимального потенциала, поэтому основывается на оценке технических аспектов и аспектов безопасности каждого приложения:

            • Категоризация данных, требования к безопасности;
            • Сложность интерфейса, аутентификация, структура данных, требования к латентности;
            • Требования к работе (SLA), интеграция, мониторинг.

            После проведения анализа эти показатели формируют счет, которая отражает сложность переноса того или иного приложения в облако. Подход «сверху-вниз» также включат оценку финансовой выгоды: TCO, сезонные скачки активности пользователей, необходимые уровни масштабируемости, отказоустойчивость.

            Одновременно может применяться подход «снизу-вверх». Как уже было отмечено, он фокусируется на технических требованиях, поэтому показатели для оценки находятся в базе данных управления конфигурацией (CMDB):

            • Память, число процессоров, занимаемое место на диске операционной системой;
            • Платы сетевого интерфейса;
            • IPv6;
            • Поддержка доменов;
            • Наличие сторонних компонентов и пакетов приложений.

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

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

            Шаг 2. Это этап выбора облачного провайдера с тестированием возможностей облачной площадки. Оцените надежность площадки провайдера и проверьте её на соответствие требованиям компании. Надежность облачной платформы зависит от используемого оборудования, поэтому стоит обратить внимание на актуальность «железа», его класс и проверить наличие резервирования (не должно быть единой точки отказа).

            Также большинство коммерческих дата-центров заявляют, что их инфраструктура соответствует стандарту по категории надежности Tier III. Однако это не всегда так. Проверить сервис-провайдера просто: запросите сертификаты, подтвержденные Uptime Institute. UTI сертификаты для российских дата-центров находятся на сайте организации.

            После того как вы определились с провайдером, в обязательном порядке проведите тестирование облачной площадки и тестовую миграцию. Например, мы в компании «IT-ГРАД» по запросу клиента предоставляем бесплатный доступ VMware vCloud на две недели. Это позволит вам убедиться, что все сервисы работают правильно.

            Шаг 3. При переносе IT-инфраструктуры в облако следует выбрать миграционный путь: постепенный или полный переход.

            Постепенная миграция подходит для крупных компаний с разветвлённой инфраструктурой. Такой переход выполняется на протяжении нескольких месяцев или даже лет (например, случай компании Netflix, о которой мы говорили выше). В этом случае составляется поэтапный план переноса с указанием критичных сервисов и приоритетов.

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

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

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

            Шаг 4. Далее, можно приступать к миграции, придерживаясь выбранной стратегии. После остается выполнить проверку и тестирование сервисов. Если ошибок нет — сервисы выводятся в продакшн.


            / Flickr / Peter Stevens / CC

            Инструменты и способы миграции


            Выбор способа миграции в облако зависит от конкретной ситуации, например, текущей инфраструктуры компании и возможностей. Это может быть перенос физической IT-инфраструктуры в виртуальную среду, перенос локальной виртуальной инфраструктуры в облако, переход от одного провайдера к другому и др. Ниже рассмотрим возможные варианты миграции подробнее. Часть из них выполняется с помощью инструментов компании VMware.

            Импорт-экспорт ВМ
            Если у компании уже есть виртуальная инфраструктура на базе VMware, то её виртуальное окружение позволяет «перекинуть» сразу несколько ВМ.

            Все параметры виртуальных машин «упаковываются» в файлы формата OVF/OVA. Затем они используются экспорта на платформу виртуализации VMware vSphere и другие. Переносить приложения в облако также позволяет VMware vCloud Connector. Как работать с этим инструментом ми писали в нашем блоге.

            Миграция на уровне сервисов
            Провайдер создает у себя дублирующий сервис. Этот сервис синхронизируется с локальным сервисом клиента. Как пример можно привести миграцию Active Directory. Облачный провайдер разворачивает у себя необходимое количество ВМ, после чего база данных реплицируется в облако вместе с контроллерами.

            «Горячее» и «холодное» клонирование
            При «горячем» клонировании выполняется перенос работающего узла. Оригинальный сервер останавливается только в момент переключения. Утилита vCenter Converter автоматически определяет диски, разделы и сетевые интерфейсы, оперативную память и процессоры. На основе этих данных создается новая виртуальная машина на ESXi-хосте.

            VMware vCenter Converter также может выполнять «холодное» клонирование. Этот вид клонирования рекомендован для миграции Active Directory и почтовых серверов. Машина останавливается, создается образ жесткого диска и выполняется конвертация в ВМ.

            Установка с нуля
            Провайдер создает ВМ, и на неё накатывается операционная система и необходимое программное обеспечение. Затем происходит переключение клиентов.

            Риски при миграции и как их избежать


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

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

            Например, мы в «ИТ-ГРАД» предлагаем несколько площадок для размещения данных. Клиент может создавать несколько виртуальных дата-центров и настраивать параметры производительности и безопасности для каждого. Мы также предлагаем разные модели оплаты, включая Pay-As-You-Go.

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

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

            Например, как говорят в «Хантердон Медикал Сентер», они совершили эту ошибку во время первой миграции систем в облако. Сотрудники не провели глубокий анализ приложений. Специалисты переносили почтовую систему в G Suite, однако не учли, что именно пользователям было нужно от старой системы, и не проанализировали, как она себя поведет в новом окружении.

            Нет плана миграции
            До начала миграции ответьте для себя на вопросы: что вы будете переносить в облако, в каком порядке, когда и за сколько? Составьте план переноса.

            Эксперты из технологической компании DoubleHorn также советуют для составления плана прибегнуть к помощи третьих лиц и консалтинговых фирм. Они помогут найти и применить лучшие практики и избежать дорогостоящих ошибок.

            Забыли про политики безопасности
            Перевод инфраструктуры в облако часто ведет к неполноте политик безопасности, которые не согласуются с новыми стандартами. Все организации имеют требования к безопасности для авторизации пользователей, настройки приложений и мониторинга систем. Эти политики зачастую не изменяются, однако при работе с облаком они должны быть усилены. Лорен Худзиак (Loren Hudziak) из Google отмечает, что организациям нужно обратить внимание на безопасность по всем фронтам, чтобы получить все преимущества облачной инфраструктуры.

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

            «Если клиент, разворачивая инфраструктуру, решает установить приложения, не поддерживающие cloud-архитектуру и заточенные только на on-premise-инсталляцию, провал гарантирован, — говорит Екатерина Юдина, руководитель проекта, контент-инженер компании «IT-ГРАД». — Чтобы избежать подобной ситуации, поставщик услуг предлагает возможность бесплатного и заблаговременного тестирования».

            По данным облачных провайдеров, многие организации, мигрирующие в облако, не имеют установленного плана восстановления в случае катастрофы. А многие из тех, кто его имеет, никогда его не тестировали.

            Когда округ Кинг в Вашингтоне, мигрировал в облако бэкап-систему, они провели тестирование того, как данные передаются в облако. Все работало хорошо. Однако они не протестировали их восстановление. По словам Тэмуджина Бейкера (Temujin Baker), руководителя по работе с архитектурой приложений, они не учли особенности работы с облаком, когда сервису требуется время на получение данных.

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



            P.S. Еще несколько материалов по теме из нашего блога:

            Original source: habrahabr.ru (comments, light).

            https://habrahabr.ru/post/337834/


            Метки:  

            Компьютерное зрение. Задайте вопрос эксперту Intel

            Понедельник, 18 Сентября 2017 г. 13:07 + в цитатник
            saul сегодня в 13:07 Разработка

            Компьютерное зрение. Задайте вопрос эксперту Intel

              Далеко не все ответы можно найти в Интернет. Особенно если вопрос ваш относится к достаточно узкой или новой области — тут необходима консультация гуру, Владельца Тайного Знания. В традициях блога Intel — проведение блого-семинаров, построенных на вопросах читателей. На эти вопросы отвечают эксперты Intel, принимавшие непосредственное участие в создании технологий и продуктов — кому, как не им знать все детали?
              В этом месяце место на трибуне предоставлено создателям библиотеки компьютерного зрения OpenCV (Open Source Computer Vision Library), бывшим сотрудникам компании Itseez, вошедшей в состав Intel — Вадиму Писаревскому и Анатолию Бакшееву. Итак, если у вас назрел вопрос об OpenCV, машинном зрении, распознавании образов и других смежных темах, но вы не знали, кому его задать — приглашаем вас в комментарии и личку. Вопросы принимаются до 24 сентября. Автор лучшего вопроса получит приз от Intel — набор фирменных принадлежностей для уютного отдыха.
              Под катом — краткая информация о наших экспертах.

              Анатолий Бакшеев, руководитель команды Computer Vision for Retail в Intel. Занимается компьютерным зрением и машинным обучением вот уже более 11 лет, в том числе последние несколько лет Deep Learning’ом. Начал свою карьеру в компании Itseez и проработал там более 10 лет до тех пор, пока компания не была приобретена Intel. За это время в его портфолио накопилось множество выполненных проектов, спроектированных и оптимизированных алгоритмов, большое количество кода, выложенного в библиотеку OpenCV, а также ее оптимизация под NVIDIA GPU.

              Вадим Писаревский, бессменный руководитель команды OpenCV, начиная с 2000 года.
              Закончил ВМК ННГУ, работал в компании Intel, затем в Itseez. Вернулся в Intel для продолжения работы над библиотекой и перевода ее на рельсы Deep Learning. Со-автор нескольких патентов и статей по компьютерному зрению. Периодически выступает с докладами и лекциями по OpenCV в различных университетах и на разных конференциях.

              Ждем ваши вопросы экспертам в комментариях к этому посту или личных сообщениях saul до воскресенья, 24 сентября, включительно.
              Original source: habrahabr.ru (comments, light).

              https://habrahabr.ru/post/337810/


              Метки:  

              Nuklear+ — миниатюрный кроссплатформенный GUI

              Понедельник, 18 Сентября 2017 г. 12:47 + в цитатник
              DeXPeriX сегодня в 12:47 Разработка

              Nuklear+ — миниатюрный кроссплатформенный GUI

                Nuklear+ (читается как "Nuklear cross", значит "кроссплатформенный Nuklear") — это надстройка над GUI библиотекой Nuklear, которая позволяет абстрагироваться от драйвера вывода и взаимодействия с операционной системой. Нужно написать один простой код, а он потом уже сможет скомпилироваться под все поддерживаемые платформы.


                Я уже писал на хабре статью "Nuklear — идеальный GUI для микро-проектов?". Тогда задача была простой — сделать маленькую кроссплатформенную утилиту с GUI, которая будет примерно одинаково выглядеть в Windows и Linux. Но с тех самых пор меня не отпускал вопрос, а можно ли на Nuklear сделать что-то более-менее сложное? Можно ли целиком на нём сделать какой-нибудь реальный проект, которым будут пользоваться?


                Веб-демо Wordlase


                Именно поэтому следующую свою игру, Wordlase, я делал на чистом Nuklear. И без всякого там OpenGL. Даже фоновые картинки у меня имеют тип nk_image. В конечном итоге это дало возможность выбора драйвера отрисовки, вплоть до чистого X11 или GDI+.


                Ещё в прошлой своей статье я заложил основы Nuklear+ — библиотеки, призванной спрятать всю "грязь" от программиста и дать ему сфокусироваться на создании интерфейса. Библиотека умеет загружать шрифты, картинки, создавать окно операционной системы и контекст отрисовки.


                Полный пример кода есть в Readme на GitHub. Там можно увидеть, что код получается довольно простой. Также я перенёс на Nuklear+ свои проекты dxBin2h и nuklear-webdemo. И сделать это было очень просто — вся инициализация заменяется на один вызов nkc_init, события обрабатываются nkc_poll_events, отрисовка функцией nkc_render, а в качестве деструктора вызывается nkc_shutdown.


                Демо Nuklear


                Но вернёмся к Wordlase, на примере которой и построена данная публикация. С недавних пор у игры есть веб-демо. Я не писал какого-то специфичного веб-кода для игры — это чистое С89 приложение, скомпилированное с помощью Emscripten. И если полностью следовать примеру из Readme Nuklear+ (а именно, использовать nkc_set_main_loop), то веб-версия приложения будет получена абсолютно на халяву, без особых лишних затрат.


                Бэкэнд и фронтэнд


                Самой интересной частью Nuklear+ являются поддерживаемые фронтэнды и бэкэнды. В данном случае под фронтэндом понимается часть, ответственная за взаимодействие с ОС и отрисовку окна. Т.е. непосредственно то, что видит пользователь. Реализации лежат в папке nkc_frontend. Сейчас поддерживаются: SDL, GLFW, X11, GDI+. Они не равносильны. Например, GDI+ использует WinAPI даже для рендера шрифтов и загрузки изображений, т.е. получить ровно такую же картинку в других ОС будет проблематично. Реализация так же не везде одинакова. Например, реализация Х11 пока не умеет изменять разрешение экрана в полноэкранном режиме (буду рад видеть Pull Request)


                Выбрать фронтэнд для своего приложения просто — нужно установить переменную препроцессора NKCD=NKC_x, где x это одно из: SDL, GLFW, XLIB, GDIP. Например: gcc -DNKCD=NKC_GLFW main.c


                Бэкэнд в данном случае выполняет непосредственно отрисовку. Реализация в папке nuklear_drivers. Отрисовка средствами любой версии OpenGL выдаёт примерно одинаковую картинку на всех ОС и фронтэндах. Ведь для загрузки изображений там всегда используется stb_image, а шрифт рендерится стандартными средствами Nuklear (тоже основано на stb). В то же время чистый Х11 драйвер даже не умеет загружать шрифты. Так что не забывайте тестировать своё приложение для выбранной пары бэкэнд+фронтэнд.


                Например: Wordlase, GLFW3, OpenGL 2, Windows
                Wordlase, GLFW3, OpenGL 2, Windows


                Или: Wordlase, SDL2, OpenGL ES, Linux
                Wordlase, SDL2, OpenGL ES, Linux


                В качестве бэкэнда по умолчанию выбран OpenGL2, если доступен. Можно задать NKC_USE_OPENGL=3 для OpenGL 3, и NKC_USE_OPENGL=NGL_ES2 для OpenGL ES 2.0. Для использования чистого Х11 отрисовщика константу NKC_USE_OPENGL указывать не надо. Также OpenGL опции не влияют на GDI+ — там отрисовка всегда идёт своими средствами.


                Вот скриншот с GDI+: Wordlase, GDI+, без OpenGL, Windows


                Wordlase, GDI+, без OpenGL, Windows


                Этот бэкэнд полноценно поддерживает полупрозрачные изображения, картинка близка к оригиналу. Разница в шрифте: хинтинг, сглаживание, да даже размер (также буду рад Pull Request'у для автоматической подстройки размера GDI+ шрифта под размер stb_ttf).


                И самый ужасный случай — чистый Х11 отрисовщик, который до моего pull request даже не умел загружать картинки. Wordlase, X11, без OpenGL, Linux:


                Wordlase, X11, без OpenGL, Linux


                Вот здесь уже довольно много отличий: логотип, солнечные лучи, более острый край девушки, шрифт. Почему? Фон в игре на лету собирается из нескольких полупрозрачных PNG. Но чистый Х11 поддерживает только битовую прозрачность, прямо как GIF. Также отрисовщик Х11 очень медленно работает на больших изображениях с прозрачностью. А если в движке отключить прозрачность, то картинка становится ещё хуже. Wordlase, X11, без OpenGL, без прозрачности:


                Wordlase, X11, без OpenGL, без прозрачности


                Так зачем вообще нужны отрисовщики GDI+ и Х11, если они так уродливы? Потому, что они плохи только для больших изображений с прозрачностью. А если делать маленькую утилиту, где картинки используются только как иконки пользовательского интерфейса, то эти отрисовщики становятся вовсе неплохим вариантом, т.к. имеют минимальное количество зависимостей. Также я пользовался чистым Х11 отрисовщиком на слабых системах, где OpenGL только программный. В таком случае Х11 работает быстрее OpenGL. Подсказка: если вместо кучи полупрозрачных PNG использовать один большой JPEG, то Х11 будет работать быстро и корректно.


                Пример хорошего использования чистого Х11 бэкэнда — главное игровое окно Wordlase. Больших картинок там почти нет, зато есть несколько интерфейсных иконок, которые вполне корректно отображаются:


                Wordlase, X11, gameplay


                Отлично, отрисовщик выбран, окно ОС создаётся. Теперь самое время заняться GUI!


                Фишки Nuklear


                Самым первым в Wordlase показывается экран выбора языка:


                Wordlase, экран выбора языка


                Здесь видны сразу 2 интересных техники: несколько картинок на фоне окна и центрирование виджетов.


                Поместить картинку на фон окна достаточно просто:


                nk_layout_space_push(ctx, nk_rect(x, y, width, height));
                nk_image(ctx, img);

                x и y — позиция на экране, width и height — размеры изображения.


                Центрирование является более сложной задачей, т.к. не поддерживается Nuklear напрямую. Нужно вычислять положение самостоятельно:


                if ( nk_begin(ctx, WIN_TITLE, 
                        nk_rect(0, 0, winWidth, winHeight), NK_WINDOW_NO_SCROLLBAR)
                ) {
                    int i;
                    /* 0.2 are a space skip on button's left and right, 0.6 - button */
                    static const float ratio[] = {0.2f, 0.6f, 0.2f};  /* 0.2+0.6+0.2=1 */
                
                    /* Just make vertical skip with calculated height of static row  */
                    nk_layout_row_static(ctx, 
                        (winHeight - (BUTTON_HEIGHT+VSPACE_SKIP)*langCount )/2, 15, 1
                    );
                
                    nk_layout_row(ctx, NK_DYNAMIC, BUTTON_HEIGHT, 3, ratio);
                    for(i=0; i* skip 0.2 left */
                        if( nk_button_image_label(ctx, image, caption, NK_TEXT_CENTERED) 
                        ){
                            loadLang(nkcHandle, ctx, i);
                        }
                        nk_spacing(ctx, 1); /* skip 0.2 right */
                    }
                }
                nk_end(ctx);

                Следующая прикольная штучка — выбор темы оформления в настройках:


                Wordlase, выбор темы оформления


                Реализуется тоже просто:


                if (nk_combo_begin_color(ctx, themeColors[s.curTheme], 
                    nk_vec2(nk_widget_width(ctx), (LINE_HEIGHT+5)*WTHEME_COUNT) ) 
                ){
                    int i;
                    nk_layout_row_dynamic(ctx, LINE_HEIGHT, 1);
                    for(i=0; icode>

                Здесь главное понимать, что всплывающее поле combo — это такое же окно, как и главное. И располагать там можно что угодно.


                Самым сложно выглядящим окном является основное игровое окно:


                Wordlase, основное игровое окно


                На самом деле, тут тоже нет ничего сложного. На экране всего 4 ряда:


                1. Верхняя линия с выбором уровня (виджет nk_property_int)
                2. Список слов (nk_group_scrolled)
                3. Кнопки текущего слова
                4. Строка с подсказкой

                Единственный непонятный момент здесь — задание точных размеров элементам. Выполняется это с помощью соотношения ряда:


                float ratio[] = {
                    (float)BUTTON_HEIGHT/winWidth, /* square button */
                    (float)BUTTON_HEIGHT/winWidth,  /* square button */
                    (float)topWordSpace/winWidth, 
                    (float)WORD_WIDTH/winWidth
                };
                nk_layout_row(ctx, NK_DYNAMIC, BUTTON_HEIGHT, 4, ratio);

                BUTTON_HEIGHT и WORD_WIDTH — константы, измеряются в пикселях; topWordSpace вычисляется как ширина экрана минус ширины всех остальных элементов.


                И последнее сложно выглядящее окно — статистика:


                Wordlase, статистика


                Расположение элементов регулируется с помощью группировки. Ведь всегда можно сказать Nuklear: "в этом ряду будет 2 виджета". Но группа тоже является виджетом. Т.е. можно просто создать группу с помощью nk_group_begin и nk_group_end, а дальше позиционироваться внутри неё как внутри обычного окна (nk_layout_row и пр.).


                Заключение


                Nuklear уже готов даже для коммерческих игр и приложений. А Nuklear+ может сделать их создание более приятным.


                Полезные ссылки


                Original source: habrahabr.ru (comments, light).

                https://habrahabr.ru/post/338106/


                [Перевод] PHP жив. PHP 7 на практике

                Понедельник, 18 Сентября 2017 г. 12:42 + в цитатник
                pik4ez сегодня в 12:42 Разработка

                PHP жив. PHP 7 на практике

                • Перевод

                Недавно PHP-проекты Avito перешли на версию PHP 7.1. По этому случаю мы решили вспомнить, как происходил переход на PHP 7.0 у нас и наших коллег из OLX. Дела давно минувших дней, но остались красивые графики, которые хочется показать миру.


                Первая часть рассказа основана на статье PHP’s not dead! PHP7 in practice, которую написал наш коллега из OLX Lukasz Szyma'nski (Лукаш Шиманьски): переход OLX на PHP 7. Во второй части — опыт перехода Avito на PHP 7.0 и PHP 7.1: процесс, трудности, результаты с графиками.


                Часть 1. PHP 7 в OLX


                Компания OLX Europe управляет десятью сайтами, самый большой из которых — OLX.pl. Все наши сайты должны работать максимально эффективно, поэтому миграция на PHP 7 стала для нас основным приоритетом.


                В этом посте расскажем, с какими проблемами пришлось столкнуться и чего удалось получить с переходом на PHP 7. Про переход было рассказано на конференции PHPers Summit 2016.


                Слайды

                Переход


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


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


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


                Memcache


                Отказ от поддержки Memcache в PHP 7 подтолкнул нас к переходу на Memcached. Пришлось поддержать две версии сайта: PHP 5 + Memcache и PHP 7 + Memcached.


                Для решения задачи использовали простенькую обёртку. Она выбирает подходящий PHP-модуль для соединения с кэшом, исходя из информации о сервере, на котором выполняется код.


                https://habrahabr.ru/post/338140/


                Метки:  

                Набор полезных советов для эффективного использования FreeIPA

                Понедельник, 18 Сентября 2017 г. 12:18 + в цитатник
                Vrenskiy сегодня в 12:18 Администрирование

                Набор полезных советов для эффективного использования FreeIPA



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

                  Содержание:

                  1. FreeIPA агенте в lxc контейнерах
                  2. Библиотека для использования API в python
                  3. Несколько слов про Ansible модули
                  4. FreeIPA агент в debian
                  5. Реплика в Амазоне

                  FreeIPA агент в lxc контейнерах


                  У нас для dev-окружений в некоторых местах используется такая штука, как Proxmox и lxc-контейнеры в нём. Темплейт для контейнера был взят стандартный centos-7-default версии 20170504, который мы кастомизировали. Но при банальной установке агента он отказался работать. После разбора выяснилось, что в этой сборке нет пакетов с sudo и в контейнерах нет SELinux. Итак, по пунктам, что нужно сделать:

                  • yum install sudo
                  • устанавливаем и конфигурируем
                  • в файле /etc/sssd/sssd.conf, в секцию [domain/$DOMAINNAME] добавляем строку selinux_provider=none
                  • рестартим sssd systemctl restart sssd

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

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

                  ansible_virtualization_role == "guest" and ansible_virtualization_type == "lxc"

                  Библиотека для использования API в Python


                  В современных версиях FreeIPA появился замечательный API, но вот полноценных библиотек для Python нам найти не удалось. На гитхабе есть репозиторий, но реализованного там нам оказалось мало. Так как решение распространяется под MIT лицензией, мы решили скопировать его и дополнить сами. Наша реализация доступна по этой ссылке.

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

                  Несколько слов про Ansible модули


                  Оговорюсь сразу, речь пойдёт про версию Ansible 2.3.1.0, установленную через pip. В целом модули добавления юзеров и групп работают нормально. Но при добавлении sudoroles возникли некоторые проблемы. Первая и самая неприятная — они просто не добавляются. Ошибка выглядит вот так:

                  get_sudorule_diff() takes exactly 2 arguments (3 given)

                  Лечится на скорую руку, это довольно элементарно. В файле модуля ipa_sudorule.py нам нужна строка 307. Вот она:

                  diff = get_sudorule_diff(client, ipa_sudorule, module_sudorule)

                  Меняем ее на такую:

                  diff = get_sudorule_diff(ipa_sudorule, module_sudorule)

                  Добавление начинает работать. Прочитать про это можно тут и тут, но нами еще не проверено.

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

                  FreeIPA агент в debian


                  Установка агента в debian like системы почему-то у некоторых людей вызывает ряд проблем. Я хочу изложить наш вариант развертки агентов на debian подобных системах:

                  1. Добавляем репозиторий
                  wget -qO - http://apt.numeezy.fr/numeezy.asc | apt-key add -
                  echo -e 'deb http://apt.numeezy.fr  jessie main' >> /etc/apt/sources.list
                  
                  2. Устанавливаем пакеты
                  apt-get update
                  apt-get install -y freeipa-client
                  
                  3. Создаём директории
                  mkdir -p /etc/pki/nssdb
                  certutil -N -d /etc/pki/nssdb
                  mkdir -p /var/run/ipa
                  
                  4. Убираем дефолтный конфиг
                  mv  /etc/ipa/default.conf ~/
                  
                  5. Устанавливаем и настраиваем клиент
                  ipa-client-install
                  
                  6. Включаем создание директорий 
                  echo 'session required pam_mkhomedir.so' >> /etc/pam.d/common-session
                  
                  7. Проверяем, чтобы в /etc/nsswitch.conf был указан sss провайдер
                  passwd: files sss
                  group: files sss
                  shadow: files sss
                  
                  8. Перезагружаем sssd
                  systemctl restart sssd

                  Реплика в Amazon


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

                  Для решения этой проблемы при установке достаточно на время установки добавить на любой интерфейс внешний IP. Как пример, это можно сделать при помощи ip addr add:

                  ip addr add $ADDR  dev $IFace

                  После успешной установки и настройки с помощью ip addr del:

                  ip addr del $ADDR

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

                  В итоге мы получаем, что клиенты в lxc и debian подобных системах вполне реальны и никаких особых проблем не имеют. Все эти решения работают у нас без каких-либо заметных проблем уже довольно длительное время. Управлять полноценно доступами через Ansible не вполне удобно, но можно ускорить и автоматизировать часть рутинной работы. Что касается библиотеки для Python — надо реализовать еще довольно много, но все основные функции там уже имеются. Впрочем, новые идеи тоже приветствуются.
                  Original source: habrahabr.ru (comments, light).

                  https://habrahabr.ru/post/337454/


                  Метки:  

                  Ходим за покупками с full-stack redux

                  Понедельник, 18 Сентября 2017 г. 12:07 + в цитатник
                  glebsmagliy сегодня в 12:07 Разработка

                  Ходим за покупками с full-stack redux

                    Всем привет! В этой статье я хочу на простом примере рассказать о том, как синхронизировать состояние redux-приложений между несколькими клиентами и сервером, что может оказаться полезным при разработке realtime-приложений.


                    Redux


                    Приложение


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


                    • Возможность одновременно изменять и удалять позиции на нескольких устройствах в режиме реального времени
                    • Возможность сохранять список в локальной памяти сервера
                    • Возможность создавать несколько списков и иметь к ним доступ

                    Дабы обойтись минимальными трудозатратами, не будем делать UI для доступа ко всем спискам, а просто будем различать их по идентификатору в URL.


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


                    В планах у меня также написание следующей статьи, где на примере этого же приложения мы рассмотрим, как сохранять redux-состояние в DynamoDB и выкатывать упакованное в Docker приложение в AWS.


                    Приступаем к разработке


                    Для создания среды разработки воспользуемся замечательным инструментом create-react-app. Создавать прототипы с ним довольно легко: он подготовит все необходимое для продуктивной разработки: webpack c hot-reload, начальный набор файлов, jest-тесты. Можно было бы настроить это все самостоятельно для большего контроля над процессом сборки, но в данном приложении это не принципиально.


                    Придумываем название для нашего приложения и создаем его, передав в качестве аргумента в create-react-app:


                    create-react-app deal-on-meal
                    cd deal-on-meal

                    Структура проекта


                    create-react-app создал нам некоторую структуру проекта, но на самом деле для его корректной работы необходимо лишь, чтобы файл ./src/index.js являлся точкой входа. Поскольку же наш проект подразумевает использование как клиента, так и сервера, то изменим начальную структуру на следующую:


                    src  
                    +--client
                      +--modules
                      +--components
                      +--index.js
                      +--create-store.js
                      +--socket-client.js
                      +--action-emitter.js
                    +--constants
                      +--socket-endpoint-port.js
                    +--server
                      +--modules
                      +--store
                      +--utils
                      +--bootstrap.js
                      +--connection-handler.js
                    +--server.js
                    +--index.js
                    +-- registerServiceWorker.js

                    Добавим так же в package.json команду для старта сервера node ./src/server.js


                    Клиент-серверное взаимодействие


                    Состояние приложения redux хранит в так называемом store в виде javascript-объекта любого типа. При этом любое изменение состояния должен проводить reducer — чистая функция, на вход которой подается текущее состояние и action (тоже javascript-объект). Возвращает же она новое состояние с изменениями.


                    Мы можем использовать нашу клиентскую логику для списка покупок, которую реализует reducer, как на стороне браузера, так и в node.js окружении. Таким образом мы сможем хранить состояние списка независимо от клиента и сохранять его в БД.


                    Для работы с сервером будем использовать давно уже ставшую стандартом для работы с websockets библиотеку socket.io. Для каждого списка заведем свой room и будем посылать каждый action тем пользователям, которые находятся в этой же комнате. Помимо этого, для каждой комнаты на сервере будем хранить свой store с состоянием для этого списка.


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



                    То есть каждый раз когда происходит какой-либо action, то:


                    • Клиент пропускает его через свой store
                    • Через middleware он передается на сервер
                    • Сервер по URL страницы понимает, из какой комнаты пришел action и пропускает его через соответствующий store
                    • А также вещает этот action всем клиентам в данной комнате

                    В основном в redux-мире взаимодействие с сервером происходит по http, через библиотеки вроде redux-thunk или redux-saga, которые позволяют добавить немного асинхронности и сайд-эффектов в синхронный, чистый мир redux. Хотя нам не требуется такого рода связь с сервером, мы тоже будем использовать redux-saga на клиенте, но только для одной задачи: перенаправления на только что созданный список в случае, если в URL нет идентификатора.


                    Пишем код клиента


                    Не буду заострять особого внимания на инициализации, необходимой для работы redux, это хорошо описано в официальной документации redux, скажу лишь, что нам необходимо зарегистрировать два middleware: из упомянутого пакета redux-saga и наш emitterMiddleware. Первый нужен для редиректа, как уже было упомянуто, а последний мы напишем для синхронизации экшенов с сервером через socket.io-client.


                    Синхронизация состояний между клиентами и сервером


                    Создадим файл ./src/client/action-emitter.js в котором и будет реализация упомянутого emitterMiddleware:


                    export const syncSocketClientWithStore = (socket, store) =>
                    {
                      socket.on('action', action => store.dispatch({ ...action, emitterExternal: true }));
                    };
                    
                    export const createEmitterMiddleware = socket => store => next => action =>
                    {
                      if(!action.emitterExternal)
                      {
                        socket.emit('action', action);
                      }
                    
                      return next(action);
                    };

                    • createEmitterMiddleware является фабрикой наших middleware и нужна для того, чтобы хранить в себе ссылку на socket, переданный снаружи. Также здесь есть еще один нюанс: экшены, которые приходят снаружи, не нужно отправлять на сервер. Для этого я предлагаю их помечать (в данном случае полем emitterExternal), и в случае такого экшена middleware ничего не должен делать. Можно было бы использовать экшен-декоратор, но нужды в этом я не вижу.
                    • syncSocketClientWithStore совершенно прост: он слушает сокет на сообщение action и просто передает принятое действие в store, помечая его уже упомянутым флагом.

                    Получение начального состояния списка


                    Как я уже упоминал, мы будем использовать на клиенте redux-saga, чтобы при первом заходе местоположение клиента менялось на соответствующее списку, который мог быть создан только что. Незамысловатым образом в ./src/client/modules/products-list/saga/index.js опишем сагу, реагирующую на получение списка продуктов и комнаты, в которой находится клиент:


                    import { call, takeLatest } from 'redux-saga/effects'
                    import actionTypes from '../action-types';
                    
                    export function* onSuccessGenerator(action)
                    {
                      yield call(window.history.replaceState.bind(window.history), {}, '', `/${action.roomId}`);
                    }
                    
                    export default function* ()
                    {
                      yield takeLatest(actionTypes.FETCH_PRODUCTS_SUCCESS, onSuccessGenerator);
                    }

                    Сервер


                    Входной точкой для сервера будет добавленный в скрипты в package.json ./src/server.js:


                    require('babel-register')({
                      presets: ['env', 'react'],
                      plugins: ['transform-object-rest-spread', 'transform-regenerator']
                    });
                    
                    require('babel-polyfill');
                    
                    const port = require('./constants/socket-endpoint-port').default;
                    const clientReducer = require('./client').rootReducer;
                    
                    require('./server/bootstrap').start({ clientReducer, port });
                    

                    Стоит обратить внимание, что при старте нашего сервера ему передается клиентский reducer: это необходимо для того, чтобы сервер тоже мог поддерживать актуальное состояние списков, получая только действия, а не все состояние целиком. Заглянем в ./src/server/bootstrap.js:


                    import createSocketServer from 'socket.io';
                    import connectionHandler from './connection-handler';
                    import createStore from './store';
                    
                    export const start = ({ clientReducer, port }) =>
                    {
                      const socketServer = createSocketServer(port);
                      const store = createStore({ socketNamespace: socketServer.of('/'), clientReducer });
                    
                      socketServer.on('connection', connectionHandler(store));
                    
                      console.log('listening on:', port);
                    }

                    Серверная логика


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


                    • Добавление пользователя в комнату
                    • Создание комнаты и соответствующего ей store при добавлении пользователя в случае, если такой комнаты еще нет
                    • Удаление пользователя из комнаты
                    • Удаление store комнаты, если там не осталось пользователей
                    • Изменение состояние комнаты по пришедшему экшену
                    • Ретрансляция action всем пользователям в комнате

                    Все эти действия я предлагаю также описать с помощью redux и для этого сделать модуль ./src/server/modules/room-service, содержащий соответствующие saga и reducer. Там же мы сделаем простейшее хранилище для наших комнатных store ./src/server/modules/room-service/data/in-memory.js:


                    export default class InMemoryStorage
                    {
                      constructor()
                      {
                        this.innerStorage = {};
                      }
                    
                      getRoom(roomId)
                      {
                        return this.innerStorage[roomId];
                      }
                    
                      saveRoom(roomId, state)
                      {
                        this.innerStorage[roomId] = state;
                      }
                    
                      deleteRoom(roomId)
                      {
                        delete this.innerStorage[roomId];
                      }
                    }

                    Синхронизация состояний сервера и клиентов


                    При событии socket-сервера будем просто делать dispatch c соответствующим action из модуля room-service на серверном store. Опишем это в ./src/server/connection-handler.js:


                    import { actions as roomActions } from './modules/room-service';
                    import templateParseUrl from './utils/template-parse-url';
                    
                    const getRoomId = socket => templateParseUrl('/list/{roomId}', socket.handshake.headers.referer).roomId.toString() || socket.id.toString().slice(1, 6);
                    
                    export default store => socket =>
                    {
                      const roomId = getRoomId(socket);
                      store.dispatch(roomActions.userJoin({ roomId, socketId: socket.id }));
                    
                      socket.on('action', action => store.dispatch(roomActions.dispatchClientAction({ roomId, clientAction: action, socketId: socket.id })));
                      socket.on('disconnect', () => store.dispatch(roomActions.userLeft({ roomId })));
                    };

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


                    • Поменять состояние в соответствующем store
                    • Отправить этот action всем клиентам в комнате

                    За первое отвечает генератор ./src/server/modules/room-service/saga/dispatch-to-room.js:


                    import { call, put } from 'redux-saga/effects';
                    import actions from '../actions';
                    import storage from '../data';
                    
                    const getRoom = storage.getRoom.bind(storage);
                    
                    export default function* ({ socketServer, clientReducer }, action)
                    {
                      const storage = yield call(getRoom, action.roomId);
                      yield call(storage.store.dispatch.bind(storage.store), action.clientAction);
                      yield put(actions.emitClientAction({ roomId: action.roomId, clientAction: action.clientAction, socketId: action.socketId }));
                    };

                    Он же кладет следующий action модуля room-serviceemitClientAction, на который реагирует ./src/server/modules/room-service/saga/emit-action.js:


                    import { call, select } from 'redux-saga/effects';
                    
                    export default function* ({ socketNamespace }, action)
                    {
                      const socket = socketNamespace.connected[action.socketId];
                    
                      const roomEmitter = yield call(socket.to.bind(socket), action.roomId);
                    
                      yield call(roomEmitter.emit.bind(roomEmitter), 'action', action.clientAction);
                    };

                    Вот таким незамысловатым образом экшены попадают на остальных клиентов в комнате. В простоте, на мой взгляд, и кроется прелесть full-stack redux, а также мощь переиспользования клиентской логики для воспроизведения состояния на сервере и других клиентах.


                    Заключение


                    Хоть немного сумбурно, но мой рассказ подходит к концу. Я не акцентировал внимание на вещах, о которых уже есть множество статей и уроков (в любом случае, полный код приложения можно посмотреть в репозитории), а остановился на том, о чем информации, на мой взгляд, меньше. Так что если остались какие-то вопросы — прошу в комменты.

                    Original source: habrahabr.ru (comments, light).

                    https://habrahabr.ru/post/338142/


                    Метки:  

                    Поиск сообщений в rss_rss_hh_new
                    Страницы: 1437 ... 1148 1147 [1146] 1145 1144 ..
                    .. 1 Календарь