2011-11-17

Harmattan. Пишем в Event Feed


В MeeGo Harmattan как известно левый экран отдан под список событий. В этой статье я расскажу, как в него писать на примере приложения Calendar Feed (о нем позже).

Итак, мы хотим писать в EventFeed. Причем хотим это делать независимо ни от какого клиентского приложения, а в фоновом режиме. Для этих целей нам понадобится SyncFW. Сама работа с EventFeed происходит через класс MEventFeed. К сожалению, все это документировано не то чтобы уж очень хорошо (если не сказать совсем по другому), поэтому часть информации неизвестна, часть получена опытным путем.

SyncFW
Этот фреймворк является этаким cron'ом. Для добавления своих задач в него надо написать плагин, который состоит из библиотеки с плагином и 3 xml-файлов. Важно учесть, что использование дефиса в названии плагина ведет к не очень приятным последствиям в виде отказа работать. В дальнейшем в качестве названия плагина у нас будет фигурировать calendarfeed (который, собственно, и является тем самым примером Calendar Feed).

Итак, файлы, необходимые для работы плагина:
  • /usr/lib/sync/libcalendarfeed-client.so
  • /etc/sync/profiles/client/calendarfeed.xml
  • /etc/sync/profiles/service/calendarfeed.xml
  • /etc/sync/profiles/sync/calendarfeed.xml
Ну, сам плагин оставим на потом, а пока пройдемся по xml'ям.

/etc/sync/profiles/client/calendarfeed.xml
<?xml version="1.0" encoding="UTF-8"?>
<profile name="calendarfeed" type="client" >
</profile>
Предельно простой файл с одной нодой profile. Описывает как называется профиль клиента (клиентом является библиотека с плагином).


/etc/sync/profiles/service/calendarfeed.xml
<?xml version="1.0" encoding="UTF-8"?>
<profile  name="calendarfeed" type="service">
   <key name="destinationtype" value="online"/>
   <profile name="calendarfeed" type="client" >
   </profile>
</profile>
Этот файлик помогает сервису SyncFW загрузить клиентскую часть (плагин), а ключ destinationtype активирует его.

/etc/sync/profiles/sync/calendarfeed.xml
<?xml version="1.0" encoding="UTF-8"?>
<profile name="calendarfeed" type="sync" >
    <key name="displayname" value="Calendar Feed"/>
    <key name="enabled" value="true" />
    <key name="use_accounts" value="false" />
    <key name="hidden" value="true" />
    <profile type="service" name="calendarfeed" >
    </profile>
    <schedule enabled="true" interval="20" days="1,2,3,4,5,6,7" 
                syncconfiguredtime="" time="">
       <rush begin="00:00:00" enabled="true" interval="15" 
                end="17:00:00" days="1,2,3,4,5,6,7"/>
   </schedule>
</profile>
Тут информации побольше. Стандартно указывается название профиля. Нода profile содержит ключи:
  • displayname - имя, которое будет отображаться в UI
  • enabled - активирован или нет профиль
  • use_accounts - нужно ли использовать фреймворк аккаунтов
  • hidden - спрятан ли данный профиль в UI
Также есть ссылка на профиль сервиса и расписание. Расписание позволяет задать два режима обновление: стандартное и rush. Обычно второе используется для более активного обновления в нужные часы (например в рабочее время). Интервал обновления в расписании указывается в минутах.


Плагин SyncFW
А теперь рассмотрим сам плагин. Он состоит из одного класса-наследника Buteo::ClientPlugin и двух функций: createPlugin() и destroyPlugin().

class CalendarFeedPlugin : public Buteo::ClientPlugin
{
    Q_OBJECT
public:
    CalendarFeedPlugin( const QString &pluginName,
                         const Buteo::SyncProfile &profile,
                         Buteo::PluginCbInterface *cbInterface );
    virtual ~CalendarFeedPlugin();
    virtual bool init();
    virtual bool uninit();
    virtual bool startSync();
    virtual void abortSync(Sync::SyncStatus status = Sync::SYNC_ABORTED);
    virtual Buteo::SyncResults getSyncResults() const;
    virtual bool cleanUp();
public slots:
    virtual void connectivityStateChanged( Sync::ConnectivityType type,
                                           bool state );
protected slots:
    void syncSuccess();
    void syncFailed();
    void updateFeed();
private:
    void updateResults(const Buteo::SyncResults &results);
    Buteo::SyncResults m_results;
};
extern "C" CalendarFeedPlugin* createPlugin( const QString& pluginName,
           const Buteo::SyncProfile &profile,
           Buteo::PluginCbInterface *cbInterface );
extern "C" void destroyPlugin( CalendarFeedPlugin *client );

В основном тут находятся переопределенные pure virtual методы. Рассмотрим их назначение.
virtual bool init();
Вызывается в самом начале и нужен для проверки может ли плагин инициализировать сам себя. В случае успеха возвращается true и процесс идет дальше.
virtual bool uninit();
Вызывается перед выгрузкой плагина, тут обычно происходит очистка.
virtual bool startSync();
Метод проверяет может ли (и должен ли) он стартовать обновление и стартует его (асинхронно). Возвращает результат успеха старта обновления.
virtual void abortSync(Sync::SyncStatus status = Sync::SYNC_ABORTED);
Вызывается когда пользователь отменил обновление. Нужно для работы с аккаунтами (где есть такая кнопка), в нашем же случае возможности отменить обновление у пользователя нет.
virtual Buteo::SyncResults getSyncResults() const;
Возвращает результаты последнего обновления
virtual bool cleanUp();
Вызывается при удалении аккаунта. В нашем случае опять же не нужен (хотя в документации написано что возможно какое-нибудь применение ему и найдется позже).
virtual void connectivityStateChanged(Sync::ConnectivityType type,bool state);
Вызывается если изменилось состояние подключения. Если плагин не работает с сетью, то этот слот также будет пустым

Как видно из описаний, все самое интересное в методе startSync(). В самом простом случае в этом методе мы проверяем, должны ли мы что-либо сделать и, если должны, запускаем singleShot-таймер со слотом, выполняющим само действие (в этом примере это метод updateFeed()).

Теперь посмотрим что нам нужно в pro-файле для этого плагина.

TEMPLATE = lib
TARGET = calendarfeed-client
DEPENDPATH += .
INCLUDEPATH += . \
                /usr/include/libsynccommon \
                /usr/include/libsyncprofile
LIBS += -lsyncpluginmgr -lsyncprofile
CONFIG += debug plugin meegotouchevents
QT -= gui
#input
SOURCES += \
    calendarfeedplugin.cpp
HEADERS +=\
    calendarfeedplugin.h
QMAKE_CXXFLAGS = -Wall \
    -g \
    -Wno-cast-align \
    -O2 -finline-functions
#install
target.path = /usr/lib/sync
client.path = /etc/sync/profiles/client
client.files = xml/calendarfeed.xml
sync.path = /etc/sync/profiles/sync
sync.files = xml/sync/calendarfeed.xml
service.path = /etc/sync/profiles/service
service.files = xml/service/calendarfeed.xml
settingsdesktop.path = /usr/share/duicontrolpanel/desktops
settingsdesktop.files = settings/calendarfeed.desktop
settingsxml.path = /usr/share/duicontrolpanel/uidescriptions
settingsxml.files = settings/calendarfeed.xml
INSTALLS += target client sync service settingsdesktop settingsxml

Это минимальный набор, без которого плагин работать не будет.

MEventFeed
Собственно говоря, самое интересное и самое основное я решил оставить напоследок. Итак, запись в Event Feed. Класс MEventFeed является синглтоном и просто блещет богатством интерфейса. Он содержит (кроме instance()) целых 2 метода (в документации заявлено 3, но в harmattan target в QtSDK видимо все еще старая версия, поэтому их там 2). Один для добавления элемента, другой для удаления всех элементов из определенного источника.
qlonglong addItem(const QString &icon,
                  const QString &title,
                  const QString &body,
                  const QStringList &imageList,
                  const QDateTime &timestamp,
                  const QString &footer,
                  bool video,
                  const QUrl &url,
                  const QString &sourceName,
                  const QString &sourceDisplayName);
Параметров у метода немало. Можно задать иконку (отображается слева), заголовок (отображается над элементов болдом), сам текст, список изображений (отображаются под текстом), время (отображается под текстом темно-серым шрифтом), футер (рядом с временем), флаг видео (видео может быть только одно, и если флаг взведен, то элемент из imageList можно будет воспроизвести), название источника (внутреннее), лейбл источника (для отображения в UI). Возвращает метод либо -1 (если что-то пошло не так), либо уникальный идшник элемента (нужен в третьем, отсутствующем методе, который позволяет удалять элементы по ид).

void removeItemsBySourceName(const QString &sourceName);
Метод удаления прост как топор и удаляет все элементы, принадлежащие указанному
источнику.

Установка плагина
Как таковая, установка ничем не отличается от установки обычного приложения. За одним исключением. При установке/удалении нужно перезагрузить телефон, чтобы он нашел/потерял плагин. Начиная с прошивки 1.1 мы можем избежать этого досадного действа, включив в deb-пакет два файла: postinst (выполняется сразу после установки) и prerm (выполняется непосредственно перед удалением).

postinst:
#!/bin/sh
/usr/bin/aegis-exec -s -u user dbus-send --dest=com.meego.msyncd --print-reply /synchronizer com.meego.msyncd.installPlugin string:'calendarfeed'
exit 0

Мы через dbus пинаем syncfw, чтобы он подтянул новый плагин.

prerm:
#!/bin/sh
/usr/bin/aegis-exec -s -u user dbus-send --dest=com.meego.msyncd --print-reply /synchronizer com.meego.msyncd.uninstallPlugin string:'calendarfeed'
/usr/bin/aegis-exec -s -u user dbus-send --dest=com.nokia.home.EventFeed --print-reply /eventfeed com.nokia.home.EventFeed.removeItemsBySourceName string:'SyncFW-calendarfeed'>
exit 0


Мы через dbus сначала удаляем плагин из syncfw, а потом удаляем все элементы, принадлежащие источнику SyncFW-calendarfeed из event feed.

Известные ограничения
Самое интересное и самое грустное.
Синхронизация не работает без подключенного интернета (wifi или 3g, независимо от того, нужен интернет или нет). Опровергнуто
Встроенная в event feed кнопка Refresh не обновляет сторонние плагины, она работает только для встроенных приложений. Опровергнуто
Нельзя поменять расписание работы плагина (или как минимум я не нашел как. Вариант написать GUI-приложение, которое будет править файлы в /etc мне не очень нравится).

Комментариев нет:

Отправить комментарий