Добавляем вывод количества просмотров записи из Jetpack Stats

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

Попытка №1 — решение в лоб с кешированием

Получение количества просмотров работает чрезвычайно медленно. Например, некоторые мои страницы при работе напрямую с плагином загружались аж ПЯТЬДЕСЯТ ТРИ (!!!) секунды. Поэтому в один прекрасный день я выключил этот функционал. Код был такой (functions.php), не используйте его, он здесь только для истории:

# Используем класс WPCOM_Stats из Jetpack
use Automattic\Jetpack\Stats\WPCOM_Stats;

# Метод получения количества просмотров с кэшированием
function get_post_views($post_id) {
  # Ключ кэша "wp_cached_counter_номер_поста"
  $cache_key = 'wp_cached_counter_' . $post_id;

  # Получим значение кэша по ключу
  $counter = get_transient($cache_key);

  # Если значения нет, значит создадим
  if (false == $counter) {
    # Объект класса для работы со статистикой Jetpack
    $wp_stats = new WPCOM_Stats();

    # Получим статистику просмотров для $post_id и преобразуем её в объект (тут может пройти до ДЕСЯТИ секунд)
    $stats = $wp_stats->convert_stats_array_to_object($wp_stats->get_post_views($post_id));

    # Количество просмотров
    $counter = intval($stats->views);

    # Сохраним в кэше на час
    set_transient($cache_key, $counter, HOUR_IN_SECONDS);
  }

  return $counter;
}

# Печатаем количество просмотров поста
function print_view_counter() {
  # Получим значение
  $counter = get_post_views(get_the_ID());

  # Нулевые счётчики не выводим
  if ($counter) {
    return;
  }

  # Если больше тысячи, выведем количество тысяч с суффиксом K. То есть вместо 6404 будет 6K
  if (intval($counter) > 1000) {
    printf('<span class="view" title="%s">%sK</span>', $counter, intdiv($counter, 1000));
  }
  else {
    printf('<span class="view">%s</span>', $counter);
  }
}

Затем вставляем в любом месте темы код:

<?php print_view_counter(); ?>

и получаем цифру просмотров.

Спустя какое-то время я увидел, что страницы грузятся безбожно долго, особенно страницы с категориями или тегами. Некоторые грузились по минуте, потому что запрос количества просмотров идёт с сайта stats.wp.com и иногда доходит до ДЕСЯТИ СЕКУНД на запрос.

Попытка №2 — использование WPCron без кеширования

В итоге я решил запускать обновление счётчиков периодически по крону, записывать/получать их значения с помощью update_post_meta и get_post_meta соответственно в поле _post_counter. В моём самописном плагине я ежедневно обновляю информацию:

# Используем Jetpack WPCOM_Stats
use Automattic\Jetpack\Stats\WPCOM_Stats;

# Метод получения счётчика без кеширования, возвращает цифру
function b2k_get_post_counter($wp_stats, $post_id) {
  $stats = $wp_stats->convert_stats_array_to_object($wp_stats->get_post_views($post_id));
  return intval($stats->views);
}

# Обновление счётчиков у всех страниц в блоге
function b2k_update_all_counters() {
  $posts = get_posts(
    array(
      'post_type' => 'any',
      'posts_per_page' => -1,
      'post_status' => 'publish'
    )
  );

  # Создадим один раз объект для запроса статистики
  $wp_stats = new WPCOM_Stats();

  # Для каждой страницы
  foreach ($posts as $post) {
    # Запрашиваем счётчик
    $counter = b2k_get_post_counter($wp_stats, $post->ID);
    # Записываем его в мета поле с именем _post_counter каждой странице
    update_post_meta($post->ID, '_post_counter', $counter);
  }
}
# Создаём событие b2k_update_counters_event
add_action('b2k_update_counters_event', 'b2k_update_all_counters');

# Во время активации плагина
function b2k_cron_activation() {
  # Убираем хук b2k_update_counters_event
  wp_clear_scheduled_hook('b2k_update_counters_event');
  # Добавляем ежедневный запуск события b2k_update_counters_event
  wp_schedule_event(time(), 'daily', 'b2k_update_counters_event');
}
register_activation_hook(__FILE__, 'b2k_cron_activation');

# Во время деактивации плагина
function b2k_cron_deactivation() {
  # Удаляем хук
  wp_clear_scheduled_hook('b2k_update_counters_event');
}
register_deactivation_hook(__FILE__, 'b2k_cron_deactivation');

В теме я просто получаю значение поля:

function print_view_counter() {
  # Получить значение мета поля _post_counter, всегда строка
  $counter = get_post_meta($post_id, "_post_counter", true);
  # Если счётчика нет, значит ноль
  if (!$counter) {
    $counter = 0;
  }
  else {
    # Иначе нам нужна цифра
    $counter = intval($counter);
  }

  # Если больше тысячи, выведем количество тысяч с суффиксом K. То есть вместо 6404 будет 6K
  if ($counter > 1000) {
    printf('<span class="view" title="%s">%sK</span>', $counter, intdiv($counter, 1000));
  }
  else {
    printf('<span class="view">%s</span>', $counter);
  }
}

В вычислении скорости загрузи меня выручил плагин Query Monitor. Для работы кода обязателен плагин Jetpack с включенным модулем Stats. Для первоначальной установки Cron события необходимо выключить и включить плагин.

Устройство движка для квестов Trickster Games

Всем привет! В результате недавних трудов у меня появились исходники движка для квестов компании Trickster Games. Выкладывать я их не буду, потому что не знаю насколько он лицензионно чистый. Я не юрист, в таких вопросах не разбираюсь. Поэтому расскажу своими словами — как устроен движок для квестов Trickster Games.

Поколений движков было два — первый использовался для игр Танита. Морское Приключение, Приключения Барона Мюнхгаузена на Луне и использовал lua в качестве скриптового языка.

Танита. Морское приключение и Приключения Барона Мюнхгаузена на Луне

Начиная с игры «Петрович и все, все, все» движок переходит на использование Python 2.xx вместо lua. Я пришёл в Trickster Games, когда начали делать Петрович, так что про предыдущие движки и игры практически ничего не знаю.

Итак, приступим!

Описание редактора игрового движка Trickster Games

Небольшое отступление: движок казался гораздо проще. Однако, он содержит в себе множество разных python классов, буду заново разбираться по ходу написания статьи.

Итак, у нас есть редактор (это первый уровень игры «Петрович и все все все»):

Редактор Trickster Games

Это тот же самый движок, но запущенный в специальном режиме.
Слева у нас окно дерева объектов, снизу свойства объекта, а справа самый большой экран — экран предпросмотра уровня. Фон заполняется жёлтым цветом, чтобы на сцене сразу были видны пустые места. В режиме игры, фон заполняется чёрным цветом, так что это не бросается в глаза в обычном режиме.

Почти все объекты наследуются от ItemBase и рисуются относительно координат родителя. При перемещении родительского элемента на сцене, его дети перемещаются вместе с ним. В дереве объектов элементы можно перетаскивать с помощью drag-n-drop или добавлять новые правой кнопкой мыши. У каждого элемента есть замочек и глазик — блокировать перемещение/выделение при клике на сцену и показать/спрятать элемент при отрисовке сцены. Отрисовка элементов идёт по дереву от корневого узла вниз.

Уровень (Location)

Собственно уровень игры, рутовый элемент нашей сцены, содержит параметры width, height — ширина локации, высота локации. Вот её панель свойств:

Свойства локации в игровом редакторе Trickster Games

Может содержать в качестве детей только слои.

Слой (Layer)

Слой локации, имеет параметр parallax — это отставание при прокрутке. С помощью него создаётся эффект глубины при скроллинге локации по горизонтали или вертикали. Вот её панель свойств:
Свойства слоя в игровом редакторе Trickster Games Может содержать в качестве детей статические изображения, регионы, пути, анимированные объекты и точки.

Статическое изображение (LayerImage)

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

Свойства статического изображения в игровом редакторе Trickster Games

Анимированный объект (AnimatedObject)

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

Свойства игрового объекта в игровом редакторе Trickster Games

Он может наследоваться от особенных классов, например, Playable Character — это наш главный герой, который умеет ходить по локации, или Takeable Item — это вещь, которую можно забрать себе в инвентарь. Он может загружаться согласно определённым условиям из скрипта, если поставить флажок «Conditional object loading». Пример условной загрузки элемента «Item_parfume» (условия описываются в родительском элементе):

class FloorLayer:
    def should_load_Item_parfume(self):
        return not world.parfume_taken

Также у него есть pivot — начало его координат и позиция на локации абсолютная и относительная. К нему могут привязываться дочерние AnimatedObject, Region и StateSequence. Содержит список состояний. Представляет собой конечный автомат, который меняет своё состояние в зависимости от внешних условий. Может загружать дочерние элементы типа AnimatedObject по некоторым условиям.

Состояние объекта (AnimatedSequence)

Анимация и состояние AnimatedObject. Анимация неотделима от состояния объекта. Пока AnimatedObject находится в определённом состоянии, проигрывается анимация этого состояния из одного или нескольких кадров. К любому кадру можно привязать автоматическое проигрывание звука.
Свойства состояния объекта в игровом редакторе Trickster Games

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

Регион (Region)

Некий замкнутый набор точек. Определяет область для хождения главного героя на сцене (на скриншоте редактора это R_Floor) и области для клика мышкой. Имеет свойство
«.click» или «.click_with_» для проверки клика на этот регион, второе свойство динамическое и проверяется клик с предметом из трея. Предоставляет метод для поиска кратчайшего пути между точками для хождения главного героя. Может иметь несколько типов (не помню, кто за что отвечает) и менять курсор мыши при наведении.
Свойства региона в игровом редакторе Trickster Games

Путь (Path)

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

Точка (Point)

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

Описание игрового движка Trickster Games

Движок написан на C++, для рендера используется DirectX 9.0, для связки Python 2.xx и C++ используется Boost.Python. Для сборки и компиляции исходников движка применяется (БУДЬТЕ ПРОКЛЯТЫ ЕГО СОЗДАТЕЛИ!) Boost Jam и Visual Studio 2007.

Базовый элемент в игровом движке — это GameObject, который имеет матрицу трансформации себя относительно родителя, список детей типа GameObject, и используется для создания дерева сцены. Простыми словами, для отрисовки дерева сцены вызывается GameObject::update(dt), который последовательно вызывает обновления всех детей, начиная от корня дерева вниз по иерархии. От него торчат уши в Python. Python-часть движка инициализируется следующим кодом:

std::string setup(
"sys.path = [r'Data\Scripts1.pak', r'Data\Scripts2.pak', "
                    "r'Data\Scripts3.pak', r'Data\Scripts3.pak\CommonClasses', "
                    "r'Data\Scripts4.pak', r'Data\Scripts4.pak\World', '.', 'Lib/std']n");
run_string(setup);

Затем вызываем метод on_init из файла Lib/engine.py:

boost::python::call<void>(bp::object(py["Lib"].attr("engine").attr("on_init")).ptr(), long(application::Application::window()));
py_on_frame = py["Lib"].attr("engine").attr("on_frame");

И после делаем on_frame из того же файла каждый кадр:

boost::python::call<void>(py_on_frame.ptr(), dt / 1000.0f, just_redraw, cursor_position, mouse_button_state);

Всё, никакой магии.

Форматы файлов

При подготовке релиза происходит преобразование .py файлов в .pyc, PNG в DDS, и всё это запаковывается в .zip файл.

Анимация

Анимация делалась в Adobe Flash и экспортировалась в набор PNG файлов, которые при запаковке релиза превращаются в DDS — это родной формат для DirectX SDK.

Звуки

Звуки — это .wav файлы

Видео

Для видео использовался формат OGV (Theora Vorbis).

Вроде всё, что мог, описал. Спрашивайте, если что-то осталось непонятным.

Как досрочно погасить кредит к определенной дате

Как досрочно погасить кредит к определенной дате? Как вычислить сумму досрочного платежа?

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

Пример: допустим у меня есть кредит в 250 000 ₽, сейчас февраль и в обычном режиме он погасится через пару лет. Я хочу, чтоб он был погашен в августе, смотрю остаток на август и он составляет 150 000 ₽, до августа осталось 7 месяцев, включая текущий. Значит 150 000 / 7 ≈ 21 429 ₽ мне надо вносить сверх обычного платежа и к августу остаток будет нулевым.

Этот способ не учитывает сокращение процентов, так что фактически кредит будет погашен немного раньше нужного срока. Плохо что ли? Хорошо!

Все цифры и даты выдуманы с потолка, все совпадения случайны.

Нет сил больше молчать, буду хвастаться.

Всем привет! Мне нужно срочно поделиться с миром, какой я весь из себя молодец, нет сил больше держать это в себе. В общем, встала задача восстановить данные с жёстких дисков сервера фирмы, где я раньше работал. Так исторически сложилось, что сервер после развала фирмы по договорённости с руководством переехал ко мне домой. Родилась идея восстановить данные с сервера. Сервер давно не включается, в нём старые HDD IDE диски. Всего дисков три штуки: 160Gb, 320Gb и 1.5Tb. Файловая система на них ext2fs или часть RAID массива. Была идея подключить их к виртуальной машине Debian и получить доступ к файлам.

Как я поступил изначально — купил переходник IDE->USB и попробовал подключить диск к компьютеру.
IDE to USB converter
Ничего не получилось даже с отдельным блоком питания — отправил переходник обратно продавцу. Стало понятно, что подключить диски напрямую малой кровью не получится, значит, надо делать копию дисков и подключать к виртуальной машине виртуальные жёсткие диски с этих копий. Постепенно родился такой план решения задачи:

  1. Получить посекторные дампы с дисков в виде огромных файлов
  2. Получить виртуальные жёсткие диски с этих файлов и подключить их к виртуальной машине
  3. Получить доступ к файлам из этих дампов из виртуальной машины

Посекторные дампы дисков

Отнёс диски в специализированную контору, которая сделала посекторную копию двух дисков на принесённый мною новый SATA диск. Каждая копия обошлась мне в 1500₽. Новый SATA диск вышел в районе 4000₽. Через пару недель принёс диск с копиями домой, воткнул в компуктер и получил вот такую картину:
Список дампов
Второй файл оказался битый, потому что новый диск для хранения дампов оказался барахлом. НИКОГДА НЕ ПОКУПАЙТЕ Seagate ST2000VX000 — у меня он начал сыпаться после 19 часов работы. Так что сначала запустил команду для восстановления всех файлов, которая справилась за каких-то три часа:

chkdsk /F /R /X

и приступил к следующему шагу.

Конвертирование дампов в виртуальные жёсткие диски для виртуальной машины и их подключение

Ни одна из известных мне виртуальных машин (VirtualBox, VMWare, Hyper-V) не поддерживает сырые данные в виде виртуального жёсткого диска, так что в процессе поиска решения, благодаря статье, наткнулся на бесплатную утилиту Startwind V2V Converter, при помощи которой и сконвертировал сырые дампы в .vdi (VirtualBox Disk) и .vmdk (VMWare Virtual Disk). Программа очень проста в использовании. Выбираем источник данных Local File, место назначения Local File. Потом тип файла и его подтип. Мне пришлось сделать два разных типа, потому что, как я писал выше, второй файл битый, и в формате .vdi VirtualBox его переварить не смогла, а вот в формате .vmdk вполне.
Виртуальную машину я использовал Oracle VirtualBox, просто потому что она бесплатная. Хотел использовать Hyper-V, встроенную в Windows 10 Pro, но она ни в каком виде не смогла подключить второй битый файл.
Подключение расписывать тоже не буду, покажу финальный скриншот:
VirtualBox virtual HDD

Финальный аккорд — получаем доступ к файлам

После подключения дисков и запуска Debian необходимо определить, куда они подключились. В линуксе это устройства /dev/sd[a-z], при этом /dev/sda уже занят. Значит, это диски sdb и sdc. Запускаем

fdisk -l /dev/sd[b,c]

И получаем следующую картину:
fdisk
Отсюда следует, что /dev/sdc2 можно сразу монтировать командой:

mount /dev/sdc2 /mnt/memory-150

А на диске /dev/sdb располагается кусок raid массива. Я в них абсолютно ничего не понимаю, зато умею гуглить, и нашёл статью, как подключить один кусок из программного raid массива — тыц. Как бездумная, но опытная обезьянка, выполнил шаманство с запуском pvscan, mdamd, lvm2 и прочими командами, и у меня появилось устройства /dev/onraid/root /dev/onraid/shares /dev/onraid/var, которые я успешно смонтировал командами:

mount /dev/onraid/root /mnt/memory-300-root
mount /dev/onraid/shares /mnt/memory-300-shares
mount /dev/onraid/var /mnt/memory-300-var

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

P.S. Кому интересно, устройство игрового движка Trickster Games

Инструкция по замене третьего стоп-сигнала на SEAT Altea Freetrack

Всем привет! Давно бесил меня нерабочий третий стоп-сигнал и буквально недавно я его поменял вот этими вот самыми программистскими руками. Поделюсь с вами инструкцией.

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

Для замены стоп-сигнала требуется провести следующие операции:

  1. Снять нижнюю часть обшивки. Снимаем предварительно заглушки, выкручиваем под ними два шурупа, затем два шурупа в выемке и тянем обшивку вниз, взявшись за отверстия от заглушек. Фоток не делал, шурупы найдёте самостоятельно.
  2. Снять верхнюю часть обшивки. Сначала тянем обшивку за бока к центру, чтобы она сузилась, затем вытягиваем центр на себя, согласно стрелочкам: Верхняя часть обшивки пятой двери
  3. Разделить стоп-сигнал на две части, потянув за язычок вправо через отверстие справа, указанное стрелочкой, зацепив за язычок ключом на 10: Отверстие в двери для доступа к правой части стоп-сигнала Третий стоп-сигнал SEAT Altea Freetrack. Код запчасти 5P0945097A
  4. После щелчка поддеть красный плафон снаружи пластиковой карточкой и извлечь его из гнезда
  5. Вытащить всю конструкции наружу с внешней стороны двери
  6. Поменять стоп-сигнал на новый, благо он доступен по цене, если вы сын депутата или простого русского олигарха. Номер запчасти 5P0945097A. Лично я заказывал б/у в компании Ted Auto через сайт zzap.ru.
  7. Проверить работоспособность
  8. Собрать всё в обратном порядке

Единственный момент с рывками был во время снятия обеих частей обшивки. Стоп-сигнал разделился на две части без рывков, с некоторым усилием. Все работы выполняйте в тёплое время года, чтобы пластиковые детали были более пластичными, в холодное время повышается риск отломать клипсы и сломать стоп-сигнал. Я делал это при температуре +26°C.

UPD: На Алиэкспрес есть третий стоп сигнал подешевле, сам не заказывал, на ваш страх и риск: третий стоп-сигнал Seat Altea Freetrack

Личный блог Евгения Жирнова