Разработка приложений для Meego Harmattan

    Этот пост участвует в конкурсе „Умные телефоны за умные посты“.

    image
    В данной статье хотелось бы поделится с Хабрасообществом своим опытом по разработке софта с использованием QtComponents'ов на примере Meego Harmattan'а. Писать мы будем редактор заметок с синхронизацией средствами Ubuntu One.



    Вся разработка будет вестись при помощи scratchbox'а, он имеет некоторые преимущества в сравнении с madde, но работает исключительно в linux системах.
    Среди ключевых преимуществ хочется отметить то, что сборка производится в chroot'е и в случае armel для эмуляции используется qemu. Условия максимально приближены к боевым. Это позволяет избежать дополнительной возни с настройкой кросскомпиляции. Дополнительным плюсом является наличие apt-get'а, способного установить все зависимости, необходимые для сборки, что несомненно понадобится при написании приложения сложнее, чем helloworld.

    Установка и настройка scratchbox'а



    Для того, чтобы установить scratchbox нужно скачать и запустить от рута этот скрипт и в дальнейшем следовать его указаниям.
    # ./harmattan-sdk-setup.py

    После установки необходимо перелогинится, чтобы пользователь был успешно добавлен в группу sbox.
    Запускать scratchbox мы будем с помощью команды:
    $ /scratchbox/login

    Если установщик правильно отработал, то должно появится приглашение примерно следующего содержания:
    [sbox-HARMATTAN_ARMEL: ~] > 

    Если login ругается, то попробуйте выполнить скрипт run_me_first.sh, лежащий в корне scratchbox'а. Нужный таргет можно выбрать с помощью sb_menu. Остальное руководство по использованию scratchbox'а можно найти здесь.

    Создание cmake проекта



    В качестве сборщика я использую не привычный qmake, а более мощный cmake, который умеет искать зависимости, имеет кучу опций настройки и гораздо лучше подходит для кроссплатформенной разработки. В данной статье я не буду сильно углубляться в разбор системы сборки, поэтому для лучшего понимания рекомендую прочесть эту статью.
    Единственный минус в том, что cmake не умеет Symbian, поэтому об этой платформе пока можно забыть или же написать вручную специальный проект для сборки именно под эту платформу. Со всеми остальными cmake справляется с легкостью, поэтому в дальнейшем я планирую портировать это приложение на настольные системы и, возможно, на Андроид или даже на iOS.
    Проект состоит из некоторого количества зависимых библиотек, которые подключены при помощи git submodule к основному репозиторию, для каждой из них написан свой cmake проект. Все они лежат в каталоге 3rdparty и подключены к основному проекту, поэтому сборка идёт сразу с основными зависимостями, которых нет в репозиториях harmattan'а.

    Список 3rdparty библиотек:
    • QOauth — реализация протокола Oauth на Qt
    • k8json — очень быстрый парсер JSON
    • QmlObjectModel — Класс, реализующий модель — список обьектов

    Помимо этого есть ещё внешние библиотеки, необходимые для сборки, но присутствующие в основных репах Harmattan'а, к ним относится qca, давайте сразу её установим, а также установим cmake:
    [sbox-HARMATTAN_ARMEL: ~] > apt-get install libqca2-dev cmake

    Для того, чтобы её можно было использовать необходимо написать специальный cmake файл, который бы смог найти каталог с заголовочными файлами библиотеки и сам файл библитеки для того, чтобы с ним слинковаться.
    include(FindLibraryWithDebug)
    if(QCA2_INCLUDE_DIR AND QCA2_LIBRARIES)
        # in cache already
        set(QCA2_FOUND TRUE)
    else(QCA2_INCLUDE_DIR AND QCA2_LIBRARIES)
        if(NOT WIN32)
            find_package(PkgConfig)
            pkg_check_modules(PC_QCA2 QUIET qca2)
            set(QCA2_DEFINITIONS ${PC_QCA2_CFLAGS_OTHER})
        endif(NOT WIN32)
    
        find_library_with_debug(QCA2_LIBRARIES
            WIN32_DEBUG_POSTFIX d
            NAMES qca
            HINTS ${PC_QCA2_LIBDIR} ${PC_QCA2_LIBRARY_DIRS} ${QT_LIBRARY_DIR})
    
        find_path(QCA2_INCLUDE_DIR QtCrypto
            HINTS ${PC_QCA2_INCLUDEDIR} ${PC_QCA2_INCLUDE_DIRS} ${QT_INCLUDE_DIR}}
            PATH_SUFFIXES QtCrypto)
    
        include(FindPackageHandleStandardArgs)
        find_package_handle_standard_args(QCA2 DEFAULT_MSG QCA2_LIBRARIES QCA2_INCLUDE_DIR)
        mark_as_advanced(QCA2_INCLUDE_DIR QCA2_LIBRARIES)
    endif(QCA2_INCLUDE_DIR AND QCA2_LIBRARIES)
    

    В таком же стиле написан поиск большинства зависимостей. Для систем с pgkconfig'ом, к которым относится и Harmattan всё просто и ясно, для систем, где его нет, будем искать в каталоге $QTDIR. В случае, если cmake автоматически не нашел библиотеку, он предложит вручную задать переменные QCA2_INCLUDE_DIR QCA2_LIBRARIES. Такой подход здорово облегчает жизнь на системах, в которых отсутствует менеджер пакетов.
    В cmake'е есть переменные, которые позволяют определить платформу, на которой собирается та или иная программа, например:
    if(WIN32)
    ....
    elseif(APPLE)
     ...
    elseif(LINUX)
    ...
    endif()
    

    К сожалению, cmake ничего не знает про Harmattan, самым простым решением является запуск cmake'а с ключем -DHARMATTAN=ON. Теперь у нас определена переменная HARMATTAN, и можно писать подобные вещи:
    if(HARMATTAN)
    	add_definitions(-DMEEGO_EDITION_HARMATTAN) #дефайн для компилятора, без него приложение не будет разворачиваться на весь экран.
    endif()
    

    С помощью этих же переменных можно определять, какая именно реализация GUI будет устанавливаться.
    if(HARMATTAN)
    	set(CLIENT_TYPE meego)
    	message(STATUS "Using meego harmattan client")
    else()
    	set(CLIENT_TYPE desktop)
    	list(APPEND QML_MODULES QtDesktop)
    	message(STATUS "Using desktop client")
    endif()
    set(QML_DIR "${CMAKE_CURRENT_SOURCE_DIR}/qml/${CLIENT_TYPE}")
    ...
    install(DIRECTORY ${QML_DIR} DESTINATION ${SHAREDIR}/qml)
    

    Для разработки большую часть времени будет достаточно QtSDK с harmattan quick components и ключа -DHARMATTAN при сборке. В scratchbox'е имеет смысл собирать уже более-менее конечные версии.

    С++ плагин, реализующий Tomboy notes API



    Сам API я решил вынести в отдельный qml модуль, который будет доступен через директиву import. Сделано это для удобства создания множества различных реализаций GUI интерфейса.
    Самым сложным в процессе разработки оказалось реализовать авторизацию средствами OAuth, в процессе которой было перебрано несколько различных реализаций библиотек и на данный момент я остановился на QOauth, которая конечно не идеальна, но является вполне рабочей. На хабре есть статья с описанием этой библиотеки, поэтому сразу перейдем к решению насущных проблем. Перво-наперво нам нужно получить тот самый вожделенный token.
    Дело это не хитрое, просто посылаем запрос на адрес и ждём, когда же нам прилетит запрос на basic авторизацию по https:
    UbuntuOneApi::UbuntuOneApi(QObject *parent) :
    	QObject(parent),
    	m_manager(new QNetworkAccessManager(this)),
    	m_oauth(new QOAuth::Interface(this))
    {
    ...
    	connect(m_manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
    			SLOT(onAuthenticationRequired(QNetworkReply*,QAuthenticator*)));
    }
    ...
    	void UbuntuOneApi::requestToken(const QString &email, const QString &password)
    {
    	m_email = email;
    	m_password = password;
    
    	QUrl url("https://login.ubuntu.com/api/1.0/authentications");
    	url.addQueryItem(QLatin1String("ws.op"), QLatin1String("authenticate"));
    	url.addQueryItem(QLatin1String("token_name"), QLatin1Literal("Ubuntu One @ ") % m_machineName);
    
    	qDebug() << url.toEncoded();
    
    	QNetworkRequest request(url);
    	QNetworkReply *reply = m_manager->get(request);
    	reply->setProperty("email", email);
    	reply->setProperty("password", password);
    
    	connect(reply, SIGNAL(finished()), SLOT(onAuthReplyFinished()));
    }
    ...
    void UbuntuOneApi::onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *auth)
    {
    	auth->setUser(reply->property("email").toString());
    	auth->setPassword(reply->property("password").toString());
    }
    

    Как вы могли заметить, для авторизации используется стандартный для QNetworkAccessManager'а сигнал authenticationRequired, а логин и пароль я просто запоминаю обычными пропертями. Удобно и не засоряет интерфейс лишними деталями.
    По завершению в reply должен прийти ответ в json формате, который содержит искомый токен и прочую важную информацию. Тут-то нам и понадобится библиотека k8json.
    	QNetworkReply *reply = static_cast<QNetworkReply*>(sender());
    	QVariantMap response = Json::parse(reply->readAll()).toMap();
    	if (response.isEmpty()) {
    		emit authorizationFailed(tr("Unable to recieve token"));
    	}
    
    	m_token = response.value("token").toByteArray();
    	m_tokenSecret = response.value("token_secret").toByteArray();
    	m_oauth->setConsumerKey(response.value("consumer_key").toByteArray());
    	m_oauth->setConsumerSecret(response.value("consumer_secret").toByteArray());
    
    	QUrl url("https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/" + reply->property("email").toString());
    	connect(get(url), SIGNAL(finished()), SLOT(onConfirmReplyFinished()));
    

    Следующим шагом будет отправка подтверждения того факта, что мы получили токен (обратите внимание на последнюю строчку). В результате нам должен прийти ответ ok.
    void UbuntuOneApi::onConfirmReplyFinished()
    {
    	QNetworkReply *reply = static_cast<QNetworkReply*>(sender());
    	QByteArray data = reply->readAll();
    	if (data.contains("ok")) {
    		emit hasTokenChanged();
    

    Если такое слово есть в ответе, то всё, можно радостно прыгать и посылать сигнал о том, что токен наконец получен и можно начинать работать с заметками, но не тут-то было! Для совместимости с tomboy api сервер заметок требует авторизацию посредством веб браузера. Пока мне не удалось обойти эту проблему и, скрипя зубами, мне пришлось добавить в приложение webkit окошко, которое содержит кнопку «разрешить данному пользователю доступ к заметкам». Этому webkit окошку мы даем указатель на наш QNetworkAccessManager и по успешному завершению авторизации он станет обладателем заветных cookies с данными, необходимыми для авторизации.
    А чтобы пользователю по новой не пришлось вбивать логин и пароль, мы заполним эти поля через DOM дерево.
    		QWebFrame *frame = page()->mainFrame();
    		QWebElement email = frame->findFirstElement("#id_email");
    		email.setAttribute("value", m_email);
    		QWebElement pass = frame->findFirstElement("#id_password");
    		pass.setAttribute("value", m_password);
    
    		QWebElement submit = frame->findFirstElement("#continue");
    		submit.setFocus();
    

    Не забудем сохранить полученные кукисы, мы же хотим быть слишком навязчивыми.
    void Notes::onWebAuthFinished(bool success)
    {
    	if (success) {
    		QNetworkCookieJar *jar = m_api->manager()->cookieJar();
    		QList<QNetworkCookie> cookies = jar->cookiesForUrl(m_apiRef);
    		QSettings settings;
    		settings.beginWriteArray("cookies", cookies.count());
    		for (int i = 0; i != cookies.count(); i++) {
    			settings.setArrayIndex(i);
    			settings.setValue("cookie", cookies.at(i).toRawForm());
    		}
    		settings.endArray();
    		sync();
    	}
    }
    

    Для того, чтобы сервер успешно обрабатывал наши запросы нужно, чтобы они в заголовке содержали полученный нами ранее токен. Тут нам и пригодится QOauth.
    QNetworkReply *UbuntuOneApi::get(const QUrl &url)
    {
    	QByteArray header = m_oauth->createParametersString(url.toEncoded(), QOAuth::GET, m_token, m_tokenSecret,
    														QOAuth::HMAC_SHA1, QOAuth::ParamMap(),
    														QOAuth::ParseForHeaderArguments);
    
    	QNetworkRequest request(url);
    	request.setRawHeader("Authorization", header);
    	return m_manager->get(request);
    }
    

    Теперь с легким сердем можно приступать к реализации tomboy api.
    Для простоты работы из qml'я, я каждую заметку решил представить отдельным QObject'ом, а список заметок реализовал через QObjectListModel, реализацию которой нашел на просторах qt labs'ов. У каждой заметки свой guid, зная который можно с ней работать. Guid генерируетя на клиентской стороне, для этого в Qt есть соответствующие методы, находящиеся в классе QUuid, поэтому при конструировании новой заметки нужно сгенерировать для неё уникальный идентификатор, по которому мы будем обращаться к ней в дальнейшем.
    Note::Note(Notes *notes) :
    	QObject(notes),
    	m_notes(notes),
    	m_status(StatusNew),
    	m_isMarkedForRemoral(false)
    {
    	QUuid uid = QUuid::createUuid();
    	m_guid = uid.toString();
    	m_createDate = QDateTime::currentDateTime();
    }
    

    Основные действия с заметками:
    • Синхронизировать заметки с сервером
    • Добавить новую заметку
    • Обновить заметку
    • Удалить заметку

    Исходя из этих действий и будем проектировать API, в модели заметок сделаем метод sync, а в самой заметке методы save, remove. Ну и конечно реализуем свойства title и content:
    class Note : public QObject
    {
    	Q_OBJECT
    
    	Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
    	Q_PROPERTY(QString content READ content WRITE setContent NOTIFY textChanged)
    	Q_PROPERTY(int revision READ revision NOTIFY revisionChanged)
    	Q_PROPERTY(Status status READ status NOTIFY statusChanged)
    	Q_PROPERTY(QDateTime createDate READ createDate NOTIFY createDateChanged)
    ...
    

    Неплохой идеей также было бы добавить свойство статуса заметки, который можно было бы использовать в states'ах в qml'е.
    	Q_ENUMS(Status)
    public:
    	enum Status
    	{
    		StatusNew,
    		StatusActual,
    		StatusOutdated,
    		StatusSyncing,
    		StatusRemoral
    	};
    

    Для этого мы используем волшебный макрос Q_ENUMS, который генерирует метаинформацию для перечислений. Теперь в qml коде можно получать их численное значение и сравнивать между собой.
                State {
                    name: "syncing"
                    when: note.status === Note.StatusSyncing
    

    Удобно, читабельно и быстро. Всё-таки сравниваются числа, а не строки!
    По умолчанию в QObjectListModel к элементу модели из делегата можно обращаться по имени object, но меня это не очень устраивает, поэтому я просто унаследовался от модели и поменял имя для роли ObjectRole на note.
    NotesModel::NotesModel(QObject *parent) :
    	QObjectListModel(parent)
    {
    	QHash<int, QByteArray> roles;
    	roles[ObjectRole] = "note";
    	setRoleNames(roles);
    }
    


    А теперь я рассмотрю создание самого qml модуля. Для того, чтобы нашу реализацию api можно было использовать через import в qml мы должны в нашем модуле создать класс, унаследованный от QDeclarativeExtensionPlugin и реализовать в нем метод registerTypes, который бы зарегистрировал все наши методы и классы.
    void QmlBinding::registerTypes(const char *uri)
    {
    	Q_ASSERT(uri == QLatin1String("com.ubuntu.one"));
    
    	qmlRegisterType<UbuntuOneApi>(uri, 1, 0, "Api");
    	qmlRegisterType<ProgressIndicatorBase>(uri, 1, 0, "ProgressIndicatorBase");
    
    	qmlRegisterUncreatableType<Notes>(uri, 1, 0, "Notes", tr("Use Api.notes property"));
    	qmlRegisterUncreatableType<Account>(uri, 1, 0, "Account", tr("Use Api.account property"));
    	qmlRegisterUncreatableType<Note>(uri, 1, 0, "Note", tr(""));
    	qmlRegisterUncreatableType<NotesModel>(uri, 1, 0, "NotesModel", tr(""));
    }
    
    Q_EXPORT_PLUGIN2(qmlbinding, QmlBinding)
    

    Вы наверное обратили внимание на assert и хотите спросить. А откуда же берётся это самое uri? А берётся оно из названия каталога, в котором лежит наш модуль. То есть Qt будет искать наш модуль в:
    $QML_IMPORTS_DIR/com/ubuntu/one/
    

    Но и это ещё не всё. Чтобы Qt нашла и заимпортила наш модуль нужно, чтобы в директории лежал правильно составленный файл qmldir, в котором перечислены бинарные плагины, qml и js файлы.
    plugin qmlbinding
    


    Разработка qml интерфейса для Meego Harmattan



    Основной большинства приложений на Meego является элемент PageStackWindow, который, как это не странно, являет собой стек страниц. Страницы добавляются в стек при помощи метода push, а извлекаются при помощи pop'а. Одна из страниц должна быть назначена как исходная. У каждой страницы может быть свой собственный тулбар. Можно же нескольким страницам назначать один и тот же.
    import QtQuick 1.1
    import com.nokia.meego 1.0
    import com.ubuntu.one 1.0 //наш искомый модуль с notes API
    
    PageStackWindow {
    	id: appWindow
    	initialPage: noteListPage
    
    	Api { //обьект, реализующий API
    		id: api
    		Component.onCompleted: checkToken()
    		onHasTokenChanged: checkToken()
    
    		function checkToken() {
    			if (!hasToken)
    				loginPage.open();
    			else
    				api.notes.sync();
    		}
    	}
    ...
    

    Теперь давайте создадим все нужные нам страницы а также стандартный toolbar, в котором будет кнопка добавить заметку и меню с действиями:
    ...
    	LoginPage {
    		id: loginPage
    		onAccepted: api.requestToken(email, password);
    	}
    
    	NoteListPage {
    		id: noteListPage
    		notes: api.notes
    	}
    
    	NoteEditPage {
    		id: noteEditPage
    	}
    
    	AboutPage { id: aboutPage }
    	ToolBarLayout {
    		id: commonTools
    		visible: true
    
    		ToolIcon {
    			iconId: "toolbar-add"
    			onClicked: {
    				noteEditPage.note = api.notes.create();
    				pageStack.push(noteEditPage);
    			}
    		}
    		ToolIcon {
    			platformIconId: "toolbar-view-menu"
    			anchors.right: (parent === undefined)? undefined: parent.right
    			onClicked: (menu.status === DialogStatus.Closed)? menu.open(): menu.close()
    		}
    	}
    
    	Menu {
    		id: menu
    		visualParent: pageStack
    		MenuLayout {
    			MenuItem {
    				text: qsTr("About")
    				onClicked: {menu.close(); pageStack.push(aboutPage)}
    			}
    			MenuItem {
    				text: qsTr("Sync")
    				onClicked: api.notes.sync();
    			}
    			MenuItem {
    				text: api.hasToken ? qsTr("Logout") : qsTr("Login")
    				onClicked: {
    					if (api.hasToken)
    						api.purge();
    					else
    						loginPage.open();
    				}
    			}
    		}
    	}
    


    Теперь рассмотрим что же из себя представляет отдельная страница на примере NoteListPage, реализация которой лежит в NoteListPage.qml:
    import QtQuick 1.1
    import com.nokia.meego 1.0
    import com.ubuntu.one 1.0
    
    Page {
    	id: noteListPage
    	property QtObject notes: null
    	tools: commonTools //тот самый тулбар, обьявленный в main.qml
    
    	PageHeader { //Красивый оранжевый заголовок. По сути представляет из себя обычный оранжевый прямоугольник с текстом
    		id: header
    		text: qsTr("Notes:")
    	}
    
    	ListView {
    		id: listView
    		anchors.top: header.bottom
    		anchors.left: parent.left
    		anchors.right: parent.right
    		anchors.bottom: parent.bottom
    		anchors.margins: 11
    		clip: true
    		focus: true
    		model: notes.model
    		delegate: ItemDelegate {
    			title: note.title //обращение к описанным выше свойствам заметки
    			subtitle: truncate(note.content, 32)
    			onClicked: {
    				noteEditPage.note = note;
    				pageStack.push(noteEditPage); //добавляем страничку в стек
    			}
    
    			function truncate(str, n, suffix) {
    				str = str.replace(/\r\n/g, "");
    				if (suffix === undefined)
    					suffix = "...";
    				if (str.length > n)
    					str = str.substring(0, n) + suffix;
    				return str;
    			}
    		}
    	}
    
    	ScrollDecorator {
    		flickableItem: listView 
    	}
    
    }
    

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


    Для окошка логина я использовал объект Sheet, который представляет из себя страничку, выезжающую сверху. Обычно с помощью него у пользователя запрашивают какую либо информацию.
    import QtQuick 1.0
    import com.nokia.meego 1.0
    import "constants.js" as UI //еще один интересный финт ушами - js файл с константами. Заодно можно увидеть способ реализации namespace'ов в qml'е.
    
    Sheet {
    	id: loginPage
    	property alias email: loginInput.text //альясы. На самом деле, теперь при обращении к этому свойству мы обращаемся к свойству loginInput.text
    	property alias password: passwordInput.text
    
    	content: Column { //содержимое sheet'а
    		anchors.topMargin: UI.MARGIN_DEFAULT
    		anchors.horizontalCenter: parent.horizontalCenter
    
    		Image {
    			id: logo
    			source: "images/UbuntuOneLogo.svg"
    		}
    
    		Text {
    			id: loginTitle
    
    			width: parent.width
    			text: qsTr("Email:")
    			font.pixelSize: UI.FONT_DEFAULT_SIZE
    			color: UI.LIST_TITLE_COLOR
    		}
    
    		TextField {
    			id: loginInput
    			width: parent.width
    		}
    
    		Text {
    			id: passwordTitle
    
    			width: parent.width
    			text: qsTr("Password:")
    			font.pixelSize: UI.FONT_DEFAULT_SIZE
    			color: UI.LIST_TITLE_COLOR
    		}
    
    		TextField {
    			id: passwordInput
    			width: parent.width
    			echoMode: TextInput.Password
    		}
    	}
    	acceptButtonText: qsTr("Login")
    	rejectButtonText: qsTr("Cancel")
    }
    
    

    Выглядеть всё это великолепие будет вот так:

    На страничках редактирования и about нужно реализовать кнопку назад, которая бы возращала нас к списку заметок. Для этого commonTools уже не очень подходит, нужны свои тулбары:
    	ToolBarLayout {
    		id: aboutTools
    		visible: true
    		ToolIcon {
    			iconId: "toolbar-back"
    			onClicked: {
    				pageStack.pop()
    			}
    		}
    	}
    


    Иконка запуска



    Чтобы у приложения появилась иконка запуска создадим знакомый всем линуксоидам .desktop файл:
    [Desktop Entry]
    Name=ubuntuNotes
    Name[ru]=ubuntuNotes
    GenericName=ubuntuNotes 
    GenericName[ru]=ubuntuNotes
    Comment=Notes editor with sync
    Comment[ru]=Редактор заметок с синхронизацией
    Exec=/usr/bin/single-instance /opt/ubuntunotes/bin/ubuntuNotes %U
    Icon=/usr/share/icons/hicolor/80x80/apps/ubuntuNotes.png
    StartupNotify=true
    Terminal=false
    Type=Application
    Categories=Network;Qt;
    

    Обратите внимание на секцию Exec: таким образом мы говорим, что приложение не может быть запущено несколько раз. Если мы хотим чтобы у приложения был красивый сплеш, то можно использовать утилиту invoker.
    Exec=/usr/bin/invoker --splash=/usr/share/apps/qutim/declarative/meego/qutim-portrait.png --splash-landscape=/usr/share/apps/qutim/declarative/meego/qutim-landscape.png --type=e /usr/bin/qutim
    

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

    Сборка deb пакета



    Для сборки используется стандартный dpkg-buildpackage и обычный debian, который для удобства называется debian_harmattan, а перед непосредственно сборкой выставляется симлинк debian_harmattan > debian. Секция control стандартная для debian пакетов и ее создание уже подробно было описано во многих статьях на Хабре. Рекомендую к прочтению эту серию статей.

    Содержимое control файла:
    Source: ubuntunotes
    Section: user/network
    Priority: extra
    Maintainer: Aleksey Sidorov <gorthauer87@ya.ru>
    Build-Depends: debhelper (>= 5),locales,cmake, libgconf2-6,libssl-dev,libxext-dev,libqt4-dev,libqca2-dev,libqca2-plugin-ossl, libqtm-dev
    Standards-Version: 3.7.2
    
    Package: ubuntunotes
    Section: user/network
    Architecture: any
    Depends: ${shlibs:Depends}, ${misc:Depends},libqca2-plugin-ossl
    Description: TODO
    XSBC-Maemo-Display-Name: ubuntuNotes
    XSBC-Bugtracker: https://github.com/gorthauer/ubuntu-one-qml
    
    Package: ubuntunotes-dbg
    Section: debug
    Priority: extra
    Architecture: any
    Depends: ${misc:Depends}, qutim (= ${binary:Version})
    Description: Debug symbols for ubuntuNotes
     Debug symbols to provide extra debug info in case of crash.
    

    Для ведения changelog'а не лишним было бы установить прогу dch из пакета devscripts. Использовать же ее очень просто:
    $ dch - i

    rules файл, благодаря использованию debhelper'ов, оказался уж очень простым:
    #!/usr/bin/make -f
    %:
    	dh $@ 
    override_dh_auto_configure:
    	dh_auto_configure -- -DCMAKE_INSTALL_PREFIX=/opt/ubuntunotes -DHARMATTAN=ON
    override_dh_auto_install:
    	dh_auto_install --destdir=$(CURDIR)/debian/ubuntunotes
    

    Этот же файл подойдет почти для любого проекта с минимальными изменениями.
    Сборка пакета тоже тривиальна:
    $ ln -s ./debian_harmattan ./debian
    $ dpkg-buildpackage -b 
    


    Заключение



    Теперь можно спокойно устанавливать и запускать получившийся пакет и наслаждаться быстрым и отзывчивым интерфейсом. Исходные коды для самостоятельной сборки можно скачать на гитхабе. Там же лежит собранный deb пакет. Я надеюсь эта статья поможет начинающим разработчикам под Harmattan и не только быстрее начать писать свои первые приложения. В будущем я, возможно, постараюсь получше осветить Хабрасообществу тонкости работы с cmake'ом, многие уже жаловались на недостаток статей про него.

    Комментарии 9

      +2
      где тег конкурсный? :)
      +7
      Спасибо, почитал с удовольствием. Интересная платформа это meego — дай бог ей выжить в сегодняшних непростых, для нее, условиях
        0
        Согласен с вами, очень жаль, но для meego условия настолько непростые, что N9 последний телефон на ней от Nokia. А как хочется верить, что вдруг одумаются и запилят еще что-нибудь.
        +1
        Интересная статья, однозначно плюс. Жалко у меня нет возможности протестировать.
          +1
          А чем основан выбор уже устаревшей QOauth?
          Ведь уже давно существует новая, удобная и не требующая QCA: KQOauth
            0
            Где в KQOauth аналог метода
            QNetworkReply *UbuntuOneApi::get(const QUrl &url)
            {
                QByteArray header = m_oauth->createParametersString(url.toEncoded(), QOAuth::GET, m_token, m_tokenSecret,
                                                                    QOAuth::HMAC_SHA1, QOAuth::ParamMap(),
                                                                    QOAuth::ParseForHeaderArguments);
            
                QNetworkRequest request(url);
                request.setRawHeader("Authorization", header);
                return m_manager->get(request);
            }
            

            Я что-то не вижу. Да и они мне обе не нравятся в плане API, отвязать QOauth от qca — дело 5 минут, просто нет необходимости. Да и в целом отвязать либу от QOauth немногим дольше, но опять же, необходимости пока нет. Есть более серьезные заморочки с самим tomboy api, точнее с ревизиями. Да и сервер что-то нестабильно работает последнее время, даже tomboy не пускает.
              0
              А он не нужен, ибо KQOauth сильнее инкапсулирует работу с протоколом. В то время как QOauth лишь предоставляет некоторые утилитарные методы для упрощения работы через обычный QNetworkAccessManager.
                0
                мне и нужна именно работа через QNetworAccessManager, я и сам инкапсулирую что мне надо. Да и не увидел я в либе basic авторизацию.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое