software simian's typewritings

CSS content и псевдоэлементы

кнопки open google reader выглядят по-разному в обычной и мобильной версиях. В обычной на них есть текст, в мобильной только юникодные символы-иконки. Долгое время за выбор одного или другого отвечал яваскрипт, но тут я решил, что пора бы это перевести на CSS. В нём как раз есть удобное свойство content для того, чтобы подставлять нужный текст. И перевёл, и возрадовался.

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

жаль, конечно, что это не сработало. Та же проблема, что и с элементом img: посредством CSS его пока что не изменишь.

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

,

ловушка self и this

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

итак, пример прототипного наследования:

function Parent() {
    this.test = function() {};
}
function Child() {}
Child.prototype = new Parent();
child = new Child();

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

function Example() {
    var NS = some.very.long.namespace,
        self = this;
    self.value = new NS.Value();
}

и комбинированный пример с ловушкой в действии:

function Parent() {
    var self = this;
    self.test = function() {
        self.value = true;
    };
}
function Child() {
    var self = this;
    self.value = false;
}
Child.prototype = new Parent();
child = new Child();
console.log(child.value); // false
child.test();
console.log(child.value); // false

Проблема возникает из-за того, что Child и Child.prototype — глобальные объекты. Когда создаётся Child.prototype, конструктор Parent сохраняет в self именно этот объект, общий для всех экземпляров Child. Естественно, он не совпадает ни с одним из этих экземпляров. И код внутри Parent работает со свойствами именно этого объекта.

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

ссылки о hypermedia APIs

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

вкратце идею можно описать так: когда мы запрашиваем html-ресурс, мы обычно получаем и кучку ссылок на связанные объекты, а также возможные действия. Хочется иметь то же самое для JSON, ведь там обычно просто голые данные. Так давайте договоримся о стандартном способе прицеплять к ним ссылки и, возможно, сами связанные объекты. Hypertext Application Language (HAL) этим и ограничивается. Collections+JSON идёт дальше и определяет кучу других ограничений, поэтому и нравится мне меньше.

в тему метаданных хочу дать и ещё одну ссылку: про версионность API. Я пока что видел мало примеров этого, и лучшим вариантом мне казалось включение версии непосредственно в адрес: /api/v2/resource. Однако Peter Williams разумно замечает, что такой подход приводит к тому, что один и тот же ресурс с разными версиями API — это разные ресурсы. Он же предлагает и красивый альтернативный способ добавлять версию в API: Accept: application/vnd.mycompany.myapp-v2+xml. Концептуально это мне очень нравится, хотя не могу не обратить внимания на недостатки: такой интерфейс сложнее исследовать браузером, и тип всё-таки нужно регистрировать. Но красиво.

,

сохранение jabber-статусов в atom

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

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

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

class Client(JabberClient):
    def __init__(self):
        JabberClient.__init__(self,
            JID(JID_VALUE),
            PASSWORD,
            server='talk.google.com',
            auth_methods=['plain'],
            tls_settings=TLSSettings(require=True, verify_peer=False))

client = Client()
client.connect()
client.loop(1)

настроек у клиента могло бы быть и поменьше, но Google Talk очень особенный. Зато дальше становится проще. Вот так подключается обработчик событий:

self.get_stream().set_presence_handler('available', self.presence)

а вот, собственно, и сам обработчик:

def presence(self, stanza):
    status = stanza.get_status()

    if stanza.get_show() is not None or not status or status == self.current_status:
        return True

    self.current_status = status
    self.update_feed(status)
    publish(PUSH_URL, SELF_URL)

    syslog.syslog('New status: %s' % (status.encode('utf8')))

    return True

здесь пояснения требуют get_show и publish. Первый возвращает None, когда контакт именно в онлайне, а не отошёл, недоступен или что-то ещё. Второй публикует обновление на PubSubHubbub-хабе.

кстати, мне хотелось, чтобы этот бот был полноценным сервисом на моём сервисе. Для этого пришлось разобрать основы системы upstart, которая призвана заменить предыдущего управляющего сервисами init. Оказалось, с новой системой простые вещи действительно просты. Вот такой файл я добавил в /etc/init:

description "Logger of IM statuses"
author  "Artemy Tregubenko <me@arty.name>"

start on runlevel [234]
stop on runlevel [0156]

expect daemon
respawn

chdir /var/www/shared_arty_name/im-status/
exec python /var/www/shared_arty_name/im-status/IMStatus.py

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

def start():
    client = Client()
    client.connect()
    client.loop(1)

def stop():
    client.disconnect()

context = daemon.DaemonContext(
    signal_map={signal.SIGTERM: stop},
    gid=grp.getgrnam('www-data').gr_gid,
    uid=grp.getgrnam('www-data').gr_gid,
    detach_process=True,
)

with context: start()

а вот и код бота целиком. Интересно, есть ли смысл превращать его в сервис? Глядишь, кто-нибудь на пиво и пожертвует : )

, , ,

gravatar, pavatar, libravatar

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

это было намного удобнее, чем прежде, но недостаточно хорошо, потому что это была централизованная система. Всё завязано на единый сайт, и если с ним что-то случится, или он задумает сменить бизнес-модель, будет неловко. Распределённые системы в этом смысле лучше. Тут под рукой удачно оказался OpenID, который был распределённый, и у каждого пользователя была своя страничка. Что может быть очевиднее, чем дать на этой страничке ссылку на аватар? Так появился pavatar.

однако у pavatar были недостатки по сравнению с gravatar. Во-первых, пользователи без OpenID, с одним только емейлом, оставались не у дел. Во-вторых, разбор HTML-страницы, чтобы извлечь из неё ссылку на pavatar, — довольно затратный процесс, и это особенно заметно при большом числе комментаторов. Нужен какой-то другой способ совместить распределённость с простотой.

и вот Libravatar взял идею Gravatar «имя картинки — это хеш от емейла», развил её до «имя картинки — это хеш от емейла или OpenID», а распределённость сделал через SRV-запись в DNS. При этом сервера самого Libravatar служат в качестве запасного варианта. Ну и код Libravatar открытый.

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

<link rel="pavatar" href="http://arty.name/img/1344467.png">

я создал такую запись в DNS:

_avatars._tcp.arty.name. 86400 IN SRV 0 0 80 arty.name.

и скопировал 1344467.png в md5('me@arty.name') = ad4f3a81155f469603be3b8bd5cf3348 и sha256('http://arty.name') = 3876b8005186bddc104495cd6c81f160f990f7fec7e96d89cfd185668bc2886d в корневом каталоге сайта, в соответствии с API Libravatar. Когда в проверялке Libravatar очнётся кеш, она должна начать показывать мой аватар по емейлу и OpenID.

, ,

больше перепостов, хороших и разных

я всё ещё пользуюсь Open Google Reader и не собираюсь с него никуда уходить, но одна из его способностей со временем обесценилась в ноль. Для комментариев в нём по-прежнему используется сервер гугла, на котором сейчас уже никто из моих друзей ничего не комментирует.

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

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

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

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

,

ubuntu, unity, launcher

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

встречайте «Main Menu»! Достаточно запустить его, нажать «New Item», заполнить очевидные поля в диалоговом окне — и готово! Этот ярлык уже можно найти поиском в Unity и добавить в панель приложений. Если же Main Menu не установлено в вашей системе по умолчанию, то в Software Center его можно найти ещё и под именем alacarte.

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

запрет на изучение кода веб-сайтов

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

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

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

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

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

persona: система аутентификации от мозиллы

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

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

к сожалению, массовый юзер имеет только емейл, поэтому непосредственная идея OpenID не прижилась. К ней пришлось придумывать дополнительные штуки типа «у ней внутре неонка URL, но провайдер пользователю его не покажет, он спросит логин и пароль». А это привело к nascar-панелям с иконками провайдеров и посторонним сервисам аутентификации.

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

,