...

суббота, 24 января 2015 г.

Краткий курс компьютерной графики: пишем упрощённый OpenGL своими руками, статья 4б из 6

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

Чтобы не было совсем скучно, вот вам тонировка Гуро:



Я убрал текстуры, чтобы было виднее. Тонировка Гуро очень проста: добрый дяденька-моделёр дал нам нормальные вектора к каждой вершине объекта, они хранятся в строчках vn x y z файла .obj. Мы считаем интенсивность освещения для каждой вершины треугольника и просто интерполируем интенсивность внутри. Ровно как мы делали для глубины z или для текстурных координат uv!


Кстати, если бы дяденька-моделёр был не таким добрым, то мы могли бы посчитать нормали к вершине как среднее нормалей граней, прилегающих к этой вершине.


Текущий код, который сгенерировал эту картинку, находится здесь.




В евклидовом пространстве система координат (репер) задаётся точкой отсчёта и базисом пространства. Что означает, что в репере (O, i,j,k) точка P имеет координаты (x,y,z)? Это означает, что вектор OP задаётся следующим образом:


Теперь представим, что у нас есть второй репер (O',i',j',k'). Как нам преобразовать координаты точки, данные в одном репере, в другой репер? Для начала заметим, что, так как (i,j,k) и (i',j',k') — это базисы, то существует невырожденная матрица М, такая что:


Давайте нарисуем иллюстрацию, чтобы было нагляднее:


Распишем представление вектора OP:



подставим во вторую часть выражение замены базиса:


И это нам даст формулу замены координат для двух базисов.




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

Давайте поставим задачу следующим образом: мы хотим сделать так, чтобы камера находилась в точке e (eye), смотрела в точку c (center) и чтобы заданный вектор u (up) в нашей финальной картинке был бы вертикален.


Вот иллюстрация:



Это просто означает, мы делаем рендер в репере (c, x'y'z'). Но ведь модель задана в репере (O, xyz), значит, нам нужно посчитать репер x'y'z' и соответствующую матрицу перехода. Вот код, который возвращает нужную нам матрицу:



Matrix lookat(Vec3f eye, Vec3f center, Vec3f up) {
Vec3f z = (eye-center).normalize();
Vec3f x = (up^z).normalize();
Vec3f y = z^x;
Matrix res = Matrix::identity(4);
for (int i=0; i<3; i++) {
res[0][i] = x[i];
res[1][i] = y[i];
res[2][i] = z[i];
res[i][3] = -center[i];
}
return res;
}

Начнём с того, что z' — это просто вектор ce (не забудем его нормализовать, так проще работать). Как посчитать x'? Просто векторным произведением между u и z'. Затем считаем y', который будет ортогонален уже посчитанным x' и z' (напоминаю, что по условию задачи вектор ce и u не обязательно ортогональны). Самым последним аккордом делаем параллельный перенос в c, и наша матрица пересчёта координат готова. Достаточно взять любую точку с координатами (x,y,z,1) в старом базисе, умножить её на эту матрицу, и мы получим координаты в новом базисе! В OpenGL эта матрица называется матрицей вида (view matrix).




Если вы помните, то у меня в коде встречались подобные конструкции:

screen_coords[j] = Vec2i((v.x+1.)*width/2., (v.y+1.)*height/2.);



Что это означает? У меня есть точка Vec2f v, которая принадлежит квадрату [-1,1]*[-1,1]. Я хочу её нарисовать на картинке размером (width, height). Вектор (v.x+1) меняется в пределах от 1 до 2, (v.x+1.)/2. в пределах от нуля до единицы, ну а (v.x+1.)*width/2. заметает всю картинку, что мне и надо.

Но мы переходим к матричному представлению аффинных отображений, поэтому давайте рассмотрим следующий код:



Matrix viewport(int x, int y, int w, int h) {
Matrix m = Matrix::identity(4);
m[0][3] = x+w/2.f;
m[1][3] = y+h/2.f;
m[2][3] = depth/2.f;

m[0][0] = w/2.f;
m[1][1] = h/2.f;
m[2][2] = depth/2.f;
return m;
}



Он строит вот такую матрицу:



Это означает, что куб мировых координат [-1,1]*[-1,1]*[-1,1] отображается в куб экранных координат (да, куб, т.к. у нас есть z-буфер!) [x,x+w]*[y,y+h]*[0,d], где d — это разрешение z-буфера (у меня 255, т.к. я храню его непосредственно в чёрно-белой картинке).

В мире OpenGL эта матрица называется viewport matrix.




Итак, резюмируем. Модели (например, пресонажи) сделаны в своей локальной системе координат (object coordinates). Они вставляются в сцену, которая выражена в мировых координатах (world coordinates). Переход от одних к другим осуществляется матрицей Model. Дальше, мы хотим выразить это дело в репере камеры (eye coordinates), матрица перехода от мировых к камере называется View. Затем, мы осуществляем перспективное искажение при помощи матрицы Projection (см. статью 4а), она переводит сцену в так называемые clip coordinates. Ну и затем мы отображаем это всё дело на экране, матрица прехода к экранным координатам это Viewport.

То есть, если мы прочитали точку v из файла, то чтобы показать её на экране, мы проделываем умножение



Viewport * Projection * View * Model * v.

Если посмотреть в код на гитхабе, то мы увидим такие строчки:



Vec3f v = model->vert(face[j]);
screen_coords[j] = Vec3f(ViewPort*Projection*ModelView*Matrix(v));



Так как я рисую только один объект, то матрица Model у меня просто единичная, я её объединил с матрицей View.

Широко известен следующий факт:

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

Что-что?!


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



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


Итак, у нас есть вектор нормали a=(A,B,C). Мы знаем, что плоскость, проходящая через начало координат, и имеющая нормалью вектор a (на нашей иллюстрации это наклонное ребро левого треугольника), задаётся уравнением Ax+By+Cz=0. Давайте запишем это уравнение в матричном виде, причём сразу в однородных координатах:



Напоминаю, что (A,B,C) — это вектор, поэтому получает ноль в последнюю компоненту при погружении в четырёхмерное пространство, а (x,y,z) — это точка, поэтому к нему приписываем 1.


Давайте добавим единичную матрицу (М, умноженная на обратную к ней) в середину этой записи:


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

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


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


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


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


Счастливого программирования!


Recommended article: Chomsky: We Are All – Fill in the Blank.

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


Must-read книги за 2014 год по ИБ и программированию

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

Тактика хакера: практическое руководство по тестированию на проникновение (The Hacker Playbook: Practical Guide To Penetration Testing)





Книга написана в стиле планирования футбольной игры. Здесь подробно и пошагово разобраны проблемы и трудности, с которыми сталкиваются специалисты по безопасности, тестируя системы защиты. В частности, рассматриваются атаки на различные типы сетей, обход антивирусов и взлом систем безопасности. Автор книги — Питер Ким, специалист по IT-безопасности с многолетним опытом, CEO компании Secure Planet.



Искусство анализа памяти: обнаружение вредоносного ПО и угроз в Windows, Linux и Mac (The Art of Memory Forensics: Detecting Malware and Threats in Windows, Linux, and Mac Memory)





Как не трудно догадаться, книга посвящена анализу оперативной памяти с целью предотвращения и расследования киберпреступлений. Текст основан на программе 5-дневного мастер-класса, который преподают авторы. Причём это одна из немногих книг, посвящённая данной тематике. Здесь рассмотрены следующие темы:



  • Как анализ энергозависимой памяти способствует расследованию киберпреступлений

  • Порядок действий по обнаружению скрытого вредоносного ПО и комплексных угроз

  • Использование opensource-инструментов для обнаружения и анализа

  • Как защитить память компьютера от потенциальных угроз




Инструкция по реагированию на киберугрозы (Blue Team Handbook: Incident Response Edition: A condensed field guide for the Cyber Security Incident Responder)





Книга представляет собой сборник инструкций и рекомендаций по реагированию на инциденты, связанные с информационной безопасностью. Здесь описаны распространённые способы атак, программные инструменты, методология анализа сетей, примеры использования tcpdump и Snort IDS, а также многие другие вопросы. В основном содержание книги отражает опыт авторов, имеющих опыт преподавания и работы в сфере сетевой безопасности.


Практическое пособие по кибербезопасности для руководителей (Cybersecurity for Executives: A Practical Guide)





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


Социальная инженерия: человеческий фактор и безопасность (Unmasking the Social Engineer: The Human Element of Security)





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


Пуленепробиваемые SSL и TLS: развёртывание SSL/TLS и PKI на серверах и в веб-приложениях (Bulletproof SSL and TLS: Understanding and Deploying SSL/TLS and PKI to Secure Servers and Web Applications)





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


Реверсинг на практике: x86, x64, ARM, ядро Windows, утилиты и обфускация (Practical Reverse Engineering: x86, x64, ARM, Windows Kernel, Reversing Tools, and Obfuscation)





Хакеры могут применять реверсинг для обнаружения уязвимостей в системах. В книге рассказывается об особенностях обратного инжиниринга в архитектурах х86, х64 и ARM. Рассмотрены технологии защиты виртуальных машин, а также использование руткитов и пошаговый анализ драйверов для режима ядра Windows. Последний вопрос, кстати, нечасто освещается в литературе. Книга ценна своим систематической подачей материала, с большим количеством практических примеров и самостоятельных заданий. В бонусной главе также рассматриваются инструменты для реверсинга.


Броненосец Java: создание защищённых веб-приложений (Iron-Clad Java: Building Secure Web Applications)





В книге рассматриваются такие вопросы, как:



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

  • создание системы жёсткого контроля доступа в многопользовательских веб-приложениях,

  • противодействие межсайтовому скриптингу, подделке межсайтовых запросов и кликджекингу (clickjacking)

  • защита важных данных во время хранения и пересылки,

  • предотвращение атак с помощью внедрения кода, включая SQL,

  • обеспечение безопасности операций ввода-вывода файлов и загрузки,

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




Создание инфраструктуры безопасности облачного проекта (Building the Infrastructure for Cloud Security: A Solutions View (Expert's Voice in Internet Security))





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




Умный способ изучения JavaScript (A Smarter Way to Learn JavaScript: The new approach that uses technology to cut your effort in half)





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


Мануал жизни разработчика приложений (Soft Skills: The software developer's life manual)





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


Создание аркадных игр на Python и с помощью Pygame (Program Arcade Games: With Python and Pygame)





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


Учимся программированию на С за один день (C Programming Success in a Day: Beginners' Guide To Fast, Easy and Efficient Learning of C Programming)





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


Шаблоны программирования игры (Game Programming Patterns)





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


Разработка игр на Python (Game Development with Python)





У вас есть прекрасная идея для игры, но не хватает навыков и знаний для её реализации? Вы хотите узнать профессиональные подходы к разработке игр? Хотите создавать игры с использованием физики и искусственного интеллекта? Тогда эта книга для вас. Здесь вы найдёте не только теорию, но и десятки примеров кода и заданий для закрепления пройденного материала. Несмотря на то, что автором выбран язык Python, это не столь важно, поскольку куда важнее сам подход к разработке игр.


Планирование UX в приложениях для экосистемы устройств (Designing Multi-Device Experiences: An Ecosystem Approach to User Experiences across Devices)





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


JavaScript и JQuery: веб-разработка интерактивного фронт-энда (JavaScript and JQuery: Interactive Front-End Web Development)





С помощью этой книги вы:



  • научитесь основным концепциям программирования,

  • получите информацию об основных элементах языка JavaScript и сможете писать собственные скрипты,

  • сделаете первые шаги в использовании JQuery, который помогает упростить процесс написания скриптов,

  • узнаете, как самостоятельно повторить подсмотренные на других сайтах элементы и механики вроде прокруток, фильтров данных, форм, обновления контента с помощью Ajax и т.д.




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

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


Recommended article: Chomsky: We Are All – Fill in the Blank.

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


Звук на чипе AY-3-8910 (или Yamaha YM2149F) родом с ZX Spectrum на PC через USB

Прошло около года, с момента успешного подключения музыкального синтезатора YM2149F к LPT порту компьютера. LPT это конечно хорошо, однако время не стоит на месте, и найти компьютер или ноутбук с LPT портом становится все сложнее и сложнее. Да и сам автор (то есть я) устал лазить каждый раз под стол, где стоит системник, и перетыкать LPT плату на что-то другое, например программатор (у меня LPT-программатор Willem, ну да не суть). Поэтому на сей раз подключать чип YM2149F будем к USB. Ну и конечно, чтобы соотвествовать эпохе, будем это делать на копеечном древнем микроконтроллере PIC16F628.

image


Вкратце, YM2149F (или ее функциональный аналог AY-3-8910) — микросхема звукового трехголосного синтезатора, применялась в старых компьютерах типа Atari ST, Amstrad CPC, ZX Spectrum, MSX и некоторых других для проигрывания музыки. В России чип приобрел определенную известность благодаря установки в различные клоны ZX Spectrum'а. За время шествования ZX Spectrum по бывшему СССР музыкантами были написаны тысячи мелодий под этот звуковой программируемый генератор. Да и сейчас можно вполне найти людей, создающих музыку именно под этот чип. В конце статьи будут приведены ссылки на огромнейший архив чип-тюнов для YM/AY на сотни часов непрерывного прослушивания.


Демо




Как и в прошлый раз, перед началом, даю сразу ссылку на прослушивания конечного результата: http://ift.tt/1n2D5hJ Последние записи сделаны как раз с этого устройства. Записывал так-себе плеером, который пишет максимум в 128Kb/s MP3, поэтому в реальности устройство звучит «ярче». Но составить общее представление о звуке можно.

Железо




Почему такой странный выбор контроллера? Почему не AVR/ARM/iCore i7/FTDI на худой конец? Частично ответ на этот вопрос дан в начале топика: ретро синтезатору — ретро микроконтроллер! Тем более, что у AY-3-8910 и фирмы Microchip, можно сказать, общие корни. А вообще, так сложилась серия странных обстоятельств. Во-первых я наткнулся в интернете на библиотеку, реализующую программный (софтварный) стек USB 1.1 для микроконтроллеров PIC16F628 — вот эта библиотека: 16FUSB. Во-вторых, у меня давно лежала и пылилась парочка PIC16F628A, которые я не знал куда деть. В третьих, на компе уже стоял настроенный софт (MPLABX, MPASM) и имеется программатор для PIC. Ну и в отличии от программного стека V-USB на AVR, известного многим, на PIC'ах без аппаратного USB проектов мало или даже вообще нет. А это значит, что нужно восстановить историческую несправедливость.

Вот типовая схема включения с сайта библиотеки 16fusb:


image


В комплекте с библиотекой 16fusb идет хороший пример под названием «direct-io». Смысл прост — посылаем через USB байт и он «отображается» на восьми ножках микроконтроллера. Так же можно посылать дополнительно два управляющих сигнала, то есть еще два бита (или две ножки). И в обратном направлении, то есть от контроллера к хосту (компьютеру).

image


Для управления YM2149F используется восьмибитная шина данных D0-D7 и три управляющих сигнала BC1, BDIR и RESET. BC1 и BDIR управляют выбором адреса регистра и его значением, а так же переводят микросхему в неактивное состояние. Сигнал RESET используется для сброса всех регистров на первоначальное значение. Таким образом, чтение из PIC в компьютер не нужно; нужна только возможность посылать команды на YM. И нужен еще третий управляющий сигнал, а значит еще одна ножка МК.


В своей прошивке для управления конкретно YM2149F было сделано следующее:



  • выкинуто все, что связано с чтением сигналов из PIC в хост (компьютер) для увеличения быстродействия обработки реквестов USB;

  • состояние направлений портов ввода-вывода жёстко задано при инициализации МК и не изменяется в процедурах выдачи байта на ноги.

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

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

  • пофикшен глюк с зацикливанием PIC через несколько тысяч пакетов (развернут цикл RxLoop в файле isr.asm, вместо goto RxLoop вставлена проверка на признак конца пакета)

  • что-то еще, не помню


Как уже сказано выше, возникает потребность в еще одном управляющем сигнале — RESET, а свободных ножек уже нет. Поэтому для тактирования PIC применен кварцевый генератор, а не кварц, тем самым высвобождая одну ногу МК (RA6), необходимую для управления сигналом RESET. Нога RA5, торчащая в воздухе, в данном семействе работает только на вход и не может быть использована для управления выходным сигналом. На нее можно было-бы переложить функционал по отлавливанию конца USB пакета (EOP) с ножки RB2, однако это не так просто — в отличии от ножки RB2 ножка RA5 делит функционал с MCLR и VPP для программирования и внутри организован вход как триггер шмитта. Ему просто не хватит напряжения после диодов для сработки. С другой стороны, для тактирования YM2149F собран генератор на микросхеме 74HC02 и кварце 3.579545 MHz. Можно было бы попробовать использовать вторую свободную половину микросхемы для сборки аналогичного генератора и для PIC, но остановило два момента: 1) у меня нету кварца на 24МГц (а кварцевый генератор был, с какой-то древней мамки) 2) я не знаю, как поведет себя 74HC02, если с «разных боков» у нее будут разные частоты, причем одна из них довольно высокая (24МГц все таки очень большая частота). Еще один из вариантов, как освободить ногу RA6 для кварца: Сигналы BC1 и BDIR принимают только такие значения:



BC1 BDIR
0 0
0 1
1 1




И никогда BC1 = 1, BDIR = 0. Это можно использовать как RESET, добавив NOT и NOR логику из половинки микросхемы 74HC02 и проинвертировав сигнал на выходе с помощью транзистора. Конечно для выдачи BC = 1 и BDIR = 0 нужно немного подправить прошивку.

И еще, нога RA4, которая управляет сигналом BDIR, с открытым коллектором, поэтому ее обязательно нужно подтянуть к питанию — на схеме это 10K резистор R5.


Софт




Со стороны компьютера, в качестве музыкального проигрывателя, выступает отличный кросс-платформенный плеер чип-тюнов ZX Tune:

image

Напрямую он не поддерживает USB, зато если находит у себя в директории одну из библиотек http://ift.tt/1yOLUEd, то позволяет переключится в настройках вывода звука на YM-LPT (для прошлого проекта), а затем использует функцию __stdcall void DlPortWritePortUchar(unsigned short port, unsigned char val); для выдачи байт YM2149. Порт 0x378 данные, Порт 0x37a передача управляющих сигналов (D1 — ~BDIR, D2 — BC1, D3 — ~RESET). Таким образом, можно написать маленькую библиотеку-заглушку с одной единственной функцией DlPortWritePortUchar, в которой перенаправлять выдачу байт на USB-устройство, что и было сделано. Я просто взял исходники библиотеки inpout32 за основу и написал функцию-заглушку для перенаправления выдачи байт на это устройство. В итоге, достаточно положить эту библиотеку-заглушку inpout32.dll или inpoutx64.dll, в зависимости от используемой версии плеера (x86/x64), в одну директорию с плеером ZX Tune, запустить его и в настройках звука переместить устройство aylpt на самый верх (как на скриншоте выше).


Скачать бесплатно и без СМС




Драйвера для Win XP, Win 7 (x32/x64) можно скачать здесь: 16FUSB_driver-libusb-win32-1.2.6.0.zip

Схема устройства: ym-usb_scheme_1.0.rar

Скомпилированная прошивка (.hex) и скомпилированные DLL-заглушки: ym-usb_firmware_and_DLLs_v1.2.rar

Исходные коды прошивки: ym-usb_PIC16F628A_source_v1.2.rar

Исходные коды библиотеки-заглушки: inpout32-64_DLL_source_v1.2.rar

General Instruments AY-3-8910 / 8912 Programmable Sound Generator (PSG) data Manual: http://ift.tt/1JyV7Sv

Тема на форуме ZX.PK.ru, из которой «родилось» устройство: http://ift.tt/1ycigUg


Огромный архив трекерной музыки: Modland (ФТП)

ZX музыка онлайн: http://zxtunes.com/


Всем добра!


Recommended article: Chomsky: We Are All – Fill in the Blank.

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


Получение участников сообщества vk.com за считанные секунды

Ни для кого не секрет, что VK API возвращает за один запрос к методу groups.getMembers не более 1 000 участников. В одну секунду вы можете получить максимум 3 000 участников, так как установлено ограничение на количество запросов в секунду до 3. Эту проблему решает метод execute , с помощью которого вы можете получить более 100 000 участников за одну секунду и до 25 000 участников за один запрос. В этой статье я расскажу Вам, как я это реализовал.



Без использования метода execute , процесс получения участников группы с аудиторией в 4 000 000 человек займет примерно около 22 минут, и нам нужно будет выполнить около 4 000 запросов к API. С помощью метода execute мы ускорим этот процесс примерно до 40 секунд и выполним всего около 160 запросов.

Содержание:




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

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

Что поддерживает VKScript и что это?




Это язык похожий на JavaSсript или ActionScript. Алгоритм должен завершаться командой return %выражение%. Операторы должны быть разделены точкой с запятой.

Поддерживаются:



  • арифметические операции

  • логические операции

  • создание массивов и списков ([X,Y])

  • parseInt и parseDouble

  • конкатенация (+)

  • конструкция if

  • фильтр массива по параметру (@.)

  • вызовы методов API, параметр length

  • циклы, используя оператор while

  • методы Javascript: slice, push, pop, shift, unshift, splice, substr

  • оператор delete

  • присваивания элементам массива, например: row.user.action = «test»;


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




Чтобы работать с Open Api нам нужно подключить библиотеку OpenApi.




Пройдем авторизацию приложения VK и объявим массив:



VK.init({
apiId: 4235235 // ID вашего приложения VK
});

var membersGroups = []; // массив участников группы


Получим информацию о группе:



// получаем информацию о группе и её участников
function getMembers(group_id) {
VK.Api.call('groups.getById', {group_id: group_id, fields: 'photo_50,members_count', v: '5.27'}, function(r) {
if(r.response) {
$('.group_info')
.html('
'
+ r.response[0].name
+ '
Участников: ' + r.response[0].members_count);
getMembers20k(group_id, r.response[0].members_count); // получаем участников группы и пишем в массив membersGroups
}
});
}


Чтобы получить участников группы мы будем использовать execute, за один запрос мы получим 25 000 участников. Execute позволяет сделать до 25 запросов описанных с помощью языка VKScript. В параметре code нам нужно передать алгоритм на языке VKScript. Я это сделал следующим образом.



var id_app = [2866099, 4195289, 4195287, 4195284, 4161477, 4161462, 4149350, 4149349, 4149336, 2394133, 3043953];
var apiID_index = Math.floor(Math.random() * (id_app.length));
VK.init({
apiId: id_app[apiID_index] // ID вашего приложения VK
});

var membersGroups = []; // массив участников группы
getMembers(30666517);

// получаем информацию о группе и её участников
function getMembers(group_id) {
VK.Api.call('groups.getById', {group_id: group_id, fields: 'photo_50,members_count', v: '5.27'}, function(r) {
if(r.response) {
$('.group_info')
.html('
'
+ r.response[0].name
+ '
Участников: ' + r.response[0].members_count);
getMembers20k(group_id, r.response[0].members_count); // получаем участников группы и пишем в массив membersGroups
}
});
}

// получаем участников группы, members_count - количество участников
function getMembers20k(group_id, members_count) {
var code = 'var members = API.groups.getMembers({"group_id": ' + group_id + ', "v": "5.27", "sort": "id_asc", "count": "1000", "offset": ' + membersGroups.length + '}).items;' // делаем первый запрос и создаем массив
+ 'var offset = 1000;' // это сдвиг по участникам группы
+ 'while (offset < 25000 && (offset + ' + membersGroups.length + ') < ' + members_count + ')' // пока не получили 20000 и не прошлись по всем участникам
+ '{'
+ 'members = members + "," + API.groups.getMembers({"group_id": ' + group_id + ', "v": "5.27", "sort": "id_asc", "count": "1000", "offset": (' + membersGroups.length + ' + offset)}).items;' // сдвиг участников на offset + мощность массива
+ 'offset = offset + 1000;' // увеличиваем сдвиг на 1000
+ '};'
+ 'return members;'; // вернуть массив members

VK.Api.call("execute", {code: code}, function(data) {
if (data.response) {
membersGroups = membersGroups.concat(JSON.parse("[" + data.response + "]")); // запишем это в массив
$('.member_ids').html('Загрузка: ' + membersGroups.length + '/' + members_count);
if (members_count > membersGroups.length) // если еще не всех участников получили
setTimeout(function() { getMembers20k(group_id, members_count); }, 333); // задержка 0.333 с. после чего запустим еще раз
else // если конец то
alert('Ура тест закончен! В массиве membersGroups теперь ' + membersGroups.length + ' элементов.');
} else {
alert(data.error.error_msg); // в случае ошибки выведем её
}
});
}


Использование:



getMembers(IDгруппы);




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

image

Пример работы: http://ift.tt/1CAB5Hd

Исходники: http://ift.tt/1EErpKT


Recommended article: Chomsky: We Are All – Fill in the Blank.

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


Доработка USB-стека в микроконтроллерах STM32 и TivaC

Наличие USB порта в современных микроконтроллерах открывает широкие возможности для самостоятельного изготовления разнообразных управляемых с компьютера устройств. На практике, однако, выясняется, что поставляемые производителем библиотеки для работы с USB нуждаются в доработке. Если вам интересен опыт подобной доработки для двух популярных семейств МК — добро пожаловать под кат.


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




Итак, мы хотим сделать устройство, которое обменивается с компьютером сообщениями произвольной длины через USB порт. Самый простой способ сделать это — воспользоваться USB классом символьных устройств (CDC), известным также под названием 'виртуальный последовательный порт'. Тогда на хост-системе, к которой вы подключите ваше устройство, автоматически будет создан последовательный порт, через который вы сможете обмениваться данными с устройством, работая с ним как с обычным файлом. На практике, однако, выясняется, что некоторые необходимые для этого функции в USB-стеке производителя либо не реализованы вовсе, либо реализованы с ошибками. Мы начнем с рассмотрения микроконтроллеров STM32 (первый случай) и закончим другим популярным семейством — Texas Instruments Tiva C (второй случай). Оба семейства имеют архитектуру ARM Cortex M4.

STM32 — просто добавь кода




Микроконтроллеры STM обычно имеют богатый функционал при весьма демократичной цене. Производитель поставляет широкий спектр библиотек на все случаи жизни. Среди них есть и библиотеки для поддержки USB, и библиотека для работы с прочей периферией, имеющейся на кристалле. В последнее время все эти библиотеки были объединены в один мега-пакет под названием STM32Cube. При этом, однако, о совместимости особо не заботились и поменяли все, что только смогли поменять, включая названия полей в структурах, описывающих конфигурацию портов ввода-вывода, при том, что само название структуры осталось прежним. Интресно, что есть еще и третий вариант примеров и библиотек, который можно найти на сайте stm32f4-discovery.com/. Однако, автор этого варианта очень любит переименовывать файлы, позаимствованные у STM, дабы увековечить свои инициалы, что тоже не добавляет совместимости со всем остальным кодом. Учитывая все вышеизложенное, я решил взять за основу последний до-кубический вариант библиотек, поставляемых STM. Сейчас их можно найти в комплекте поставки компиляторов (я использую IAR). Чтобы потом долго не искать, библиотеки включены в состав проекта, который вы можете взять из гита по ссылке внизу. Для экспериментов я использовал плату STM32F4DISCOVERY http://ift.tt/1agoxVt. Если у вас другая плата и код сразу не заработал, дело скорее всего в частоте внешнего кварцевого генератора. Хотя библиотеки изобилуют всяческими макроопределениями, и в последней версии библиотек среди них появился и макрос для внешней тактовой частоты, в коде этот параметр по-прежнему прописан в виде числа без всяких комментариев, видимо, чтобы разработчики не теряли форму и не забывали читать мануал. Вы можете найти это число — тактовую частоту в мегагерцах — в файле system_stm32f4xx.c в определении макроса PLL_M.

Итак, берем за основу готовый пример, который перекладывает данные из USB в последовательный порт микроконтроллера и обратно. Последовательный порт нам не понадобится, а данные мы будем просто перекладывать из входного потока в выходной, то есть реализуем эхо. С помощью PuTTY убеждаемся, что оно работает. Но этого недостаточно. Для обмена данными с устройством нам понадобится слать много больше одного символа за раз. Пишем тестовую программу на питоне, которая шлет посылки случайной длины и вычитывает ответ. И тут нас ждет сюрприз. Тест работает, но недолго, после чего очередная попытка чтения либо зависает навсегда, либо завершается по таймауту, если он выставлен. Исследование проблемы с помощью отладчика показывает, что МК таки отослал все полученные данные, причем последняя посылка имела длину 64 байта. Что же произошло?


USB-стек на хост-системе имеет многослойную структуру. На уровне драйвера данные получены, но остались у него в кэше. Драйвер передает закэшированные данные приложению тогда, когда приходят новые данные и вытесняют старые, либо когда драйвер узнает, что новых данных пока ожидать не следует. Откуда же он может получить это знание? USB шина передает данные пакетами. Максимальный размер пакета в нашем случае как раз 64 байта. Если в очередном пакете данных пришло меньше, значит новых данных пока можно не ждать, и это является сигналом для того, чтобы передать приложению все полученные данные. А если данных пришло ровно 64 байта? На этот случай в протоколе предусмотрена посылка пакета нулевой длины (ZLP), который и является сигналом прерывания потока. Получив его, драйвер понимает, что новых данных пока ожидать не следует. В нашем случае он его не получил потому, что разработчики USB стека про ZLP просто ничего не знали.


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


Tiva C — слоеный пирог с багами




Для экспериментов была взята плата EK-TM4C123GXL http://ift.tt/19wxKgd. Для компиляции необходим пакет библиотек TivaWare http://ift.tt/1qPmkZK. Изучение библиотек показывает, что разработчики не обошли вниманием ни ZLP ни проблему буферизации — во входном и выходном канале имеются готовые к использованию кольцевые буфера. Однако автоматический тест дает все тот же результат — обмен данными внезапно прекращается. С помощью отладчика выясняется, что на этот раз данные застряли в кольцевом буфере передачи, причем с размером последнего пакета, а значит и с ZLP, проблема не связана никак.

Выявить проблему удается только путем тщательного изучения исходников библиотек. Оказывается, что для посылки ZLP необходимо выставить специальный флажок, который по умолчанию не выставлен. Возможно, это обстоятельство и подтолкнуло других разработчиков к тому, чтобы добавить код, посылающий ZLP еще в одном месте — на более низком уровне USB-стека, и уже без флажка. Это изменение и внесло баг, приводящий к остановке передачи. Проблема возникает следующим образом. Передатчик получает следующий пакет, когда заканчивается передача предыдущего, либо если предыдущего не было, а приложение добавило данные в буфер передачи. Код, который инициирует передачу, получает нотификацию о завершении передачи предыдущего пакета от нижнего уровня USB-стека. Проблема в том, что если нижний уровень стека инициировал передачу ZLP, то нотификацию о завершении он не присылает, т.к. инициировал передачу он сам. Верхний уровень не начинает передачу данных, пока передатчик занят передачей ZLP пакета, и не начинает передачу после ее завершения, поскольку не получает нотификации — процесс передачи останавливается. Исправить проблему очень просто — нужно убрать код нижнего уровня, посылающий ZLP, и предоставить это верхнему уровню стека. Вторая проблема, требующая решения, связана с тем, что процедура, начинающая передачу, может быть вызвана как из контекста обработчика прерывания (по завершении передачи), так и из контекста приложения по добавлении данных в буфер передачи. Чтобы сериализовать вызовы этой процедуры из разных контекстов, нужно запрещать прерывания на время ее исполнения.


Исходный код




Лежит тут http://ift.tt/1BVbfyz.

В папках stm и ti лежат по 2 тестовых проекта — usb_cdc_echo и usb_cdc_api. Первый просто посылает все полученные данные обратно, второй реализует пакетный протокол, который вы можете легко адаптировать под свои нужды. В папке tools — тестовые скрипты на питоне.

Recommended article: Chomsky: We Are All – Fill in the Blank.

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


Исследование проектов на фриланс-бирже Odesk глазами веб-разработчика


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


+ примеры использованных для работы с Odesk API скриптов и описание нескольких подводных камней

+ анализ более чем 200 000 выполненных проектов на общую сумму свыше $40 000 000 USD

+ знакомство с программой для визуализации отчетов Tibco Spotfire

+ немаленькое количество разных интересных графиков

+ скандалы, интриги, расследования



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


1) Подготовка к сбору данных


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


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


2) Сбор данных


Приведенный ниже скрипт на Ruby позволяет выгружать данные по проектам с интересующими вас тегами, просто перечислите их на 32 строке. При желании вы можете выгружать данные и по открытым вакансиям, заменив «completed» на «open» в 34 строке. Я выгружал базу кусками, начиная в первую очередь с интересующих меня тегов и постепенно добавляя в список попадавшие с ними заодно в базу смежные теги, которые меня заинтересовали. Таким образом данные я выгрузил с одной стороны довольно быстро, с другой — за несколько итераций захватил множество популярных тегов. Основные из них: html, html5, css, ccc3, javascript, jquery, php, wordpress, magento, drupal, python, django, ruby, ruby-on-rails, mysql, postgresql, mongodb, linux.


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



require 'odesk/api'
require 'odesk/api/routers/auth'
require 'odesk/api/routers/jobs/search'

require 'mysql2'
require 'sequel'
db = Sequel.connect('mysql2://XXX:XXX@127.0.0.1/odesk')

config = Odesk::Api::Config.new({
'consumer_key' => 'XXX',
'consumer_secret' => 'XXX',
'access_token' => 'XXX', # assign if known
'access_secret' => 'XXX', # assign if known
'debug' => false
})

# setup client
client = Odesk::Api::Client.new(config)

# run authorization in case we haven't done it yet
# and do not have an access token-secret pair
if !config.access_token and !config.access_secret
authz_url = client.get_authorization_url

puts "Visit the authorization url and provide oauth_verifier for further authorization"
puts authz_url
verifier = gets.strip
p client.get_access_token(verifier)
else
jobs = Odesk::Api::Routers::Jobs::Search.new(client)

search_skills = %w(ruby-on-rails)
search_skills.each do |search_skill|
params = {job_status: 'completed', skills: search_skill, paging: '0;100'}
begin
p params

results = jobs.find(params)
p results

results['jobs'].each do |job|
p job['id']
p job['date_created']

job['client'].each {|key, value| job['client_' + key] = value}
job.delete 'client'

if db[:jobs][id: job['id']].nil?
skills = job.delete 'skills'
db[:jobs].insert job

# may not exist!
unless skills.nil?
# eliminate joined tags (bug in Odesk API)
skills.map! { |s| s.split(',') }
skills.flatten.uniq.each do |skill|
s = db[:skills][skill: skill]
s_id = s.nil? ? db[:skills].insert(skill: skill) : s[:id]
db[:jobs_skills].insert job_id: job['id'], skill_id: s_id
end
end
end
end

done = params[:paging].split(';')[0].to_i + 100
params[:paging] = "#{done};100"
p done.to_f / results['paging']['total'].to_f * 100
end until results['paging']['offset'] + results['paging']['count'] >= results['paging']['total']
end

# clean too old entries
db[:jobs_skills].where('job_id IN ?', db[:jobs].select(:id).where('date_created <= ?', '2007'))
db[:jobs].where('date_created <= ?', '2007').delete
end


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


3) Подготовка к обработке данных


Для построения визуализаций в интерактивном режиме удобно подключить напрямую к БД какую-нибудь программу для бизнес-аналитики. Я использовал Tibco Spotfire Desktop. На официальном сайте указано, что программа вроде как платная, но при скачивании ознакомительной версии никаких ограничений в ней я не нашел (хотя искал очень тщательно) и с удовольствием этим воспользовался. Также можно воспользоваться бесплатными продуктами от QlikView. Ну а Excel я изначально даже не рассматривал конкретно для этой задачи — маловато там нужных инструментов для продвинутых графиков. Пожалуй, самая продвинутая программа в этой нише из тех что я видел — Tableaue Desktop. Более интуитивная, чем Spotfire. С бОльшим количеством тонких настроек. Но она бесплатная только в течение 14-дневного trial периода, а так стоит $2000.


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


image


В 2007 году таких проектов было всего 613 с общим бюджетом $250 000. За период с 2004 по 2007 бюджеты и того меньше, так что эти данные я просто выкинул из рассмотрения. Также изначально в районе 2011-2012 годов возник на удивление большой «горб» и я увидел его причину, когда отдельно рассматривал разбивку проектов по размерам их бюджетов. В базу затесалось меньше десятка явно тестовых проектов с бюджетами от $50 000 до $1 000 000, которые в сумме дали бюджет аж почти на $5 000 000 и неслабо перекосили всю статистику. Естественно, эти аномалии я тоже выкинул из базы. Проектов с бюджетом выше $10 000 оказалось всего 92 штуки и они меня мало интересовали, поэтому их я тоже вынес за рамки исследования. Итого за 2007-2014 года получилось 103 159 завершенных проектов с фиксированной оплатой и общим бюджетом $20 000 000. Вот этот период мы далее и будем анализировать. При этом в лучший 2012 год было завершено 23 000+ таких проектов с общим бюджетом $5 000 000+


Немного забегая вперед скажу, что сначала я сильно увлекся различными графиками и до меня не сразу дошел вопрос «где деньги, Зин»? Ведь Odesk официально заявляет http://ift.tt/1t95v11, что уже к августу 2013 года заказчики потратили на бирже миллиадр долларов! На фоне этого заявления анализируемые 20 миллионов кажутся сущими крохами и никак не стыкуются с реальностью. Ведь 20 миллионов на зарплату веб-разработчикам это сущие копейки, когда речь идет о разработчиках по всему миру! Так что по этому поводу позже пришлось провести отдельное мини-расследование. Как я и обещал, сканадалы-интриги ждут вас в конце статьи :) Пока же попробуем выжать максимум из тех данных, которые получилось собрать на данный момент.


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


image


Тут все понятно почти без слов. PHP рулит и педалит — 124 182 проекта. Сопутствующие html, javascript, mysql, css тоже тут как тут — у них в среднем по 45 000 проектов. Из CMS явный лидер WordPress, Joomla уступает ему аккурат в два раза. Drupal встречается в два раза реже, чем Joomla. Magento всего в 1,5 раза менее популярна чем Drupal и вообще единственная специализированная CMS для магазинов, которая видна на этой карте. Python и Ruby on Rails примерно равны по числу проектов — в районе 5000.


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


image


Ну и посмотрим ради интереса на «длинный хвост» тегов:


image


Ну а где же здесь фреймворки типа Yii, Symfony, Zend, Laravel, AngularJS и прочие модные штучки? А их нет! По крайней мере заказчики с Odesk больше всего платят за проверенные годами повсеместно распространенные простые технологии. Предполагаю, что всякие модные крутые современные штучки это либо удел избранных, либо некритичное пожелание заказчиков (т.е. без тега), либо их выбор остается на усмотрение разработчика. Например, в самом конце данного графика, прямо перед ecommerce-consulting оказался node.js с общим бюджетом всего $155 000, что и в подметки не годится PHP с бюджетом $12 000 000, т.е. в 80+ раз больше! Этот момент был для меня откровением. Я конечно предполагал различие как минимум на порядок, но никак не мог представить, что оно так велико. Почти то же самое можно сказать и про разработчиков на Python/Ruby — их суммарные бюджеты меньше $600 000.


Впрочем, общие бюджеты — далеко не единственный интересный показатель. Ведь понятно же, что на PHP не только огромное количество проектов, но и разработчиков. При этом общий небольшой бюджет какого-то тега не означает, что разработчик будет жить впроголодь. Уверен, что множество проектов на PHP просто копеечные. Давайте посмотрим, какие навыки в среднем ценятся больше. Тут самое время оценить стоимость «среднего» проекта для того или иного тега. К сожалению, Spotfire не разрешил применить функцию медианного среднего для расчета бюджета на следующем графике. Но ограничимся хотя бы средним арифметическим, при этом сопоставим его с суммарным бюджетом:


image


О, вот и свежий взгляд! PHP это на самом деле черная дыра, которая поглощает разработчиков проектов со средним бюджетом $200 :) Хуже только Joomla и WordPress — причем как по числу проектов, так и по их средней стоимости. Ну оно и понятно в принципе. При этом Ruby on Rails — самые высокооплачиваемые в среднем разработчики, средний проект с их привлечением стоит $380. Если убрать из окошка PHP, четко увидим, что хорошо оплачиваются ajax — $347, flash — $323, html/xml — $294, mysql — $282.


image


Также я вручную с помощью SQL посчитал пересечения самых популярных тегов. Было интересно — как обстоят дела у тех, кто знает только php без js или наоборот. Как говорилось в одной КВНовской сценке: «я, конечно, догадываюсь, но хотелось бы знать точно...» Проекты с тегом php: 119 427, И БЕЗ тега javascript: 98 800. Проекты с тегом javascript: 45 179, И БЕЗ тега PHP: 24 553. Т.е. если берешься за проект с PHP — обычно, будь добр и JS знать! А вот в случае с JS дополнительно знать PHP нужно всего лишь в половине случаев. html/css уже дальше я проверять не стал, их и так все знают :)


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


image


Обратите внимание на выдающиеся столбики с отметками $500 и $1000. Видимо это психологически важные для заказчиков уровни. Как сказал бы мой знакомый трейдер — уровни поддержки и сопротивления. Учитывая оборот проектов с этими суммами — думаю, весьма перспективным при поиске заказов будет нацеливаться именно на них. Если, конечно, вы разрабатываете на PHP/JS :) Также интересна «долина смерти» в диапазоне $500-$1000. Выбирайте, где вы хотите быть — левее или правее. Забавно, что именно начиная с $500+ на этом графике появляются html5, css3 и jquery вдобавок к обычным html, css, javascript.


Впрочем, мы можем сравнить количество и бюджеты проектов между разными технологиями. Далее будет просто безобразный с точки зрения график, который мне тем не менее весьма интересен. Он отображает в виде карты общие бюджеты проектов с определенными суммами. При этом цвет означает количество проектов. Период выборки — 2014 год. В общем и целом он подтверждает гипотезу о том, что $500, 1000$ — просто очень популярные суммы.


image


Ну а теперь время скандалов, интриг и расследований! :) Ведь пока что за кадром остались бюджеты проектов с почасовой оплатой и поиск призрачного миллиарда долларов. Начнем с простого — проектов с почасовой оплатой. Там конечно не получится сделать такой аккуратный анализ бюджетов как с проектами с фиксированной ценой, но хотя бы объемы оценить будет можно! Правда, понадобится дописать еще один скрипт для вытягивания данные о выплатах по таким проектам. К сожалению, для каждого проекта придется делать отдельный запрос… но что поделать! Зато у вас если что есть готовый скрипт. Кстати, в нем опять учтен подводный камень — видимо, в разные периоды времени информация в базе структурировалась различным способом и данные приходят иногда в разных структурах. Также на удивление нередко данные по старым проектам оказываются закрытыми по причине блокировки аккаунта или в связи с настройками их приватности, но общую картину получить вроде как можно.



require 'odesk/api'
require 'odesk/api/routers/auth'
require 'odesk/api/routers/jobs/profile'

require 'mysql2'
require 'sequel'
db = Sequel.connect('mysql2://XXX:XXX@127.0.0.1/odesk')

config = Odesk::Api::Config.new({
'consumer_key' => 'XXX',
'consumer_secret' => 'XXX',
'access_token' => 'XXX', # assign if known
'access_secret' => 'XXX', # assign if known
'debug' => false
})

# setup client
client = Odesk::Api::Client.new(config)

profile = Odesk::Api::Routers::Jobs::Profile.new(client)
#single hired
#job_id = '~0103a7d3ef942b98f6'
#info = profile.get_specific()['profile']['assignment_info']['info']
#two hired
#
#
db[:jobs].select(:id).where(profile_disabled: false).each do |row|
job_id = row[:id]

if db[:assignments][job_id: job_id].nil?
ap job_id

info = profile.get_specific(job_id)
# there may be error such as 'profile disabled'
if info['error'].nil?
p = info['profile']
assignments = p['assignment_info']['info'] || p['assignments']['assignment']
assignments = [assignments] unless assignments.kind_of? Array

assignments.compact.each do |a|
a.delete 'feedback'
a.delete 'feedback_for_provider'
a.delete 'feedback_for_buyer'
ap a

freelancer_id = a['ciphertext_developer_recno'] || a['as_ciphertext']
if db[:assignments][job_id: job_id, freelancer_id: freelancer_id].nil?
row = {
job_id: job_id,
freelancer_id: freelancer_id,
total_charge: a['total_charge'] || 0,
total_hours: a['tot_hours'] || 1
}

db[:assignments].insert row
end
end
else
db[:jobs].where('id = ?', job_id).update(profile_disabled: true)
ap info['error']
end
end
end




Перед запуском этого скрипта у меня было две мысли. Первая: поскольку количество выполненных проектов с фиксированной ценой и почасовой оплатой примерно равно, то вероятно и общие их бюджеты будут примерно равны. С другой стороны, Odesk выгодно отличается от российский бирж как раз возможностью почасовой оплаты и поэтому такие проекты могут быть тут популярнее. По факту бюджеты разных типов проектов оказались действительно примерно равными, это кстати меня немного удивило. С другой стороны хорошо — значимость тегов вероятно проанализирована верно. Итого общий бюджет всех проектов в базе увеличился как раз в два раза и составил примерно $40 000 000. И вот тут я крепко задумался… Как же так выходит, если Odesk показывает красивые графики с общим бюджетом больше миллиарда за последние годы?

Ни в коем случае не хочу никого обвинять и в первую очередь хочу найти этому разумное объяснение. Может быть, я выгрузил данные только о малой части проектов? Судя по другому графику (см. слайд 23), бюджеты на веб-разработку составляют чуть ли не 30% всего оборота биржи, и PHP используется больше чем в половине проектов в этой сфере. Значит по скромным подсчетам только на проектах с PHP должно выходить около $160 000 000, а это в 4 раза больше, чем в моей базе! Может быть, API выдавало мне не все данные? Я проверил это, сделав запросы об открытых проектах с разными тегами через API и через сайт. Удивительно, но различие действительно нашлось. Хотя и не очень большое — сайт показывал примерно на 10% больше проектов, это никак не разница в 4 раз. В чем же тут дело? Может ли быть так, что недостающие деньги просто скрыты в приватных проектах, в которые заказчики напрямую приглашают исполнителей? Технически и практически это возможно. Правда я сомневаюсь, что закрытых проектов на бирже в 4 раза больше, чем открытых. Так что этот момент пока для меня остался самой большой загадкой в данном исследовании. Очень надеюсь, что знающие люди в комментариях помогут мне разобраться в этом вопросе, чтобы провести более точный анализ и вообще понять общее состояние биржи на данный момент.


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


Recommended article: Chomsky: We Are All – Fill in the Blank.

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


[Из песочницы] Был получен несанкционированный доступ к более чем 20 000 камерам видео-наблюдения Москвы (теперь вы тоже)

Привет, Хабрахабр! Наверняка многие из вас помнят легендарный пост «Были получены исходники 3300 глобальных интернет-проектов», который долгое время был первым в рейтинге всех публикаций на сайте. Несмотря на схожий заголовок у моего поста, не претендую на первое место, но считаю, что вам стоит обратить внимание.





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


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


Ссылка:



http://ift.tt/1xNM1M3



Будучи вдумчивым по своей природе, я заглянул в source и обнаружил следующее:

window.APP_VERSION = '0.1';
window.BUILD_ID = 'public';
window.embeddedCamera = {"id":******,"name":"MMC_***; такой-то парк","shortName":"MMC_***","address":"такой-то адрес ","description":null,"type":{"id":4,"color":"0D0D0D"},"cameraType":4,"apiType":"ECHD_CISCO_VSM","fixed":false,"status":0,"lat":"***","lng":"***"};
window.Request = {"id":"4a6a19d5-ab03-4cb1-8fc4-281345c873f5","innerId":"******","userId":***,"ttl":1422353451835}; // Mapped HTTP request (POST/GET params)




Несложно догадаться, что используя следующий URL

http://ift.tt/1z20DgV



Я получил следующий набор потоков:

{"666":{"archive":{"control":["http://ift.tt/1xNM1M5","http://ift.tt/1xNLZUn"],"shot":{"control":["http://ift.tt/1xNM1M9","http://ift.tt/1xNM1Me"],"url":["http://ift.tt/1z20Dxh","http://ift.tt/1z20El1"]},"url":["http://ift.tt/1xNM22x","http://ift.tt/1xNLZUr"]},"live":{"ios":{"url":["http://ift.tt/1z20Dxn","http://ift.tt/1xNM22C"]},"shot":{"url":["http://ift.tt/1z20EBk","http://ift.tt/1xNLZUA"]},"url":["http://ift.tt/1z20Dxq","http://ift.tt/1xNM0aR"]},"version":100}}




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

Открыв URL любым плеером (VLC, например), мы можем увидеть следующее:



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


Короче. Вставляете ссылку вида http://ift.tt/1z20EBt в браузер (где ХХХ – произвольный айдишник, я пробовал числа от 50 до 20 000), получаете набор потоков, который можно вставить в медиаплеер на своём смартфоне и при должном упорстве вы можете увидеть себя, курящим у подъезда своего дома…



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


Спасибо за внимание.


Recommended article: Chomsky: We Are All – Fill in the Blank.

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


[Перевод] Perl 6 и Rakudo: заметки от 2009 года

Введение в топологические пространства. Программирование конечных топологий на Java

Горный ЦОД

В наше время сооружение дата-центров стало весьма обыденным событием. К этому естественно привело то впечатляющее и все возрастающее на протяжении последнего десятилетия количество, такого рода, новых сооружений. Типовые проекты производственных зданий, совершенно безликих, как внутри так и снаружи, задвигают на второй план ту важную роль которую они осуществляют в современном мире. ЦОД о который далее пойдет речь – это скорее исключение из правил. Более необычной ИТ-инфратструктуры не стоит даже и искать. Этому есть целый ряд удивительных причин. Размещенный глубоко под землей, в недрах горы швейцарских Альп – Форт Кнокс (Fort Knox) – дата-центр, который стал обязан своему созданию компании SIAG. Осуществление проекта стало следствием понимания руководством компании тех простых вещей, что информация – это ценность сродни золоту. Даже само название ИТ-комплекса не случайно перекликается со всем известной военной базой в штате Кентукки, США, которая в свою очередь является одним из наиболее известных в мире хранилищ золота. Давайте же рассмотрим более детально на сколько тщательно «дух горы» бережет свои сокровища.


Локация




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

Бывший командный бункер руководства Швейцарии, спроектированный на случай атомной войны, стал великолепным убежищем для серверов этого современного дата-центра. Расположенный неподалеку от горнолыжного курорта Гштаад (Gstaat), он вместил в себя большую часть инфраструктуры весьма обширного ИТ-комплекса. Бывший военный объект, площадью в 130 тысяч квадратных метров, стал идеальным кандидатом на роль ЦОДа по целому ряду причин. Находясь фактически в самом сердце Европы, в регионе с высокоразвитым ИТ-сектором, сами серверы «спрятаны» в изолированном и труднодоступном месте. Такое положение вещей дало возможность, сотрудникам компании SIAG, организовать высокоэффективную, сродни армейской, систему безопасности.



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



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




Необходимость создания сверх защищенного хранилища назрела не сегодня, но прийти к ее реализации в современном формате было не так просто, и как следствие, процесс построения этой конструкции занял не мало время. Сначала было «слово», создание концепции о реализации проекта столь необычного ЦОДа, началось еще в далеком 1992 году. В этом же году основатели компании SIAG и присмотрели собственно понравившейся им бункер. После выбора площадки, долгие три года проект ожидал успешного окончания переговоров с военными, которые должны были легальным образом передать в частную собственность военный объект. Только в 1995 году началась реконструкция законсервированного армией комплекса, под ИТ-узел, ознаменовав тем самым начало практической реализации проекта «Швейцарский Форт Кнокс». Уже через год после начала работ дата-центр начал принимать своих первых клиентов. Действительно знаковым для всего ИТ-проекта стал 2001 год. Именно в этом году был введен в эксплуатацию полностью дублирующий существующие на то время мощности ЦОД. Как следствие даже само название дата-центра претерпело изменений. С этого момента дата-центр, размещенный в горе, стал именоваться как Swiss Fort Knox I, соответственно дублирующий ЦОД получил название Swiss Fort Knox IІ. На протяжении всех этих годов, что существует ИТ-комплекс, шла безостановочная модернизация и усовершенствование способов защиты данных и способов безопасно получать клиентам компании получать доступ к ним, как физически, так и аппаратно. Определенной кульминацией этого процесса стало введение нового особо-защищенного, но при этом открытого для свободного предзаказа всеми желающими, бэкап-сервиса под названием MOUNTH10.


Технические составная




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


Точные технические характеристики ИТ-комплекса особо не афишируются, и в этом естественно есть логика, ведь бесконтрольное распространение такой информации может поставить под удар безопасность самого ЦОД. Точно известно, что серверные мощности этого ЦОД дублируются по оптико-волоконному соединению в близ расположенном дата-центре Швейцарский Форт Кнокс ІІ, который получил название Швейцарский Форт Кнокс ІІ, а это в свою очередь дает дополнительный уровень безопасности размещенной клиентами информации. Так же известна площадь всего комплекса дата-центра, это 130 тысяч квадратных метров. Кроме серверных залов и обслуживающих их технических помещений, в состав дата-центра также входит станция спутниковой связи, вертолетная площадка, мини отель для посетителей и даже персональный аэропорт, через который могут прибывать иностранцы, ведь тут действует полноценный таможенный пункт пропуска. Особо стоит отметить уникальную систему охлаждения дата-центра, специфика ее состоит в том, что для терморегуляции в помещении используется ледяная вода, поступающая от расположенного рядом ледника. Поскольку температура воды в горных ручьях редко когда подымается больше +6 С, она идеально подходит для отвода лишнего тепла, производимого серверными стойками.



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


Услуги и клиентская база




Сама необычность структуры и перечень услуг дата-центра намекает на то, что и клиенты, размещенные в нем, не совсем обычны. Услуги ЦОДа актуальны, прежде всего, для состоятельных людей, людей, которые ценят сервис и свои данные порою больше, нежели деньги, ведь цены на предоставляемые тут услуг на порядки выше, нежели в привычных нам дата-центрах. Но не только стоимость, а и сама номенклатура предоставляемых услуг может возбудить сознание рядового пользователя хостинга. К примеру, в случае возникшей необходимости получить физически свои данные, клиент может воспользоваться услугой вертолетной доставки физического носителя, плата за час работы летательного апарата для клиента составит $5950, при этом диапазон действия услуги — вся Европа. Сам выезд сотрудника компании к вам в офис, для проведения каких либо работ, обойдется не менее чем в $1512.

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


С полноценным введением в работу в 2010 году компанией SIAG сервиса «ДНК Банк», дата-центр стал принимать на хранение, кроме цифровых данных, еще и настоящий генетический материал. Клиенты, которые пожелали сохранить частичку себя в этом сервисе, получают оговоренное дисковое пространство в ЦОДе, под хранение своих оцифрованных личностных данных, и физическое место для хранения герметической колбы с образцами ДНК. Стоимость такого сервиса составляет $299 за 1 гигабайт места на сервере, и $399 за возможность к 1 гигабайту места разместить в Швейцарском Форте Кноксе образец своего ДНК. Стоит лишь отметить, что стоимость этой услуги разовая, и долговременное хранение данных больше не потребует от Вас дополнительных расходов.


Проект Planets




В рамках ИТ-проэкта Planets (Preservation and Long-term Access through Networked Services) была инициирована программа «Капсула Времени» (Time Capsule). Суть программы состоит в том, чтоб систематизировать и сохранить исчезающие цифровые форматы чтения данных. Даже не стоит упоминать, на сколько это может стать критическим, в нашем активно развивающемся мире, где каждый день рождаются новые, и постепенно отходят в небытие старые форматы. И не стало удивительным, что для столь почетной миссии было выбрано место, где будет расположен металлический ящик, наполненный разнородными носителями данных, начиная от флеш накопителей, микрофильмов, и до бумажных носителей, именно в подземельях Швейцарского Форт Кнокса. Каждый объект данных сохраняется как в оригинальном формате, так и в «трансформированном», зашифрованном виде. Также, кроме редких сейчас форматов чтения, на носителях хранятся объекты сопутствующего им программного обеспечения: операционные системы, файловые системы, методы кодирования. Все эти данные естественно продублированы на серверных мощностях дата-центра, и доступны для авторизированных пользователей через мировую сеть Интернет.


Приоткрыв завесу столь неординарного дата-центра, становится понятным, что финансы – это всего лишь инструмент, который стоит на службе у более высоких человеческих понятий. Люди готовы платить за сохранность своей истории, готовы вкладывать огромные ресурсы в безопасность персональных данных. Швейцарский Форт Кнокс, это больше чем просто высокотехнологический ИТ-узел на карте Европы, он подарил многим клиентам спокойный сон, чувство безопасности за самое дорогое, что они имеют. Для кого то же он стал еще одной ступенькой на пути к извечному ориентиру человечества – бессмертию. Хотя генетический материал и не дает возможности восстановить личность, наука не стоит на месте. До того момента как только ученные смогут произвести полноценную трансплантацию личности, все Ваши данные для регенерации тела будут тщательно оберегаться, в надежном месте и все что Вам понадобится для вечной жизни, это всего лишь получить доступ к своему аккаунту в Швейцарском Форте Кнокс.



Recommended article: Chomsky: We Are All – Fill in the Blank.

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