Pull to refresh

Opera Unite для разработчиков

Reading time9 min
Views871
imageА не написать ли нам какой-нибудь сервис под Опера Юнайт? Здесь я расскажу об особенностях программирования под новинку от Оперы на примере своего сервиса "Stream media". Официальная документация пока что неполная и содержит много ошибок, и в этой статье мы попытаемся их обойти.
image
Далее, будем вникать в технологию постепенно. Желательно, чтобы Вы уже прочли мануал по созданию первого приложения, поскольку некоторые очевидные факты здесь опускаются.


config.xml


imageГлавный файл Вашего сервиса. После установки он кэшируется и его изменения не приводят к изменениям в реальном времени. Можно только удалить сервис и установить его заново. Это отличается от поведения серверного javascript (помогает вкл-выкл сервиса) и уж тем более от клиентского javascript и html (меняются в режиме реального времени).

<widget network="public private">
<widgetname>Stream media</widgetname>
<description>Stream media online from your own computer with Opera Unite! Audio, video and etc.</description>
<author>
<name>avenu</name>
<organisation></organisation>
</author>
<feature name="http://xmlns.opera.com/webserver">
<param name="type" value="service"/>
<param name="servicepath" value="stream"/>
</feature>
<feature name="http://xmlns.opera.com/fileio">
<param name="folderhint" value="home" />
</feature>
</widget>


Обратите внимание на атрибут network=«public private» у тега widget — рекомендую включить, если Вы будете работать с внешними сайтами. Учтите, из-за особенностей AJAX Вы не сможете к ним подключиться простым способом. Но с помощью этого атрибута сам виджет сможет соединяться с интернетом, используя тот же AJAX на сервере.
Кроме того, здесь подключается File I/O API:
<feature name="http://xmlns.opera.com/fileio">
<param name="folderhint" value="home" />
</feature>


и то, как будет выглядеть адрес сервиса:
<param name="servicepath" value="stream"/>

Javascript


imageДля начала занесем себе в мозг информацию о том, что Opera Unite сервис — это особый вид виджета (да-да, те самые виджеты из Оперы) с рядом особенностей. Отсюда следует и то, что к нему применимы функции API виджетов.
Javascript делится на серверный и клиентский. На серверном прописывается вся логика Опера Юнайт (считайте, аналог PHP/Ruby/Python/etc.), на клиентском же реализуется логика сайта, который видит пользователь. Например, можно использовать jQuery. HTML в Юнайте напоминает view из MVC (соответственно, серверный javascript аналог controller'а). Юнайт генерит HTML в зависимости от переменных, которые ему передал скрипт.
Соответственно, клиентские скрипты, CSS, файлы кладутся в папку по умолчанию public_html. Серверный скрипт и HTML можно положить в любые папки, путь к ним Вы укажете сами. Для этого в корне, рядом с будущим файлом конфигурации создается файл index.html, в нем указывается конструкция типа:
<!DOCTYPE html>
<script src="script/markuper.js"></script>
<script src="script/functions.js"></script>
<script src="script/operafunctions.js"></script>
<script src="script/script.js"></script>


HTML подключим чуть позже с помощью механизмов маркапов.
Далее, в одном из созданных и подключенных javascript, например script.js из папки script, подключаем обработку события «сервер включился», и добавляем в скриптах обработку запросов (URL):
window.onload = function ()
webserver = opera.io.webserver
if (webserver)
{
//Handle requests for various URLs
webserver.addEventListener('_index', showEntryList, false);
webserver.addEventListener('shared', download, false);
}
}


_index — это идентификатор начальной страницы сервиса. Такое наименование идет по умолчанию. В указанном выше коде по доступу в корень сайта будет вызвана функция showEntryList. Аналогично, я добавил по доступу к адресу типа /service-name/shared/bla/bla/bla вызов функции download, чтобы разрулить некоторые особенности шаринга файлов в Юнайте.

В функциях будет доступна переменная типа Event, из которой можно достать разные полезные объекты:
var response = e.connection.response;
var request = e.connection.request;


Впрочем, достать объект Request или Respone можно и с помощью глобального объекта opera, но на момент доступа к нему, Request может быть уже новым:
opera.io.webserver.connections.request
opera.io.webserver.connections.response


С помощью Request мы можем вытащить данные из GET/POST (соответственно) запроса:
var index = request.queryItems['id'][0];
var password = request.bodyItems.passwords[0];


Reponse же выдаст страничку для пользователя:
response.write( '<!DOCTYPE html>'
+ '<html><head><title>Entries</title></head>'
+ '<body><ul>'
);


Однако, это не самый удобный способ работы с HTML. Рассмотрим механизм Markuper.

HTML


imageMarkuper позволяет создавать HTML файлы с серверной логикой. Это что-то вроде шаблонизаторов а-ля Tidy. Для начала работы с ним, нам необходимо в index.html в корне папки сервиса добавить ссылку на файл markuper.js, который будет лежать рядом с Вашими серверными скриптами. Скачать его надо тут: dev.opera.com/libraries/markuper/markuper.js. Обратите внимание — маркапер _нужно сохранить в папке сервиса и подключить_!.. Он недоступен по умолчанию.
Теперь можно создать файл-шаблон, например index-template.html и добавить его в какую-нибудь папку в корне (или глубже), например templates. Тогда в скрипте новый объект Маркапера будет выглядеть следующим образом:
var template = new Markuper( 'templates/index-template.html');
Сам файл будет выглядеть примерно так:
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Tutorial</title>
</head>
<body>
<h1>{{name}} Tutorial</h1>
<p>
This variable is further down the data object hierarchy:
'{{further.down.the.hierarchy}}'
</p>
</body>
</html>

{{path.to.variable}} — это путь в переменной по ее свойствам. Ее мы передадим из скрипта примерно так:
function handleRequest( event )
{

var response = event.connection.response;

var data =
{
name : 'Template',
further : {down: {the : {hierarchy: 'yes it is!' } } }
};
var template = new Markuper( 'templates/tutorial.html', data );

response.write( template.parse().html() );
response.close();

}


Обратите внимание на необходимость парсинга Маркапера: template.parse() и преобразования объекта в HTML: template.parse().html().

Шаринг файлов


imageСамая главная функция, о которой очень мало рассказано в API и вообще ничего нет в примерах. Итак, указав ранее в config.xml следующую конструкцию, мы заставляем после установки сервиса спросить у пользователя, какую папку он хочет расшарить.
<feature name="http://xmlns.opera.com/fileio">
<param name="folderhint" value="home" />
</feature>


home здесь папка пользователя на диске (типа Documents and Settings/user). Остальные примеры папок есть в API. Однако, просто путь к папке Вам не дадут указать. Кроссплатформенность, понимаете ли.
На этом начинаются сложности. Папка расшарена, но файлы в ней (!) нет. Хотя из мануалов к Юнайту косвенно следует обратное. Кроме того, что файлы придется расшаривать, существует еще одна сложность. Из-за нестабильности сервиса невозможно полагаться на то, что расшаренный один раз файл, будет в течение сессии расшаренным. Потому рекомендую обрабатывать доступ к файлам по мере поступления запросов. Запросил пользователь файл — расшарили, скачал — отняли статус расшаренного. Еще одна сложность — к сожалению, наличиствующие функции unshareFile, shareFile выбрасывают фатальную ошибку, если подсунуть им уже нерасшаренный файл или расшаренный файл соответственно. Т.е. дважды расшарить не то что не получится, Вы еще и получите нерабочий сервер.
Расшаренная папка имеет алиас shared. Кроме нее есть application (папка сервиса), storage (папка, выделенная для данных сервиса). Подключим shared к нашему серверу:
window.onload = function () {
webserver = opera.io.webserver;
if (webserver)
{
opera.io.filesystem.mountSystemDirectory('shared');
webserver.addEventListener('shared', download, false);
webserver.addEventListener('_index', showEntryList, false);
}
}


После включения сервера и сервиса, мы маунтим нашу папку shared. Учитывая сказанное выше про нестабильность Оперы, предлагаем серверу вручную обрабатывать запрос.
Теперь создадим саму функцию download:
function download(e) {
var req = e.connection.request;
var res = e.connection.response;
var mp = opera.io.filesystem.mountPoints;
var filePath = fullPathWithoutServiceName(req.uri);
var file = mp.resolve(filePath);
if(file.exists) {
if(file.isFile) {
opera.io.webserver.shareFile( file, filePath );
res.closeAndRedispatch();
opera.io.webserver.unshareFile(file);
}
if(file.isDirectory) {
//deep inside... folders, folders, folders. total commander :)
showEntryList(e);
res.closeAndRedispatch();
}
}
else {
res.closeAndRedispatch();
}
res.close();
}


Здесь долго останавливаться не будем, все примерно понятно по названиям функций API. Маунтим путь после названия сервиса в урле. fullPathWithoutServiceName — это моя самописная функция, парсит урл. Если по нему действительно существует файл, проверяем — папка он или просто тут тусуется. В случае папки передаем действие нашему обработчику showEntryList, который просто выводит список файлов и папок в директории. В случае файла расшариваем, передаем бразды правления серверу, а возвратившись, отнимаем права у файла. Перед выводом имен файлов, обратите внимение, что все символы там закодированы и перед их выводом необходимо их обернуть в функцию unescape. Иначе, в простейшем случае, вместо пробелов имени файла, юзер увидет %20.
С помощью функции closeAndRedispatch возвращаем серверу его функции и он продолжает свою работу. Т.о. я настроил работу с многоуровневыми папками и файлами.

Обновление версий


imageАналогичных механизмов оповещения об апдейтах, как в Файерфоксе, тут нету. Как вариант, можно использовать информацию о последней версии приложения через файл DOAP, который лежит на сайте Юнайта. Так, например, если сервис имеет адрес:
unite.opera.com/service/322
То его DOAP-файл лежит по адресу:
unite.opera.com/service/doap/322
Поскольку это обычный XML, его можно распарсить, проверить версию и предложить во всплывающем окне обновится. Вот пример обработки:
function getLastVersion() {
try {
var req = new XMLHttpRequest();
req.onreadystatechange = function(x) {
if (this.readyState == 4) {
if(this.status == 200) {
var xml = this.responseXML;
var release = xml.getElementsByTagName('release').item(0);
var version = release.getElementsByTagName('Version').item(0);
var revision = version.getElementsByTagName('revision').item(0).firstChild.data;
var lastCheckedVersion = getLastCheckedVersion();
if(revision && lastCheckedVersion && revision!=lastCheckedVersion) {
widget.showNotification("New version "+revision+" is available. Click here to update.", function() {
widget.openURL(globalSettings.url);});
setLastCheckedVersion(revision);
} } } };
req.open('GET', globalSettings.doap);
req.send(null);
}
catch(e) {
}
}

function getLastCheckedVersion() {
return widget.preferenceForKey('last_checked_version');
}

function setLastCheckedVersion(version) {
widget.setPreferenceForKey(version, 'last_checked_version');
}



Обратите внимание, используется доступ к внешним ресурсам, надо поправить config.xml, как описано ранее.
УРЛ unite.opera.com/service/322 можно получить и автоматически в теории, примаунтив директорию сервиса и покопавшись в файле config.xml — именно туда будет вписан УРЛ в виде ID. Но мне было влом копаться в этом, если кто напишет и откроет код — буду рад :) А так, к моменту написания этой функции, я уже знал этот УРЛ.

Дебаг


imageОбработка ошибок пока что нулевая. Часто сервер отрубается от фатальных ошибок, часто эти ошибки непонятно что означает — мануалов-то нет, а абстрактные названия мало о чем говорят. Никаких алертов на серверном javascript не сделаешь, единственный выход в люди — это функция opera.postError('error'); которая отпечает ошибку в консоли разработчика Оперы.

Рейтинги на Unite.opera.com


imageФигово сделано… Во-первых, никак нельзя проконтролировать номер версии приложения. По умолчанию, после проверки, сервис получает версию 1.0. При апдейте можно лишь указать, что изменения мелкие (версия 1.1) или значительные (версия 2.0). Обратите внимание, при этом рейтинг скачек за последние 7 дней обнуляется и Вы резко падаете вниз на последнее место. Т.о. у разработчика нет стимула закачивать новые объявления, ибо следующую версию может никто и не заметить, в рейтинге Вы будете на нуле. Приложение, которое сто лет не обновлялось может вечно торчать в топе и не уходить оттуда за счет хомячков. Так что на данный момент рекомендую Вам не обновлять версию до исправления значительного количества ошибок.

Stream media


image
Результатом моих стараний стал сервис Stream media, который позволяет на раз-два сделать что-то вроде Ютуба для своих. Можно проигрывать видео и аудио. Конечно, если бы они ограничивались только форматами MP3 и FLV, все было бы кроссбраузерно. Но большинство хранит видео в AVI и т.п., потому возникают вопросы кроссбраузерности и кроссплатформенности. Тут я уж я не могу ничем помочь, и так все максимально «кросс» за счет плагина к jQuery. Однако, с AVI в Windows Media Player придется ждать, пока он не скачает полностью файл. Максимум, что могу предложить, либо конвертировать в FLV, тогда запустится флеш-плеер, либо скачать клиенту файл перед просмотром.
Скачать можно тут:
unite.opera.com/service/322

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

P.S. Думаю, что смысла как-то монетизировать сервис нет. Потому, надеюсь, что скоро выйдет версия с дизайном получше, общаюсь с одним человеком на эту тему, на альтруистских основах :)
Tags:
Hubs:
Total votes 62: ↑55 and ↓7+48
Comments4

Articles