![image](http://unite.opera.com/images/default_icon_64.png)
![image](https://habrastorage.org/getpro/habr/post_images/7a5/09b/8a3/7a509b8a3f0bd3e03848bd78ef33a19f.jpg)
Далее, будем вникать в технологию постепенно. Желательно, чтобы Вы уже прочли мануал по созданию первого приложения, поскольку некоторые очевидные факты здесь опускаются.
config.xml
<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
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
Теперь можно создать файл-шаблон, например 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().
Шаринг файлов
<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 возвращаем серверу его функции и он продолжает свою работу. Т.о. я настроил работу с многоуровневыми папками и файлами.
Обновление версий
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. Но мне было влом копаться в этом, если кто напишет и откроет код — буду рад :) А так, к моменту написания этой функции, я уже знал этот УРЛ.
Дебаг
Рейтинги на Unite.opera.com
Stream media
Результатом моих стараний стал сервис Stream media, который позволяет на раз-два сделать что-то вроде Ютуба для своих. Можно проигрывать видео и аудио. Конечно, если бы они ограничивались только форматами MP3 и FLV, все было бы кроссбраузерно. Но большинство хранит видео в AVI и т.п., потому возникают вопросы кроссбраузерности и кроссплатформенности. Тут я уж я не могу ничем помочь, и так все максимально «кросс» за счет плагина к jQuery. Однако, с AVI в Windows Media Player придется ждать, пока он не скачает полностью файл. Максимум, что могу предложить, либо конвертировать в FLV, тогда запустится флеш-плеер, либо скачать клиенту файл перед просмотром.
Скачать можно тут:
unite.opera.com/service/322
Вот так немножко сумбурно, пришлось залезть в поле деятельности официальных мануалов, но без повторения пройденного я не смог бы рассказать об особенностях, которые не описаны на сайте Юнайта. Если есть вопросы, пишите, попробуем разобраться вместе.
P.S. Думаю, что смысла как-то монетизировать сервис нет. Потому, надеюсь, что скоро выйдет версия с дизайном получше, общаюсь с одним человеком на эту тему, на альтруистских основах :)