./0000755000015600001650000000000012675036254011107 5ustar jenkinsjenkins./cmake/0000755000015600001650000000000012675036254012167 5ustar jenkinsjenkins./cmake/GdbusCodegen.cmake0000644000015600001650000000246512675036254015531 0ustar jenkinsjenkinscmake_minimum_required(VERSION 2.6) if(POLICY CMP0011) cmake_policy(SET CMP0011 NEW) endif(POLICY CMP0011) find_program(GDBUS_CODEGEN NAMES gdbus-codegen DOC "gdbus-codegen executable") if(NOT GDBUS_CODEGEN) message(FATAL_ERROR "Excutable gdbus-codegen not found") endif() macro(add_gdbus_codegen outfiles name prefix service_xml) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" "${CMAKE_CURRENT_BINARY_DIR}/${name}.c" COMMAND "${GDBUS_CODEGEN}" --interface-prefix "${prefix}" --generate-c-code "${name}" "${service_xml}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${ARGN} "${service_xml}" ) list(APPEND ${outfiles} "${CMAKE_CURRENT_BINARY_DIR}/${name}.c") endmacro(add_gdbus_codegen) macro(add_gdbus_codegen_with_namespace outfiles name prefix namespace service_xml) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${name}.h" "${CMAKE_CURRENT_BINARY_DIR}/${name}.c" COMMAND "${GDBUS_CODEGEN}" --interface-prefix "${prefix}" --generate-c-code "${name}" --c-namespace "${namespace}" "${service_xml}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${ARGN} "${service_xml}" ) list(APPEND ${outfiles} "${CMAKE_CURRENT_BINARY_DIR}/${name}.c") endmacro(add_gdbus_codegen_with_namespace) ./tests/0000755000015600001650000000000012675036254012251 5ustar jenkinsjenkins./tests/dbus-fixture.h0000644000015600001650000000321312675036254015042 0ustar jenkinsjenkins/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: * Ted Gould */ #ifndef DBUS_FIXTURE_H #define DBUS_FIXTURE_H #include #include struct DBusFixture : public ::testing::Test { protected: GDBusConnection* m_bus = nullptr; virtual void SetUp() { BeforeBusSetUp(); m_bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); g_dbus_connection_set_exit_on_close(m_bus, FALSE); g_object_add_weak_pointer(G_OBJECT(m_bus), (gpointer*)&m_bus); } virtual void TearDown() { BeforeBusTearDown(); g_object_unref(m_bus); unsigned int cleartry = 0; while (m_bus != nullptr && cleartry < 100) { g_usleep(100000); while (g_main_pending()) { g_main_iteration(TRUE); } cleartry++; } ASSERT_LT(cleartry, 100); } virtual void BeforeBusSetUp() {} virtual void BeforeBusTearDown() {} }; #endif // DBUS_FIXTURE_H ./tests/libpay-iap-tests.cpp0000644000015600001650000002647612675036254016163 0ustar jenkinsjenkins/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: * Charle Kerr */ #include "dbus-fixture.h" #include #include #include static constexpr char const * BUS_NAME {"com.canonical.payments"}; static constexpr char const * GAME_NAME {"SwordsAndStacktraces.developer"}; struct IapTests: public DBusFixture { void wait_for_store_service() { auto on_name_appeared = [](GDBusConnection*, const char*, const char*, gpointer gloop) { g_main_loop_quit(static_cast(gloop)); }; auto watch_name_tag = g_bus_watch_name(G_BUS_TYPE_SESSION, BUS_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE, on_name_appeared, nullptr, m_main_loop, nullptr); g_main_loop_run(m_main_loop); g_bus_unwatch_name(watch_name_tag); } protected: GMainLoop* m_main_loop {}; GTestDBus* m_test_bus {}; void BeforeBusSetUp() override { // use a fake bus m_test_bus = g_test_dbus_new(G_TEST_DBUS_NONE); g_test_dbus_up(m_test_bus); // start the store const gchar* child_argv[] = { "python3", "-m", "dbusmock", "--template", STORE_TEMPLATE_PATH, nullptr }; GError* error = nullptr; g_spawn_async(nullptr, (gchar**)child_argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, &error); g_assert_no_error(error); } void SetUp() override { DBusFixture::SetUp(); m_main_loop = g_main_loop_new(nullptr, false); wait_for_store_service(); } void TearDown() override { g_main_loop_unref(m_main_loop); DBusFixture::TearDown(); g_clear_object(&m_test_bus); } struct IAP { uint64_t completed_timestamp; uint64_t acknowledged_timestamp; uint64_t purchase_id; const char* state; const char* price; PayItemType type; const char* sku; const char* title; const char* description; }; void CompareItemToIAP(const IAP& expected, const PayItem* item) { EXPECT_TRUE(item != nullptr); EXPECT_EQ(expected.acknowledged_timestamp, pay_item_get_acknowledged_timestamp(item)); EXPECT_EQ(expected.completed_timestamp, pay_item_get_completed_timestamp(item)); EXPECT_STREQ(expected.description, pay_item_get_description(item)); EXPECT_STREQ(expected.price, pay_item_get_price(item)); EXPECT_STREQ(expected.sku, pay_item_get_sku(item)); EXPECT_STREQ(expected.title, pay_item_get_title(item)); EXPECT_EQ(expected.type, pay_item_get_type(item)); } std::map& get_game_iaps() { static std::map items; if (items.empty()) { const uint64_t now = time(nullptr); const IAP tmp[] = { { 0, 0, 0, "available", "$1", PAY_ITEM_TYPE_UNLOCKABLE, "sword", "Sword", "A Sword." }, { now-10, 0, 100, "approved", "$1", PAY_ITEM_TYPE_UNLOCKABLE, "shield", "Shield", "A Shield." }, { now-10, now-9, 101, "purchased", "$1", PAY_ITEM_TYPE_UNLOCKABLE, "amulet", "Amulet", "An Amulet." } }; for (const auto& item : tmp) items[item.sku] = item; } return items; } void AddGame() { GVariantBuilder bitems; g_variant_builder_init(&bitems, G_VARIANT_TYPE("aa{sv}")); for(const auto it : get_game_iaps()) { const auto& item = it.second; GVariantBuilder bitem; g_variant_builder_init(&bitem, G_VARIANT_TYPE_VARDICT); struct { const char* key; GVariant* value; } entries[] = { { "completed_timestamp", g_variant_new_uint64(item.completed_timestamp) }, { "completed_timestamp", g_variant_new_uint64(item.completed_timestamp) }, { "acknowledged_timestamp", g_variant_new_uint64(item.acknowledged_timestamp) }, { "description", g_variant_new_string(item.description) }, { "price", g_variant_new_string(item.price) }, { "purchase_id", g_variant_new_uint64(item.purchase_id) }, { "sku", g_variant_new_string(item.sku) }, { "state", g_variant_new_string(item.state) }, { "title", g_variant_new_string(item.title) }, { "type", g_variant_new_string(item.type==PAY_ITEM_TYPE_UNLOCKABLE ? "unlockable" : "consumable") } }; for (const auto& entry : entries) g_variant_builder_add(&bitem, "{sv}", entry.key, entry.value); g_variant_builder_add_value(&bitems, g_variant_builder_end(&bitem)); } auto props = g_variant_builder_end(&bitems); GVariant* args[] = { g_variant_new_string(GAME_NAME), props }; GError *error {}; auto v = g_dbus_connection_call_sync(m_bus, BUS_NAME, "/com/canonical/pay/store", "com.canonical.pay.storemock", "AddStore", g_variant_new_tuple(args, G_N_ELEMENTS(args)), nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); g_assert_no_error(error); g_clear_pointer(&v, g_variant_unref); } struct StatusObserverData { PayPackage* package; std::string sku; PayPackageItemStatus status = PAY_PACKAGE_ITEM_STATUS_UNKNOWN; uint64_t num_calls = 0; }; void InstallStatusObserver(PayPackage* package, StatusObserverData& data) { auto observer = [](PayPackage* package, const char* sku, PayPackageItemStatus status, void* vdata) { auto data = static_cast(vdata); data->package = package; data->sku = sku; data->status = status; data->num_calls++; }; auto install_result = pay_package_item_observer_install (package, observer, &data); EXPECT_TRUE(install_result); } }; TEST_F(IapTests, GetItem) { AddGame(); auto package = pay_package_new(GAME_NAME); for(const auto it : get_game_iaps()) { const auto& iap = it.second; auto item = pay_package_get_item(package, iap.sku); EXPECT_TRUE(item != nullptr); CompareItemToIAP(iap, item); pay_item_unref(item); } pay_package_delete(package); } TEST_F(IapTests, GetPurchasedItems) { AddGame(); // calculate what we expect std::map purchased; for(const auto it : get_game_iaps()) { const auto& iap = it.second; if (!g_strcmp0(iap.state,"approved") || !g_strcmp0(iap.state,"purchased")) purchased[iap.sku] = iap; } auto package = pay_package_new(GAME_NAME); auto items = pay_package_get_purchased_items(package); // test the results size_t i = 0; while (items && items[i]) { auto& item = items[i]; auto it = purchased.find(pay_item_get_sku(item)); ASSERT_NE(it, purchased.end()); ASSERT_NE(0, pay_item_get_purchase_id(item)); CompareItemToIAP(it->second, item); purchased.erase(it); pay_item_unref(item); ++i; } EXPECT_TRUE(purchased.empty()); free(items); pay_package_delete(package); } TEST_F(IapTests, PurchaseItem) { AddGame(); auto package = pay_package_new(GAME_NAME); const char* sku = "sword"; // precondition: confirm the item being tested isn't purchased yet ASSERT_STREQ("available", get_game_iaps()[sku].state); const auto expected_status = PAY_PACKAGE_ITEM_STATUS_APPROVED; // install a status observer StatusObserverData data; InstallStatusObserver(package, data); // start the purchase auto start_result = pay_package_item_start_purchase(package, sku); EXPECT_TRUE(start_result); // wait for the purchase to complete while (data.num_calls < 2) { g_usleep(G_USEC_PER_SEC/10); } // confirm that the item's status changed EXPECT_EQ(2, data.num_calls); EXPECT_EQ(package, data.package); EXPECT_EQ(sku, data.sku); EXPECT_EQ(expected_status, data.status); // now get the PayItem and test it auto item = pay_package_get_item(package, sku); EXPECT_STREQ(sku, pay_item_get_sku(item)); EXPECT_EQ(expected_status, pay_item_get_status(item)); EXPECT_NE(0, pay_item_get_completed_timestamp(item)); ASSERT_NE(0, pay_item_get_purchase_id(item)); g_clear_pointer(&item, pay_item_unref); // cleanup pay_package_delete(package); } TEST_F(IapTests, AcknowledgeItem) { AddGame(); auto package = pay_package_new(GAME_NAME); const char* sku = "shield"; // precondition: confirm the item being tested is approved but not acked ASSERT_STREQ("approved", get_game_iaps()[sku].state); // install a status observer StatusObserverData data; InstallStatusObserver(package, data); // start the purchase auto start_result = pay_package_item_start_acknowledge(package, sku); EXPECT_TRUE(start_result); // wait for the status observer to be called while (data.num_calls == 0) { g_usleep(G_USEC_PER_SEC/10); } // confirm that the item's status changed EXPECT_EQ(1, data.num_calls); EXPECT_EQ(package, data.package); EXPECT_EQ(sku, data.sku); // now get the PayItem and test it auto item = pay_package_get_item(package, sku); EXPECT_STREQ(sku, pay_item_get_sku(item)); EXPECT_NE(0, pay_item_get_acknowledged_timestamp(item)); ASSERT_NE(0, pay_item_get_purchase_id(item)); g_clear_pointer(&item, pay_item_unref); // cleanup pay_package_delete(package); } TEST_F(IapTests, NoStore) { auto package = pay_package_new(GAME_NAME); auto items = pay_package_get_purchased_items (package); ASSERT_TRUE(items != nullptr); ASSERT_TRUE(items[0] == nullptr); auto item = pay_package_get_item (package, "twizzle.twazzle.twozzle.twome"); ASSERT_TRUE(item == nullptr); // cleanup free(items); pay_package_delete(package); } ./tests/setup-staging.sh0000755000015600001650000000463112675036254015406 0ustar jenkinsjenkins#!/bin/bash -e # This small script sets up the variables needed for acceptance # testing of the pay-service and pay related things. SSO_AUTH_BASE_URL=https://login.staging.ubuntu.com SSO_UONE_BASE_URL=https://staging.one.ubuntu.com PAY_BASE_URL=https://myapps.developer.staging.ubuntu.com URL_PACKAGE_INFO=https://search.apps.staging.ubuntu.com/api/v1/package/ ACCOUNT_CREDS_URL=https://login.staging.ubuntu.com/api/v2/tokens/oauth U1_SEARCH_BASE_URL=https://search.apps.staging.ubuntu.com/ CLICK_STORE_ENABLE_PURCHASES=1 echo "Setting up upstart environment variables" /sbin/initctl set-env --global SSO_AUTH_BASE_URL=$SSO_AUTH_BASE_URL /sbin/initctl set-env --global SSO_UONE_BASE_URL=$SSO_UONE_BASE_URL /sbin/initctl set-env --global PAY_BASE_URL=$PAY_BASE_URL /sbin/initctl set-env --global URL_PACKAGE_INFO=$URL_PACKAGE_INFO /sbin/initctl set-env --global ACCOUNT_CREDS_URL=$ACCOUNT_CREDS_URL /sbin/initctl set-env --global U1_SEARCH_BASE_URL=$U1_SEARCH_BASE_URL /sbin/initctl set-env --global CLICK_STORE_ENABLE_PURCHASES=$CLICK_STORE_ENABLE_PURCHASES echo "Setting up dbus environment variables" gdbus call --session \ --dest org.freedesktop.DBus \ --object-path / \ --method org.freedesktop.DBus.UpdateActivationEnvironment \ "[{'SSO_AUTH_BASE_URL', '$SSO_AUTH_BASE_URL'}, {'SSO_UONE_BASE_URL', '$SSO_UONE_BASE_URL'}, {'PAY_BASE_URL', '$PAY_BASE_URL'}, {'URL_PACKAGE_INFO', '$URL_PACKAGE_INFO'}, {'ACCOUNT_CREDS_URL', '$ACCOUNT_CREDS_URL'}, {'U1_SEARCH_BASE_URL', '$U1_SEARCH_BASE_URL'}, {'CLICK_STORE_ENABLE_PURCHASES', '$CLICK_STORE_ENABLE_PURCHASES'}]" echo "Restarting scope registry" /sbin/restart scope-registry STAGING_KEY_ID=9D7FAC7F5DEEC972 STAGING_KEYRING_PATH=/usr/share/debsig/keyrings/${STAGING_KEY_ID} STAGING_POLICY_PATH=/etc/debsig/policies/${STAGING_KEY_ID} PROD_KEY_ID=608FF2D200A0A71F PROD_POLICY_PATH=/etc/debsig/policies/${PROD_KEY_ID} if [ ! -d ${STAGING_KEYRING_PATH} -o ! -d ${STAGING_POLICY_PATH} ]; then echo "Setting up staging GPG key" sudo mount -o remount,rw / sudo mkdir -p ${STAGING_KEYRING_PATH} sudo gpg --no-default-keyring \ --keyring ${STAGING_KEYRING_PATH}/click-store.gpg \ --keyserver keyserver.ubuntu.com --recv-keys ${STAGING_KEY_ID} sudo cp -rf ${PROD_POLICY_PATH} ${STAGING_POLICY_PATH} sudo perl -p -i -e "s/${PROD_KEY_ID}/${STAGING_KEY_ID}/g" \ ${STAGING_POLICY_PATH}/generic.pol echo "Finished importing staging GPG key" fi ./tests/libpay-package-tests.cpp0000644000015600001650000002474612675036254017003 0ustar jenkinsjenkins/* * Copyright © 2014 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: * Ted Gould */ #include "dbus-fixture.h" #include #include static constexpr char const * BUS_NAME {"com.canonical.payments"}; struct LibpayPackageTests: public DBusFixture { void wait_for_store_service() { auto on_name_appeared = [](GDBusConnection*, const char*, const char*, gpointer gloop) { g_main_loop_quit(static_cast(gloop)); }; auto watch_name_tag = g_bus_watch_name(G_BUS_TYPE_SESSION, BUS_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE, on_name_appeared, nullptr, m_main_loop, nullptr); g_main_loop_run(m_main_loop); g_bus_unwatch_name(watch_name_tag); } protected: GMainLoop* m_main_loop {}; GTestDBus* m_test_bus {}; void BeforeBusSetUp() override { // use a fake bus m_test_bus = g_test_dbus_new(G_TEST_DBUS_NONE); g_test_dbus_up(m_test_bus); // start the store const gchar* child_argv[] = { "python3", "-m", "dbusmock", "--template", STORE_TEMPLATE_PATH, nullptr }; GError* error = nullptr; g_spawn_async(nullptr, (gchar**)child_argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, &error); g_assert_no_error(error); } void SetUp() override { DBusFixture::SetUp(); m_main_loop = g_main_loop_new(nullptr, false); wait_for_store_service(); const guint64 now = time(nullptr); const struct { const char * sku; const char * state; guint64 refundable_until; } prefab_apps[] = { { "available_app", "available", 0 }, // not purchased { "newly_purchased_app", "purchased", now+(60*14) }, // purchased, refund window open { "old_purchased_app", "purchased", now-(60*30) } // purchased, refund window closed }; GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("aa{sv}")); for (const auto& app : prefab_apps) { GVariantBuilder app_props; g_variant_builder_init(&app_props, G_VARIANT_TYPE_VARDICT); const struct { const char* key; GVariant* value; } entries[] = { { "sku", g_variant_new_string(app.sku) }, { "state", g_variant_new_string(app.state) }, { "refundable_until", g_variant_new_uint64(app.refundable_until) } }; for (const auto& entry : entries) g_variant_builder_add(&app_props, "{sv}", entry.key, entry.value); g_variant_builder_add_value(&b, g_variant_builder_end(&app_props)); } auto props = g_variant_builder_end(&b); GVariant* args[] = { g_variant_new_string("click-scope"), props }; GError *error {}; auto v = g_dbus_connection_call_sync(m_bus, BUS_NAME, "/com/canonical/pay/store", "com.canonical.pay.storemock", "AddStore", g_variant_new_tuple(args, G_N_ELEMENTS(args)), nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); g_assert_no_error(error); g_clear_pointer(&v, g_variant_unref); } void TearDown() override { g_main_loop_unref(m_main_loop); DBusFixture::TearDown(); g_clear_object(&m_test_bus); } struct StatusObserverData { PayPackage* package; std::string sku; PayPackageItemStatus status = PAY_PACKAGE_ITEM_STATUS_UNKNOWN; uint64_t num_calls = 0; }; void InstallStatusObserver(PayPackage* package, StatusObserverData& data) { auto observer = [](PayPackage* package, const char* sku, PayPackageItemStatus status, void* vdata) { auto data = static_cast(vdata); data->package = package; data->sku = sku; data->status = status; data->num_calls++; }; auto install_result = pay_package_item_observer_install (package, observer, &data); EXPECT_TRUE(install_result); } }; TEST_F(LibpayPackageTests, InitTest) { auto package = pay_package_new("package-name"); pay_package_delete(package); } TEST_F(LibpayPackageTests, PurchaseItem) { auto package = pay_package_new("click-scope"); const char* sku {"available_app"}; // pre-purchase tests EXPECT_EQ(PAY_PACKAGE_ITEM_STATUS_NOT_PURCHASED, pay_package_item_status(package, sku)); EXPECT_EQ(PAY_PACKAGE_REFUND_STATUS_NOT_PURCHASED, pay_package_refund_status(package, sku)); // install a status observer StatusObserverData data; InstallStatusObserver(package, data); EXPECT_TRUE(pay_package_item_start_purchase(package, sku)); // wait for the call to complete while (data.num_calls < 2) { g_usleep(G_USEC_PER_SEC/10); } // confirm that the item's status changed EXPECT_EQ(2, data.num_calls); EXPECT_EQ(package, data.package); EXPECT_EQ(sku, data.sku); EXPECT_EQ(PAY_PACKAGE_ITEM_STATUS_PURCHASED, data.status); EXPECT_EQ(PAY_PACKAGE_ITEM_STATUS_PURCHASED, pay_package_item_status(package, sku)); // cleanup pay_package_delete(package); } TEST_F(LibpayPackageTests, PurchaseItemCancelled) { auto package = pay_package_new("click-scope"); // install a status observer StatusObserverData data; InstallStatusObserver(package, data); const char* sku = "cancel"; EXPECT_TRUE(pay_package_item_start_purchase(package, sku)); // wait for the call to complete while (data.num_calls < 2) { g_usleep(G_USEC_PER_SEC/10); } // confirm that the item's status changed EXPECT_EQ(2, data.num_calls); EXPECT_EQ(package, data.package); EXPECT_EQ(sku, data.sku); EXPECT_EQ(PAY_PACKAGE_ITEM_STATUS_NOT_PURCHASED, data.status); // cleanup pay_package_delete(package); } TEST_F(LibpayPackageTests, PurchaseItemError) { auto package = pay_package_new("org.foo.bar"); // install a status observer StatusObserverData data; InstallStatusObserver(package, data); const char* sku = "denied"; EXPECT_TRUE(pay_package_item_start_purchase(package, sku)); // wait for the call to complete while (data.num_calls < 2) { g_usleep(G_USEC_PER_SEC/10); } // confirm that the item's status changed EXPECT_EQ(2, data.num_calls); EXPECT_EQ(package, data.package); EXPECT_EQ(sku, data.sku); EXPECT_EQ(PAY_PACKAGE_ITEM_STATUS_UNKNOWN, data.status); // cleanup pay_package_delete(package); } TEST_F(LibpayPackageTests, RefundItem) { auto package = pay_package_new("click-scope"); const char* sku {"newly_purchased_app"}; // pre-refund tests EXPECT_EQ(PAY_PACKAGE_ITEM_STATUS_PURCHASED, pay_package_item_status(package, sku)); EXPECT_EQ(PAY_PACKAGE_REFUND_STATUS_REFUNDABLE, pay_package_refund_status(package, sku)); // install a status observer StatusObserverData data; InstallStatusObserver(package, data); EXPECT_TRUE(pay_package_item_start_refund(package, sku)); // wait for the call to complete while (!data.num_calls) { g_usleep(G_USEC_PER_SEC/10); } // confirm that the item's status changed EXPECT_EQ(1, data.num_calls); EXPECT_EQ(package, data.package); EXPECT_EQ(sku, data.sku); EXPECT_EQ(PAY_PACKAGE_ITEM_STATUS_NOT_PURCHASED, data.status); EXPECT_EQ(PAY_PACKAGE_ITEM_STATUS_NOT_PURCHASED, pay_package_item_status(package, sku)); EXPECT_EQ(PAY_PACKAGE_REFUND_STATUS_NOT_PURCHASED, pay_package_refund_status(package, sku)); // cleanup pay_package_delete(package); } TEST_F(LibpayPackageTests, VerifyItem) { auto package = pay_package_new("click-scope"); // install a status observer StatusObserverData data; InstallStatusObserver(package, data); const char* sku = "item"; EXPECT_TRUE(pay_package_item_start_verification(package, sku)); // wait for the call to complete while (!data.num_calls) { g_usleep(G_USEC_PER_SEC/10); } // confirm that the item's status changed EXPECT_EQ(1, data.num_calls); EXPECT_EQ(package, data.package); EXPECT_EQ(sku, data.sku); EXPECT_EQ(PAY_PACKAGE_ITEM_STATUS_NOT_PURCHASED, data.status); // cleanup pay_package_delete(package); } TEST_F(LibpayPackageTests, ColdCacheStatus) { auto package = pay_package_new("click-scope"); const struct { const char * sku; PayPackageItemStatus expected_item_status; PayPackageRefundStatus expected_refund_status; } tests[] = { { "available_app", PAY_PACKAGE_ITEM_STATUS_NOT_PURCHASED, PAY_PACKAGE_REFUND_STATUS_NOT_PURCHASED }, { "newly_purchased_app", PAY_PACKAGE_ITEM_STATUS_PURCHASED, PAY_PACKAGE_REFUND_STATUS_REFUNDABLE }, { "old_purchased_app", PAY_PACKAGE_ITEM_STATUS_PURCHASED, PAY_PACKAGE_REFUND_STATUS_NOT_REFUNDABLE } }; for (const auto& test : tests) { EXPECT_EQ(test.expected_item_status, pay_package_item_status(package, test.sku)); EXPECT_EQ(test.expected_refund_status, pay_package_refund_status(package, test.sku)); } // cleanup pay_package_delete(package); } ./tests/CMakeLists.txt0000644000015600001650000000363412675036254015017 0ustar jenkinsjenkinsset(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${GCOV_FLAGS}") # Build with system gmock and embedded gtest set (GMOCK_INCLUDE_DIR "/usr/include/gmock/include" CACHE PATH "gmock source include directory") set (GMOCK_SOURCE_DIR "/usr/src/gmock" CACHE PATH "gmock source directory") set (GTEST_INCLUDE_DIR "${GMOCK_SOURCE_DIR}/gtest/include" CACHE PATH "gtest source include directory") set (GMOCK_BOTH_LIBRARIES gmock gmock_main) add_subdirectory(${GMOCK_SOURCE_DIR} "${CMAKE_CURRENT_BINARY_DIR}/gmock") find_package(Threads) add_definitions(-DCORE_DBUS_ENABLE_GOOGLE_TEST_FIXTURE) include_directories( ${CMAKE_SOURCE_DIR} "${CMAKE_SOURCE_DIR}/common" ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_BINARY_DIR} ${GMOCK_INCLUDE_DIR} ${GTEST_INCLUDE_DIR} ) ############################# # staging script ############################# install(PROGRAMS setup-staging.sh DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}) ############################# # dbusmock template ############################# set(STORE_TEMPLATE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/com_canonical_pay_store.py") add_definitions(-DSTORE_TEMPLATE_PATH="${STORE_TEMPLATE_FILE}") # Get the python3 package directory execute_process ( COMMAND python3 -c "from distutils import sysconfig; print(sysconfig.get_python_lib())" COMMAND sed -r -e "s|/usr/(local/)?||g" OUTPUT_VARIABLE PYTHON_PACKAGE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE ) install( FILES ${STORE_TEMPLATE_FILE} DESTINATION "${PYTHON_PACKAGE_DIR}/dbusmock/templates/" ) ############################# # libpay tests ############################# function(add_test_by_name name) set(TEST_NAME ${name}) add_executable(${TEST_NAME} ${TEST_NAME}.cpp ${libpay-tests-generated}) target_link_libraries(${TEST_NAME} libpay ${GMOCK_BOTH_LIBRARIES}) add_test(${TEST_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}) endfunction() add_test_by_name(libpay-iap-tests) add_test_by_name(libpay-package-tests) ./tests/com_canonical_pay_store.py0000644000015600001650000002241612675036254017502 0ustar jenkinsjenkinsimport dbus import time from dbusmock import mockobject '''com.canonical.pay.store mock template This creates the expected methods of a pay.store and adds some extra control methods for adding/updating items and their states. ''' # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 3 of the License, or (at your option) any # later version. See http://www.gnu.org/copyleft/lgpl.html for the full text # of the license. __author__ = 'Charles Kerr' __email__ = 'charles.kerr@canonical.com' __copyright__ = '(c) 2015 Canonical Ltd.' __license__ = 'LGPL 3+' BUS_NAME = 'com.canonical.payments' MAIN_OBJ = '/com/canonical/pay/store' MAIN_IFACE = 'com.canonical.pay.storemock' SYSTEM_BUS = False STORE_PATH_PREFIX = MAIN_OBJ STORE_IFACE = 'com.canonical.pay.store' ERR_PREFIX = 'org.freedesktop.DBus.Error' ERR_INVAL = ERR_PREFIX + '.InvalidArgs' ERR_ACCESS = ERR_PREFIX + '.AccessDenied' # # Util # def encode_path_element(element): encoded = [] first = True for ch in element: if (ch.isalpha() or (ch.isdigit() and not first)): encoded.append(ch) else: encoded.append('_{:02x}'.format(ord(ch))) return ''.join(encoded) def build_store_path(package_name): return STORE_PATH_PREFIX + '/' + encode_path_element(package_name) # # Store # class Item: __default_bus_properties = { 'acknowledged_timestamp': dbus.UInt64(0.0), 'completed_timestamp': dbus.UInt64(0.0), 'description': dbus.String('The is a default item'), 'price': dbus.String('$1'), 'purchase_id': dbus.UInt64(0.0), 'refundable_until': dbus.UInt64(0.0), 'sku': dbus.String('default_item'), 'state': dbus.String('available'), 'type': dbus.String('unlockable'), 'title': dbus.String('Default Item') } def __init__(self, sku): self.bus_properties = Item.__default_bus_properties.copy() self.bus_properties['sku'] = sku def serialize(self): return dbus.Dictionary(self.bus_properties) def set_property(self, key, value): if key not in self.bus_properties: raise dbus.exceptions.DBusException( ERR_INVAL, 'Invalid item property {0}'.format(key)) self.bus_properties[key] = value def store_add_item(store, properties): if 'sku' not in properties: raise dbus.exceptions.DBusException( ERR_INVAL, 'item has no sku property') sku = properties['sku'] if sku in store.items: raise dbus.exceptions.DBusException( ERR_INVAL, 'store {0} already has item {1}'.format(store.name, sku)) item = Item(sku) store.items[sku] = item store.set_item (store, sku, properties) def store_set_item(store, sku, properties): try: item = store.items[sku] for key, value in properties.items(): item.set_property(key, value) except KeyError: raise dbus.exceptions.DBusException( ERR_INVAL, 'store {0} has no such item {1}'.format(store.name, sku)) def store_get_item(store, sku): try: return store.items[sku].serialize() except KeyError: if store.path.endswith(encode_path_element('click-scope')): return dbus.Dictionary( { 'sku': sku, 'state': 'available', 'refundable_until': 0, }) else: raise dbus.exceptions.DBusException( ERR_INVAL, 'store {0} has no such item {1}'.format(store.name, sku)) def store_get_purchased_items(store): items = [] for sku, item in store.items.items(): if item.bus_properties['state'] in ('approved', 'purchased'): items.append(item.serialize()) return dbus.Array(items, signature='a{sv}', variant_level=1) def store_purchase_item(store, sku): if store.path.endswith(encode_path_element('click-scope')): if sku != 'cancel': item = Item(sku) item.bus_properties = { 'state': 'purchased', 'refundable_until': dbus.UInt64(time.time() + 15*60), 'sku': sku, } store.items[sku] = item return store_get_item(store, sku) try: if sku == 'denied': raise dbus.exceptions.DBusException( ERR_ACCESS, 'User denied access.') elif sku != 'cancel': item = store.items[sku] item.set_property('state', 'approved') item.set_property('purchase_id', dbus.UInt64(store.next_purchase_id)) item.set_property('completed_timestamp', dbus.UInt64(time.time())) store.next_purchase_id += 1 return store_get_item(store, sku) except KeyError: raise dbus.exceptions.DBusException( ERR_INVAL, 'store {0} has no such item {1}'.format(store.name, sku)) def store_refund_item(store, sku): if not store.path.endswith(encode_path_element('click-scope')): raise dbus.exceptions.DBusException( ERR_INVAL, 'Refunds are only available for packages') try: item = store.items[sku] if (item.bus_properties['state'] == 'purchased' and item.bus_properties['refundable_until'] > dbus.UInt64(time.time())): del store.items[sku] return dbus.Dictionary({ 'state': 'available', 'sku': sku, 'refundable_until': 0 }) else: return dbus.Dictionary({ 'state': 'purchased', 'sku': sku, 'refundable_until': 0 }) except KeyError: return dbus.Dictionary({'state': 'available', 'sku': sku, 'refundable_until': 0}) def store_acknowledge_item(store, sku): if store.path.endswith(encode_path_element('click-scope')): raise dbus.exceptions.DBusException( ERR_INVAL, 'Only in-app purchase items can be acknowledged') try: item = store.items[sku] item.set_property('acknowledged_timestamp', dbus.UInt64(time.time())) item.set_property('state', dbus.String('purchased')) return item.serialize() except KeyError: raise dbus.exceptions.DBusException( ERR_INVAL, 'store {0} has no such item {1}'.format(store.name, sku)) # # Main 'Storemock' Obj # def main_add_store(mock, package_name, items): path = build_store_path(package_name) mock.AddObject(path, STORE_IFACE, {}, []) store = mockobject.objects[path] store.name = package_name store.items = {} store.next_purchase_id = 1 store.add_item = store_add_item store.set_item = store_set_item store.get_item = store_get_item store.get_purchased_items = store_get_purchased_items store.purchase_item = store_purchase_item store.refund_item = store_refund_item store.acknowledge_item = store_acknowledge_item store.AddMethods(STORE_IFACE, [ ('AddItem', 'a{sv}', '', 'self.add_item(self, args[0])'), ('SetItem', 'sa{sv}', '', 'self.set_item(self, args[0], args[1])'), ('GetItem', 's', 'a{sv}', 'ret = self.get_item(self, args[0])'), ('GetPurchasedItems', '', 'aa{sv}', 'ret = self.get_purchased_items(self)'), ('PurchaseItem', 's', 'a{sv}', 'ret = self.purchase_item(self, args[0])'), ('RefundItem', 's', 'a{sv}', 'ret = self.refund_item(self, args[0])'), ('AcknowledgeItem', 's', 'a{sv}', 'ret = self.acknowledge_item(self, args[0])'), ]) for item in items: store.add_item(store, item); def main_get_stores(mock): names = [] for key, val in mockobject.objects.items(): try: names.append(val.name) except AttributeError: pass return dbus.Array(names, signature='s', variant_level=1) def main_add_item(mock, package_name, item): try: path = build_store_path(package_name) # mock.log('store {0} adding item {1}'.format(path, item)) mockobject.objects[path].StoreAddItem(item) except KeyError: raise dbus.exceptions.DBusException( ERR_INVAL, 'no such package {0}'.format(package_name)) def main_set_item(mock, package_name, item): try: path = build_store_path(package_name) store = mock.objects[path] store.set_item(store, item) except KeyError: raise dbus.exceptions.DBusException( ERR_INVAL, 'error adding item to package {0}'.format(package_name)) def load(main, parameters): main.add_store = main_add_store main.add_item = main_add_item main.set_item = main_set_item main.get_stores = main_get_stores main.AddMethods(MAIN_IFACE, [ ('AddStore', 'saa{sv}', '', 'self.add_store(self, args[0], args[1])'), ('AddItem', 'sa{sv}', '', 'self.add_item(self, args[0], args[1])'), ('SetItem', 'sa{sv}', '', 'self.set_item(self, args[0], args[1])'), ('GetStores', '', 'as', 'ret = self.get_stores(self)'), ]) ./README0000644000015600001650000000007412675036254011770 0ustar jenkinsjenkinsClient side service and access library for doing purchases. ./po/0000755000015600001650000000000012675036254011525 5ustar jenkinsjenkins./po/pl.po0000644000015600001650000000521512675036254012503 0ustar jenkinsjenkins# Polish translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-05 15:53+0000\n" "Last-Translator: Rodney Dawes \n" "Language-Team: Polish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Finalizacja zakupu" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Hasło nieprawidłowe, proszę spróbować ponownie." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Zakup nie powiódł się" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Zakup nie mógł zostać zrealizowany." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Błąd połączenia z serwerem" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Spróbować ponownie?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Dodawanie karty kredytowej nie powiodło się" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Przetwarzanie zakupu" #: ../app/payui.qml:327 msgid "Loading" msgstr "Wczytywanie" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Proszę czekać…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Anuluj" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Dodaj płatność" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Płatność" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Wprowadź swoje hasło Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Proszę wprowadzić swój kod weryfikacyjny:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "dwuczęściowy kod urządzenia" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Kup teraz" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Dodaj kartę kredytową/debetową" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Ponów" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Zamknij" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pay UI" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Aplikacja płatnicza" ./po/genpotfiles.sh0000755000015600001650000000016712675036254014407 0ustar jenkinsjenkins#!/bin/sh sed '/^#/d s/^[[].*] *// /^[ ]*$/d' \ "`dirname ${0}`/POTFILES.in" | sed '$!s/$/ \\/' > POTFILES ./po/az.po0000644000015600001650000000461512675036254012505 0ustar jenkinsjenkins# Azerbaijani translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2014-09-05 16:56+0000\n" "Last-Translator: Nicat Məmmədov \n" "Language-Team: Azerbaijani \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Yanlış şifrә. Xahiş edirik yenidәn cәhd edin." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Alğı uğursuz oldu" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Alğı tamamlana bilmәdi." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Yenidәn cәhd etmәk istәyirsiz?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "" #: ../app/payui.qml:327 msgid "Loading" msgstr "Yüklәnilir" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Xahiş edirik gözlәyin..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "İmtina" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Ödәmә" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "İndi al" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Yenidən cəhd elә" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Bağla" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "" ./po/gl.po0000644000015600001650000000521012675036254012465 0ustar jenkinsjenkins# Galician translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-02-25 16:51+0000\n" "Last-Translator: Marcos Lans \n" "Language-Team: Galician \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Rematar a compra" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Contrasinal incorrecto, ténteo de novo." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "A compra fallou" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Non foi posíbel rematar a compra." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Erro ao contactar co servidor" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Desexa tentalo de novo?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Fallo ao engadir a tarxeta de crédito" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Procesando a compra" #: ../app/payui.qml:327 msgid "Loading" msgstr "Cargando" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Agarde…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Cancelar" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Engadir pagamento" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Pagamento" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Escriba o contrasinal de Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Escriba o código de comprobación:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Código de 2 factores do dispositivo" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Comprar agora" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Engadir tarxeta de crédito/débito" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Reintentar" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Pechar" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Interface de pagamentos" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Aplicativo para realizar compras." ./po/nl.po0000644000015600001650000000520312675036254012476 0ustar jenkinsjenkins# Dutch translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-03 15:24+0000\n" "Last-Translator: rob \n" "Language-Team: Dutch \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Aankoop voltooien" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Onjuist wachtwoord, probeer het a.u.b. opnieuw." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Aankoop mislukt" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "De aankoop kon niet voltooid worden." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Fout bij het contact maken met de server" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Wilt u het opnieuw proberen?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Creditcard toevoegen mislukt" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Aankoop verwerken" #: ../app/payui.qml:327 msgid "Loading" msgstr "Laden" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Even geduld a.u.b…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Annuleren" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Betaling toevoegen" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Betaling" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Voer uw Ubuntu One-wachtwoord in" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Voer uw verificatiecode in:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Apparaatcode in twee stappen" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Nu kopen" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Credit-/debitcard toevoegen" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Opnieuw proberen" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Sluiten" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Betalingsinterface" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "De app om aankopen mee te kunnen doen." ./po/ca.po0000644000015600001650000000536612675036254012462 0ustar jenkinsjenkins# Catalan translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-03 10:59+0000\n" "Last-Translator: David Planella \n" "Language-Team: Catalan \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Finalitza la compra" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "La contrasenya és incorrecta, torneu-ho a provar." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Ha fallat la compra" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "No s'ha pogut completar la compra" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "S'ha produït un error en contactar el servidor" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Voleu tornar-ho a intentar?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Ha fallat l'addició d'una targeta de crèdit" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "S'està processant la compra" #: ../app/payui.qml:327 msgid "Loading" msgstr "S’està carregant" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Espereu…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Cancel·la" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Afegeix un pagament" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Pagament" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Introduïu la contrasenya de l'Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Escriviu el codi de verificació:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Codi de verificació per l'autenticació de 2 factors" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Compra-ho ara" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Afegeix una targeta de crèdit o de dèbit" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Torna-ho a provar" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Tanca" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pagaments" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "L'aplicació per completar compres." ./po/zh_TW.po0000644000015600001650000000512212675036254013120 0ustar jenkinsjenkins# Chinese (Traditional) translation for pay-ui # Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2015. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-01 09:19+0000\n" "Last-Translator: Cheng-Chia Tseng \n" "Language-Team: Chinese (Traditional) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "完成購買" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "密碼錯誤,請重試一次。" #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "購買失敗" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "無法完成購買。" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "聯絡伺服器時發生錯誤" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "您是想再重試一次?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "信用卡加入失敗" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "正在處理購買" #: ../app/payui.qml:327 msgid "Loading" msgstr "載入中" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "請稍候…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "取消" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "加入支付" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "支付" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "輸入您的 Ubuntu One 密碼" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "輸入您的驗證碼:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "兩門檻裝置認證碼" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "立刻購買" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "加入信用卡/金融卡" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "重試" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "關閉" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "支付介面" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "完成購買用的應用程式。" ./po/ca@valencia.po0000644000015600001650000000536012675036254014257 0ustar jenkinsjenkins# Catalan translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-05 16:16+0000\n" "Last-Translator: David Planella \n" "Language-Team: Catalan \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Finalitza la compra" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "La contrasenya és incorrecta, torneu-ho a provar." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Ha fallat la compra" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "No s'ha pogut completar la compra" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "S'ha produït un error en contactar el servidor" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Voleu tornar-ho a intentar?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Ha fallat l'addició d'una targeta de crèdit" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "S'està processant la compra" #: ../app/payui.qml:327 msgid "Loading" msgstr "S’està carregant" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Espereu…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Cancel·la" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Afig un pagament" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Pagament" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Introduïu la contrasenya de l'Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Escriviu el codi de verificació:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Codi de verificació per l'autenticació de 2 factors" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Compra-ho ara" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Afig una targeta de crèdit o de dèbit" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Torna-ho a provar" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Tanca" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pagaments" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "L'aplicació per completar compres." ./po/nb.po0000644000015600001650000000516212675036254012470 0ustar jenkinsjenkins# Norwegian Bokmal translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-02-26 13:25+0000\n" "Last-Translator: Åka Sikrom \n" "Language-Team: Norwegian Bokmal \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Fullfør kjøp" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Feil passord. Prøv igjen." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Kjøp mislyktes" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Klarte ikke å fullføre kjøpet." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Fikk ikke kontakt med tjeneren" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Vil du prøve på nytt?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Klarte ikke å legge til kredittkort" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Behandler kjøp" #: ../app/payui.qml:327 msgid "Loading" msgstr "Laster inn" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Vent litt …" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Avbryt" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Legg til betaling" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Betaling" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Skriv inn Ubuntu One-passord" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Skriv inn bekreftelseskoden:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Enhetskode for toveisautentisering" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Kjøp nå" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Legg til kreditt-/betalingskort" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Prøv igjen" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Lukk" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Betalingsgrensesnitt" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Programmet som lar deg fullføre et kjøp." ./po/cs.po0000644000015600001650000000520112675036254012470 0ustar jenkinsjenkins# Czech translation for pay-ui # Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2015. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-05-06 19:33+0000\n" "Last-Translator: Vojtěch Daněk \n" "Language-Team: Czech \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Dokončit nákup" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Nesprávné heslo, zkuste to, prosím, znovu." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Nákup selhal" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Nákup nemohl být dokončen." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Chyba kontaktování serveru" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Chcete to zkusit znovu?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Přidání platební karty selhalo" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Platba probíhá" #: ../app/payui.qml:327 msgid "Loading" msgstr "Načítá se" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Čekejte prosím…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Zrušit" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Přidal platbu" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Platba" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Vložte své Ubuntu One heslo" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Napište ověřovací kód:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Kód zařízení pro dvoufázové ověření" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Koupit nyní" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Přidat kreditní/debetní kartu" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Opakovat" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Zavřít" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Platební rozehraní" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Aplikace pro dokončení nákupu." ./po/fa.po0000644000015600001650000000574312675036254012464 0ustar jenkinsjenkins# Persian translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-08 04:32+0000\n" "Last-Translator: Danial Behzadi \n" "Language-Team: Persian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "خاتمه‌ی خرید" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "گذرواژه نادرست بود، لطفاً دوباره تلاش کنید." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "خرید شکست خورد" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "خرید نتوانست کامل شود." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "خطا در ارتباط با سرویس‌دهنده" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "می‌خواهید دوباره تلاش کنید؟" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "افزودن کارت اعتباری شکست خورد" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "در حال پردازش خرید" #: ../app/payui.qml:327 msgid "Loading" msgstr "در حال بار کردن" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "لطفاً صبر کنید…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "لغو" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "افزودن پرداخت" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "پرداخت" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "گذرواژه‌ی اوبونتووانتان را وارد کنید" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "رمز تأییدیه‌ی خود را بنویسید:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "کد دستگاه مرحله ۲" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "همین الآن بخرید" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "افزودن کارت اعتباری/نقدی" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "تلاش دوباره" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "بستن" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "رابط کاربری پرداخت" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "برنامه‌ی کاربردی برای کامل کردن یک خرید." ./po/pt.po0000644000015600001650000000522312675036254012512 0ustar jenkinsjenkins# Portuguese translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-09-11 15:02+0000\n" "Last-Translator: Ivo Xavier \n" "Language-Team: Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Finalizar compra" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Palavra-passe incorreta, tente novamente." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Compra falhada" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "A compra não foi completada." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Erro ao contactar o servidor" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Quer tentar novamente?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Falha ao adicionar cartão de crédito" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "A processar compra" #: ../app/payui.qml:327 msgid "Loading" msgstr "A carregar" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Por favor, aguarde..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Cancelar" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Adicionar pagamento" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Pagamento" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Insira a palavra-passe do Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Insira o código de verificação:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Código de equipamento de 2-fatores" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Comprar agora" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Adicionar cartão crédito/débito" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Tentar novamente" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Fechar" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pay UI" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "App para concluir uma compra." ./po/en_GB.po0000644000015600001650000000513112675036254013037 0ustar jenkinsjenkins# English (United Kingdom) translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-04 19:15+0000\n" "Last-Translator: Rodney Dawes \n" "Language-Team: English (United Kingdom) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Finish Purchase" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Incorrect Password, please try again." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Purchase failed" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "The purchase could not be completed." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Error contacting the server" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Do you want to try again?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Adding Credit Card failed" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Processing Purchase" #: ../app/payui.qml:327 msgid "Loading" msgstr "Loading" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Please wait…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Cancel" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Add Payment" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Payment" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Enter your Ubuntu One password" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Type your verification code:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "2-factor device code" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Buy Now" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Add credit/debit card" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Retry" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Close" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pay UI" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "The application for completing a purchase." ./po/sq.po0000644000015600001650000000433612675036254012516 0ustar jenkinsjenkins# Albanian translation for pay-ui # Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2015. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-09-04 14:21+0000\n" "Last-Translator: Rodney Dawes \n" "Language-Team: Albanian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "" #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "" #: ../app/payui.qml:327 msgid "Loading" msgstr "Duke Ngarkuar" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Të lutem prit..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Anullo" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Pagesë" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Bli Tani" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Riprovo" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Mbylle" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "" ./po/pt_BR.po0000644000015600001650000000532512675036254013100 0ustar jenkinsjenkins# Brazilian Portuguese translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-17 21:15+0000\n" "Last-Translator: Tiago Hillebrandt \n" "Language-Team: Brazilian Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Finalizar compra" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Senha incorreta, por favor tente novamente." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Compra falhou" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "A compra não pôde ser realizada." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Erro ao contactar servidor." #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Você quer tentar novamente?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Falha ao adicionar cartão de crédito" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Processando compra" #: ../app/payui.qml:327 msgid "Loading" msgstr "Carregando" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Por favor, aguarde…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Cancelar" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Adicionar pagamento" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Pagamento" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Informe sua senha do Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Informe seu código de verificação:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "código do dispositivo 2-factor" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Comprar agora" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Adicionar cartão de crédito/débito" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Tentar novamente" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Fechar" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Interface de pagamento" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "O aplicativo para completar uma compra." ./po/sv.po0000644000015600001650000000517712675036254012527 0ustar jenkinsjenkins# Swedish translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # clone , 2015. msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-04-03 05:35+0000\n" "Last-Translator: Josef Andersson \n" "Language-Team: Swedish\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" "Language: sv\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Avsluta köp" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Felaktigt lösenord, vänligen försök igen." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Köpet misslyckades" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Köpet kunde inte slutföras." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Fel uppstod vid kontakt med server" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Vill du försöka igen?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Kunde inte lägga till betalkort" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Behandlar köp" #: ../app/payui.qml:327 msgid "Loading" msgstr "Laddar" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Vänta…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Avbryt" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Lägg till betalning" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Betalning" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Ange ditt Ubuntu One-lösenord" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Skriv in din verifieringskod:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Tvåvägsenhetskod" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Köp nu" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Lägg till kredit/bankkort" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Försök igen" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Stäng" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pay UI" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Programmet för att slutföra ett köp." ./po/id.po0000644000015600001650000000424512675036254012466 0ustar jenkinsjenkins# Indonesian translation for pay-ui # Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2015. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-02-15 08:37+0000\n" "Last-Translator: FULL NAME \n" "Language-Team: Indonesian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "" #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "" #: ../app/payui.qml:327 msgid "Loading" msgstr "" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "" ./po/ko.po0000644000015600001650000000514412675036254012502 0ustar jenkinsjenkins# Korean translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2014-09-17 03:43+0000\n" "Last-Translator: MinSik CHO \n" "Language-Team: Korean \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "잘못된 비밀번호입니다. 다시 시도해주세요." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "구매에 실패하였습니다" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "구매를 완료할 수 없습니다." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "서버와 통신 중에 오류가 발생하였습니다." #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "다시 시도할까요?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "신용카드 추가에 실패하였습니다" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "구매 진행 중" #: ../app/payui.qml:327 msgid "Loading" msgstr "불러오는 중" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "잠시만 기다려주세요..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "취소" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "구매 추가하기" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "결제" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "지금 구입" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "신용/직불카드 추가하기" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "다시 시도" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "닫기" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "결제 UI" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "구매를 하기 위한 애플리케이션" ./po/hu.po0000644000015600001650000000526312675036254012507 0ustar jenkinsjenkins# Hungarian translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-03 12:15+0000\n" "Last-Translator: Richard Somlói \n" "Language-Team: Hungarian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Vásárlás befejezése" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Helytelen jelszó, próbálja meg újra." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "A vásárlás sikertelen" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "A vásárlást nem lehet befejezni." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Kiszolgálóelérési hiba" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Szeretné megpróbálni újra?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Hitelkártya hozzáadása sikertelen" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Vásárlás feldolgozása" #: ../app/payui.qml:327 msgid "Loading" msgstr "Betöltés" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Kérem várjon…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Mégse" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Fizetés hozzáadása" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Kifizetés" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Ubuntu One jelszó megadása" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Adja meg a megerősítő kódot:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Kétfaktoros eszközkód" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Vásárlás most" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Hitelkártya/bankkártya hozzáadása" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Újra" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Bezárás" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Fizetési felület" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Alkalmazás a vásárlás befejezéséhez." ./po/br.po0000644000015600001650000000532312675036254012473 0ustar jenkinsjenkins# Breton translation for pay-ui # Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2015. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-06-05 06:28+0000\n" "Last-Translator: Fohanno Thierry \n" "Language-Team: Breton \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Echuiñ ar brenadenn" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Ger-tremen direizh, esaeit en-dro, mar plij." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Prenadenn c'hwitet" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "N'eus ket bet gallet echuiñ ar brenadenn" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Fazi o vont e darempred gant ar servijer" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Ha fellout a ra deoc'h esaeañ en-dro ?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "N'eus ket bet gallet ouzhpennañ ar gartenn-gred" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Oc'h ober war-dro ar brenadenn" #: ../app/payui.qml:327 msgid "Loading" msgstr "O kargañ" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Gortozit, mar plij..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Nullañ" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Ouzhpennañ ur paeamant" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Paeamant" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Skrivit ho ker-tremen Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Skrivit ho kof gwiriañ :" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Kod stignad gant 2 faktor" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Prenañ bremañ" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Ouzhpennañ ur gartenn-gred/dle" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Esaeañ en-dro" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Serriñ" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Etrefas paeañ" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "An arload evit ober ur brenadenn." ./po/sl.po0000644000015600001650000000520712675036254012507 0ustar jenkinsjenkins# Slovenian translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-04 09:43+0000\n" "Last-Translator: Damir Jerovšek \n" "Language-Team: Slovenian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Dokončaj nakup" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Napačno geslo, poskusite ponovno." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Nakup je spodletel" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Nakupa ni mogoče dokončati." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Napaka med navezovanjem stika s strežnikom" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Ali želite ponovno poskusiti?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Dodajanje kreditne kartice je spodletelo" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Obdelovanje nakupa" #: ../app/payui.qml:327 msgid "Loading" msgstr "Nalaganje" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Počakajte ..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Prekliči" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Dodaj plačilo" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Plačilo" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Vnesite svoje geslo od Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Vpišite potrditveno kodo:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "2-stopenjska koda naprave" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Kupi zdaj" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Dodaj kreditno/debetno kartico" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Poskusi znova" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Zapri" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Uporabniški vmesnik za plačila" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Program za dokončanje nakupa." ./po/cy.po0000644000015600001650000000511612675036254012503 0ustar jenkinsjenkins# Welsh translation for pay-ui # Copyright (c) 2016 Rosetta Contributors and Canonical Ltd 2016 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2016. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2016-02-24 15:41+0000\n" "Last-Translator: Owen Llywelyn \n" "Language-Team: Welsh \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2016-02-25 05:24+0000\n" "X-Generator: Launchpad (build 17925)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Gorffen Pryniad" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Cyfrinair anghywir, rhowch gynnig arall arni" #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Methodd y pryniad" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Methwyd cwblhau'r pryniad" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Gwall wrth gysylltu â'r gweinydd" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Hoffech chi gynnig eto?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Methwyd ychwanegu cerdyn credyd" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Prosesu'r Pryniad" #: ../app/payui.qml:327 msgid "Loading" msgstr "Llwytho" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Arhoswch..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Diddymu" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Ychwanegu Taliad" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Taliad" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Cyflwynwch eich cyfrinair Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Teipiwch eich cod gwirio:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Cod dyfais 2-ffactor" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Prynu Nawr" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Ychwanegu cerdyn credyd/debyd" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Ailgynnig" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Cau" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "UI Talu" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Yr ap ar gyfer cwblhau pryniad" ./po/it.po0000644000015600001650000000526412675036254012510 0ustar jenkinsjenkins# Italian translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-05 15:51+0000\n" "Last-Translator: Milo Casagrande \n" "Language-Team: Italian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Concludere l'acquisto" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Password non corretta, provare nuovamente." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Acquisto non riuscito" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "L'acquisto non può essere completato." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Errore contattando il server" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Provare nuovamente?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Aggiunta della carta di credito non riuscita" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Elaborazione acquisto" #: ../app/payui.qml:327 msgid "Loading" msgstr "Caricamento" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Attendere…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Annulla" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Aggiungi pagamento" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Pagamento" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Inserire la propria password per Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Digitare il codice di verifica:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Codice dispositivo 2 fattori" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Acquista ora" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Aggiungi carta di credito/debito" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Riprova" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Chiudi" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Interfaccia di pagamento" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Applicazione per completare gli acquisti." ./po/gd.po0000644000015600001650000000555712675036254012473 0ustar jenkinsjenkins# Gaelic; Scottish translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # GunChleoc , 2014. msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-03 12:13+0000\n" "Last-Translator: Akerbeltz \n" "Language-Team: Fòram na Gàidhlig\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" "Language: gd\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Coilean an ceannach" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Facal-faire cearr, am feuch thu ris a-rithist?" #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Cha do shoirbhich leis a' cheannach" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Cha b' urrainn dhuinn an ceannach a choileanadh." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Mearachd a' conaltradh ris an fhrithealaiche" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "A bheil thu airson feuchainn a-rithist?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Cha b' urrainn dhuinn a' chairt-creideis a chur ris." #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "A' dèiligeadh ris a' cheannach" #: ../app/payui.qml:327 msgid "Loading" msgstr "'Ga luchdadh" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Fuirich greiseag…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Sguir dheth" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Cuir pàigheadh ris" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Pàigheadh" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Cuir a-steach am facal-faire Ubuntu One agad" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Cuir a-steach an còd dearbhaidh agad:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Còd uidheim dà-fhactarail" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Ceannaich an-dràsta" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "cuir cairt-creideis no cairt-fhiachan ris" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Feuch ris a-rithist" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Dùin" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Eadar-aghaidh a' phàighidh" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "An aplacaid gus ceannach a choileanadh." ./po/el.po0000644000015600001650000000630512675036254012471 0ustar jenkinsjenkins# Greek translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-12 18:07+0000\n" "Last-Translator: Simos Xenitellis \n" "Language-Team: Greek \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Ολοκλήρωση αγοράς" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Εσφαλμένος κωδικός πρόσβασης, παρακαλώ δοκιμάστε ξανά." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Η αγορά απέτυχε" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Η αγορά δεν ήταν δυνατό να ολοκληρωθεί." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Σφάλμα κατά την επικοινωνία με το διακομιστή" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Θέλετε να προσπαθήσετε ξανά;" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Αποτυχία προσθήκης πιστωτικής κάρτας" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Γίνεται επεξεργασία της αγοράς" #: ../app/payui.qml:327 msgid "Loading" msgstr "Γίνεται φόρτωση" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Παρακαλώ περιμένετε..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Ακύρωση" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Προσθήκη πληρωμής" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Πληρωμή" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Εισάγετε τον κωδικό σας στο Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Γράψτε τον κωδικό επιβεβαίωσης:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Κωδικός συσκευής 2-σημείων" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Άμεση αγορά" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Προσθήκη πιστωτικής/χρεωστικής κάρτας" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Προσπάθεια ξανά" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Κλείσιμο" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pay UI (διεπαφή χρήστη για πληρωμές)" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Η εφαρμογή για την πραγματοποίηση αγορών." ./po/pay-service.pot0000644000015600001650000000425012675036254014501 0ustar jenkinsjenkins# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-03-08 16:23-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../pay-ui/app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "" #: ../pay-ui/app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "" #: ../pay-ui/app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "" #: ../pay-ui/app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "" #: ../pay-ui/app/ui/CheckoutPage.qml:370 ../pay-ui/app/payui.qml:342 msgid "Cancel" msgstr "" #: ../pay-ui/app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "" #: ../pay-ui/app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "" #: ../pay-ui/app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "" #: ../pay-ui/app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "" #: ../pay-ui/app/payui.qml:147 msgid "Finish Purchase" msgstr "" #: ../pay-ui/app/payui.qml:162 msgid "Incorrect Password, please try again." msgstr "" #: ../pay-ui/app/payui.qml:276 msgid "Purchase failed" msgstr "" #: ../pay-ui/app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "" #: ../pay-ui/app/payui.qml:292 msgid "Error contacting the server" msgstr "" #: ../pay-ui/app/payui.qml:293 ../pay-ui/app/payui.qml:309 msgid "Do you want to try again?" msgstr "" #: ../pay-ui/app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "" #: ../pay-ui/app/payui.qml:327 msgid "Processing Purchase" msgstr "" #: ../pay-ui/app/payui.qml:327 msgid "Loading" msgstr "" #: ../pay-ui/app/payui.qml:328 msgid "Please wait…" msgstr "" #: ../pay-ui/app/payui.qml:403 msgid "Add Payment" msgstr "" #: ../service-ng/src/pay-service-2/service/pay_service.go:351 msgid "Allow requesting in-app purchases?" msgstr "" ./po/sr.po0000644000015600001650000000571612675036254012522 0ustar jenkinsjenkins# Serbian translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-10-26 21:48+0000\n" "Last-Translator: Данило Шеган \n" "Language-Team: Serbian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Заврши куповину" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Нетачна лозинка, пробајте поново." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Неуспешна куповина" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Куповина није завршена." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Грешка у повезивању са сервером" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Желите ли да покушате поново?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Неуспешно додавање кредитне картице" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Обрађује куповину" #: ../app/payui.qml:327 msgid "Loading" msgstr "Учитава" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Сачекајте..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Откажи" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Додај уплату" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Плаћање" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Унесите лозинку за ваш Убунту налог" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Унесите ваш код за проверу:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Код за уређај 2. чиниоца" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Купи одмах" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Додај кредитну или дебитну картицу" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Покушај поново" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Затвори" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Плаћање" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Програм за вршење куповина." ./po/eu.po0000644000015600001650000000517512675036254012506 0ustar jenkinsjenkins# Basque translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-04-20 23:27+0000\n" "Last-Translator: Ibai Oihanguren Sala \n" "Language-Team: Basque \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Amaitu erosketa" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Okerreko pasahitza, saiatu berriro." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Erosketak huts egin du" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Erosketa ezin izan da burutu." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Errorea zerbitzariarekin kontaktuan jartzean" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Berriro saiatu nahi duzu?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Huts egin du kreditu-txartela gehitzeak" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Erosketa prozesatzen" #: ../app/payui.qml:327 msgid "Loading" msgstr "Kargatzen" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Itxaron..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Utzi" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Gehitu ordainketa" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Ordainketa" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Sartu zure Ubuntu One-eko pasahitza" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Idatzi egiaztatze-kodea:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "2 faktoreko gailu-kodea" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Erosi orain" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Gehitu kreditu/zordunketa-txartela" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Saiatu berriz" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Itxi" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Ordainketa-interfazea" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Erosketak burutzeko aplikazioa." ./po/is.po0000644000015600001650000000440112675036254012477 0ustar jenkinsjenkins# Icelandic translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-10-08 05:02+0000\n" "Last-Translator: Stefán Örvar Sigmundsson \n" "Language-Team: Icelandic \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "" #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "" #: ../app/payui.qml:327 msgid "Loading" msgstr "Hleður" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Bíddu aðeins..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Hætta við" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Greiðsla" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Reyna aftur" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Loka" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "" ./po/en_AU.po0000644000015600001650000000511612675036254013057 0ustar jenkinsjenkins# English (Australia) translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-04 19:15+0000\n" "Last-Translator: Rodney Dawes \n" "Language-Team: English (Australia) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Finish Purchase" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Incorrect Password, please try again." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Purchase failed" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "The purchase couldn't be completed." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Error contacting the server" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Do you want to try again?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Adding Credit Card failed" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Processing Purchase" #: ../app/payui.qml:327 msgid "Loading" msgstr "Loading" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Please wait…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Cancel" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Add Payment" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Payment" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Enter your Ubuntu One password" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Type your verification code:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "2-factor device code" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Buy Now" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Add credit/debit card" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Retry" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Close" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pay UI" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "The application for completing a purchase." ./po/uk.po0000644000015600001650000000611212675036254012504 0ustar jenkinsjenkins# Ukrainian translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-20 09:15+0000\n" "Last-Translator: Yuri Chornoivan \n" "Language-Team: Ukrainian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Завершити купівлю" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Помилковий пароль, повторіть спробу." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Спроба купівлі зазнала невдачі" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Не вдалося завершити процедуру купівлі." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Помилка під час встановлення з’єднання з сервером" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Хочете повторити спробу?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Спроба додавання кредитної картки завершилася невдало" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Обробка купівлі" #: ../app/payui.qml:327 msgid "Loading" msgstr "Завантаження" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Заждіть…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Скасувати" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Додати сплату" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Сплата" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Введіть ваш пароль Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Введіть ваш код підтвердження:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "двофакторний код пристрою" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Придбати зараз" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Додати кредитну картку" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Повторити" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Закрити" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Інтерфейс сплати" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Програма для купівлі речей." ./po/fi.po0000644000015600001650000000520312675036254012463 0ustar jenkinsjenkins# Finnish translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-02-25 17:02+0000\n" "Last-Translator: Jiri Grönroos \n" "Language-Team: Finnish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Viimeistele ostos" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Virheellinen salasana, yritä uudelleen." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Ostaminen epäonnistui" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Ostosta ei voitu suorittaa loppuun." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Virhe palvelimeen yhdistäessä" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Haluatko yrittää uudelleen?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Luottokortin lisääminen epäonnistui" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Käsitellään ostosta" #: ../app/payui.qml:327 msgid "Loading" msgstr "Ladataan" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Odota hetki…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Peru" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Lisää maksu" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Maksu" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Anna Ubuntu One -salasanasi" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Anna vahvistuskoodi:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Kaksivaiheisen tunnistamisen laitekoodi" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Osta nyt" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Lisää luotto- tai pankkikortti" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Yritä uudelleen" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Sulje" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Maksukäyttöliittymä" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Sovellus ostosten käsittelyä varten." ./po/ru.po0000644000015600001650000000560412675036254012520 0ustar jenkinsjenkins# Russian translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2014-10-24 10:16+0000\n" "Last-Translator: ☠Jay ZDLin☠ \n" "Language-Team: Russian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Завершить покупку" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Введён неверный пароль, попробуйте ещё раз." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Сбой покупки" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Не удалось завершить покупку." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Ошибка соединения с сервером" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Попробовать снова?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Не удалось добавить кредитную карту" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Обработка покупки" #: ../app/payui.qml:327 msgid "Loading" msgstr "Загрузка" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Подождите..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Отменить" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Добавить платёж" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Платёж" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Введите проверочный код:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "2-факторный код устройства" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Купить" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Добавить кредитную/дебетовую карту" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Повторить" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Закрыть" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pay UI" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Приложение для совершения покупок." ./po/ro.po0000644000015600001650000000532512675036254012512 0ustar jenkinsjenkins# Romanian translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-03 19:56+0000\n" "Last-Translator: Meriuță Cornel \n" "Language-Team: Romanian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Termină cumpărăturile" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Parolă greșită, încercați din nou." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Achiziția a eșuat" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Achiziția nu poate fi dusă la bun sfârșit." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Eroare la contactarea serverului" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Doriți să încercați din nou?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Eșec la adăugarea cărții de credit" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Procesare achiziție" #: ../app/payui.qml:327 msgid "Loading" msgstr "Se încarcă" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Vă rugăm așteptați..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Anulează" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Adăugare metodă de plată" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Metodă de plată" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Introduceţi parola pentru contul Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Introduceți codul de verificare" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Cod dispozitiv cu 2 factori" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Cumpărare acum" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Adăugare card de credit/debit" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Reîncercați" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Închide" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pay UI" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Aplicație pentru achiziții." ./po/he.po0000644000015600001650000000530012675036254012457 0ustar jenkinsjenkins# Hebrew translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2014-12-23 13:23+0000\n" "Last-Translator: Yaron \n" "Language-Team: Hebrew \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "סיום הרכישה" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "הססמה שגויה, נא לנסות שוב." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "הרכישה נכשלה" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "לא ניתן להשלים את הרכישה." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "אירעה שגיאה ביצירת קשר עם השרת" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "לנסות שוב?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "הוספת כרטיס האשראי נכשלה" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "הרכישה בעיבוד" #: ../app/payui.qml:327 msgid "Loading" msgstr "בטעינה" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "נא להמתין…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "ביטול" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "הוספת תשלום" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "תשלום" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "נא להזין את קוד האימות שלך:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "לרכוש כעת" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "הוספת כרטיס אשראי/חיוב" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "ניסיון חוזר" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "סגירה" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "מנשק קנייה" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "היישום להשלמת רכישות." ./po/am.po0000644000015600001650000000573612675036254012475 0ustar jenkinsjenkins# Amharic translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-03 14:18+0000\n" "Last-Translator: samson \n" "Language-Team: Amharic \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "ግዢውን መጨረሻ" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "የተሳሳተ የመግቢያ ቃል እባክዎን እንደገና ይሞክሩ" #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "ግዢው አልተሳካም" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "ግዢውን መፈጸም አልተቻለም" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "ስህተት ተፈጥሯል ሰርቨሩን በመገናኘት ላይ እንዳለ" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "እንደገና መሞከር ይፈልጋሉ?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "የ ክሬዲር ካርድ መጨመር አልተቻለም" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "ግዢውን በማካሄድ ላይ" #: ../app/payui.qml:327 msgid "Loading" msgstr "በመጫን ላይ" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "እባክዎን ይጠብቁ…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "መሰረዣ" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "ክፍያውን መጨመሪያ" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "ክፍያ" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "የ እርስዎን የ ኡቡንቱ ዋን የ መግቢያ ቃል ያስገቡ" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "የ እርስዎን ማረጋገጫ ኮድ ይጻፉ:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "2-ፋክተር የ አካል ኮድ" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "አሁን መግዣ" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "ክሬዲት/ዴቢት ካርድ መጨመሪያ" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "እንደገና ይሞክሩ" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "መዝጊያ" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "መክፈያ UI" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "መተግበሪያ ግዢውን ለመፈጸም" ./po/es.po0000644000015600001650000000525312675036254012501 0ustar jenkinsjenkins# Spanish translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-04 19:08+0000\n" "Last-Translator: Alejandro J. Cura \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Terminar la compra" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "La contraseña es incorrecta. Inténtelo de nuevo." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "La compra falló" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "La compra no se pudo completar." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Error al contactar con el servidor" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "¿Quiere intentar de nuevo?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Error al añadir la tarjeta de crédito" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Procesando la compra" #: ../app/payui.qml:327 msgid "Loading" msgstr "Cargando" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Espere…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Cancelar" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Añadir pago" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Pago" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Ingrese su contraseña de Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Escriba su código de verificación:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Código de disp. de autent. en 2 pasos" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Comprar ahora" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Añadir tarjeta de crédito o débito" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Reintentar" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Cerrar" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Interfaz de pagos" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "La aplicación para completar una compra." ./po/ug.po0000644000015600001650000000603012675036254012477 0ustar jenkinsjenkins# Uyghur translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # Gheyret Kenji , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2014-10-06 00:37+0000\n" "Last-Translator: Gheyret T.Kenji \n" "Language-Team: Uyghur Computer Science Association \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "سېتىۋېلىش تامام" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "ئىم خاتا، قايتا سىناڭ." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "سېتىۋېلىش مەغلۇپ بولدى" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "بۇ سېتىۋېلىش تاماملانمىدى." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "مۇلازىمېتىر بىلەن ئالاقە قىلىش مەغلۇپ بولدى." #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "قايتا سىناپ كۆرەمسىز؟" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "كرېدىت كارتىسى قوشۇش مەغلۇپ بولدى" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "سېتىۋېلىشنى بىر تەرەپ قىلىۋاتىدۇ" #: ../app/payui.qml:327 msgid "Loading" msgstr "يۈكلەۋاتىدۇ" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "سەل كۈتۈڭ…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "ئەمەلدىن قالدۇر" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "تۆلەش ئۇسۇلىنى قوشۇش" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "تۆلەش" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "دەلىللەش كودىنى كىرگۈزۈڭ:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "ئىككى ئېلېمېنتلىق ئۈسكۈنە كودى" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "ھازىر سېتىۋالىمەن" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "كرېدىت كارتا/دەبىت كارتا قوش" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "قايتا سىنا" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "ياپ" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pay UI" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "بۇ سېتىۋېلىش مەشغۇلاتىنى ئېلىپ بارىدىغان پروگراممىدۇر." ./po/CMakeLists.txt0000644000015600001650000000264112675036254014270 0ustar jenkinsjenkinsinclude(FindGettext) find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext) find_program(INTLTOOL_UPDATE intltool-update) set(GETTEXT_PACKAGE ${PROJECT_NAME}) set(POT_FILE ${GETTEXT_PACKAGE}.pot) file(GLOB POFILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.po) # Creates POTFILES add_custom_target(POTFILES ALL COMMENT "Generating POTFILES" COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/genpotfiles.sh ${CMAKE_SOURCE_DIR} ) # Creates the .pot file containing the translations template set(INTLTOOL_ENV XGETTEXT="${GETTEXT_XGETTEXT_EXECUTABLE}" XGETTEXT_ARGS="--keyword=Gettext;--keyword=NGettext:1,2;--keyword=tr;--keyword=tr:1,2;--keyword=N_" srcdir="${CMAKE_CURRENT_SOURCE_DIR}" ) add_custom_target(${POT_FILE} COMMENT "Generating translation template" COMMAND ${INTLTOOL_ENV} ${INTLTOOL_UPDATE} --gettext-package ${GETTEXT_PACKAGE} --pot COMMAND cp -f ${POT_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/ DEPENDS POTFILES ) # Builds the binary translations catalog for each language # it finds source translations (*.po) for foreach(POFILE ${POFILES}) string(REPLACE ".po" "" LANG ${POFILE}) list(APPEND PO_FILES "${POFILE}") gettext_process_po_files(${LANG} ALL PO_FILES "${POFILE}") set(INSTALL_DIR ${CMAKE_INSTALL_LOCALEDIR}/${LANG}/LC_MESSAGES) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${LANG}.gmo DESTINATION ${INSTALL_DIR} RENAME ${GETTEXT_PACKAGE}.mo ) endforeach(POFILE) ./po/fr.po0000644000015600001650000000531512675036254012500 0ustar jenkinsjenkins# French translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-08 16:19+0000\n" "Last-Translator: Anne \n" "Language-Team: French \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Terminer l'achat" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Mot de passe incorrect, veuillez réessayer." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Échec de l'achat" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "L'achat n'a pas pu être effectué." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Erreur lors du contact avec le serveur" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Voulez-vous réessayer ?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "L'ajout de carte de crédit a échoué" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Traitement de l'achat" #: ../app/payui.qml:327 msgid "Loading" msgstr "Chargement" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Veuillez patienter…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Annuler" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Ajouter un paiement" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Paiement" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Saisissez votre mot de passe pour Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Saisissez votre code de vérification :" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Code de dispositif à 2 facteurs" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Acheter maintenant" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Ajouter une carte de crédit/débit" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Recommencer" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Fermer" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Interface utilisateur de paiement" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "L'application pour effectuer un achat." ./po/vi.po0000644000015600001650000000424512675036254012510 0ustar jenkinsjenkins# Vietnamese translation for pay-ui # Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2015. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-04-02 19:35+0000\n" "Last-Translator: FULL NAME \n" "Language-Team: Vietnamese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "" #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "" #: ../app/payui.qml:327 msgid "Loading" msgstr "" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "" ./po/POTFILES.in0000644000015600001650000000053512675036254013305 0ustar jenkinsjenkinspay-ui/app/components/ConfirmDialog.qml pay-ui/app/components/PromptDialog.qml pay-ui/app/components/BeforeUnloadDialog.qml pay-ui/app/components/SecurityCertificatePopover.qml pay-ui/app/components/AlertDialog.qml pay-ui/app/ui/CheckoutPage.qml pay-ui/app/ui/ErrorDialog.qml pay-ui/app/payui.qml service-ng/src/pay-service-2/service/pay_service.go ./po/ja.po0000644000015600001650000000517212675036254012464 0ustar jenkinsjenkins# Japanese translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2014-09-18 10:28+0000\n" "Last-Translator: Kentaro Kazuhama \n" "Language-Team: Japanese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "誤ったパスワードです、再入力してください。" #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "支払いに失敗しました" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "購入処理が完了できませんでした。" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "サーバーへの接続エラー" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "再実行しますか?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "クレジットカードの追加に失敗しました" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "購入処理中" #: ../app/payui.qml:327 msgid "Loading" msgstr "読み込み中" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "お待ちください…" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "キャンセル" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "支払を追加" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "支払い" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "今すぐ購入" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "クレジット/デビットカードの追加" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "閉じる" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Pay UI" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "購入を行うためのアプリケーション。" ./po/de.po0000644000015600001650000000532112675036254012456 0ustar jenkinsjenkins# German translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-03-05 15:49+0000\n" "Last-Translator: Phillip Sz \n" "Language-Team: German \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Kauf abschließen" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Feherhaftes Passwort, bitten versuchen Sie es erneut." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Kauf fehlgeschlagen" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Der Kauf konnte nicht abgeschlossen werden." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Server konnte nicht erreicht werden" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Möchten Sie es ein weiteres Mal versuchen?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Hinzufügen der Kreditkarte fehlgeschlagen" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Kauf wird verarbeitet" #: ../app/payui.qml:327 msgid "Loading" msgstr "Wird geladen …" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Bitte warten …" #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Abbrechen" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Zahlungsmittel hinzufügen" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Zahlung" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Bitte geben Sie Ihr Ubuntu One Passwort ein" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Geben Sie den Verifikations-Code ein:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "Zwei-Faktor-Code:" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Jetzt kaufen" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Kredit/EC-Karte hinzufügen" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Wiederholen" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Schließen" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Bezahl-Benutzerschnittstelle" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Die Anwendung für Verkaufsvorgänge." ./po/zh_CN.po0000644000015600001650000000507312675036254013073 0ustar jenkinsjenkins# Chinese (Simplified) translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-02-28 03:06+0000\n" "Last-Translator: Luo Lei \n" "Language-Team: Chinese (Simplified) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "完成购买" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "密码不正确,请重试。" #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "购买失败" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "购买无法完成。" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "服务器联机错误" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "要重试吗?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "添加信用卡失败" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "正在处理购买请求" #: ../app/payui.qml:327 msgid "Loading" msgstr "加载中" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "请稍候..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "取消" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "添加支付方式" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "支付" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "输入您的 Ubuntu One 密码" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "请输入验证码:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "两步验证设备码" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "立即购买" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "添加信用卡/借记卡" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "重试" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "关闭" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "支付界面" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "完成购买过程的应用程序。" ./po/aa.po0000644000015600001650000000504112675036254012446 0ustar jenkinsjenkins# Afar translation for pay-ui # Copyright (c) 2015 Rosetta Contributors and Canonical Ltd 2015 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2015. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-04-26 13:56+0000\n" "Last-Translator: Charif AYFARAH \n" "Language-Team: Afar \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Limok gaba kal" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "Cuumiti makot yan, maganak kaadu gabbat." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Limo makkinna" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "limo ma duuduminna." #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Yayfaayi angaaraw soka le" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "Kaadu gabbattam maay faxxa?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Abuúd Karti edde osisaanam makkinna" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Limô gexso" #: ../app/payui.qml:327 msgid "Loading" msgstr "Qulluumah" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Maganak qambal..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Bayis" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Mekla edde osis" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Mekla" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Ubuntu One cuumita culus" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Mudenti diggoyso culus:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "2-afeetah aalatih mudenta" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Away xaamit" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Abuú/Raci karti edde osis" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Qagis" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Alif" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "UI mekel" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "Limô duddoh abnisso" ./po/ast.po0000644000015600001650000000522712675036254012662 0ustar jenkinsjenkins# Asturian translation for pay-ui # Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 # This file is distributed under the same license as the pay-ui package. # FIRST AUTHOR , 2014. # msgid "" msgstr "" "Project-Id-Version: pay-service\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2015-02-19 15:01-0500\n" "PO-Revision-Date: 2015-06-09 15:40+0000\n" "Last-Translator: enolp \n" "Language-Team: Asturian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-12-25 05:38+0000\n" "X-Generator: Launchpad (build 17865)\n" #: ../app/payui.qml:148 msgid "Finish Purchase" msgstr "Acabar la compra" #: ../app/payui.qml:163 msgid "Incorrect Password, please try again." msgstr "La contraseña ye incorreuta. Inténtalo de nueves." #: ../app/payui.qml:276 msgid "Purchase failed" msgstr "Falló la compra" #: ../app/payui.qml:277 msgid "The purchase couldn't be completed." msgstr "Nun pudo completase la compra" #: ../app/payui.qml:292 msgid "Error contacting the server" msgstr "Fallu al contautar col sirvidor" #: ../app/payui.qml:293 ../app/payui.qml:309 msgid "Do you want to try again?" msgstr "¿Quies intentalo otra vegada?" #: ../app/payui.qml:308 msgid "Adding Credit Card failed" msgstr "Fallu al amestar tarxeta de creitu" #: ../app/payui.qml:327 msgid "Processing Purchase" msgstr "Procesando la compra" #: ../app/payui.qml:327 msgid "Loading" msgstr "Cargando" #: ../app/payui.qml:328 msgid "Please wait…" msgstr "Espera, por favor..." #: ../app/payui.qml:342 ../app/ui/CheckoutPage.qml:370 msgid "Cancel" msgstr "Encaboxar" #: ../app/payui.qml:401 msgid "Add Payment" msgstr "Amestar pagu" #: ../app/ui/CheckoutPage.qml:27 msgid "Payment" msgstr "Pagu" #: ../app/ui/CheckoutPage.qml:228 msgid "Enter your Ubuntu One password" msgstr "Introduz la to contraseña d'Ubuntu One" #: ../app/ui/CheckoutPage.qml:271 msgid "Type your verification code:" msgstr "Escribi'l códigu de comprobación:" #: ../app/ui/CheckoutPage.qml:282 msgid "2-factor device code" msgstr "códigu de preséu 2-factor" #: ../app/ui/CheckoutPage.qml:379 msgid "Buy Now" msgstr "Mercar Agora" #: ../app/ui/CheckoutPage.qml:393 msgid "Add credit/debit card" msgstr "Amestar tarxeta de creitu o débitu" #: ../app/ui/ErrorDialog.qml:45 msgid "Retry" msgstr "Reintentar" #: ../app/ui/ErrorDialog.qml:55 msgid "Close" msgstr "Zarrar" #: payui_payui.desktop.in.in.h:1 msgid "Pay UI" msgstr "Interfaz de pagos" #: payui_payui.desktop.in.in.h:2 msgid "The application for completing a purchase." msgstr "L'aplicación pa completar una compra." ./COPYING0000644000015600001650000010451312675036254012146 0ustar jenkinsjenkins GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ./HACKING0000644000015600001650000000514012675036254012076 0ustar jenkinsjenkinspay-service hacking guide =============================== Getting pay-service ------------------------- To get the main branch of pay-service: $ bzr branch lp:pay-service Getting dependencies -------------------- To succesfully build pay-service extra packages are required: $ sudo apt-get build-dep pay-service Building pay-service ------------------ This app is built using cmake. Here's an example on how to build it: $ mkdir build $ cd build $ cmake .. $ make -j 8 Running the unit tests ---------------------- $ make test Running the autopilot tests --------------------------- To run the autopilot tests locally, you first need to build pay-service. $ make autopilot The autopilot tests can also be run against a built debian package, under qemu. To do so, you will need some additional packages. Most importantly, you will need the latest version of the autopkgtest package. You can download it at https://launchpad.net/ubuntu/+source/autopkgtest by selecting the most recent build for the most recent version of Ubuntu. $ sudo dpkg -i autopkgtest*.deb $ sudo apt-get install qemu After installing autopkgtest and qemu, you need to build an image for qemu to use. We use vivid here, as building an image to closely resemble the actual stable phone images is quite difficult. When this is easier in the future, we will switch to using stable phone images for this. The architecture argument should match the architecture of the debian package you are trying to test. We output the image to ~/ rather than the current directory, so it will be in a safer place to avoid rebuilding images every time. You can store it in any directory you wish. $ adt-buildvm-ubuntu-cloud -r vivid -a amd64 -o ~/ Then the tests may be run using adt-run with the qemu virtualization host. The output directory option here can be wherever you like, and is where the test artifacts will be placed. The ordering of the arguments to adt-run is important so try to keep them in this order. $ adt-run --click-source . \ --source ../pay-service*.dsc \ -o /tmp/adt-payui-test \ --setup-commands "add-apt-repository \ ppa:ci-train-ppa-service/stable-phone-overlay" \ --apt-pocket proposed \ --setup-commands "apt-get update" \ --setup-commands ubuntu-touch-session \ --- qemu ~/adt-vivid-amd64-cloud.img To examine the test results, which are in subunit format, additional tools are required. $ sudo add-apt-repository ppa:thomir/trv $ sudo apt-get update $ sudo apt-get install trv $ trv /tmp/adt-payui-test/artifacts/autopilot.subunit ./libpay/0000755000015600001650000000000012675036254012367 5ustar jenkinsjenkins./libpay/libpay.map0000644000015600001650000000007612675036254014351 0ustar jenkinsjenkins{ global: pay_package_*; pay_item_*; local: *; }; ./libpay/pay-item.cpp0000644000015600001650000000453512675036254014627 0ustar jenkinsjenkins/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include #include /*** **** Life Cycle ***/ void pay_item_ref (PayItem* item) { g_return_if_fail(item != nullptr); item->ref(); } void pay_item_unref (PayItem* item) { g_return_if_fail(item != nullptr); item->unref(); } /*** **** Properties ***/ time_t pay_item_get_acknowledged_timestamp (const PayItem* item) { g_return_val_if_fail(item != nullptr, 0); return item->acknowledged_timestamp(); } const char* pay_item_get_description (const PayItem* item) { g_return_val_if_fail(item != nullptr, nullptr); return item->description().c_str(); } const char* pay_item_get_sku (const PayItem* item) { g_return_val_if_fail(item != nullptr, nullptr); return item->sku().c_str(); } const char* pay_item_get_price (const PayItem* item) { g_return_val_if_fail(item != nullptr, nullptr); return item->price().c_str(); } time_t pay_item_get_completed_timestamp (const PayItem* item) { g_return_val_if_fail(item != nullptr, 0); return item->completed_timestamp(); } PayPackageItemStatus pay_item_get_status (const PayItem* item) { g_return_val_if_fail(item != nullptr, PAY_PACKAGE_ITEM_STATUS_UNKNOWN); return item->status(); } const char* pay_item_get_title (const PayItem* item) { g_return_val_if_fail(item != nullptr, nullptr); return item->title().c_str(); } PayItemType pay_item_get_type (const PayItem* item) { g_return_val_if_fail(item != nullptr, PAY_ITEM_TYPE_UNKNOWN); return item->type(); } uint64_t pay_item_get_purchase_id (const PayItem* item) { g_return_val_if_fail(item != nullptr, 0); return item->purchase_id(); } ./libpay/internal/0000755000015600001650000000000012675036254014203 5ustar jenkinsjenkins./libpay/internal/item.h0000644000015600001650000000557712675036254015330 0ustar jenkinsjenkins/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #pragma once #include #include #include #include namespace Pay { namespace Internal { class Item { const std::string m_sku; std::string m_description; std::string m_price; std::string m_title; PayItemType m_type = PAY_ITEM_TYPE_UNKNOWN; PayPackageItemStatus m_status = PAY_PACKAGE_ITEM_STATUS_UNKNOWN; int m_ref_count = 1; uint64_t m_purchase_id = 0; time_t m_completed_timestamp = 0; time_t m_acknowledged_timestamp = 0; time_t m_refundable_until = 0; public: /** life cycle **/ explicit Item (const std::string& sku): m_sku(sku) {} ~Item() =default; void ref() {++m_ref_count;} void unref() { if (!--m_ref_count) delete this; } /** accessors **/ const std::string& description() const {return m_description;} const std::string& sku() const {return m_sku;} const std::string& price() const {return m_price;} uint64_t purchase_id() const {return m_purchase_id;} PayPackageItemStatus status() const {return m_status;} time_t completed_timestamp() const {return m_completed_timestamp;} time_t acknowledged_timestamp() const {return m_acknowledged_timestamp;} time_t refundable_until() const {return m_refundable_until;} const std::string& title() const {return m_title;} PayItemType type() const {return m_type;} /** setters **/ void set_description(const std::string& val) {m_description = val;} void set_price(const std::string& val) {m_price = val;} void set_purchase_id(uint64_t val) {m_purchase_id = val;} void set_status(PayPackageItemStatus val) {m_status = val;} void set_completed_timestamp(time_t val) {m_completed_timestamp = val;} void set_acknowledged_timestamp(time_t val) {m_acknowledged_timestamp = val;} void set_refundable_until(time_t val) {m_refundable_until = val;} void set_title(const std::string& val) {m_title = val;} void set_type(PayItemType val) {m_type = val;} bool operator<(const Item& that) const {return sku() < that.sku();} }; } // namespace Internal } // namespace Pay struct PayItem_: public Pay::Internal::Item { explicit PayItem_(const std::string& product_id): Pay::Internal::Item(product_id) {} }; ./libpay/internal/CMakeLists.txt0000644000015600001650000000016212675036254016742 0ustar jenkinsjenkinsset(SRC ${CMAKE_CURRENT_SOURCE_DIR}/package.cpp ) set(libpay-sources ${libpay-sources} ${SRC} PARENT_SCOPE) ./libpay/internal/package.h0000644000015600001650000000651112675036254015752 0ustar jenkinsjenkins/* * Copyright © 2014-2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: * Ted Gould * Charles Kerr */ #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace Pay { namespace Internal { class Package { const std::string id; std::map , core::ScopedConnection> statusObservers; std::map , core::ScopedConnection> refundObservers; core::Signal statusChanged; void updateStatus(const std::string& sku, PayPackageItemStatus); GLib::ContextThread thread; std::shared_ptr storeProxy; constexpr static uint64_t expiretime{60}; // 60 seconds prior status is "expiring" template bool removeObserver(Collection& collection, const typename Collection::key_type& key); template bool startStoreAction(const std::shared_ptr& bus_proxy, const gchar* function_name, GVariant* params, gint timeout_msec) noexcept; public: explicit Package (const std::string& packageid); ~Package(); PayPackageItemStatus itemStatus (const std::string& sku) noexcept; PayPackageRefundStatus refundStatus (const std::string& sku) noexcept; PayPackageRefundStatus calcRefundStatus (PayPackageItemStatus item, uint64_t refundtime); bool addStatusObserver (PayPackageItemObserver observer, void* user_data) noexcept; bool removeStatusObserver (PayPackageItemObserver observer, void* user_data) noexcept; bool addRefundObserver (PayPackageRefundObserver observer, void* user_data) noexcept; bool removeRefundObserver (PayPackageRefundObserver observer, void* user_data) noexcept; bool startVerification (const std::string& sku) noexcept; bool startPurchase (const std::string& sku) noexcept; bool startRefund (const std::string& sku) noexcept; bool startAcknowledge (const std::string& sku) noexcept; std::shared_ptr getItem(const std::string& sku) noexcept; std::vector> getPurchasedItems() noexcept; }; } // namespace Internal } // namespace Pay struct PayPackage_: public Pay::Internal::Package { explicit PayPackage_(const std::string& package_name): Pay::Internal::Package(package_name) {} }; ./libpay/internal/package.cpp0000644000015600001650000004124712675036254016312 0ustar jenkinsjenkins/* * Copyright © 2014-2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: * Ted Gould * Charles Kerr */ #include #include #include namespace Pay { namespace Internal { Package::Package (const std::string& packageid) : id(packageid) , thread([]{}, [this]{storeProxy.reset();}) { /* Fire up a glib thread to create the proxies. Block on it here so the proxies are ready before this ctor returns */ const auto errorStr = thread.executeOnThread([this]() { const auto encoded_id = BusUtils::encodePathElement(id); GError* error = nullptr; // create the pay-service-ng proxy... std::string path = "/com/canonical/pay/store/" + encoded_id; storeProxy = std::shared_ptr( proxy_pay_store_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, "com.canonical.payments", path.c_str(), thread.getCancellable().get(), &error), [](proxyPayStore * store){g_clear_object(&store);} ); if (error != nullptr) { const std::string tmp { error->message }; g_clear_error(&error); return tmp; } return std::string(); // no error }); if (!errorStr.empty()) { throw std::runtime_error(errorStr); } if (!storeProxy) { throw std::runtime_error("Unable to build proxy for pay-service"); } } Package::~Package () { thread.quit(); } PayPackageItemStatus Package::itemStatus (const std::string& sku) noexcept { const auto item = getItem(sku); return item ? item->status() : PAY_PACKAGE_ITEM_STATUS_UNKNOWN; } PayPackageRefundStatus Package::refundStatus (const std::string& sku) noexcept { const auto item = getItem(sku); return item ? calcRefundStatus(item->status(), item->refundable_until()) : PAY_PACKAGE_REFUND_STATUS_NOT_REFUNDABLE; } PayPackageRefundStatus Package::calcRefundStatus (PayPackageItemStatus item, uint64_t refundtime) { g_debug("Checking refund status with timeout: %lld", refundtime); if (item != PAY_PACKAGE_ITEM_STATUS_PURCHASED) { return PAY_PACKAGE_REFUND_STATUS_NOT_PURCHASED; } const auto now = uint64_t(std::time(nullptr)); if (refundtime < (now + 10u /* seconds */)) // Honestly, they can't refund this quickly anyway { return PAY_PACKAGE_REFUND_STATUS_NOT_REFUNDABLE; } if (refundtime < (now + expiretime)) { return PAY_PACKAGE_REFUND_STATUS_WINDOW_EXPIRING; } return PAY_PACKAGE_REFUND_STATUS_REFUNDABLE; } /*** **** Observers ***/ template bool Package::removeObserver(Collection& collection, const typename Collection::key_type& key) { bool removed = false; auto it = collection.find(key); if (it != collection.end()) { collection.erase(it); removed = true; } return removed; } bool Package::addStatusObserver (PayPackageItemObserver observer, void* user_data) noexcept { /* Creates a connection to the signal for the observer and stores the connection object in the map so that we can remove it later, or it'll get disconnected when the whole object gets destroyed */ statusObservers.emplace(std::make_pair(observer, user_data), statusChanged.connect([this, observer, user_data] ( const std::string& sku, PayPackageItemStatus status, uint64_t /*refund*/) { observer(reinterpret_cast(this), sku.c_str(), status, user_data); })); return true; } bool Package::removeStatusObserver (PayPackageItemObserver observer, void* user_data) noexcept { return removeObserver(statusObservers, std::make_pair(observer, user_data)); } bool Package::addRefundObserver (PayPackageRefundObserver observer, void* user_data) noexcept { refundObservers.emplace(std::make_pair(observer, user_data), statusChanged.connect([this, observer, user_data] ( const std::string& sku, PayPackageItemStatus status, uint64_t refund) { observer(reinterpret_cast(this), sku.c_str(), calcRefundStatus(status, refund), user_data); })); return true; } bool Package::removeRefundObserver (PayPackageRefundObserver observer, void* user_data) noexcept { return removeObserver(refundObservers, std::make_pair(observer, user_data)); } /*** **** ***/ namespace // helper functions { PayItemType type_from_string(const std::string& str) { if (str == "consumable") return PAY_ITEM_TYPE_CONSUMABLE; if (str == "unlockable") return PAY_ITEM_TYPE_UNLOCKABLE; return PAY_ITEM_TYPE_UNKNOWN; } std::shared_ptr create_pay_item_from_variant(GVariant* item_properties) { std::shared_ptr item; if (item_properties == nullptr) { g_warning("%s item_properties variant is NULL", G_STRLOC); } else if (!g_variant_is_of_type(item_properties, G_VARIANT_TYPE_VARDICT)) { g_warning("%s item_properties variant is not a vardict", G_STRLOC); } else { // make sure we've got a valid sku to construct the PayItem with const char* sku {}; g_variant_lookup(item_properties, "sku", "&s", &sku); if (!sku || !*sku) { g_warning("%s item_properties variant has no sku entry", G_STRLOC); } else { auto pay_item_deleter = [](PayItem* p){p->unref();}; item.reset(new PayItem(sku), pay_item_deleter); // now loop through the dict to build the PayItem's properties GVariantIter iter; gchar* key; GVariant* value; g_variant_iter_init(&iter, item_properties); while (g_variant_iter_loop(&iter, "{sv}", &key, &value)) { if (!g_strcmp0(key, "acknowledged_timestamp")) { item->set_acknowledged_timestamp(g_variant_get_uint64(value)); } else if (!g_strcmp0(key, "description")) { item->set_description(g_variant_get_string(value, nullptr)); } else if (!g_strcmp0(key, "sku") || !g_strcmp0(key, "package_name")) { // no-op; we handled the sku/package_name first } else if (!g_strcmp0(key, "price")) { item->set_price(g_variant_get_string(value, nullptr)); } else if (!g_strcmp0(key, "completed_timestamp")) { item->set_completed_timestamp(g_variant_get_uint64(value)); } else if (!g_strcmp0(key, "refundable_until")) { item->set_refundable_until(g_variant_get_uint64(value)); } else if (!g_strcmp0(key, "purchase_id")) { item->set_purchase_id(g_variant_get_uint64(value)); } else if (!g_strcmp0(key, "state")) { auto state = g_variant_get_string(value, nullptr); if (!g_strcmp0(state, "purchased")) { item->set_status(PAY_PACKAGE_ITEM_STATUS_PURCHASED); } else if (!g_strcmp0(state, "approved")) { item->set_status(PAY_PACKAGE_ITEM_STATUS_APPROVED); } else { item->set_status(PAY_PACKAGE_ITEM_STATUS_NOT_PURCHASED); } } else if (!g_strcmp0(key, "type")) { item->set_type(type_from_string(g_variant_get_string(value, nullptr))); } else if (!g_strcmp0(key, "title")) { item->set_title(g_variant_get_string(value, nullptr)); } else { auto valstr = g_variant_print(value, true); g_warning("Unhandled item property '%s': '%s'", key, valstr); g_free(valstr); } } } } return item; } } // anonymous namespace /*** **** IAP ***/ std::shared_ptr Package::getItem(const std::string& sku) noexcept { struct CallbackData { GVariant* properties {}; std::promise promise; ~CallbackData() { g_clear_pointer(&properties, g_variant_unref); } }; CallbackData data; auto on_async_ready = [](GObject* o, GAsyncResult* res, gpointer gdata) { auto data = static_cast(gdata); GError* error = nullptr; proxy_pay_store_call_get_item_finish(PROXY_PAY_STORE(o), &data->properties, res, &error); if ((error != nullptr) && !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { std::cerr << "Error getting item: " << error->message << std::endl; } data->promise.set_value(error == nullptr); g_clear_error(&error); }; auto thread_func = [this, sku, &on_async_ready, &data]() { proxy_pay_store_call_get_item(storeProxy.get(), sku.c_str(), thread.getCancellable().get(), // GCancellable on_async_ready, &data); }; thread.executeOnThread(thread_func); auto future = data.promise.get_future(); future.wait(); return create_pay_item_from_variant(data.properties); } std::vector> Package::getPurchasedItems() noexcept { struct CallbackData { GVariant* v {}; std::promise promise; ~CallbackData() { g_clear_pointer(&v, g_variant_unref); } }; CallbackData data; auto on_async_ready = [](GObject* o, GAsyncResult* res, gpointer gdata) { auto data = static_cast(gdata); GError* error {}; proxy_pay_store_call_get_purchased_items_finish(PROXY_PAY_STORE(o), &data->v, res, &error); if ((error != nullptr) && !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { std::cerr << "Error getting purchased items: " << error->message << std::endl; } data->promise.set_value(error == nullptr); g_clear_error(&error); }; auto thread_func = [this, &on_async_ready, &data]() { proxy_pay_store_call_get_purchased_items(storeProxy.get(), thread.getCancellable().get(), // GCancellable on_async_ready, &data); }; thread.executeOnThread(thread_func); auto future = data.promise.get_future(); future.wait(); std::vector> items; auto v = data.v; if (v != nullptr) { GVariantIter iter; g_variant_iter_init(&iter, v); GVariant* child; while ((child = g_variant_iter_next_value(&iter))) { auto item = create_pay_item_from_variant(child); if (item) { items.push_back(item); } g_variant_unref(child); } } return items; } /** * We call com.canonical.pay.store's Purchase, Refund, and Acknowledge * items in nearly identical ways: make the call asynchronously, and * when the service responds, update our status cache with the returned item. * * This method folds together the common code for these actions. */ template bool Package::startStoreAction(const std::shared_ptr& bus_proxy, const gchar* function_name, GVariant* params, gint timeout_msec) noexcept { struct CallbackData { GVariant* v {}; Package* pkg; ~CallbackData() { g_clear_pointer(&v, g_variant_unref); } }; auto data = new CallbackData; auto on_async_ready = [](GObject* o, GAsyncResult* res, gpointer gdata) { auto data = static_cast(gdata); GError* error {}; GVariant* v {}; finish_func(reinterpret_cast(o), &v, res, &error); if (error == nullptr) { auto item = create_pay_item_from_variant(v); if (item) { const auto sku = item->sku(); const auto status = item->status(); uint64_t refund_timeout{0}; auto rv = g_variant_lookup_value(v, "refundable_until", G_VARIANT_TYPE_UINT64); if (rv != nullptr) { refund_timeout = g_variant_get_uint64(rv); g_variant_unref(rv); } data->pkg->statusChanged(sku, status, refund_timeout); } } else if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { std::cerr << "Error calling method: " << error->message << std::endl; auto param = g_variant_get_child_value(data->v, 0); auto sku = g_variant_get_string(param, NULL); data->pkg->statusChanged(sku, PAY_PACKAGE_ITEM_STATUS_UNKNOWN, 0); } g_clear_error(&error); g_clear_pointer(&v, g_variant_unref); delete data; }; data->v = g_variant_ref(params); data->pkg = this; thread.executeOnThread([this, bus_proxy, function_name, params, data, timeout_msec, &on_async_ready]() { g_dbus_proxy_call(G_DBUS_PROXY(bus_proxy.get()), function_name, params, G_DBUS_CALL_FLAGS_NONE, timeout_msec, thread.getCancellable().get(), // GCancellable on_async_ready, data); }); return true; } /*** **** ***/ bool Package::startVerification (const std::string& sku) noexcept { g_debug("%s %s", G_STRFUNC, sku.c_str()); auto ok = startStoreAction ( storeProxy, "GetItem", g_variant_new("(s)", sku.c_str()), -1); g_debug("%s returning %d", G_STRFUNC, int(ok)); return ok; } bool Package::startPurchase (const std::string& sku) noexcept { g_debug("%s %s", G_STRFUNC, sku.c_str()); statusChanged(sku, PAY_PACKAGE_ITEM_STATUS_PURCHASING, 0); auto ok = startStoreAction ( storeProxy, "PurchaseItem", g_variant_new("(s)", sku.c_str()), 300 * G_USEC_PER_SEC); g_debug("%s returning %d", G_STRFUNC, int(ok)); return ok; } bool Package::startRefund (const std::string& sku) noexcept { g_debug("%s %s", G_STRFUNC, sku.c_str()); auto ok = startStoreAction ( storeProxy, "RefundItem", g_variant_new("(s)", sku.c_str()), -1); g_debug("%s returning %d", G_STRFUNC, int(ok)); return ok; } bool Package::startAcknowledge (const std::string& sku) noexcept { g_debug("%s %s", G_STRFUNC, sku.c_str()); auto ok = startStoreAction ( storeProxy, "AcknowledgeItem", g_variant_new("(s)", sku.c_str()), -1); g_debug("%s returning %d", G_STRFUNC, int(ok)); return ok; } } // namespace Internal } // namespace Pay ./libpay/pay-item.h0000644000015600001650000000350612675036254014271 0ustar jenkinsjenkins/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef LIBPAY_ITEM_H #define LIBPAY_ITEM_H 1 #include #include /* uint64_t */ #include /* time_t */ #pragma GCC visibility push(default) #ifdef __cplusplus extern "C" { #endif /*** **** PayItem Accessors ***/ void pay_item_ref (PayItem*); void pay_item_unref (PayItem*); const char* pay_item_get_description (const PayItem* item); const char* pay_item_get_price (const PayItem* item); const char* pay_item_get_sku (const PayItem* item); PayPackageItemStatus pay_item_get_status (const PayItem* item); time_t pay_item_get_completed_timestamp (const PayItem* item); time_t pay_item_get_acknowledged_timestamp (const PayItem* item); const char* pay_item_get_title (const PayItem* item); PayItemType pay_item_get_type (const PayItem* item); uint64_t pay_item_get_purchase_id (const PayItem* item); #ifdef __cplusplus } #endif #pragma GCC visibility pop #endif /* LIBPAY_ITEM_H */ ./libpay/pay-package.h0000644000015600001650000001717412675036254014734 0ustar jenkinsjenkins/* * Copyright © 2014-2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authored by: Ted Gould */ #ifndef PAY_PACKAGE_H #define PAY_PACKAGE_H 1 #include #pragma GCC visibility push(default) #ifdef __cplusplus extern "C" { #endif /** * pay_package_new: * @package_name: name of package that the items are related to * * Allocates a package object to get information on the items * that are related to that package. * * Return value: (transfer full): Object to interact with items * for the package. */ PayPackage* pay_package_new (const char* package_name); /** * pay_package_delete: * @package: package object to free * * Frees the resources associated with the package object, should be * done when the application is finished with them. */ void pay_package_delete (PayPackage* package); /** * pay_package_item_status: * @package: Package the item is related to * @sku: short string that uniquely identifies the item to use * * Checks the status of an individual item. * * Return value: The status of the item on the local pay service */ PayPackageItemStatus pay_package_item_status (PayPackage* package, const char* sku); /** * pay_package_item_is_refundable: * @package: Package the item is related to * @sku: short string that uniquely identifies the item to use * * Checks whether it is refundable. Check with the status * and makes sure it is REFUNDABLE. * * Return value: Non-zero if the item is refundable */ int pay_package_item_is_refundable (PayPackage* package, const char* sku); /** * pay_package_refund_status: * @package: Package the item is related to * @sku: short string that uniquely identifies the item to use * * Checks the refund status of an individual item. * * Return value: The refund status of the item */ PayPackageRefundStatus pay_package_refund_status (PayPackage* package, const char* sku); /** * pay_package_item_observer_install: * @package: Package to watch items on * @observer: Function to call if items change state * @user_data: Data to pass to @observer * * Registers a function to be called if an item changes state. This * can be used to know when an item is being verified and completes * the step or if it is purchased. All state changes are reported. * * Return value: zero when fails to install */ int pay_package_item_observer_install (PayPackage* package, PayPackageItemObserver observer, void* user_data); /** * pay_package_item_observer_uninstall: * @package: Package to remove watch from * @observer: Function to call if items change state * @user_data: Data to pass to @observer * * Stops an observer from getting called. * * Return value: zero when fails to uninstall */ int pay_package_item_observer_uninstall (PayPackage* package, PayPackageItemObserver observer, void* user_data); /** * pay_package_refund_observer_install: * @package: Package to watch items on * @observer: Function to call if items changes refund staus * @user_data: Data to pass to @observer * * Registers a function to call if the items refund status * changes. This can be used to know when it is no longer * refundable or when it is about to become unrefundable. * * Return value: zero when fails to install */ int pay_package_refund_observer_install (PayPackage* package, PayPackageRefundObserver observer, void* user_data); /** * pay_package_refund_observer_uninstall: * @package: Package to remove watch from * @observer: Function to call if items change state * @user_data: Data to pass to @observer * * Stops a refund observer from getting called. * * Return value: zero when fails to uninstall */ int pay_package_refund_observer_uninstall (PayPackage* package, PayPackageRefundObserver observer, void* user_data); /** * pay_package_item_start_verification: * @package: package to verify item for * @sku: short string that uniquely identifies the item to use * * Asks the pay service to ask the server to verify * the status of an item. It will go on the network and * request the status, and update the state of the item * appropriately. Most users of this API will want to set * up an observer to see the state changes. * * Return value: zero when unable to make request to pay service */ int pay_package_item_start_verification (PayPackage* package, const char* sku); /** * pay_package_item_start_purchase: * @package: package to purchase item for * @sku: short string that uniquely identifies the item to use * * Requests that the pay-service start the process of purchasing * the item specified by @sku. This requires launching UI elements * that will cover the application requesting the payment. When * the UI determines that the purchase is complete, or the user * terminates the pay action the UI will be dismissed and the status * of the item will be updated. * * Return value: zero when unable to make request to pay service */ int pay_package_item_start_purchase (PayPackage* package, const char* sku); /** * pay_package_item_start_refund: * @package: package the item was purchased for * @sku: short string that uniquely identifies the item to use * * Requests that the pay-service start the process of refunding * the item specified by @sku. * * Return value: zero when unable to make request to pay service */ int pay_package_item_start_refund (PayPackage* package, const char* sku); /** * pay_package_item_start_acknowledge: * @package: package the item was purchased for * @sku: SKU of the in-app purchase to acknowledge * * Requests that the pay-service initiate acknowledgement * of the in-app purchase specified by @sku. * * Return value: zero when unable to make request to pay service */ int pay_package_item_start_acknowledge (PayPackage* package, const char* sku); /** * pay_package_get_purchased_items: * @package: Package whose purchased items are to be retrieved * * When done, the caller should unref each PayItem * with pay_item_unref() and free the array with free(). * * Return value: a NULL-terminated array of PayItems */ PayItem** pay_package_get_purchased_items (PayPackage* package); /** * pay_package_get_item: * @package: Package whose item is to be retrieved * @sku: The item's sku * * If a match is found, then when done the caller should * unref it with pay_item_unref(). * * Return value: a reffed PayItem, or NULL if no match was found */ PayItem* pay_package_get_item (PayPackage* package, const char* sku); #ifdef __cplusplus } #endif #pragma GCC visibility pop #endif /* PAY_PACKAGE_H */ ./libpay/pay.pc.in0000644000015600001650000000040712675036254014112 0ustar jenkinsjenkinslibdir=@CMAKE_INSTALL_FULL_LIBDIR@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ Cflags: -I${includedir}/libpay-@api-version@ Requires: glib-2.0 gio-2.0 Libs: -L${libdir} -lpay Name: libpay Description: A library for in-application payment Version: @abi-version@ ./libpay/pay-package.cpp0000644000015600001650000001336612675036254015266 0ustar jenkinsjenkins/* * Copyright © 2014 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authored by: Ted Gould */ #include #include PayPackage* pay_package_new (const char* package_name) { g_return_val_if_fail(package_name != nullptr, nullptr); g_return_val_if_fail(*package_name != '\0', nullptr); try { return new PayPackage(package_name); } catch (std::runtime_error& /*error*/) { return nullptr; } } void pay_package_delete (PayPackage* package) { g_return_if_fail(package != nullptr); delete package; } PayPackageItemStatus pay_package_item_status (PayPackage* package, const char* sku) { g_return_val_if_fail(package != nullptr, PAY_PACKAGE_ITEM_STATUS_UNKNOWN); g_return_val_if_fail(sku != nullptr, PAY_PACKAGE_ITEM_STATUS_UNKNOWN); return package->itemStatus(sku); } int pay_package_item_is_refundable (PayPackage* package, const char* sku) { const auto status = pay_package_refund_status(package, sku); return (status == PAY_PACKAGE_REFUND_STATUS_REFUNDABLE || status == PAY_PACKAGE_REFUND_STATUS_WINDOW_EXPIRING); } PayPackageRefundStatus pay_package_refund_status (PayPackage* package, const char* sku) { g_return_val_if_fail(package != nullptr, PAY_PACKAGE_REFUND_STATUS_NOT_REFUNDABLE); g_return_val_if_fail(sku != nullptr, PAY_PACKAGE_REFUND_STATUS_NOT_REFUNDABLE); return package->refundStatus(sku); } int pay_package_item_observer_install (PayPackage* package, PayPackageItemObserver observer, void* user_data) { g_return_val_if_fail(package != nullptr, 0); g_return_val_if_fail(observer != nullptr, 0); return package->addStatusObserver(observer, user_data) ? 1 : 0; } int pay_package_item_observer_uninstall (PayPackage* package, PayPackageItemObserver observer, void* user_data) { g_return_val_if_fail(package != nullptr, 0); g_return_val_if_fail(observer != nullptr, 0); return package->removeStatusObserver(observer, user_data) ? 1 : 0; } int pay_package_refund_observer_install (PayPackage* package, PayPackageRefundObserver observer, void* user_data) { g_return_val_if_fail(package != nullptr, 0); g_return_val_if_fail(observer != nullptr, 0); return package->addRefundObserver(observer, user_data) ? 1 : 0; } int pay_package_refund_observer_uninstall (PayPackage* package, PayPackageRefundObserver observer, void* user_data) { g_return_val_if_fail(package != nullptr, 0); g_return_val_if_fail(observer != nullptr, 0); return package->removeRefundObserver(observer, user_data) ? 1 : 0; } int pay_package_item_start_verification (PayPackage* package, const char* sku) { g_return_val_if_fail(package != nullptr, 0); g_return_val_if_fail(sku != nullptr, 0); g_return_val_if_fail(*sku != '\0', 0); return package->startVerification(sku); } int pay_package_item_start_purchase (PayPackage* package, const char* sku) { g_return_val_if_fail(package != nullptr, 0); g_return_val_if_fail(sku != nullptr, 0); g_return_val_if_fail(*sku != '\0', 0); return package->startPurchase(sku); } int pay_package_item_start_refund (PayPackage* package, const char* sku) { g_return_val_if_fail(package != nullptr, 0); g_return_val_if_fail(sku != nullptr, 0); g_return_val_if_fail(*sku != '\0', 0); return package->startRefund(sku); } int pay_package_item_start_acknowledge (PayPackage* package, const char* sku) { g_return_val_if_fail (package != nullptr, 0); g_return_val_if_fail (sku != nullptr, 0); g_return_val_if_fail (*sku != '\0', 0); return package->startAcknowledge(sku) ? 1 : 0; } /** *** PayItem accessors **/ /*** **** Item Enumerators ***/ PayItem** pay_package_get_purchased_items (PayPackage* package) { g_return_val_if_fail (package != nullptr, static_cast(calloc(1,sizeof(PayItem*)))); auto items = package->getPurchasedItems(); const auto n = items.size(); auto ret = static_cast(calloc(n+1, sizeof(PayItem*))); // +1 to null terminate the array for (size_t i=0; iref(); // caller must unref ret[i] = item.get(); } return ret; } PayItem* pay_package_get_item (PayPackage* package, const char* sku) { g_return_val_if_fail (package != nullptr, nullptr); g_return_val_if_fail (sku != nullptr, nullptr); g_return_val_if_fail (*sku != '\0', nullptr); PayItem* ret {}; auto item = package->getItem(sku); if (item) { item->ref(); // caller must unref ret = item.get(); } return ret; } ./libpay/pay-types.h0000644000015600001650000000605112675036254014475 0ustar jenkinsjenkins/* * Copyright © 2014-2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authored by: Ted Gould */ #ifndef PAY_TYPES_H #define PAY_TYPES_H 1 #pragma GCC visibility push(default) #ifdef __cplusplus extern "C" { #endif typedef struct PayItem_ PayItem; typedef struct PayPackage_ PayPackage; /** * PayPackageItemStatus: * * The states that an purchased item can be in. */ typedef enum { /*< prefix=PAY_PACKAGE_ITEM_STATUS */ PAY_PACKAGE_ITEM_STATUS_UNKNOWN, /*< nick=unknown */ PAY_PACKAGE_ITEM_STATUS_VERIFYING, /*< nick=verifying */ PAY_PACKAGE_ITEM_STATUS_PURCHASED, /*< nick=purchased */ PAY_PACKAGE_ITEM_STATUS_PURCHASING, /*< nick=purchasing */ PAY_PACKAGE_ITEM_STATUS_NOT_PURCHASED, /*< nick=not-purchased */ PAY_PACKAGE_ITEM_STATUS_REFUNDING, /*< nick=refunding */ PAY_PACKAGE_ITEM_STATUS_APPROVED /*< nick=approved */ } PayPackageItemStatus; /** * PayItemType: * * The type of the product. */ typedef enum { /*< prefix=PAY_PRODUCT_TYPE */ PAY_ITEM_TYPE_UNKNOWN = -1, /*< nick=unknown */ PAY_ITEM_TYPE_CONSUMABLE = 0, /*< nick=consumable */ PAY_ITEM_TYPE_UNLOCKABLE = 1 /*< nick=unlockable */ } PayItemType; /** * PayPackageRefundStatus: * * The states of refundable an item an be in. */ typedef enum { /*< prefix=PAY_PACKAGE_REFUND_STATUS */ PAY_PACKAGE_REFUND_STATUS_REFUNDABLE, /*< nick=refundable */ PAY_PACKAGE_REFUND_STATUS_NOT_REFUNDABLE, /*< nick=not-refundable */ PAY_PACKAGE_REFUND_STATUS_NOT_PURCHASED, /*< nick=not-purchased */ PAY_PACKAGE_REFUND_STATUS_WINDOW_EXPIRING /*< nick=window-expiring */ } PayPackageRefundStatus; /** * PayPackageItemObserver: * * Function to call when an item changes state for the * package it's registered for. */ typedef void (*PayPackageItemObserver) (PayPackage* package, const char* sku, PayPackageItemStatus status, void* user_data); /** * PayPackageItemRefundableObserver: * * Function to call when an item changes whether it is * refundable or not. */ typedef void (*PayPackageRefundObserver) (PayPackage* package, const char* sku, PayPackageRefundStatus status, void* user_data); #ifdef __cplusplus } #endif #pragma GCC visibility pop #endif /* PAY_TYPES_H */ ./libpay/CMakeLists.txt0000644000015600001650000000432612675036254015134 0ustar jenkinsjenkins set(libpay-headers pay-package.h pay-item.h pay-types.h) set(libpay-sources pay-package.cpp pay-item.cpp) set(api-version 2) set(abi-version 2) add_subdirectory(internal) ###################### # DBus Proxies ###################### set(libpay-generated) add_gdbus_codegen_with_namespace(libpay-generated proxy-store com.canonical. proxy ${CMAKE_SOURCE_DIR}/data/com.canonical.pay.store.xml) ###################### # Lib Building ###################### set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${GCOV_FLAGS} -fvisibility=hidden") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g ${GCOV_FLAGS} -fvisibility=hidden") include_directories(SYSTEM ${CMAKE_BINARY_DIR}) include_directories(${CMAKE_SOURCE_DIR}) add_library(libpay SHARED ${libpay-sources} ${libpay-generated}) set_property(SOURCE ${libpay-sources} APPEND_STRING PROPERTY COMPILE_FLAGS " ${CXX_WARNING_ARGS}") set_target_properties(libpay PROPERTIES VERSION ${api-version}.0.0 SOVESRION ${abi-version} OUTPUT_NAME "pay" ) target_link_libraries(libpay ${SERVICE_DEPS_LIBRARIES} common-lib) set_target_properties(libpay PROPERTIES LINK_FLAGS "${ldflags} -Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/libpay.map ") set_target_properties(libpay PROPERTIES LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/libpay.map") ###################### # Package Config ###################### configure_file("pay.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/pay-${api-version}.pc" @ONLY) ###################### # Style Checking ###################### macro(list_prefix _outvar _listvar _prefix) set(${_outvar}) foreach(_item IN LISTS ${_listvar}) list(APPEND ${_outvar} ${_prefix}${_item}) endforeach() endmacro(list_prefix) list_prefix(libpay-headers-full libpay-headers "${CMAKE_CURRENT_SOURCE_DIR}/") list_prefix(libpay-sources-full libpay-sources "${CMAKE_CURRENT_SOURCE_DIR}/") ###################### # Installation ###################### install( TARGETS libpay LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) install( FILES ${libpay-headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libpay-${api-version}/libpay" ) install ( FILES "${CMAKE_CURRENT_BINARY_DIR}/pay-${api-version}.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" ) ./data/0000755000015600001650000000000012675036254012020 5ustar jenkinsjenkins./data/com.canonical.pay.payui.xml0000644000015600001650000000035012675036254017162 0ustar jenkinsjenkins ./data/com.canonical.pay.package.xml0000644000015600001650000000156312675036254017435 0ustar jenkinsjenkins ./data/CMakeLists.txt0000644000015600001650000000102412675036254014555 0ustar jenkinsjenkins ############################## # Upstart Jobs ############################## install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/pay-service-trust-stored.conf DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/upstart/sessions/) # New dbus service file configure_file(${CMAKE_CURRENT_SOURCE_DIR}/com.canonical.payments.service.in ${CMAKE_CURRENT_BINARY_DIR}/com.canonical.payments.service @ONLY ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/com.canonical.payments.service DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/dbus-1/services/ ) ./data/com.canonical.payments.service.in0000644000015600001650000000014212675036254020347 0ustar jenkinsjenkins[D-BUS Service] Name=com.canonical.payments Exec=@CMAKE_INSTALL_FULL_PKGLIBEXECDIR@/pay-service-2 ./data/com.canonical.pay.store.xml0000644000015600001650000000573312675036254017201 0ustar jenkinsjenkins ./data/pay-service-trust-stored.conf0000644000015600001650000000132612675036254017575 0ustar jenkinsjenkinsdescription "Pay Service Trust Store Daemon" # Try to start this once pay-service finishes starting start on (started dbus and xsession SESSION=ubuntu-touch) or \ dbus BUS=session SIGNAL=NameOwnerChanged INTERFACE=org.freedesktop.DBus OBJPATH=/org/freedesktop/DBus ARG0=com.canonical.payments ARG2!="" stop on desktop-end respawn script # XXX LP #1369692 sleep 2 exec /usr/bin/trust-stored-skeleton \ --remote-agent SessionServiceDBusRemoteAgent \ --bus=session \ --local-agent MirAgent \ --trusted-mir-socket=/var/run/user/$(id -u)/mir_socket_trusted \ --for-service InAppPurchases \ --with-text-domain pay-service \ --store-bus session end script ./MERGE-REVIEW0000644000015600001650000000075012675036254012672 0ustar jenkinsjenkins This documents the expections that the project has on what both submitters and reviewers should ensure that they've done for a merge into the project. == Submitter Responsibilities == * Ensure the project compiles and the test suite executes without error * Ensure that non-obvious code has comments explaining it == Reviewer Responsibilities == * Did the Jenkins build compile? Pass? Run unit tests successfully? * Are there appropriate tests to cover any new functionality? ./COPYING.LGPL0000644000015600001650000001674312675036254012712 0ustar jenkinsjenkins GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ./common/0000755000015600001650000000000012675036254012377 5ustar jenkinsjenkins./common/bus-utils.h0000644000015600001650000000167712675036254014512 0ustar jenkinsjenkins/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: * Ted Gould */ #ifndef PAY_BUS_UTILS_H #define PAY_BUS_UTILS_H #include class BusUtils { public: static std::string encodePathElement(const std::string&); static std::string decodePathElement(const std::string&); }; #endif // PAY_BUS_UTILS_H ./common/bus-utils.cpp0000644000015600001650000000406612675036254015040 0ustar jenkinsjenkins/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: * Ted Gould */ #include "bus-utils.h" std::string BusUtils::encodePathElement(const std::string& input) { std::string output = ""; bool first = true; for (unsigned char c : input) { std::string retval; if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9' && !first)) { retval = std::string((char*)&c, 1); } else { char buffer[5] = {0}; std::snprintf(buffer, 4, "_%2x", c); retval = std::string(buffer); } output += retval; first = false; } return output; } std::string BusUtils::decodePathElement(const std::string& input) { std::string output; try { for (size_t i = 0; i < input.size(); i++) { if (input[i] == '_') { char buffer[3] = {0}; buffer[0] = input[i + 1]; buffer[1] = input[i + 2]; unsigned char value = std::stoi(buffer, nullptr, 16); output += value; i += 2; } else { output += input[i]; } } } catch (...) { /* We can get out of bounds on the parsing if the string is invalid. Just return what we have. */ } return output; } ./common/glib-thread.h0000644000015600001650000000514512675036254014737 0ustar jenkinsjenkins/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: * Ted Gould */ #include #include #include namespace GLib { class ContextThread { std::thread _thread; std::shared_ptr _context; std::shared_ptr _loop; std::shared_ptr _cancel; public: ContextThread (std::function beforeLoop = [] {}, std::function afterLoop = [] {}); ~ContextThread (); void quit (); bool isCancelled (); std::shared_ptr getCancellable (); void executeOnThread (std::function work); template auto executeOnThread (std::function work) -> T { if (std::this_thread::get_id() == _thread.get_id()) { /* Don't block if we're on the same thread */ return work(); } std::promise promise; std::function magicFunc = [&promise, &work] () { promise.set_value(work()); }; executeOnThread(magicFunc); auto future = promise.get_future(); future.wait(); return future.get(); } void timeout (const std::chrono::milliseconds& length, std::function work); template void timeout (const std::chrono::duration& length, std::function work) { return timeout(std::chrono::duration_cast(length), work); } void timeoutSeconds (const std::chrono::seconds& length, std::function work); template void timeoutSeconds (const std::chrono::duration& length, std::function work) { return timeoutSeconds(std::chrono::duration_cast(length), work); } private: void simpleSource (std::function srcBuilder, std::function work); }; } ./common/glib-thread.cpp0000644000015600001650000001223112675036254015264 0ustar jenkinsjenkins/* * Copyright © 2015 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: * Ted Gould */ #include "glib-thread.h" namespace GLib { ContextThread::ContextThread (std::function beforeLoop, std::function afterLoop) { _cancel = std::shared_ptr(g_cancellable_new(), [](GCancellable * cancel) { if (cancel != nullptr) { g_cancellable_cancel(cancel); g_object_unref(cancel); } }); std::promise, std::shared_ptr>> context_promise; /* NOTE: We copy afterLoop but reference beforeLoop. We're blocking so we know that beforeLoop will stay valid long enough, but we can't say the same for afterLoop */ _thread = std::thread([&context_promise, &beforeLoop, afterLoop, this]() { /* Build up the context and loop for the async events and a place for GDBus to send its events back to */ auto context = std::shared_ptr(g_main_context_new(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); }); auto loop = std::shared_ptr(g_main_loop_new(context.get(), FALSE), [](GMainLoop * loop) { g_clear_pointer(&loop, g_main_loop_unref); }); g_main_context_push_thread_default(context.get()); beforeLoop(); /* Free's the constructor to continue */ auto pair = std::pair, std::shared_ptr>(context, loop); context_promise.set_value(pair); if (!g_cancellable_is_cancelled(_cancel.get())) { g_main_loop_run(loop.get()); } afterLoop(); }); /* We need to have the context and the mainloop ready before other functions on this object can work properly. So we wait for them and set them on this thread. */ auto context_future = context_promise.get_future(); context_future.wait(); auto context_value = context_future.get(); _context = context_value.first; _loop = context_value.second; if (!_context || !_loop) { throw std::runtime_error("Unable to create GLib Thread"); } } ContextThread::~ContextThread () { quit(); } void ContextThread::quit () { g_cancellable_cancel(_cancel.get()); /* Force the cancellation on ongoing tasks */ if (_loop) { g_main_loop_quit(_loop.get()); /* Quit the loop */ } /* Joining here because we want to ensure that the final afterLoop() function is run before returning */ if (std::this_thread::get_id() != _thread.get_id()) { if (_thread.joinable()) { _thread.join(); } } } bool ContextThread::isCancelled () { return g_cancellable_is_cancelled(_cancel.get()) == TRUE; } std::shared_ptr ContextThread::getCancellable () { return _cancel; } void ContextThread::simpleSource (std::function srcBuilder, std::function work) { if (isCancelled()) { throw std::runtime_error("Trying to execute work on a GLib thread that is shutting down."); } /* Copy the work so that we can reuse it */ /* Lifecycle is handled with the source pointer when we attach it to the context. */ auto heapWork = new std::function(work); auto source = std::shared_ptr(srcBuilder(), [](GSource * src) { g_clear_pointer(&src, g_source_unref); } ); g_source_set_callback(source.get(), [](gpointer data) { auto heapWork = static_cast *>(data); (*heapWork)(); return G_SOURCE_REMOVE; }, heapWork, [](gpointer data) { auto heapWork = static_cast *>(data); delete heapWork; }); g_source_attach(source.get(), _context.get()); } void ContextThread::executeOnThread (std::function work) { simpleSource(g_idle_source_new, work); } void ContextThread::timeout (const std::chrono::milliseconds& length, std::function work) { simpleSource([length]() { return g_timeout_source_new(length.count()); }, work); } void ContextThread::timeoutSeconds (const std::chrono::seconds& length, std::function work) { simpleSource([length]() { return g_timeout_source_new_seconds(length.count()); }, work); } } // ns GLib ./common/CMakeLists.txt0000644000015600001650000000100012675036254015126 0ustar jenkinsjenkins set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_WARNING_ARGS} -std=c++11 -g -fPIC ${GCOV_FLAGS}") set(COMMON_SOURCES glib-thread.cpp glib-thread.h bus-utils.cpp bus-utils.h) add_library(common-lib STATIC ${COMMON_SOURCES}) macro(list_prefix _outvar _listvar _prefix) set(${_outvar}) foreach(_item IN LISTS ${_listvar}) list(APPEND ${_outvar} ${_prefix}${_item}) endforeach() endmacro(list_prefix) list_prefix(common-full-src COMMON_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/") ./pay-test-app/0000755000015600001650000000000012675036254013433 5ustar jenkinsjenkins./pay-test-app/qtquick2applicationviewer.cpp0000644000015600001650000000465712675036254021364 0ustar jenkinsjenkins// checksum 0x4f6f version 0x90005 /* This file was generated by the Qt Quick 2 Application wizard of Qt Creator. QtQuick2ApplicationViewer is a convenience class containing mobile device specific code such as screen orientation handling. Also QML paths and debugging are handled here. It is recommended not to modify this file, since newer versions of Qt Creator may offer an updated version of it. */ #include "qtquick2applicationviewer.h" #include #include #include class QtQuick2ApplicationViewerPrivate { QString mainQmlFile; friend class QtQuick2ApplicationViewer; static QString adjustPath(const QString &path); }; QString QtQuick2ApplicationViewerPrivate::adjustPath(const QString &path) { #if defined(Q_OS_MAC) if (!QDir::isAbsolutePath(path)) return QString::fromLatin1("%1/../Resources/%2") .arg(QCoreApplication::applicationDirPath(), path); #elif defined(Q_OS_BLACKBERRY) if (!QDir::isAbsolutePath(path)) return QString::fromLatin1("app/native/%1").arg(path); #elif !defined(Q_OS_ANDROID) QString pathInInstallDir = QString::fromLatin1("%1/../%2").arg(QCoreApplication::applicationDirPath(), path); if (QFileInfo(pathInInstallDir).exists()) return pathInInstallDir; pathInInstallDir = QString::fromLatin1("%1/%2").arg(QCoreApplication::applicationDirPath(), path); if (QFileInfo(pathInInstallDir).exists()) return pathInInstallDir; #endif return path; } QtQuick2ApplicationViewer::QtQuick2ApplicationViewer(QWindow *parent) : QQuickView(parent) , d(new QtQuick2ApplicationViewerPrivate()) { connect(engine(), SIGNAL(quit()), SLOT(close())); setResizeMode(QQuickView::SizeRootObjectToView); } QtQuick2ApplicationViewer::~QtQuick2ApplicationViewer() { delete d; } void QtQuick2ApplicationViewer::setMainQmlFile(const QString &file) { d->mainQmlFile = QtQuick2ApplicationViewerPrivate::adjustPath(file); #ifdef Q_OS_ANDROID setSource(QUrl(QLatin1String("assets:/")+d->mainQmlFile)); #else setSource(QUrl::fromLocalFile(d->mainQmlFile)); #endif } void QtQuick2ApplicationViewer::addImportPath(const QString &path) { engine()->addImportPath(QtQuick2ApplicationViewerPrivate::adjustPath(path)); } void QtQuick2ApplicationViewer::showExpanded() { #if defined(Q_WS_SIMULATOR) || defined(Q_OS_QNX) showFullScreen(); #else show(); #endif } ./pay-test-app/pay-test-app.qml0000644000015600001650000000427112675036254016476 0ustar jenkinsjenkinsimport QtQuick 2.0 import Ubuntu.Components 1.1 import Ubuntu.Components.ListItems 1.0 as ListItem import Pay 1.0 MainView { id: mainview automaticOrientation: true Package { id: pkg pkgname: "pay-test-app" onItemStatusChanged: { statusBox.text = pkg.itemStatus(idBox.text) } } Page { title: i18n.tr("Pay Test") Column { width: parent.width height: parent.height ListItem.SingleControl { control: TextField { id: idBox x: units.gu(1) y: units.gu(1) width: parent.width - units.gu(2) text: i18n.tr("Item ID") onTextChanged: { statusBox.text = pkg.itemStatus(idBox.text) } } } ListItem.Divider { } ListItem.SingleControl { control: TextField { id: statusBox x: units.gu(1) y: units.gu(1) width: parent.width - units.gu(2) text: i18n.tr("Unknown") readOnly: true hasClearButton: false } } ListItem.Divider { } ListItem.SingleControl { control: Button { x: units.gu(1) y: units.gu(1) width: parent.width - units.gu(2) text: i18n.tr("Verify") onClicked: { pkg.verifyItem(idBox.text) return false } } } ListItem.SingleControl { control: Button { x: units.gu(1) y: units.gu(1) width: parent.width - units.gu(2) text: i18n.tr("Purchase") onClicked: { pkg.purchaseItem(idBox.text) return false } } } } } } ./pay-test-app/pay-test-app.svg0000644000015600001650000001214512675036254016503 0ustar jenkinsjenkins image/svg+xml £ ¥ $ ./pay-test-app/pay-test-app.desktop.in0000644000015600001650000000034112675036254017755 0ustar jenkinsjenkins[Desktop Entry] Name=Pay Test App Comment=Used for testing pay service, nothing else Exec=@pkglibexecdir@/pay-test-app Path=@pkglibexecdir@ Type=Application Icon=pay-test-app X-Ubuntu-Single-Instance=true X-Ubuntu-Touch=true ./pay-test-app/qtquick2applicationviewer.h0000644000015600001650000000162412675036254021020 0ustar jenkinsjenkins// checksum 0xfde6 version 0x90005 /* This file was generated by the Qt Quick 2 Application wizard of Qt Creator. QtQuick2ApplicationViewer is a convenience class containing mobile device specific code such as screen orientation handling. Also QML paths and debugging are handled here. It is recommended not to modify this file, since newer versions of Qt Creator may offer an updated version of it. */ #ifndef QTQUICK2APPLICATIONVIEWER_H #define QTQUICK2APPLICATIONVIEWER_H #include class QtQuick2ApplicationViewer : public QQuickView { Q_OBJECT public: explicit QtQuick2ApplicationViewer(QWindow *parent = 0); virtual ~QtQuick2ApplicationViewer(); void setMainQmlFile(const QString &file); void addImportPath(const QString &path); void showExpanded(); private: class QtQuick2ApplicationViewerPrivate *d; }; #endif // QTQUICK2APPLICATIONVIEWER_H ./pay-test-app/CMakeLists.txt0000644000015600001650000000173212675036254016176 0ustar jenkinsjenkins set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g -Wall -Werror") find_package(Qt5Core) find_package(Qt5Qml) find_package(Qt5Quick) find_package(PkgConfig) include_directories (${CMAKE_SOURCE_DIR}) set(pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}") add_executable(pay-test-app pay-test-app.cpp package.h package.cpp qtquick2applicationviewer.cpp qtquick2applicationviewer.h) qt5_use_modules(pay-test-app Core Qml Quick) target_link_libraries(pay-test-app libpay) install(TARGETS pay-test-app RUNTIME DESTINATION ${pkglibexecdir}) install(FILES pay-test-app.qml DESTINATION ${pkglibexecdir}) install(FILES pay-test-app.svg DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps/") configure_file("pay-test-app.desktop.in" "${CMAKE_CURRENT_BINARY_DIR}/pay-test-app.desktop" @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pay-test-app.desktop" DESTINATION "${CMAKE_INSTALL_DATADIR}/applications/") ./pay-test-app/package.h0000644000015600001650000000147312675036254015204 0ustar jenkinsjenkins #ifndef PACKAGE_H #define PACKAGE_H 1 #include #include #include class Package : public QObject { Q_OBJECT Q_PROPERTY(QString pkgname READ pkgname WRITE setPkgname NOTIFY pkgnameChanged) public: QString pkgname() const; void setPkgname(const QString &pkgname); Package (QObject * parent = nullptr); Q_INVOKABLE QString itemStatus (const QString & item); Q_INVOKABLE bool verifyItem (const QString & item); Q_INVOKABLE bool purchaseItem (const QString & item); signals: void pkgnameChanged(); void itemStatusChanged(const QString &item, const QString& status); private: std::shared_ptr pkg; QString _pkgname; public: void emitItemChanged(const QString& item, const QString& status); }; #endif /* PACKAGE_H */ ./pay-test-app/pay-test-app.cpp0000644000015600001650000000064412675036254016467 0ustar jenkinsjenkins #include #include #include #include "qtquick2applicationviewer.h" #include "package.h" int main (int argc, char * argv[]) { QGuiApplication app(argc, argv); qmlRegisterType("Pay", 1, 0, "Package"); QtQuick2ApplicationViewer view; view.setMainQmlFile(QStringLiteral("pay-test-app.qml")); view.showExpanded(); return app.exec(); } ./pay-test-app/package.cpp0000644000015600001650000000524012675036254015533 0ustar jenkinsjenkins#include "package.h" #include static QString enum2str (PayPackageItemStatus val); Package::Package (QObject * parent): QObject(parent) { return; } QString Package::pkgname (void) const { return _pkgname; } static void itemObserver (PayPackage * pkg, const char * itemid, PayPackageItemStatus status, void * user_data) { Package * notthis = reinterpret_cast(user_data); QString item(itemid); notthis->emitItemChanged(item, enum2str(status)); } void Package::emitItemChanged (const QString &item, const QString &status) { emit itemStatusChanged(item, status); } void Package::setPkgname (const QString &pkgname) { if (pkgname == _pkgname) { return; } _pkgname = pkgname; pkg = std::shared_ptr([this]() -> PayPackage * { PayPackage * pkg = pay_package_new(_pkgname.toUtf8().data()); if (pkg != nullptr) { pay_package_item_observer_install(pkg, itemObserver, this); } return pkg; }(), [this](PayPackage * pkg) { if (pkg != nullptr) { pay_package_item_observer_uninstall(pkg, itemObserver, this); pay_package_delete(pkg); }}); qDebug() << "Pay Package built for:" << _pkgname.toUtf8().data(); pkgnameChanged(); } static QString enum2str (PayPackageItemStatus val) { switch (val) { case PAY_PACKAGE_ITEM_STATUS_VERIFYING: return QString("Verifying"); case PAY_PACKAGE_ITEM_STATUS_PURCHASED: return QString("Purchased"); case PAY_PACKAGE_ITEM_STATUS_PURCHASING: return QString("Purchasing"); case PAY_PACKAGE_ITEM_STATUS_REFUNDING: return QString("Refunding"); case PAY_PACKAGE_ITEM_STATUS_NOT_PURCHASED: return QString("Not Purchased"); case PAY_PACKAGE_ITEM_STATUS_APPROVED: return QString("Purchase Approved"); case PAY_PACKAGE_ITEM_STATUS_UNKNOWN: break; } return QString("Unknown"); } QString Package::itemStatus (const QString &item) { QString retval; if (pkg == nullptr) { return retval; } return enum2str(pay_package_item_status(pkg.get(), item.toUtf8().data())); } bool Package::verifyItem (const QString & item) { if (pkg == nullptr) return false; qDebug() << "Verifying item" << item << "for package" << _pkgname; return pay_package_item_start_verification(pkg.get(), item.toUtf8().data()); } bool Package::purchaseItem (const QString & item) { if (pkg == nullptr) return false; qDebug() << "Purchasing item" << item << "for package" << _pkgname; return pay_package_item_start_purchase(pkg.get(), item.toUtf8().data()); } #include "moc_package.cpp" ./CMakeLists.txt0000644000015600001650000000555412675036254013660 0ustar jenkinsjenkinsproject (pay-service C CXX) cmake_minimum_required (VERSION 2.8.9) list (APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) set (PACKAGE_VERSION "14.04.0") set (PACKAGE ${CMAKE_PROJECT_NAME}) option (enable_tests "Build the package's automatic tests." ON) if (${enable_tests}) enable_testing () endif () ## ## GNU standard installation directories ## include (GNUInstallDirs) if (EXISTS "/etc/debian_version") # Workaround for libexecdir on debian set (CMAKE_INSTALL_LIBEXECDIR "${CMAKE_INSTALL_LIBDIR}") set (CMAKE_INSTALL_FULL_LIBEXECDIR "${CMAKE_INSTALL_FULL_LIBDIR}") endif () set (CMAKE_INSTALL_PKGLIBEXECDIR "${CMAKE_INSTALL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") set (CMAKE_INSTALL_FULL_PKGLIBEXECDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") ## ## Check for prerequisites ## set(CMAKE_AUTOMOC ON) find_package (PkgConfig REQUIRED) find_package (Qt5Core REQUIRED) pkg_check_modules (SERVICE_DEPS REQUIRED Qt5Core dbus-cpp dbustest-1 gio-2.0 gio-unix-2.0 properties-cpp trust-store ubuntu-app-launch-2>=0.5 ) include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS}) ## ## Gdbus ## include (GdbusCodegen) ## ## custom targets ## set (ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${PACKAGE_VERSION}) add_custom_target (dist COMMAND bzr export --root=${ARCHIVE_NAME} ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.gz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) add_custom_target (cppcheck COMMAND cppcheck --enable=all -q --error-exitcode=2 --inline-suppr ${CMAKE_SOURCE_DIR}/libpay ${CMAKE_SOURCE_DIR}/tests) ## ## Actual building ## set (CC_WARNING_ARGS " -Wall -Wshadow -Wextra -Wunused -Wformat=2 -Wno-missing-field-initializers") # those GActionEntry structs tickle -Wmissing-field-initializers if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Weverything -Wno-c++98-compat -Wno-documentation -Wno-padded") else() set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wall -Wextra -Wpedantic -Wunused -Wformat=2 ") endif() # service/dbus-interface.cpp gives unavoidable warnings in glib code unless we turn this warning off set (CXX_WARNING_ARGS "${CXX_WARNING_ARGS} -Wno-missing-field-initializers") ## ## Subdirectories ## add_subdirectory(common) add_subdirectory(libpay) add_subdirectory(service-ng) add_subdirectory(data) if (${enable_tests}) add_subdirectory(tests) endif () add_subdirectory(pay-test-app) add_subdirectory(pay-ui) add_subdirectory(po) ## ## Coverage Reports ## include(EnableCoverageReport) set(filter-list) list(APPEND filter-list "/usr/include") list(APPEND filter-list "${CMAKE_SOURCE_DIR}/tests/*") if (NOT ${CMAKE_BINARY_DIR} STREQUAL ${CMAKE_SOURCE_DIR}) list(APPEND filter-list "${CMAKE_BINARY_DIR}/*") endif() ENABLE_COVERAGE_REPORT( TARGETS libpay TESTS libpay-iap-tests libpay-package-tests FILTER ${filter-list} ) ./pay-ui/0000755000015600001650000000000012675036254012313 5ustar jenkinsjenkins./pay-ui/app/0000755000015600001650000000000012675036330013066 5ustar jenkinsjenkins./pay-ui/app/payui.qml0000644000015600001650000003167012675036330014737 0ustar jenkinsjenkins/* * Copyright 2014-2016 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import QtQuick.LocalStorage 2.0 import Ubuntu.Components 1.3 import Ubuntu.Components.Popups 1.3 import Ubuntu.OnlineAccounts 0.1 import Ubuntu.OnlineAccounts.Client 0.1 import payui 0.1 import "ui" /*! states: - add-payment - buy-interaction - checkout - online-accounts - error */ MainView { id: mainView // objectName for functional testing purposes (autopilot-qt5) objectName: "payui" // NOTE: Must match the gettext domain for translations. applicationName: "pay-service" /* This property enables the application to change orientation when the device is rotated. The default is false. */ // automaticOrientation: true width: units.gu(100) height: units.gu(75) property bool loading: false property bool purchasing: false property bool recentLogin: false property bool cancellable: true property string suggestedCurrency: "USD" backgroundColor: "transparent" onStateChanged: { mainView.backgroundColor = "white"; } AccountServiceModel { id: accounts provider: "ubuntuone" } Setup { id: setup applicationId: "pay-service" providerId: "ubuntuone" onFinished: { mainView.recentLogin = true; mainView.showLoading(); purchase.checkCredentials(); } } Purchase { id: purchase onItemDetailsObtained: { suggestedCurrency = currency; checkout.itemIcon = icon; checkout.itemTitle = title; checkout.itemSubtitle = publisher; checkout.price = formatted_price; purchase.getPaymentTypes(currency); } onPaymentTypesObtained: { mainView.recentLogin = mainView.recentCredentials(); checkout.beforeTimeout = mainView.recentLogin; // Check for selected payment, and keep it selected if so. if (checkout.hasSelectedPayment) { for (var i=0; i < payments.length; i++) { if (payments[i].paymentId == checkout.paymentId && payments[i].backendId == checkout.backendId) { payments[i].preferred = true; } else { payments[i].preferred = false; } } } checkout.model = payments; checkout.hasPayments = payments.length != 0; checkout.setSelectedItem(); mainView.state = "checkout"; checkout.visible = true; hideLoading(); } onNoPreferredPaymentMethod: { checkout.hasPreferredPayment = false; var values = mainView.getLastPayment(); var backendid = values[0]; var paymentid = values[1]; if (backendid != "" && paymentid != "") { checkout.hasStoredPayment = true; } } onPasswordValid: { hideLoading(); // Reset password and otp to not keep them in memory. checkout.password = ""; checkout.otp = ""; } onBuyItemFailed: { hideLoading(); purchaseErrorDialog.open = true; } onBuyItemSucceeded: { purchase.quitSuccess(); } onBuyInterationRequired: { webkit.title = i18n.tr("Finish Purchase"); webkit.url = url; mainView.state = "buy-interaction"; pageStack.push(webkit); } onError: { hideLoading(); serverErrorDialog.open = true; } onAuthenticationError: { mainView.recentLogin = false; if (pageStack.currentPage == checkout) { mainView.hideLoading(); checkout.showErrorMessage(i18n.tr("Incorrect Password, please try again.")); } else { mainView.state = "online-accounts"; setup.exec(); } } onCredentialsFound: { mainView.recentLogin = mainView.recentCredentials(); checkout.beforeTimeout = mainView.recentLogin; if (mainView.state == "online-accounts") { purchase.checkItemPurchased(); } else if (!mainView.purchasing && mainView.state != "buy-interaction") { purchase.getItemDetails(); } } onItemNotPurchased: { purchase.getItemDetails(); } onCredentialsNotFound: { mainView.recentLogin = false; if (mainView.state == "online-accounts") { purchase.quitCancel(); } else { mainView.state = "online-accounts"; setup.exec(); } } onLoginError: { mainView.recentLogin = false; mainView.hideLoading(); checkout.showErrorMessage(message); } onTwoFactorAuthRequired: { mainView.hideLoading(); checkout.showTwoFactor(); } onCertificateFound: { checkout.certificate = cert } } function showLoading() { if (!mainView.loading) { mainView.loading = true; PopupUtils.open(loadingDialogContainer); } } function hideLoading() { mainView.purchasing = false; mainView.loading = false; mainView.cancellable = true; } function createDB() { var db = LocalStorage.openDatabaseSync("PayUI", "1.0", "PayUI Credentials Date", 100); db.transaction( function(tx) { // Create the database if it doesn't already exist tx.executeSql('CREATE TABLE IF NOT EXISTS PayUIPayment(backendid TEXT, paymentid TEXT)'); } ) } function recentCredentials() { var valid = false; var date = purchase.getTokenUpdated(); var currentDate = new Date(); var msec = currentDate - date; var mm = Math.floor(msec / 1000 / 60); if (mm < 15) { valid = true; } return valid; } function getLastPayment() { var backendid = ""; var paymentid = ""; var db = LocalStorage.openDatabaseSync("PayUI", "1.0", "PayUI Credentials Date", 100); db.transaction( function(tx) { var rs = tx.executeSql('SELECT * FROM PayUIPayment'); if (rs.rows.length > 0) { backendid = rs.rows.item(0).backendid; paymentid = rs.rows.item(0).paymentid; } } ) return [backendid, paymentid]; } function updatePayment(backendid, paymentid) { var db = LocalStorage.openDatabaseSync("PayUI", "1.0", "PayUI Credentials Date", 100); db.transaction( function(tx) { var rs = tx.executeSql('SELECT * FROM PayUIPayment'); if (rs.rows.length > 0) { tx.executeSql('UPDATE PayUIPayment SET backendid = "' + backendid + '", paymentid = "' + paymentid + '"'); } else { tx.executeSql('INSERT INTO PayUIPayment VALUES(?, ?)', [backendid, paymentid]); } } ) } ErrorDialog { id: purchaseErrorDialog title: i18n.tr("Purchase failed") message: i18n.tr("The purchase couldn't be completed.") onRetry: { mainView.state = "error"; mainView.showLoading(); purchase.getItemDetails(); } onClose: { purchase.quitCancel(); } } ErrorDialog { id: serverErrorDialog title: i18n.tr("Error contacting the server") message: i18n.tr("Do you want to try again?") onRetry: { mainView.state = "error"; mainView.showLoading(); purchase.getItemDetails(); } onClose: { purchase.quitCancel(); } } ErrorDialog { id: creditCardErrorDialog title: i18n.tr("Adding Credit Card failed") message: i18n.tr("Do you want to try again?") onRetry: { mainView.state = "error"; mainView.showLoading(); purchase.getItemDetails(); } onClose: { purchase.quitCancel(); } } Component { id: loadingDialogContainer Dialog { id: loadingDialog title: mainView.purchasing ? i18n.tr("Processing Purchase") : i18n.tr("Loading") text: i18n.tr("Please wait…") ActivityIndicator { running: mainView.loading ? true : false width: parent.width onRunningChanged: { if(!running) { PopupUtils.close(loadingDialog); } } } Button { objectName: "buttonCancelLoading" text: i18n.tr("Cancel") color: UbuntuColors.orange visible: mainView.cancellable onClicked: { PopupUtils.close(loadingDialog); purchase.quitCancel(); } } } } PageStack { id: pageStack objectName: "pageStack" Component.onCompleted: { showLoading(); mainView.createDB(); purchase.checkCredentials(); } onCurrentPageChanged: { if (pageStack.currentPage == checkout) { mainView.state = "checkout"; } } CheckoutPage { id: checkout objectName: "pageCheckout" visible: false account: accounts onCancel: { purchase.quitCancel(); } onBuy: { mainView.recentLogin = mainView.recentCredentials(); checkout.beforeTimeout = mainView.recentLogin; // Pass it on. checkout.hasSelectedPayment = true; checkout.backendId = backendId; checkout.paymentId = paymentId; mainView.purchasing = true; mainView.cancellable = false; showLoading(); if (!checkout.hasPreferredPayment) { mainView.updatePayment(backendId, paymentId); } if (mainView.recentLogin) { purchase.buyItem(email, "", "", suggestedCurrency, paymentId, backendId, mainView.recentLogin); } else { purchase.buyItem(email, password, otp, suggestedCurrency, paymentId, backendId, mainView.recentLogin); } } onAddCreditCard: { webkit.title = i18n.tr("Add Payment"); webkit.url = purchase.getAddPaymentUrl(suggestedCurrency); mainView.state = "add-payment"; pageStack.push(webkit); } } UbuntuPurchaseWebkit { id: webkit visible: false onPurchaseFailed: { pageStack.pop(); hideLoading(); purchaseErrorDialog.open = true; } onPurchaseCancelled: { hideLoading(); if (mainView.state == "add-payment") { mainView.state = "checkout"; pageStack.pop(); } else { purchase.quitCancel(); } } onPurchaseSucceeded: { if (mainView.state == "add-payment") { showLoading(); pageStack.pop(); purchase.getPaymentTypes(suggestedCurrency); } else { purchase.quitSuccess(); } } onLoading: { if (value) { showLoading(); } else { hideLoading(); } } } } Rectangle { id: lockIconPlace width: units.gu(7) height: units.gu(7) anchors { right: parent.right top: parent.top } visible: false } } ./pay-ui/app/tests/0000755000015600001650000000000012675036254014235 5ustar jenkinsjenkins./pay-ui/app/tests/unit/0000755000015600001650000000000012675036254015214 5ustar jenkinsjenkins./pay-ui/app/tests/unit/js/0000755000015600001650000000000012675036254015630 5ustar jenkinsjenkins./pay-ui/app/tests/unit/js/unit_test.js0000644000015600001650000000070712675036254020210 0ustar jenkinsjenkins.pragma library // Find an object with the given name in the children tree of "obj" function findChild(obj,objectName) { var childs = new Array(0); childs.push(obj) while (childs.length > 0) { if (childs[0].objectName == objectName) { return childs[0] } for (var i in childs[0].children) { childs.push(childs[0].children[i]) } childs.splice(0, 1); } return undefined; } ./pay-ui/app/tests/unit/tst_checkoutpage.qml0000644000015600001650000000377012675036254021272 0ustar jenkinsjenkinsimport QtQuick 2.0 import QtTest 1.0 import Ubuntu.Components 0.1 import "../../ui" import "js/unit_test.js" as UT // See more details @ http://qt-project.org/doc/qt-5.0/qtquick/qml-testcase.html // Execute tests with: // qmltestrunner Item { id: root property string title: "My App" property string subtitle: "My App Subtitle" property string price: "$ 1.99" property string ubuntuid: "mail@mail.com" property bool called: false // The objects CheckoutPage { id: checkoutPage itemTitle: root.title itemSubtitle: root.subtitle price: root.price ubuntuID: root.ubuntuid onCancel: { root.called = true; } onBuy: { root.called = true; } } TestCase { name: "CheckoutPage" function init() { console.debug("Cleaning vars"); root.called = false; } function test_uiTexts() { var titleLabel = UT.findChild(checkoutPage, "titleLabel"); var subtitleLabel = UT.findChild(checkoutPage, "subtitleLabel"); var priceLabel = UT.findChild(checkoutPage, "priceLabel"); var ubuntuIdLabel = UT.findChild(checkoutPage, "ubuntuIdLabel"); var ubuntuidExpected = "Ubuntu ID: " + root.ubuntuid compare(titleLabel.text, root.title); compare(subtitleLabel.text, root.subtitle); compare(priceLabel.text, root.price); compare(ubuntuIdLabel.text, ubuntuidExpected); } function test_cancelPressed() { compare(root.called, false); var cancelButton = UT.findChild(checkoutPage, "cancelButton"); cancelButton.clicked(); compare(root.called, true); } function test_buyPressed() { compare(root.called, false); var buyButton = UT.findChild(checkoutPage, "buyButton"); buyButton.clicked(); compare(root.called, true); } } } ./pay-ui/app/tests/unit/tst_purchasewebkit.qml0000644000015600001650000000252112675036254021641 0ustar jenkinsjenkinsimport QtQuick 2.0 import QtTest 1.0 import Ubuntu.Components 0.1 import "../../ui" // See more details @ http://qt-project.org/doc/qt-5.0/qtquick/qml-testcase.html // Execute tests with: // qmltestrunner Item { id: root property bool succeeded: false property bool failed: false // The objects UbuntuPurchaseWebkit { id: purchaseWebkit onPurchaseCanceled: { root.failed = true; } onPurchaseSucceeded: { root.succeeded = true; } } TestCase { name: "UbuntuPurchaseWebkitPage" function init() { console.debug("Cleaning vars"); root.succeeded = false; root.failed = false; } function test_normalNavigation() { purchaseWebkit.url = "http://fakepage.com"; compare(root.failed, false); compare(root.succeeded, false); } function test_succeeded() { purchaseWebkit.url = "https://sc.staging.ubuntu.com/click/succeeded"; compare(root.failed, false); compare(root.succeeded, true); } function test_failed() { purchaseWebkit.url = "https://sc.staging.ubuntu.com/click/succeeded"; compare(root.failed, true); compare(root.succeeded, false); } } } ./pay-ui/app/ui/0000755000015600001650000000000012675036314013505 5ustar jenkinsjenkins./pay-ui/app/ui/CheckoutPage.qml0000644000015600001650000003037512675036314016572 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import Ubuntu.Components 1.3 import Ubuntu.Components.ListItems 1.3 as ListItem import Ubuntu.Components.Popups 1.3 import payui 0.1 as Oxide import "../components" Page { id: pageCheckout header: PageHeader { title: i18n.tr("Payment") trailingActionBar.actions: [ Action { id: lockAction iconName: pageCheckout.securityStatus.securityLevel == Oxide.SecurityStatus.SecurityLevelSecure ? "lock" : "security-alert" onTriggered: { PopupUtils.open(popoverComponent, lockIconPlace, {"securityStatus": pageCheckout.securityStatus}) } } ] flickable: checkoutFlickable } property int keyboardSize: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0 property alias selectedItem: paymentTypes.selectedIndex property alias itemIcon: iconImage.source property alias itemTitle: titleLabel.text property alias itemSubtitle: subtitleLabel.text property alias price: priceLabel.text property alias account: accountView.model property alias model: paymentTypes.model property alias password: passwordField.text property alias otp: twoFactorField.text property alias certificate: otherSecurityStatus.certificate property alias securityStatus: otherSecurityStatus property bool hasPayments: false property bool hasPreferredPayment: true property bool hasStoredPayment: false property bool beforeTimeout: false property bool hasSelectedPayment: false property string backendId: "" property string paymentId: "" signal cancel signal buy(string email, string password, string otp, string paymentId, string backendId) signal addCreditCard function launchPurchase() { var pay = paymentTypes.model[pageCheckout.selectedItem]; var email = accountView.currentItem.email; pageCheckout.buy(email, password, otp, pay.paymentId, pay.backendId); } function showErrorMessage(message) { errorLabel.text = message; errorLabel.visible = true; } function showTwoFactor() { errorLabel.visible = false; twoFactorUI.visible = true; } function setSelectedItem() { for (var i=0; i < pageCheckout.model.length; i++) { if (pageCheckout.model[i].preferred) { selectedItem = i; } } } QtObject { id: otherSecurityStatus property int securityLevel: certificate == null ? Oxide.SecurityStatus.SecurityLevelNone : Oxide.SecurityStatus.SecurityLevelSecure property var certificate: null } Component { id: popoverComponent SecurityCertificatePopover { id: certPopover securityStatus: null } } Flickable { id: checkoutFlickable anchors { left: parent.left right: parent.right top: parent.top } Item { id: header height: units.gu(8) anchors { left: parent.left right: parent.right top: parent.top topMargin: units.gu(1) } UbuntuShape { id: iconShape objectName: "iconShape" anchors { top: parent.top left: parent.left margins: units.gu(1) } image: Image { id: iconImage objectName: "iconImage" } width: units.gu(6) height: units.gu(6) } Column { id: col spacing: units.gu(0.5) anchors { left: iconShape.right top: parent.top right: priceLabel.left bottom: parent.bottom margins: units.gu(1) } Label { id: titleLabel objectName: "titleLabel" fontSize: "medium" anchors { left: parent.left right: parent.right } elide: Text.ElideRight } Label { id: subtitleLabel objectName: "subtitleLabel" fontSize: "small" anchors { left: parent.left right: parent.right } elide: Text.ElideRight } } Label { id: priceLabel objectName: "priceLabel" font.bold: true fontSize: "large" verticalAlignment: Text.AlignVCenter anchors { right: parent.right top: parent.top bottom: parent.bottom rightMargin: units.gu(2) } } } Rectangle { id: separator height: units.dp(1) color: "#d5d5d5" anchors { left: parent.left right: parent.right top: header.bottom rightMargin: units.gu(2) leftMargin: units.gu(2) topMargin: units.gu(1) } } ListView { id: accountView anchors { left: parent.left right: parent.right top: separator.bottom leftMargin: units.gu(2) rightMargin: units.gu(2) topMargin: units.gu(2) } height: units.gu(2) enabled: false delegate: Text { id: ubuntuIdLabel objectName: "ubuntuIdLabel" text: model.displayName elide: Text.ElideRight property string email: model.displayName } } TextField { id: passwordField objectName: "passwordField" placeholderText: i18n.tr("Enter your Ubuntu One password") echoMode: TextInput.Password visible: !pageCheckout.beforeTimeout anchors { left: parent.left right: parent.right top: accountView.bottom margins: units.gu(2) } Keys.onReturnPressed: launchPurchase(); } Label { id: errorLabel objectName: "errorLabel" color: "red" text: "" wrapMode: Text.WordWrap visible: false anchors { left: parent.left right: parent.right top: passwordField.bottom margins: units.gu(2) } } Column { id: twoFactorUI spacing: units.gu(2) anchors { left: parent.left right: parent.right top: errorLabel.visible ? errorLabel.bottom : passwordField.bottom margins: units.gu(2) } visible: false Label { id: twoFactorLabel objectName: "twoFactorLabel" color: "black" text: i18n.tr("Type your verification code:") wrapMode: Text.WordWrap anchors { left: parent.left right: parent.right } } TextField { id: twoFactorField objectName: "twoFactorField" placeholderText: i18n.tr("2-factor device code") inputMethodHints: Qt.ImhDigitsOnly anchors { left: parent.left right: parent.right } Keys.onReturnPressed: launchPurchase(); } } Rectangle { id: paymentSep height: units.dp(1) color: "#d5d5d5" anchors { left: parent.left right: parent.right top: twoFactorUI.visible ? twoFactorUI.bottom : (errorLabel.visible ? errorLabel.bottom : (passwordField.visible ? passwordField.bottom : accountView.bottom)) rightMargin: units.gu(2) leftMargin: units.gu(2) topMargin: units.gu(2) } } OptionSelector { id: paymentTypes objectName: "paymentTypes" anchors { left: parent.left right: parent.right top: paymentSep.bottom margins: units.gu(2) } containerHeight: units.gu(24) expanded: false delegate: OptionSelectorDelegate { Item { anchors.fill: parent Column { anchors { fill: parent leftMargin: units.gu(2) topMargin: units.gu(2) rightMargin: units.gu(5) } spacing: units.gu(0.25) Label { text: modelData.name elide: Text.ElideLeft anchors { left: parent.left right: parent.right } fontSize: "small" } Label { text: modelData.description elide: Text.ElideLeft anchors { left: parent.left right: parent.right } fontSize: "x-small" } } } height: units.gu(8) } } Row { id: rowButtons anchors { left: parent.left right: parent.right top: paymentTypes.bottom margins: units.gu(4) } spacing: units.gu(2) property int buttonsWidth: (width / 2) - (spacing / 2) Button { id: cancelButton objectName: "cancelButton" text: i18n.tr("Cancel") width: parent.buttonsWidth color: "#797979" onClicked: pageCheckout.cancel(); } Button { id: buyButton objectName: "buyButton" text: i18n.tr("Buy Now") color: UbuntuColors.orange width: parent.buttonsWidth onClicked: { launchPurchase(); } } } Label { id: addCreditCardLabel objectName: "addCreditCardLabel" textFormat: Text.RichText text: '%1'.arg(i18n.tr("Add credit/debit card")) anchors { left: parent.left right: parent.right top: rowButtons.bottom topMargin: units.gu(6) } horizontalAlignment: Text.AlignHCenter onLinkActivated: pageCheckout.addCreditCard(); } } } ./pay-ui/app/ui/ErrorDialog.qml0000644000015600001650000000361712675036314016440 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import Ubuntu.Components 1.3 import Ubuntu.Components.Popups 1.3 Item { id: dialogErrorItem property string title: "" property string message: "" property bool open: false signal retry signal close onOpenChanged: { if (dialogErrorItem.open) { PopupUtils.open(errorDialogContainer); } } Component { id: errorDialogContainer Dialog { id: errorDialog title: dialogErrorItem.title text: dialogErrorItem.message Button { text: i18n.tr("Retry") objectName: "retryErrorButton" color: UbuntuColors.orange onClicked: { dialogErrorItem.retry(); dialogErrorItem.open = false; PopupUtils.close(errorDialog); } } Button { text: i18n.tr("Close") objectName: "closeErrorButton" color: UbuntuColors.coolGrey onClicked: { dialogErrorItem.close(); dialogErrorItem.open = false; PopupUtils.close(errorDialog); } } } } } ./pay-ui/app/ui/CMakeLists.txt0000644000015600001650000000033112675036254016245 0ustar jenkinsjenkinsfile(GLOB UI_QML_JS_FILES *.qml *.js) # make the files visible in the qtcreator tree add_custom_target(payui_ui_QMlFiles ALL SOURCES ${UI_QML_JS_FILES}) install(FILES ${UI_QML_JS_FILES} DESTINATION ${PAYUI_DIR}/ui) ./pay-ui/app/ui/UbuntuPurchaseWebkit.qml0000644000015600001650000000666312675036314020356 0ustar jenkinsjenkins/* * Copyright 2013-2014 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import Ubuntu.Components 1.3 import Ubuntu.Components.Popups 1.3 import Ubuntu.Web 0.2 import payui 0.1 as Oxide import "../components" Page { id: pageWebkit objectName: "pageWebkit" signal purchaseSucceeded() signal purchaseFailed() signal purchaseCancelled() signal loading(bool value) property int keyboardSize: Qt.inputMethod.visible ? Qt.inputMethod.keyboardRectangle.height : 0 property alias url: webView.url property alias title: pageHeader.title property var securityStatus: webView.securityStatus function parseQuery(url) { var argsParsed = {}; var vars = url.split('?'); if(vars.length == 1) { return argsParsed; } var args = vars[1].split('&'); for (var i=0; i < args.length; i++) { var arg = decodeURI(args[i]); if (arg.indexOf('=') == -1) { argsParsed[arg.trim()] = true; } else { var keyvalue = arg.split('='); argsParsed[keyvalue[0].trim()] = keyvalue[1].trim(); } } return argsParsed; } header: PageHeader { id: pageHeader title: "" trailingActionBar.actions: [ Action { id: lockAction iconName: pageWebkit.securityStatus.securityLevel == Oxide.SecurityStatus.SecurityLevelSecure ? "lock" : "security-alert" onTriggered: { PopupUtils.open(popoverComponent, lockIconPlace, {"securityStatus": pageWebkit.securityStatus}) } } ] } Component { id: popoverComponent SecurityCertificatePopover { id: certPopover securityStatus: null } } WebView { id: webView objectName: "webView" anchors.fill: parent anchors.topMargin: pageHeader.height anchors.bottomMargin: pageWebkit.keyboardSize // We need to specify the dialogs to use for JS dialogs here. alertDialog: AlertDialog {} confirmDialog: ConfirmDialog {} promptDialog: PromptDialog {} beforeUnloadDialog: BeforeUnloadDialog {} onLoadingStateChanged: { pageWebkit.loading(webView.loadProgress != 100); } onUrlChanged: { var re_succeeded = new RegExp("/click/succeeded"); var re_failed = new RegExp("/click/failed"); var re_cancelled = new RegExp("/click/cancelled"); if (re_succeeded.test(webView.url)) { pageWebkit.purchaseSucceeded(); } else if (re_failed.test(webView.url)) { pageWebkit.purchaseFailed(); } else if (re_cancelled.test(webView.url)) { pageWebkit.purchaseCancelled(); } } } } ./pay-ui/app/components/0000755000015600001650000000000012675036314015255 5ustar jenkinsjenkins./pay-ui/app/components/ConfirmDialog.qml0000644000015600001650000000214412675036314020506 0ustar jenkinsjenkins/* * Copyright 2013-2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import Ubuntu.Components 1.3 ModalDialog { objectName: "confirmDialog" title: i18n.dtr("webbrowser-app", "JavaScript Confirmation") Button { objectName: "dialogOkButton" text: i18n.dtr("webbrowser-app", "OK") onClicked: model.accept() } Button { objectName: "dialogCancelButton" text: i18n.dtr("webbrowser-app", "Cancel") onClicked: model.reject() } } ./pay-ui/app/components/AlertDialog.qml0000644000015600001650000000171112675036314020157 0ustar jenkinsjenkins/* * Copyright 2013-2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import Ubuntu.Components 1.3 ModalDialog { objectName: "alertDialog" title: i18n.dtr("webbrowser-app", "JavaScript Alert") Button { objectName: "dialogOkButton" text: i18n.dtr("webbrowser-app", "OK") onClicked: model.accept() } } ./pay-ui/app/components/PromptDialog.qml0000644000015600001650000000260312675036314020372 0ustar jenkinsjenkins/* * Copyright 2013-2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import Ubuntu.Components 1.3 ModalDialog { title: i18n.dtr("webbrowser-app", "JavaScript Prompt") TextField { objectName: "dialogInput" id: input text: model.defaultValue onAccepted: model.accept(input.text) } Button { objectName: "dialogOkButton" text: i18n.dtr("webbrowser-app", "OK") color: "green" onClicked: model.accept(input.text) } Button { objectName: "dialogCancelButton" text: i18n.dtr("webbrowser-app", "Cancel") color: UbuntuColors.coolGrey onClicked: model.reject() } Binding { target: model property: "currentValue" value: input.text } } ./pay-ui/app/components/ModalDialog.qml0000644000015600001650000000204412675036314020144 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import Ubuntu.Components 1.3 import Ubuntu.Components.Popups 1.3 as Popups Popups.Dialog { text: model.message // Set the parent at construction time, instead of letting show() // set it later on, which for some reason results in the size of // the dialog not being updated. parent: QuickUtils.rootItem(this) Component.onCompleted: show() } ./pay-ui/app/components/SecurityCertificatePopover.qml0000644000015600001650000001032212675036314023313 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import Ubuntu.Components 1.3 import Ubuntu.Components.ListItems 1.3 import Ubuntu.Components.Popups 1.3 import payui 0.1 as Oxide Popover { id: certificatePopover property var securityStatus Column { width: parent.width - units.gu(4) anchors.horizontalCenter: parent.horizontalCenter spacing: units.gu(0.5) Item { height: units.gu(1.5) width: parent.width } Column { width: parent.width visible: securityStatus.securityLevel == Oxide.SecurityStatus.SecurityLevelWarning spacing: units.gu(0.5) Row { width: parent.width spacing: units.gu(0.5) Icon { name: "security-alert" height: units.gu(2) width: height } Label { width: parent.width wrapMode: Text.WordWrap text: i18n.dtr("webbrowser-app", "This site has insecure content") fontSize: "x-small" } } ThinDivider { width: parent.width anchors.leftMargin: 0 anchors.rightMargin: 0 } } Label { width: parent.width wrapMode: Text.WordWrap text: i18n.dtr("webbrowser-app", "You are connected to") fontSize: "x-small" } Label { width: parent.width wrapMode: Text.WordWrap text: securityStatus.certificate.subjectDisplayName fontSize: "x-small" } ThinDivider { width: parent.width anchors.leftMargin: 0 anchors.rightMargin: 0 visible: orgName.visible || localityName.visible || stateName.visible || countryName.visible } Label { width: parent.width wrapMode: Text.WordWrap visible: orgName.visible text: i18n.dtr("webbrowser-app", "Which is run by") fontSize: "x-small" } Label { id: orgName width: parent.width wrapMode: Text.WordWrap visible: text.length > 0 text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrOrganizationName).join(", ") fontSize: "x-small" } Label { id: localityName width: parent.width wrapMode: Text.WordWrap visible: text.length > 0 text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrLocalityName).join(", ") fontSize: "x-small" } Label { id: stateName width: parent.width wrapMode: Text.WordWrap visible: text.length > 0 text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrStateOrProvinceName).join(", ") fontSize: "x-small" } Label { id: countryName width: parent.width wrapMode: Text.WordWrap visible: text.length > 0 text: securityStatus.certificate.getSubjectInfo(Oxide.SslCertificate.PrincipalAttrCountryName).join(", ") fontSize: "x-small" } Item { height: units.gu(1.5) width: parent.width } } MouseArea { anchors.fill: parent onClicked: PopupUtils.close(certificatePopover) } } ./pay-ui/app/components/CMakeLists.txt0000644000015600001650000000040112675036254020013 0ustar jenkinsjenkinsfile(GLOB COMPONENTS_QML_JS_FILES *.qml *.js) # make the files visible in the qtcreator tree add_custom_target(payui_components_QMlFiles ALL SOURCES ${COMPONENTS_QML_JS_FILES}) install(FILES ${COMPONENTS_QML_JS_FILES} DESTINATION ${PAYUI_DIR}/components) ./pay-ui/app/components/BeforeUnloadDialog.qml0000644000015600001650000000212512675036314021455 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This file is part of webbrowser-app. * * webbrowser-app is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * webbrowser-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import Ubuntu.Components 1.3 ModalDialog { title: i18n.dtr("webbrowser-app", "Confirm Navigation") objectName: "beforeUnloadDialog" Button { objectName: "leaveButton" text: i18n.dtr("webbrowser-app", "Leave") onClicked: model.accept() } Button { objectName: "stayButton" text: i18n.dtr("webbrowser-app", "Stay") onClicked: model.reject() } } ./pay-ui/app/CMakeLists.txt0000644000015600001650000000021712675036254015633 0ustar jenkinsjenkinsfile(GLOB QML_JS_FILES *.qml *.js) install(FILES ${QML_JS_FILES} DESTINATION ${PAYUI_DIR}) add_subdirectory(components) add_subdirectory(ui) ./pay-ui/tests/0000755000015600001650000000000012675036254013455 5ustar jenkinsjenkins./pay-ui/tests/autopilot/0000755000015600001650000000000012675036254015475 5ustar jenkinsjenkins./pay-ui/tests/autopilot/pay_ui/0000755000015600001650000000000012675036254016763 5ustar jenkinsjenkins./pay-ui/tests/autopilot/pay_ui/__init__.py0000644000015600001650000000705112675036254021077 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright (C) 2015 Canonical Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import logging import ubuntuuitoolkit as uitk from autopilot import logging as autopilot_logging logger = logging.getLogger(__name__) class MainView(uitk.MainView): def _tap(self, objectName): """Find a widget and tap on it.""" item = self.wait_select_single(objectName=objectName) if item.enabled: self.pointing_device.click_object(item) def _type_text(self, objectName, text): """Find a text widget and enter some text in it.""" item = self.wait_select_single( uitk.TextField, objectName=objectName) item.write(text) @autopilot_logging.log_action(logger.info) def cancel(self): """Tap on the 'Cancel' button.""" self._tap('cancelButton') @autopilot_logging.log_action(logger.info) def buy(self): """Tap on the 'Buy Now' button.""" self._tap('buyButton') @autopilot_logging.log_action(logger.info) def enter_password(self, password): """Type the password into the entry field.""" self._type_text('passwordField', password) @autopilot_logging.log_action(logger.info) def tap_on_webview(self): """Tap in the center of the web view.""" self._tap('webView') @autopilot_logging.log_action(logger.info) def open_add_card_page(self): """Open the 'Add Credit/Debit Card' page. :return the Add Credit/Debit Card page. """ self._tap('addCreditCardLabel') return self.wait_select_single(objectName='pageWebkit') @autopilot_logging.log_action(logger.info) def get_payment_types(self): """Get the payment types option selector widget. :return the Payment Types option selector widget. """ return self.wait_select_single(objectName='paymentTypes') @autopilot_logging.log_action(logger.info) def get_checkout_page(self): """Get the widget for the Checkout page. :return the Checkout page widget.""" return self.wait_select_single(objectName='pageCheckout') @autopilot_logging.log_action(logger.info) def tap_dialog_ok_button(self): """Tap the 'OK' button in the dialog.""" self._tap('dialogOkButton') @autopilot_logging.log_action(logger.info) def tap_dialog_cancel_button(self): """Tap the 'Cancel' button in the dialog.""" self._tap('dialogCancelButton') @autopilot_logging.log_action(logger.info) def tap_dialog_leave_button(self): """Tap the 'Leave' button in the dialog.""" self._tap('leaveButton') @autopilot_logging.log_action(logger.info) def tap_dialog_stay_button(self): """Tap the 'Stay' button in the dialog.""" self._tap('stayButton') @autopilot_logging.log_action(logger.info) def input_dialog_text(self, text): """Type the text into the dialog text entry field.""" self._type_text('dialogInput', text) ./pay-ui/tests/autopilot/pay_ui/tests/0000755000015600001650000000000012675036254020125 5ustar jenkinsjenkins./pay-ui/tests/autopilot/pay_ui/tests/__init__.py0000644000015600001650000000514012675036254022236 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright (C) 2014-2016 Canonical Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import fixtures import json import os import shutil import subprocess import tempfile import ubuntuuitoolkit as uitk import pay_ui class BasePayUITestCase(uitk.base.UbuntuUIToolkitAppTestCase): """Base Autopilot test case for the Pay UI project.""" dirpath = None def setUp(self): super().setUp() build_dir = os.environ.get('BUILD_DIR', None) source_dir = os.environ.get('SOURCE_DIR', None) self.app = self.launch_application(build_dir, source_dir) def create_config_dir(self): self.dirpath = tempfile.mkdtemp() self.useFixture(fixtures.EnvironmentVariable( 'XDG_DATA_HOME', self.dirpath)) def clean_config_dir(self): if self.dirpath: shutil.rmtree(self.dirpath) def launch_application(self, build_dir=None, source_dir=None): if build_dir is None: return self.launch_installed_app() else: return self.launch_built_application(build_dir, source_dir) def launch_installed_app(self): return self.launch_test_application( '/usr/lib/payui/pay-ui', app_type='qt', emulator_base=uitk.UbuntuUIToolkitCustomProxyObjectBase) def launch_built_application(self, build_dir, source_dir): built_import_path = os.path.join(build_dir, 'backend') self.useFixture( fixtures.EnvironmentVariable( 'QML2_IMPORT_PATH', newvalue=built_import_path)) main_qml_path = os.path.join(source_dir, 'app', 'payui.qml') return self.launch_test_application( uitk.base.get_qmlscene_launch_command(), main_qml_path, '--transparent', 'purchase://com.example.testapp', app_type='qt', emulator_base=uitk.UbuntuUIToolkitCustomProxyObjectBase) @property def main_view(self): """Return main view""" return self.app.select_single(pay_ui.MainView) ./pay-ui/tests/autopilot/pay_ui/tests/test_pay_ui.py0000644000015600001650000002040112675036254023021 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright (C) 2014-2016 Canonical Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import fixtures import testtools from testtools.matchers import Equals, NotEquals from autopilot.matchers import Eventually from pay_ui import tests from pay_ui.tests import mock_server class PayUITestCase(tests.BasePayUITestCase): def setUp(self): self.clean_config_dir() self.mock_server = mock_server.MockServer() self.addCleanup(self.mock_server.shutdown) self.useFixture(fixtures.EnvironmentVariable( 'PAY_BASE_URL', self.mock_server.url() + '/' + self.id().split('.')[-1])) self.useFixture(fixtures.EnvironmentVariable( 'U1_SEARCH_BASE_URL', self.mock_server.url('iteminfo/'))) self.useFixture(fixtures.EnvironmentVariable( 'SSO_AUTH_BASE_URL', self.mock_server.url('login/'))) self.useFixture(fixtures.EnvironmentVariable('GET_CREDENTIALS', '0')) self.create_config_dir() self.addCleanup(self.clean_config_dir) super().setUp() def app_returncode(self): return self.app.process.wait(timeout=30) def test_ui_initialized(self): main = self.main_view self.assertThat(main, NotEquals(None)) def test_cancel_purchase(self): self.main_view.cancel() self.assertThat(self.app_returncode(), Equals(1)) def test_basic_purchase(self): self.skipTest('Mouse clicks on buyButton not registering.') self.main_view.enter_password('password123') self.main_view.buy() self.assertThat(self.app_returncode(), Equals(0)) def test_add_credit_card_completed(self): payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.tap_on_webview() self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) def test_add_credit_card_returns_on_cancel(self): self.mock_server.set_interaction_result_cancelled() payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.tap_on_webview() checkout_page = self.main_view.get_checkout_page() self.assertThat(checkout_page.get_properties()["active"], Eventually(Equals(True))) def test_add_credit_card_cancelled(self): self.mock_server.set_interaction_result_cancelled() payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.tap_on_webview() self.assertThat(payment_types.get_option_count, Eventually(Equals(3))) @testtools.skip('JS Alert dialog seems to not work.') def test_add_credit_card_js_alert(self): payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.tap_dialog_ok_button() self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) def test_add_credit_card_js_beforeunload(self): payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.tap_dialog_stay_button() self.main_view.tap_on_webview() self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) @testtools.skip('Clicking add card link second seems to not work.') def test_add_credit_card_js_beforeunload_twice(self): payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.tap_dialog_leave_button() self.take_screenshot('beforeunload_left') self.main_view.open_add_card_page() self.take_screenshot('beforeunload_back') self.main_view.tap_dialog_stay_button() self.main_view.tap_on_web_view() self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) def test_add_credit_card_js_beforeunload_cancelled(self): payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.tap_dialog_leave_button() self.take_screenshot('beforeunload_cancelled') self.assertThat(payment_types.get_option_count, Eventually(Equals(3))) def test_add_credit_card_js_confirm_ok(self): payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.tap_dialog_ok_button() self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) def test_add_credit_card_js_confirm_cancel(self): payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.tap_dialog_cancel_button() self.assertThat(payment_types.get_option_count, Eventually(Equals(3))) def test_add_credit_card_js_prompt_ok(self): payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.input_dialog_text('friend') self.main_view.tap_dialog_ok_button() self.assertThat(payment_types.get_option_count, Eventually(Equals(4))) def test_add_credit_card_js_prompt_ok_wrong_text(self): payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.input_dialog_text('amigo') self.main_view.tap_dialog_ok_button() self.assertThat(payment_types.get_option_count, Eventually(Equals(3))) def test_add_credit_card_js_prompt_cancel(self): payment_types = self.main_view.get_payment_types() self.assertThat(payment_types.get_option_count(), Equals(3)) self.main_view.open_add_card_page() self.take_screenshot('add_card_page') self.main_view.tap_dialog_cancel_button() self.assertThat(payment_types.get_option_count, Eventually(Equals(3))) def test_purchase_with_web_interaction_completed(self): self.skipTest('Mouse clicks on buyButton not registering.') self.mock_server.set_purchase_needs_cc_interaction() self.main_view.buy() self.main_view.tap_on_webview() self.assertThat(self.app_returncode(), Equals(0)) def test_purchase_with_web_interaction_cancelled(self): self.skipTest('Mouse clicks on buyButton not registering.') self.mock_server.set_purchase_needs_cc_interaction() self.mock_server.set_interaction_result_cancelled() self.main_view.buy() self.main_view.tap_on_webview() self.assertThat(self.app_returncode(), Equals(1)) ./pay-ui/tests/autopilot/pay_ui/tests/mock_server.py0000644000015600001650000002540312675036254023022 0ustar jenkinsjenkins# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # # Copyright (C) 2014 Canonical Ltd. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import json import socket import threading from http.server import BaseHTTPRequestHandler, HTTPServer html_success = """

Placeholder for web interaction

Click anywhere to proceed

" """ html_cancel = """

Placeholder for web interaction

Click anywhere to cancel

" """ html_completed = """ """ html_beforeunload = """

Placeholder for web interaction

Click 'Stay' and then anywhere to proceed.

""" html_alert = """

Placeholder for web interaction

Click anywhere to proceed

""" html_confirm = """

Placeholder for web interaction

Click ok to add card, or cancel to not.

""" html_prompt = """

Placeholder for web interaction

Type friend and ok to add card.

""" class MyHandler(BaseHTTPRequestHandler): def do_HEAD(self): self.send_response(200) self.send_header("X-Click-Token", "X-Click-Token") self.end_headers() def response_payment_types(self, fail=False): if fail: self.send_response(404) else: self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(bytes(json.dumps(self.server.payment_types), 'UTF-8')) def interaction_html(self): return html_cancel if self.server.interaction_cancel else html_success def response_cc_interaction(self, fail=False): if fail: self.send_response(404) else: self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() self.wfile.write(bytes(self.interaction_html(), 'UTF-8')) def response_payment_add(self, fail=False): if fail: self.send_response(404) else: self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() test = self.path.split('/')[1] if test.find('js_alert') != -1: self.wrile.write(bytes(html_alert, 'UTF-8')) elif test.find('js_beforeunload') != -1: self.wfile.write(bytes(html_beforeunload, 'UTF-8')) elif test.find('js_confirm') != -1: self.wfile.write(bytes(html_confirm, 'UTF-8')) elif test.find('js_prompt') != -1: self.wfile.write(bytes(html_prompt, 'UTF-8')) else: self.wfile.write(bytes(self.interaction_html(), 'UTF-8')) def response_payment_add_complete(self): """Add the new payment info to the list.""" self.server.payment_types[1]["choices"].append({ "currencies": ["USD"], "id": 1999, "requires_interaction": False, "preferred": False, "description": "Yet another payment method" }) self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() self.wfile.write(bytes(html_completed, 'UTF-8')) def response_buy_item(self): response = { "state": "Complete", } if self.server.buy_cc_interaction: response["state"] = "InProgress" response["redirect_to"] = self.server.buy_cc_interaction if self.server.fail: response = {} self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() try: self.wfile.write(bytes(json.dumps(response), 'UTF-8')) except socket.error: pass def response_update_credentials(self, fail=False): response = { "token_key": "token_key", "token_secret": "token_secret", "consumer_key": "consumer_key", "consumer_secret": "consumer_secret", } if fail: self.send_response(404) else: self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(bytes(json.dumps(response), 'UTF-8')) def response_item_info(self, fail=False): response = { "title": "title", "publisher": "publisher", "icon_url": "icon_url", "prices": { "USD": 2.99, "EUR": 2.49, "GBP": 1.99, }, } if fail: self.send_response(404) else: self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(bytes(json.dumps(response), 'UTF-8')) def response_auth_error(self): self.send_response(401) self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(bytes(json.dumps(dict()), 'UTF-8')) def do_POST(self): """Respond to a POST request.""" #content = self.rfile.read(int(self.headers.get('content-length'))) #structure = json.loads(content.decode('utf-8')) if self.path.find("purchases/") != -1: self.response_buy_item() def do_GET(self): """Respond to a GET request.""" fail = self.path.find("/fail/") != -1 if self.path.find("paymentmethods/add/") != -1: self.response_payment_add() elif self.path.find("paymentmethods/completeadd") != -1: self.response_payment_add_complete() elif self.path.find("paymentmethods/") != -1: self.response_payment_types(fail) elif self.path.find("creditcard_interaction/") != -1: self.response_cc_interaction() elif self.path.find("purchases/") != -1: self.response_buy_item() elif self.path.find("creds/") != -1 or self.path.find("wallet/") != -1: self.response_update_credentials(fail) elif self.path.find("iteminfo/") != -1: self.response_item_info(fail) elif self.path.find("/authError/") != -1: self.response_auth_error() def initial_payment_types(): return [ { "description": "PayPal", "id": "paypal", "preferred": False, "choices": [ { "currencies": [ "USD", "GBP", "EUR" ], "id": 532, "requires_interaction": False, "preferred": False, "description": ("PayPal Preapproved Payment " "(exp. 2014-04-12)") } ] }, { "description": "Credit or Debit Card", "id": "credit_card", "preferred": True, "choices": [ { "currencies": [ "USD" ], "id": 1767, "requires_interaction": False, "preferred": False, "description": ("**** **** **** 1111 " "(Visa, exp. 02/2015)") }, { "currencies": [ "USD" ], "id": 1726, "requires_interaction": False, "preferred": True, "description": ("**** **** **** 1111 " "(Visa, exp. 03/2015)") } ] } ] class MockServer: def __init__(self): server_address = ('', 0) self.server = HTTPServer(server_address, MyHandler) tcp_port = self.server.socket.getsockname()[1] self.base_url = "http://127.0.0.1:%d/" % tcp_port server_thread = threading.Thread(target=self.server.serve_forever) server_thread.start() self.server.payment_types = initial_payment_types() self.server.fail = False self.server.buy_cc_interaction = None self.server.interaction_cancel = False def set_purchase_needs_cc_interaction(self): # Real server returns path starting with / here, not full URL. self.server.buy_cc_interaction = "/creditcard_interaction" def set_interaction_result_cancelled(self): self.server.interaction_cancel = True def url(self, tail=""): return self.base_url + tail def shutdown(self): self.server.shutdown() ./pay-ui/tests/autopilot/run_autopilot0000755000015600001650000000005312675036254020325 0ustar jenkinsjenkinsGET_CREDENTIALS=0 autopilot3 run -v pay_ui ./pay-ui/pay-ui.in0000644000015600001650000000135212675036254014050 0ustar jenkinsjenkins#!/bin/sh # # Copyright © 2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . set -e env QML2_IMPORT_PATH=${QML2_IMPORT_PATH}:@QT_IMPORTS_DIR@ qmlscene @PAYUI_DIR@/payui.qml $@ ./pay-ui/CMakeLists.txt0000644000015600001650000000130512675036254015052 0ustar jenkinsjenkins# Need xvfb-run for some tests. set(XVFB_CMD xvfb-run -a -s "-screen 0 540x960x24") # Standard install paths set(APP_NAME payui) set(QT_IMPORTS_DIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/payui") set(PAYUI_DIR "${CMAKE_INSTALL_FULL_DATADIR}/payui/qml") add_subdirectory(app) add_subdirectory(backend) configure_file(pay-ui.in pay-ui) install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/pay-ui DESTINATION lib/payui ) add_custom_target("autopilot" COMMAND PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/tests/autopilot BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR} SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} U1_DEBUG=1 ${XVFB_CMD} ${CMAKE_CURRENT_SOURCE_DIR}/tests/autopilot/run_autopilot DEPENDS payuibackend payuibackend-qmldir ) ./pay-ui/backend/0000755000015600001650000000000012675036254013702 5ustar jenkinsjenkins./pay-ui/backend/tests/0000755000015600001650000000000012675036254015044 5ustar jenkinsjenkins./pay-ui/backend/tests/mock_click_server.py0000644000015600001650000001226412675036254021107 0ustar jenkinsjenkinsimport json import threading from http.server import BaseHTTPRequestHandler, HTTPServer KEEP_ALIVE = True class MyHandler(BaseHTTPRequestHandler): def do_HEAD(self): self.send_response(200) self.send_header("X-Click-Token", "X-Click-Token") self.end_headers() def response_payment_types(self, fail=False): types = [ { "description": "PayPal", "id": "paypal", "preferred": False, "choices": [ { "currencies": [ "USD", "GBP", "EUR" ], "id": 532, "requires_interaction": False, "preferred": False, "description": "PayPal Preapproved Payment (exp. 2014-04-12)" } ] }, { "description": "Credit or Debit Card", "id": "credit_card", "preferred": True, "choices": [ { "currencies": [ "USD" ], "id": 1767, "requires_interaction": False, "preferred": False, "description": "**** **** **** 1111 (Visa, exp. 02/2015)" }, { "currencies": [ "USD" ], "id": 1726, "requires_interaction": False, "preferred": True, "description": "**** **** **** 1111 (Visa, exp. 03/2015)" } ] } ] if fail: self.send_response(404) else: self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(bytes(json.dumps(types), 'UTF-8')) def response_buy_item(self, fail=False, interaction=False): state = "Complete" if not interaction else "InProgress" response = { "state": state, } if interaction: # Real server returns path starting with / here. response["redirect_to"] = "/redirect.url?currency=USD" if fail: response = {} if self.path.find("/notpurchased/") != -1: self.send_response(404) else: self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(bytes(json.dumps(response), 'UTF-8')) def response_item_info(self, fail, eurozone, dotar): response = { "title": "title", "publisher": "publisher", "price": 9.99, "prices": { "USD": 1.99, "EUR": 1.69, "GBP": 1.29, "ARS": 18.05, }, "icon_url": "icon_url", } if fail: self.send_response(404) else: self.send_response(200) self.send_header("Content-type", "application/json") if eurozone: self.send_header("X-Suggested-Currency", "EUR") if dotar: self.send_header("X-Suggested-Currency", "ARS") self.end_headers() self.wfile.write(bytes(json.dumps(response), 'UTF-8')) def response_auth_error(self): self.send_response(401) self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(bytes(json.dumps(dict()), 'UTF-8')) def do_POST(self): """Respond to a POST request.""" #content = self.rfile.read(int(self.headers.get('content-length'))) #structure = json.loads(content.decode('utf-8')) self.do_GET() def do_GET(self): """Respond to a GET request.""" if self.path.find("/authError/") != -1: self.response_auth_error() elif self.path.find("shutdown") != -1: global KEEP_ALIVE KEEP_ALIVE = False elif self.path.find("paymentmethods/") != -1: fail = self.path.find("/fail/") != -1 self.response_payment_types(fail) elif self.path.find("purchases/") != -1: fail = self.path.find("/fail/") != -1 interaction = self.path.find("/interaction/") != -1 self.response_buy_item(fail=fail, interaction=interaction) elif self.path.find("iteminfo/") != -1: fail = self.path.find("/fail/") != -1 eurozone = self.path.find("/eurozone/") != -1 dotar = self.path.find("/dotar/") != -1 self.response_item_info(fail, eurozone, dotar) def run_click_server(): server_address = ('', 8000) httpd = HTTPServer(server_address, MyHandler) global KEEP_ALIVE print('start') while KEEP_ALIVE: httpd.handle_request() def run_mocked_settings(): t = threading.Thread(target=run_click_server) t.start() run_mocked_settings() ./pay-ui/backend/tests/test_network.cpp0000644000015600001650000002674312675036254020314 0ustar jenkinsjenkins/* * Copyright 2014-2016 Canonical Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace UbuntuPurchase; class TestNetwork: public QObject { Q_OBJECT private slots: void initTestCase(); void testNetworkAuthenticationError(); void testNetworkGetPaymentTypes(); void testNetworkGetPaymentTypesFail(); void testNetworkBuyItem(); void testNetworkBuyItemInProgress(); void testNetworkBuyItemFail(); void testNetworkButItemWithPaymentType(); void testNetworkButItemWithPaymentTypeFail(); void testNetworkButItemWithPaymentTypeInProgress(); void testNetworkGetItemInfo(); void testNetworkGetItemInfoEurozone(); void testNetworkGetItemInfoOtherCoins(); void testNetworkGetItemInfoOverride(); void testNetworkGetItemInfoOverrideOther(); void testNetworkGetItemInfoFail(); void testUseExistingCredentials(); void testCheckAlreadyPurchased(); void testCheckAlreadyPurchasedFail(); void testSanitizeUrl(); void testEncodeQuerySlashes(); void cleanupTestCase(); private: UbuntuPurchase::Network network; QProcess* process; }; void TestNetwork::initTestCase() { setenv("SSO_AUTH_BASE_URL", "http://localhost:8000/", 1); qDebug() << "Starting Server"; network.setCredentials(Token("token_key", "token_secret", "consumer_key", "consumer_secret")); process = new QProcess(this); QSignalSpy spy(process, SIGNAL(started())); process->setWorkingDirectory(QDir::currentPath() + "/backend/tests/"); qDebug() << process->workingDirectory(); QString program = "python3"; QString script = "mock_click_server.py"; process->start(program, QStringList() << script); QTRY_COMPARE(spy.count(), 1); // Wait for server to start QTimer timer; QSignalSpy spy2(&timer, SIGNAL(timeout())); timer.setInterval(2000); timer.setSingleShot(true); timer.start(); QTRY_COMPARE(spy2.count(), 1); } void TestNetwork::testNetworkAuthenticationError() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/authError/", 1); QSignalSpy spy(&network, SIGNAL(authenticationError())); network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false); // WTF DOES THIS HAPPEN TWICE QTRY_COMPARE(spy.count(), 2); } void TestNetwork::testNetworkGetPaymentTypes() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList))); network.requestPaymentTypes("USD"); QTRY_COMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QVERIFY(arguments.at(0).toList().count() == 3); QVariantList listPays = arguments.at(0).toList(); for (int i = 0; i < listPays.count(); i++) { QVariant var = listPays.at(i); PayInfo* info = var.value(); if (i == 2) { QVERIFY(info->preferred() == true); } else { QVERIFY(info->preferred() == false); } } } void TestNetwork::testNetworkGetPaymentTypesFail() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/fail/", 1); QSignalSpy spy(&network, SIGNAL(error(QString))); network.requestPaymentTypes("USD"); // WTF DOES THIS HAPPEN TWICE QTRY_COMPARE(spy.count(), 2); QList arguments = spy.takeFirst(); QVERIFY(arguments.at(0).toString().startsWith("404:")); } void TestNetwork::testNetworkBuyItem() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); QSignalSpy spy(&network, SIGNAL(buyItemSucceeded())); network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false); QTRY_COMPARE(spy.count(), 1); } void TestNetwork::testNetworkBuyItemInProgress() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/interaction/", 1); QSignalSpy spy(&network, SIGNAL(buyInteractionRequired(QString))); network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false); QTRY_COMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QString url(arguments.at(0).toString()); QString expected("http://localhost:8000/interaction/redirect.url?currency=USD"); QVERIFY(url.startsWith(expected)); } void TestNetwork::testNetworkBuyItemFail() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/fail/", 1); QSignalSpy spy(&network, SIGNAL(buyItemFailed())); network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", false); QTRY_COMPARE(spy.count(), 1); } void TestNetwork::testNetworkButItemWithPaymentType() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList))); network.requestPaymentTypes("USD"); QTRY_COMPARE(spy.count(), 1); QSignalSpy spy2(&network, SIGNAL(buyItemSucceeded())); network.buyItemWithPreferredPaymentType("email", "password", "otp", "USD", "appid", "itemid", false); QTRY_COMPARE(spy2.count(), 1); } void TestNetwork::testNetworkButItemWithPaymentTypeFail() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList))); network.requestPaymentTypes("USD"); QTRY_COMPARE(spy.count(), 1); setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/fail/", 1); QSignalSpy spy2(&network, SIGNAL(buyItemFailed())); network.buyItemWithPreferredPaymentType("email", "password", "otp", "USD", "appid", "itemid", false); QTRY_COMPARE(spy2.count(), 1); } void TestNetwork::testNetworkButItemWithPaymentTypeInProgress() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); QSignalSpy spy(&network, SIGNAL(paymentTypesObtained(QVariantList))); network.requestPaymentTypes("USD""USD"); QTRY_COMPARE(spy.count(), 1); setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/interaction/", 1); QSignalSpy spy2(&network, SIGNAL(buyInteractionRequired(QString))); network.buyItemWithPreferredPaymentType("email", "password", "otp", "USD", "appid", "itemid", false); QTRY_COMPARE(spy2.count(), 1); } void TestNetwork::testNetworkGetItemInfo() { setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/", 1); unsetenv(CURRENCY_ENVVAR); QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); network.getItemInfo("packagename", ""); QTRY_COMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QCOMPARE(arguments.at(2).toString(), QStringLiteral("USD")); QCOMPARE(arguments.at(3).toString(), QStringLiteral("US$1.99")); } void TestNetwork::testNetworkGetItemInfoEurozone() { setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/eurozone/", 1); unsetenv(CURRENCY_ENVVAR); QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); network.getItemInfo("packagename", ""); QTRY_COMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR")); QCOMPARE(arguments.at(3).toString(), QStringLiteral("€1.69")); } void TestNetwork::testNetworkGetItemInfoOtherCoins() { setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/dotar/", 1); unsetenv(CURRENCY_ENVVAR); QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); network.getItemInfo("packagename", ""); QTRY_COMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QCOMPARE(arguments.at(2).toString(), QStringLiteral("USD")); QCOMPARE(arguments.at(3).toString(), QStringLiteral("US$1.99")); } void TestNetwork::testNetworkGetItemInfoOverride() { setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/", 1); setenv(CURRENCY_ENVVAR, "EUR", true); QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); network.getItemInfo("packagename", ""); QTRY_COMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR")); QCOMPARE(arguments.at(3).toString(), QStringLiteral("€1.69")); unsetenv(CURRENCY_ENVVAR); } void TestNetwork::testNetworkGetItemInfoOverrideOther() { setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/iteminfo/dotar/", 1); setenv(CURRENCY_ENVVAR, "EUR", true); QSignalSpy spy(&network, SIGNAL(itemDetailsObtained(QString,QString,QString,QString,QString))); network.getItemInfo("packagename", ""); QTRY_COMPARE(spy.count(), 1); QList arguments = spy.takeFirst(); QCOMPARE(arguments.at(2).toString(), QStringLiteral("EUR")); QCOMPARE(arguments.at(3).toString(), QStringLiteral("€1.69")); unsetenv(CURRENCY_ENVVAR); } void TestNetwork::testNetworkGetItemInfoFail() { setenv(SEARCH_BASE_URL_ENVVAR, "http://localhost:8000/fail/iteminfo/", 1); unsetenv(CURRENCY_ENVVAR); QSignalSpy spy(&network, SIGNAL(error(QString))); network.getItemInfo("packagename", ""); // WTF DOES THIS HAPPEN TWICE QTRY_COMPARE(spy.count(), 2); } void TestNetwork::testUseExistingCredentials() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); QSignalSpy spy(&network, SIGNAL(buyItemSucceeded())); network.buyItem("email", "password", "otp", "USD", "appid", "itemid", "paymentid", "backendid", true); QTRY_COMPARE(spy.count(), 1); } void TestNetwork::testCheckAlreadyPurchased() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/", 1); QSignalSpy spy(&network, SIGNAL(buyItemSucceeded())); network.checkItemPurchased("com.example.fakeapp", ""); QTRY_COMPARE(spy.count(), 1); } void TestNetwork::testCheckAlreadyPurchasedFail() { setenv(PAY_BASE_URL_ENVVAR, "http://localhost:8000/notpurchased/", 1); QSignalSpy spy(&network, SIGNAL(itemNotPurchased())); network.checkItemPurchased("com.example.fakeapp", ""); // WTF DOES THIS HAPPEN TWICE QTRY_COMPARE(spy.count(), 2); } void TestNetwork::testSanitizeUrl() { QUrl url("https://example.com//test/this/heavily///really%2f/"); QUrl expected("https://example.com/test/this/heavily/really%2f/"); QString result = network.sanitizeUrl(url); QTRY_COMPARE(result, expected.toString()); } void TestNetwork::testEncodeQuerySlashes() { QString query("abcdef/01235%3D"); QTRY_COMPARE(network.encodeQuerySlashes(query), QStringLiteral("abcdef%2F01235%3D")); } void TestNetwork::cleanupTestCase() { process->close(); process->deleteLater(); } QTEST_MAIN(TestNetwork) #include "test_network.moc" ./pay-ui/backend/tests/CMakeLists.txt0000644000015600001650000000172612675036254017612 0ustar jenkinsjenkins include_directories (${CMAKE_CURRENT_SOURCE_DIR}/../payui) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) find_package(Qt5Core REQUIRED) set(PURCHASE_TESTS_TARGET test-purchase-ui) add_executable(${PURCHASE_TESTS_TARGET} test_network.cpp ) target_link_libraries(${PURCHASE_TESTS_TARGET} -Wl,-rpath,${CMAKE_CURRENT_BINARY_DIR}/../payui -L${CMAKE_CURRENT_BINARY_DIR}/../payui ${PAYUI_BACKEND} ) qt5_use_modules(${PURCHASE_TESTS_TARGET} Qml Quick Core DBus Xml Network Test) add_custom_target(payui-cppunit-tests COMMAND env LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 ${XVFB_CMD} ${CMAKE_CURRENT_BINARY_DIR}/${PURCHASE_TESTS_TARGET} DEPENDS ${PURCHASE_TESTS_TARGET} mock_click_server.py ) add_test(NAME payui-cppunit-tests COMMAND make payui-cppunit-tests ) add_custom_target(mock_click_server.py COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/mock_click_server.py ${CMAKE_CURRENT_BINARY_DIR}/mock_click_server.py ) ./pay-ui/backend/modules/0000755000015600001650000000000012675036254015352 5ustar jenkinsjenkins./pay-ui/backend/modules/payui/0000755000015600001650000000000012675036254016501 5ustar jenkinsjenkins./pay-ui/backend/modules/payui/network.h0000644000015600001650000001135112675036254020344 0ustar jenkinsjenkins/* * Copyright 2014-2015 Canonical Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef NETWORK_H #define NETWORK_H #include #include #include #include #include #include #include #include #include "credentials_service.h" #include "certificateadapter.h" #include "pay_info.h" using namespace UbuntuOne; namespace UbuntuPurchase { constexpr static const char* PAY_BASE_URL_ENVVAR{"PAY_BASE_URL"}; constexpr static const char* PAY_BASE_URL{"https://myapps.developer.ubuntu.com"}; constexpr static const char* PAY_API_ROOT{"/api/2.0/click"}; constexpr static const char* IAP_API_ROOT{"/inventory/api/v1"}; constexpr static const char* CURRENCY_ENVVAR {"U1_SEARCH_CURRENCY"}; constexpr static const char* SEARCH_BASE_URL_ENVVAR{"U1_SEARCH_BASE_URL"}; constexpr static const char* SEARCH_BASE_URL{"https://search.apps.ubuntu.com"}; constexpr static const char* SEARCH_API_ROOT{"/api/v1"}; class RequestObject : public QObject { Q_OBJECT public: explicit RequestObject(QString oper, QObject *parent = 0) : QObject(parent) { operation = oper; } QString operation; }; class Network : public QObject { Q_OBJECT public: explicit Network(QObject *parent = 0); void requestPaymentTypes(const QString& currency); void buyItem(const QString& email, const QString& password, const QString& otp, const QString& appid, const QString& itemid, const QString& currency, const QString& paymentId, const QString& backendId, bool recentLogin); void buyItemWithPreferredPaymentType(const QString& email, const QString& password, const QString& otp, const QString& appid, const QString& itemid, const QString& currency, bool recentLogin); void getItemInfo(const QString& packagename, const QString& sku); void checkPassword(const QString& email, const QString& password, const QString& otp, bool purchasing=false); void getCredentials(); void setCredentials(Token token); QString getAddPaymentUrl(const QString& currency); QDateTime getTokenUpdated(); void checkItemPurchased(const QString& appid, const QString& sku); static QString getSymbolForCurrency(const QString& currency_code); static bool isSupportedCurrency(const QString& currency_code); static QString sanitizeUrl(const QUrl& url); static QString encodeQuerySlashes(const QString& query); virtual QString getEnvironmentValue(const QString& key, const QString& defaultValue); virtual QString getPayApiUrl(const QString& path); virtual QString getSearchApiUrl(const QString& path); Q_SIGNALS: void itemDetailsObtained(QString title, QString publisher, QString currency, QString formatted_price, QString icon); void paymentTypesObtained(QVariantList payments); void buyItemSucceeded(); void buyItemFailed(); void buyInteractionRequired(QString url); void error(QString message); void authenticationError(); void credentialsNotFound(); void credentialsFound(); void passwordValid(); void noPreferredPaymentMethod(); void loginError(const QString& message); void twoFactorAuthRequired(); void itemNotPurchased(); void certificateFound(QObject* cert); private Q_SLOTS: void onReply(QNetworkReply*); void handleCredentialsFound(Token token); void handleCredentialsStored(); void purchaseProcess(); private: QNetworkAccessManager m_nam; QNetworkRequest m_request; CredentialsService m_service; Token m_token; PayInfo* m_preferred; QString m_selectedPaymentId; QString m_selectedBackendId; QString m_selectedAppId; QString m_selectedItemId; QString m_currency; bool m_startPurchase = false; void signRequestUrl(QNetworkRequest& request, QString url, QString method="GET"); QString getDeviceId(); QByteArray getPartnerId(); }; } #endif // NETWORK_H ./pay-ui/backend/modules/payui/network.cpp0000644000015600001650000004354312675036254020707 0ustar jenkinsjenkins/* * Copyright 2014-2016 Canonical Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "network.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "certificateadapter.h" #define PAY_PURCHASES_PATH "/purchases" #define PAY_PAYMENTMETHODS_PATH "/paymentmethods" #define PAY_PAYMENTMETHODS_ADD_PATH PAY_PAYMENTMETHODS_PATH + "/add" #define SUGGESTED_CURRENCY_HEADER_NAME "X-Suggested-Currency" #define DEVICE_ID_HEADER "X-Device-Id" #define PARTNER_ID_HEADER "X-Partner-ID" #define PARTNER_ID_FILE "/custom/partner-id" #define PREFERED_PAYMENT_TYPE "0" #define PAYMENT_TYPES "1" #define BUY_ITEM "2" #define ITEM_INFO "3" #define UPDATE_CREDENTIALS "4" #define CHECK_PASSWORD "5" #define CHECK_PURCHASED "6" #define BUY_COMPLETE "Complete" #define BUY_IN_PROGRESS "InProgress" namespace UbuntuPurchase { Network::Network(QObject *parent) : QObject(parent), m_nam(this), m_service(this), m_preferred(nullptr) { connect(&m_nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(onReply(QNetworkReply*))); // SSO SERVICE connect(&m_service, &CredentialsService::credentialsFound, this, &Network::handleCredentialsFound); connect(&m_service, &CredentialsService::credentialsNotFound, this, &Network::credentialsNotFound); connect(&m_service, &SSOService::credentialsStored, this, &Network::handleCredentialsStored); connect(&m_service, &CredentialsService::loginError, this, &Network::loginError); connect(&m_service, &SSOService::twoFactorAuthRequired, this, &Network::twoFactorAuthRequired); } void Network::getCredentials() { qDebug() << "getting credentials"; m_service.getCredentials(); } void Network::setCredentials(Token token) { m_service.setCredentials(token); } QMap buildCurrencyMap() { /* NOTE: The list of currencies we need to handle mapping symbols of. * Please keep this list in A-Z order. */ QMap currencies_map { { "CNY", "RMB"}, { "EUR", "€"}, { "GBP", "₤"}, { "HKD", "HK$"}, { "TWD", "TW$"}, { "USD", "US$"}, }; return currencies_map; } const QString DEFAULT_CURRENCY {"USD"}; QString Network::getSymbolForCurrency(const QString ¤cy_code) { static QMap currency_map = buildCurrencyMap(); if (currency_map.contains(currency_code)) { return currency_map[currency_code]; } else{ return currency_code; } } bool Network::isSupportedCurrency(const QString& currency_code) { static QMap currency_map = buildCurrencyMap(); return currency_map.contains(currency_code); } QString Network::sanitizeUrl(const QUrl& url) { static QRegExp regexp("\\b\\/\\/+"); return url.toString().replace(regexp, "/"); } QString Network::encodeQuerySlashes(const QString& query) { static QRegExp regexp("\\b\\/"); QString temp(query); return temp.replace(regexp, "%2F"); } void Network::onReply(QNetworkReply *reply) { QVariant statusAttr = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute); if (!statusAttr.isValid()) { QString message("Invalid reply status"); qWarning() << message; Q_EMIT error(message); return; } RequestObject* state = qobject_cast(reply->request().originatingObject()); if (state->operation.isEmpty()) { QString message("Reply received for non valid state."); qWarning() << message; Q_EMIT error(message); return; } int httpStatus = statusAttr.toInt(); qDebug() << "Reply status:" << httpStatus; if (httpStatus == 200 || httpStatus == 201) { QByteArray payload = reply->readAll(); qDebug() << payload; QJsonDocument document = QJsonDocument::fromJson(payload); if (state->operation.contains(PAYMENT_TYPES) && document.isArray()) { qDebug() << "Reply state: PAYMENT_TYPES"; QVariantList listPays; QJsonArray array = document.array(); for (int i = 0; i < array.size(); i++) { QJsonObject object = array.at(i).toObject(); QString description = object.value("description").toString(); QString backend = object.value("id").toString(); bool preferredBackend = object.value("preferred").toBool(); QJsonArray choices = object.value("choices").toArray(); for (int j = 0; j < choices.size(); j++) { QJsonObject choice = choices.at(j).toObject(); QString name = choice.value("description").toString(); QString paymentId = QString::number(choice.value("id").toInt()); bool requiresInteracion = choice.value("requires_interaction").toBool(); bool preferred = choice.value("preferred").toBool() && preferredBackend; PayInfo* pay = new PayInfo(); pay->setPayData(name, description, paymentId, backend, requiresInteracion, preferred); listPays.append(qVariantFromValue((QObject*)pay)); if (preferred) { m_preferred = pay; } } } qDebug() << "Emit signal paymentTypesObtained"; Q_EMIT paymentTypesObtained(listPays); if (m_preferred == nullptr) { Q_EMIT noPreferredPaymentMethod(); } qDebug() << "Emit signal certificateFound"; CertificateAdapter* cert = new CertificateAdapter(reply->sslConfiguration().peerCertificate()); Q_EMIT certificateFound(cert); } else if (state->operation.contains(BUY_ITEM) && document.isObject()) { qDebug() << "Reply state: BUY_ITEM"; QJsonObject object = document.object(); QString state = object.value("state").toString(); if (state == BUY_COMPLETE) { qDebug() << "BUY STATE: complete"; Q_EMIT buyItemSucceeded(); } else if (state == BUY_IN_PROGRESS) { QUrl url(getPayApiUrl(object.value("redirect_to").toString())); qDebug() << "BUY STATE: in progress"; qDebug() << "BUY Redirect URL:" << url.toString(); QString sign = m_token.signUrl(url.toString(), "GET", true); url.setQuery(encodeQuerySlashes(sign)); Q_EMIT buyInteractionRequired(url.toString()); } else { qDebug() << "BUY STATE: failed"; Q_EMIT buyItemFailed(); } } else if (state->operation.contains(ITEM_INFO) && document.isObject()) { qDebug() << "Reply state: ITEM_INFO"; QJsonObject object = document.object(); QString icon; QString publisher; if (object.contains("publisher")) { publisher = object.value("publisher").toString(); } if (object.contains("icon_url")) { icon = object.value("icon_url").toString(); } else if (object.contains("icon")) { icon = object.value("icon").toString(); } QString title = object.value("title").toString(); QJsonObject prices = object.value("prices").toObject(); QString suggested_currency = DEFAULT_CURRENCY; QString currency = DEFAULT_CURRENCY; if (reply->hasRawHeader(SUGGESTED_CURRENCY_HEADER_NAME)) { suggested_currency = reply->rawHeader(SUGGESTED_CURRENCY_HEADER_NAME); } const char* env_value = std::getenv(CURRENCY_ENVVAR); if (env_value != NULL) { suggested_currency = env_value; } if (isSupportedCurrency(suggested_currency) && prices.contains(suggested_currency)) { currency = suggested_currency; } double price = 0.00; if (prices[currency].isDouble()) { price = prices[currency].toDouble(); } else if (prices[currency].isString()) { price = prices[currency].toString().toDouble(); } QLocale locale; QString formatted_price = locale.toCurrencyString(price, getSymbolForCurrency(currency)); qDebug() << "Sending signal: itemDetailsObtained: " << title << " " << formatted_price; Q_EMIT itemDetailsObtained(title, publisher, currency, formatted_price, icon); } else if (state->operation.contains(CHECK_PURCHASED)) { QJsonObject object = document.object(); auto state = object.value("state").toString(); if (state == "Complete" || state == "purchased "|| state == "approved") { Q_EMIT buyItemSucceeded(); } else { Q_EMIT itemNotPurchased(); } } else { QString message("Reply received for non valid state."); qWarning() << message; Q_EMIT error(message); } } else if (httpStatus == 401 || httpStatus == 403) { qWarning() << "Credentials no longer valid. Invalidating."; m_service.invalidateCredentials(); Q_EMIT authenticationError(); } else if (httpStatus == 404 && state->operation.contains(CHECK_PURCHASED)) { Q_EMIT itemNotPurchased(); } else { QString message(QString::number(httpStatus)); message += ": "; message += reply->readAll().data(); qWarning() << message; Q_EMIT error(message); } reply->deleteLater(); } void Network::requestPaymentTypes(const QString& currency) { QNetworkRequest request; request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QUrl url(getPayApiUrl(QString(PAY_API_ROOT) + PAY_PAYMENTMETHODS_PATH + "/")); QUrlQuery query; query.addQueryItem("currency", currency); url.setQuery(query); qDebug() << "Request Payment Types:" << url.toString(); signRequestUrl(request, url.toString()); request.setRawHeader("Accept", "application/json"); request.setUrl(url); RequestObject* reqObject = new RequestObject(QString(PAYMENT_TYPES)); request.setOriginatingObject(reqObject); m_nam.get(request); } void Network::checkPassword(const QString& email, const QString& password, const QString& otp, bool purchasing) { m_startPurchase = purchasing; m_service.login(email, password, otp); } void Network::buyItemWithPreferredPaymentType(const QString& email, const QString& password, const QString& otp, const QString& appid, const QString& itemid, const QString& currency, bool recentLogin) { m_selectedPaymentId = m_preferred->paymentId(); m_selectedBackendId = m_preferred->backendId(); m_selectedAppId = appid; m_selectedItemId = itemid; m_currency = currency; if (recentLogin) { purchaseProcess(); } else { checkPassword(email, password, otp, true); } } void Network::buyItem(const QString& email, const QString& password, const QString& otp, const QString& appid, const QString& itemid, const QString& currency, const QString& paymentId, const QString& backendId, bool recentLogin) { m_selectedPaymentId = paymentId; m_selectedBackendId = backendId; m_selectedAppId = appid; m_selectedItemId = itemid; m_currency = currency; if (recentLogin) { purchaseProcess(); } else { checkPassword(email, password, otp, true); } } void Network::purchaseProcess() { QUrl url(getPayApiUrl(QString(PAY_API_ROOT) + PAY_PURCHASES_PATH + "/")); qDebug() << "Request Purchase:" << url; qDebug() << "Payment" << m_selectedAppId << m_selectedBackendId << m_selectedPaymentId; QJsonObject serializer; serializer.insert("name", m_selectedAppId); if (!m_selectedItemId.isEmpty()) { serializer.insert("item_sku", m_selectedItemId); } serializer.insert("backend_id", m_selectedBackendId); serializer.insert("method_id", m_selectedPaymentId); serializer.insert("currency", m_currency); QJsonDocument doc(serializer); QByteArray content = doc.toJson(); QNetworkRequest request; request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setRawHeader(DEVICE_ID_HEADER, getDeviceId().toUtf8().data()); // Get the partner ID and add it to the request. QByteArray partner_id = getPartnerId(); if (!partner_id.isEmpty()) { request.setRawHeader(PARTNER_ID_HEADER, partner_id); } request.setUrl(url); signRequestUrl(request, url.toString(), QString("POST")); RequestObject* reqObject = new RequestObject(QString(BUY_ITEM)); request.setOriginatingObject(reqObject); m_nam.post(request, content); } QByteArray Network::getPartnerId() { QByteArray id; if (QFile::exists(PARTNER_ID_FILE)) { QFile pid_file(PARTNER_ID_FILE); if (pid_file.open(QIODevice::ReadOnly | QIODevice::Text)) { // Always use lowercase, and trim whitespace. id = pid_file.readLine().toLower().trimmed(); qDebug() << "Found partner ID:" << id; } else { qWarning() << "Failed to open partner ID file."; } } else { qDebug() << "No partner ID file found."; } return id; } QString Network::getDeviceId() { QDBusInterface iface("com.ubuntu.WhoopsiePreferences", "/com/ubuntu/WhoopsiePreferences", "com.ubuntu.WhoopsiePreferences", QDBusConnection::systemBus(), 0); QDBusReply reply = iface.call("GetIdentifier"); return reply.value(); } void Network::getItemInfo(const QString& packagename, const QString& sku) { QUrl url; if (sku.isEmpty()) { url = getSearchApiUrl(QString(SEARCH_API_ROOT) + "/package/" + packagename); qDebug() << "Request Item Info:" << url; QUrlQuery query; query.addQueryItem("fields", "title,description,price,icon_url"); url.setQuery(query); } else { url = getPayApiUrl(QString(IAP_API_ROOT) + "/packages/" + packagename + "/items/by-sku/" + sku); } qDebug() << "Request Item Info:" << url; QNetworkRequest request; request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setUrl(url); signRequestUrl(request, url.toString(), QStringLiteral("GET")); RequestObject* reqObject = new RequestObject(QString(ITEM_INFO)); request.setOriginatingObject(reqObject); m_nam.get(request); } QString Network::getEnvironmentValue(const QString& key, const QString& defaultValue) { QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); QString result = environment.value(key, defaultValue); return result; } QString Network::getPayApiUrl(const QString& path) { QUrl url(getEnvironmentValue(PAY_BASE_URL_ENVVAR, PAY_BASE_URL).append(path)); return sanitizeUrl(url); } QString Network::getSearchApiUrl(const QString& path) { QUrl url(getEnvironmentValue(SEARCH_BASE_URL_ENVVAR, SEARCH_BASE_URL).append(path)); return sanitizeUrl(url); } void Network::signRequestUrl(QNetworkRequest& request, QString url, QString method) { QString sign = m_token.signUrl(url, method); request.setRawHeader("Authorization", sign.toUtf8()); } QString Network::getAddPaymentUrl(const QString& currency) { QUrl payUrl(getPayApiUrl(QString(PAY_API_ROOT) + PAY_PAYMENTMETHODS_ADD_PATH + "/")); QUrlQuery query; query.addQueryItem("currency", currency); payUrl.setQuery(query); qDebug() << "Get Add Payment URL:" << payUrl; QString sign = m_token.signUrl(payUrl.toString(), QStringLiteral("GET"), true); payUrl.setQuery(encodeQuerySlashes(sign)); return payUrl.toString(); } QDateTime Network::getTokenUpdated() { return m_token.updated(); } void Network::checkItemPurchased(const QString& appid, const QString& sku) { QUrl url; if (sku.isEmpty()) { url = getPayApiUrl(QString(PAY_API_ROOT) + PAY_PURCHASES_PATH + "/" + appid + "/"); } else { url = getPayApiUrl(QString(IAP_API_ROOT) + "/packages/" + appid + "/items/by-sku/" + sku); } qDebug() << "Checking for previous purchase:" << url; QNetworkRequest request; request.setUrl(url); signRequestUrl(request, url.toString(), QStringLiteral("GET")); RequestObject* reqObject = new RequestObject(QString(CHECK_PURCHASED)); request.setOriginatingObject(reqObject); m_nam.get(request); } void Network::handleCredentialsFound(Token token) { m_token = token; Q_EMIT credentialsFound(); } void Network::handleCredentialsStored() { // Get credentials again to update token object. getCredentials(); if (m_startPurchase) { purchaseProcess(); } else { Q_EMIT passwordValid(); } } } ./pay-ui/backend/modules/payui/purchase.cpp0000644000015600001650000001157412675036254021027 0ustar jenkinsjenkins/* * Copyright 2014-2016 Canonical Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "purchase.h" #include #include #include #include #include #include namespace UbuntuPurchase { Purchase::Purchase(QObject *parent) : QObject(parent), m_network(this) { connect(&m_network, &Network::itemDetailsObtained, this, &Purchase::itemDetailsObtained); connect(&m_network, &Network::paymentTypesObtained, this, &Purchase::paymentTypesObtained); connect(&m_network, &Network::buyItemSucceeded, this, &Purchase::buyItemSucceeded); connect(&m_network, &Network::buyItemFailed, this, &Purchase::buyItemFailed); connect(&m_network, &Network::buyInteractionRequired, this, &Purchase::buyInterationRequired); connect(&m_network, &Network::error, this, &Purchase::error); connect(&m_network, &Network::authenticationError, this, &Purchase::authenticationError); connect(&m_network, &Network::credentialsNotFound, this, &Purchase::credentialsNotFound); connect(&m_network, &Network::credentialsFound, this, &Purchase::credentialsFound); connect(&m_network, &Network::passwordValid, this, &Purchase::passwordValid); connect(&m_network, &Network::noPreferredPaymentMethod, this, &Purchase::noPreferredPaymentMethod); connect(&m_network, &Network::loginError, this, &Purchase::loginError); connect(&m_network, &Network::twoFactorAuthRequired, this, &Purchase::twoFactorAuthRequired); connect(&m_network, &Network::itemNotPurchased, this, &Purchase::itemNotPurchased); connect(&m_network, &Network::certificateFound, this, &Purchase::certificateFound); QCoreApplication* instance = QCoreApplication::instance(); // Set up logging UbuntuOne::AuthLogger::setupLogging(); const char* u1_debug = getenv("U1_DEBUG"); if (u1_debug != NULL && strcmp(u1_debug, "") != 0) { UbuntuOne::AuthLogger::setLogLevel(QtDebugMsg); } // Handle the purchase: URI for (int i = 0; i < instance->arguments().size(); i++) { QString argument = instance->arguments().at(i); if (argument.startsWith("purchase://")) { QUrl data(argument); m_appid = data.host(); m_itemid = data.fileName(); qDebug() << "Purchase requested for" << m_itemid << "by app" << m_appid; break; } } } Purchase::~Purchase() { UbuntuOne::AuthLogger::stopLogging(); } void Purchase::quitCancel() { qDebug() << "Purchase Canceled: exit code 1"; QCoreApplication::exit(1); } void Purchase::quitSuccess() { qDebug() << "Purchase Succeeded: exit code 0"; QCoreApplication::exit(0); } void Purchase::checkCredentials() { m_network.getCredentials(); } QString Purchase::getAddPaymentUrl(QString currency) { return m_network.getAddPaymentUrl(currency); } void Purchase::getItemDetails() { if (m_appid.isEmpty() && m_itemid.isEmpty()) { qCritical() << "AppId or ItemId not provided"; quitCancel(); } qDebug() << "Getting Item Details"; m_network.getItemInfo(m_appid, m_itemid); } void Purchase::getPaymentTypes(const QString& currency) { m_network.requestPaymentTypes(currency); } void Purchase::checkWallet(QString email, QString password, QString otp) { m_network.checkPassword(email, password, otp); } void Purchase::buyItemWithPreferredPayment(QString email, QString password, QString otp, QString currency, bool recentLogin) { m_network.buyItemWithPreferredPaymentType(email, password, otp, m_appid, m_itemid, currency, recentLogin); } void Purchase::buyItem(QString email, QString password, QString otp, QString currency, QString paymentId, QString backendId, bool recentLogin) { m_network.buyItem(email, password, otp, m_appid, m_itemid, currency, paymentId, backendId, recentLogin); } QDateTime Purchase::getTokenUpdated() { return m_network.getTokenUpdated(); } void Purchase::checkItemPurchased() { m_network.checkItemPurchased(m_appid, m_itemid); } } ./pay-ui/backend/modules/payui/oxideconstants.h0000644000015600001650000000260212675036254021717 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef OXIDECONSTANTS_H #define OXIDECONSTANTS_H #include class SecurityStatus : public QObject { Q_OBJECT Q_ENUMS(SecurityLevel) public: enum SecurityLevel { SecurityLevelNone, SecurityLevelSecure, SecurityLevelSecureEV, SecurityLevelWarning, SecurityLevelError }; }; class SslCertificate : public QObject { Q_OBJECT Q_ENUMS(PrincipalAttr) public: enum PrincipalAttr { PrincipalAttrOrganizationName, PrincipalAttrCommonName, PrincipalAttrLocalityName, PrincipalAttrOrganizationUnitName, PrincipalAttrCountryName, PrincipalAttrStateOrProvinceName }; }; #endif // OXIDECONSTANTS_H ./pay-ui/backend/modules/payui/credentials_service.cpp0000644000015600001650000000440712675036254023227 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "credentials_service.h" #include #include #include namespace UbuntuPurchase { bool useFakeCredentials() { QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); QString getcreds = environment.value("GET_CREDENTIALS", "1"); return getcreds != "1"; } CredentialsService::CredentialsService(QObject *parent) : SSOService(parent) { connect(this, &SSOService::requestFailed, this, &CredentialsService::handleRequestFailed); } void CredentialsService::getCredentials() { if (useFakeCredentials()) { Token token("tokenkey", "tokensecret", "consumerkey", "consumersecret"); Q_EMIT credentialsFound(token); } else { if (!m_token.isValid()) { SSOService::getCredentials(); } else { Q_EMIT credentialsFound(m_token); } } } void CredentialsService::setCredentials(Token token) { m_token = token; } void CredentialsService::login(const QString email, const QString password, const QString otp) { if (m_token.isValid() || useFakeCredentials()) { Q_EMIT SSOService::credentialsStored(); } else { SSOService::login(email, password, otp); } } void CredentialsService::handleRequestFailed(const ErrorResponse& error) { if (error.httpStatus() == 0 || error.httpReason() == NO_HTTP_REASON) { emit loginError("Network error, please try again."); } else { emit loginError(error.message()); } } } ./pay-ui/backend/modules/payui/certificateadapter.h0000644000015600001650000000265312675036254022503 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CERTIFICATEADAPTER_H #define CERTIFICATEADAPTER_H #include #include #include /* * Adapt a QSSlCertificate to the interface that oxide uses for certs. */ class CertificateAdapter : public QObject { Q_OBJECT Q_PROPERTY(QString subjectDisplayName READ subjectDisplayName) public: CertificateAdapter(QSslCertificate cert, QObject *parent = 0); CertificateAdapter(const CertificateAdapter& other); CertificateAdapter(); ~CertificateAdapter(); signals: public slots: QStringList getSubjectInfo(int subject); protected: QString subjectDisplayName(); QSslCertificate m_cert; }; Q_DECLARE_METATYPE(CertificateAdapter); #endif // CERTIFICATEADAPTER_H ./pay-ui/backend/modules/payui/certificateadapter.cpp0000644000015600001650000000250412675036254023031 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include "certificateadapter.h" CertificateAdapter::CertificateAdapter() { } CertificateAdapter::CertificateAdapter(const CertificateAdapter &other) { m_cert = other.m_cert; } CertificateAdapter::~CertificateAdapter() { } CertificateAdapter::CertificateAdapter(QSslCertificate cert, QObject *parent) : QObject(parent), m_cert(cert) { } QString CertificateAdapter::subjectDisplayName() { return m_cert.subjectInfo(QSslCertificate::CommonName).join(", "); } QStringList CertificateAdapter::getSubjectInfo(int subject) { return m_cert.subjectInfo((QSslCertificate::SubjectInfo)subject); } ./pay-ui/backend/modules/payui/backend.cpp0000644000015600001650000000136012675036254020574 0ustar jenkinsjenkins#include #include #include "backend.h" #include "purchase.h" #include "certificateadapter.h" #include "oxideconstants.h" #include "pay_info.h" void BackendPlugin::registerTypes(const char *uri) { Q_ASSERT(uri == QLatin1String("payui")); qmlRegisterType(uri, 0, 1, "Purchase"); qmlRegisterType(uri, 0, 1, "PayInfo"); qmlRegisterType(uri, 0, 1, "CertificateAdapter"); qmlRegisterType(uri, 0, 1, "SecurityStatus"); qmlRegisterType(uri, 0, 1, "SslCertificate"); } void BackendPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { QQmlExtensionPlugin::initializeEngine(engine, uri); } ./pay-ui/backend/modules/payui/oxideconstants.cpp0000644000015600001650000000135112675036254022252 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "oxideconstants.h" ./pay-ui/backend/modules/payui/qmldir0000644000015600001650000000004112675036254017707 0ustar jenkinsjenkinsmodule payui plugin payuibackend ./pay-ui/backend/modules/payui/pay_info.cpp0000644000015600001650000000220012675036254021003 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "pay_info.h" namespace UbuntuPurchase { PayInfo::PayInfo(QObject *parent) : QObject(parent) { } void PayInfo::setPayData(QString name, QString description, QString payment, QString backend, bool requiresInteracion, bool preferred) { m_name = name; m_paymentId = payment; m_description = description; m_backendId = backend; m_requiresInteracion = requiresInteracion; m_preferred = preferred; } } ./pay-ui/backend/modules/payui/purchase.h0000644000015600001650000000333212675036254020465 0ustar jenkinsjenkins#ifndef PURCHASE_H #define PURCHASE_H #include #include #include #include "network.h" #include "certificateadapter.h" namespace UbuntuPurchase { class Purchase : public QObject { Q_OBJECT public: explicit Purchase(QObject *parent = 0); ~Purchase(); Q_INVOKABLE void getItemDetails(); Q_INVOKABLE void getPaymentTypes(const QString ¤cy); Q_INVOKABLE void buyItemWithPreferredPayment(QString email, QString password, QString otp, QString currency, bool recentLogin); Q_INVOKABLE void buyItem(QString email, QString password, QString otp, QString currency, QString paymentId, QString backendId, bool recentLogin); Q_INVOKABLE void checkCredentials(); Q_INVOKABLE QString getAddPaymentUrl(QString currency); Q_INVOKABLE void checkWallet(QString email, QString password, QString otp); Q_INVOKABLE void quitSuccess(); Q_INVOKABLE void quitCancel(); Q_INVOKABLE QDateTime getTokenUpdated(); Q_INVOKABLE void checkItemPurchased(); Q_SIGNALS: void itemDetailsObtained(QString title, QString publisher, QString currency, QString formatted_price, QString icon); void paymentTypesObtained(QVariantList payments); void buyItemSucceeded(); void buyItemFailed(); void buyInterationRequired(QString url); void error(QString message); void authenticationError(); void credentialsNotFound(); void credentialsFound(); void passwordValid(); void noPreferredPaymentMethod(); void loginError(const QString& message); void twoFactorAuthRequired(); void itemNotPurchased(); void certificateFound(QObject* cert); private: Network m_network; QString m_appid; QString m_itemid; }; } #endif // PURCHASE_H ./pay-ui/backend/modules/payui/backend.h0000644000015600001650000000102712675036254020241 0ustar jenkinsjenkins#ifndef BACKEND_PLUGIN_H #define BACKEND_PLUGIN_H #include #include /* ----8<----- import payui 0.1 Rectangle { width: 200 height: 200 Purchase { id: purchase } } -----8<------ */ class BackendPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") public: void registerTypes(const char *uri); void initializeEngine(QQmlEngine *engine, const char *uri); }; #endif // BACKEND_PLUGIN_H ./pay-ui/backend/modules/payui/pay_info.h0000644000015600001650000000540112675036254020456 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PAY_INFO_H #define PAY_INFO_H #include #include namespace UbuntuPurchase { class PayInfo : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString paymentId READ paymentId NOTIFY paymentIdChanged) Q_PROPERTY(QString backendId READ backendId NOTIFY backendIdChanged) Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) Q_PROPERTY(bool requiresInteracion READ requiresInteracion NOTIFY requiresInteracionChanged) Q_PROPERTY(bool preferred READ preferred WRITE setPreferred NOTIFY preferredChanged) public: explicit PayInfo(QObject *parent = 0); QString name() { return m_name; } QString paymentId() { return m_paymentId; } QString backendId() { return m_backendId; } QString description() { return m_description; } bool requiresInteracion() { return m_requiresInteracion; } bool preferred() { return m_preferred; } void setPayData(QString name, QString description, QString payment, QString backend, bool steps, bool preferred); void setName(const QString& name) { m_name = name; Q_EMIT nameChanged(); } void setDescription(const QString& description) { m_description = description; Q_EMIT descriptionChanged(); } void setPaymentId(const QString& paymentId) { m_paymentId = paymentId; Q_EMIT paymentIdChanged(); } void setbackendId(const QString& backendId) { m_backendId = backendId; Q_EMIT backendIdChanged(); } void setRequiresInteracion(bool requiresInteracion) { m_requiresInteracion = requiresInteracion; Q_EMIT requiresInteracionChanged(); } void setPreferred(bool preferred) { m_preferred = preferred; Q_EMIT preferredChanged(); } Q_SIGNALS: void nameChanged(); void paymentIdChanged(); void backendIdChanged(); void descriptionChanged(); void requiresInteracionChanged(); void preferredChanged(); private: QString m_name; QString m_paymentId; QString m_backendId; QString m_description; bool m_requiresInteracion; bool m_preferred; }; } #endif // PAY_INFO_H ./pay-ui/backend/modules/payui/credentials_service.h0000644000015600001650000000256112675036254022673 0ustar jenkinsjenkins/* * Copyright 2014 Canonical Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of version 3 of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CREDENTIALS_SERVICE_H #define CREDENTIALS_SERVICE_H #include #include #include using namespace UbuntuOne; namespace UbuntuPurchase { class CredentialsService : public SSOService { Q_OBJECT public: explicit CredentialsService(QObject *parent = 0); void getCredentials(); void setCredentials(Token token); void login(const QString email, const QString password, const QString otp = QString()); Q_SIGNALS: void loginError(const QString& message); private Q_SLOTS: void handleRequestFailed(const ErrorResponse& error); private: Token m_token; }; } #endif // CREDENTIALS_SERVICE_H ./pay-ui/backend/CMakeLists.txt0000644000015600001650000000245612675036254016451 0ustar jenkinsjenkinsset(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") find_package(Qt5Core) find_package(Qt5Qml) find_package(Qt5Quick) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) pkg_check_modules(UBUNTUONEAUTH REQUIRED ubuntuoneauth-2.0) add_definitions(${UBUNTUONEAUTH_CFLAGS} ${UBUNTUONEAUTH_CFLAGS_OTHER}) set( payuibackend_SRCS modules/payui/backend.cpp modules/payui/purchase.cpp modules/payui/network.cpp modules/payui/pay_info.cpp modules/payui/credentials_service.cpp modules/payui/certificateadapter.cpp modules/payui/oxideconstants.cpp ) set(PAYUI_BACKEND payuibackend) add_library(${PAYUI_BACKEND} SHARED ${payuibackend_SRCS} ) target_link_libraries(${PAYUI_BACKEND} ${UBUNTUONEAUTH_LDFLAGS} ) set_target_properties(${PAYUI_BACKEND} PROPERTIES LIBRARY_OUTPUT_DIRECTORY payui) qt5_use_modules(${PAYUI_BACKEND} Gui Qml Quick Network DBus) # Copy qmldir file to build dir for running in QtCreator add_custom_target(payuibackend-qmldir ALL COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/modules/payui/qmldir ${CMAKE_CURRENT_BINARY_DIR}/payui DEPENDS ${QMLFILES} ) # Install plugin file install(TARGETS ${PAYUI_BACKEND} DESTINATION ${QT_IMPORTS_DIR}/payui/) install(FILES modules/payui/qmldir DESTINATION ${QT_IMPORTS_DIR}/payui/) add_subdirectory (tests) ./service-ng/0000755000015600001650000000000012675036254013151 5ustar jenkinsjenkins./service-ng/src/0000755000015600001650000000000012675036254013740 5ustar jenkinsjenkins./service-ng/src/update-externals.sh0000755000015600001650000000302712675036254017566 0ustar jenkinsjenkins#!/bin/sh # # Copyright © 2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License version 3, as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranties of # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR # PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . set -x set -e for upstream in `cat vendor.cfg`; do if [ ! -d ${upstream} ]; then mkdir -p ${upstream} fi HOST="`echo ${upstream} | cut -d/ -f1`" CODE_TMP=`mktemp -d -u` pardir=`dirname ${upstream}` dirname=`basename ${upstream}` if [ "x${HOST}" = "xgithub.com" ]; then echo "Fetching from github..." GIT_DIR=`mktemp -d -u` git clone https://${upstream}.git ${CODE_TMP}/${dirname} --separate-git-dir=${GIT_DIR} rm -rf ${CODE_TMP}/${dirname}/.git rm -rf ${GIT_DIR} elif [ "x${HOST}" = "xlaunchpad.net" ]; then echo "Fetching from launchpad..." mkdir -p ${CODE_TMP} bzr export -d lp:${dirname} ${CODE_TMP}/${dirname} else echo "Unknown external source type: ${dirname}" fi if [ -e $CODE_TMP/${dirname} ]; then cp -a ${CODE_TMP}/${dirname} ${pardir} fi rm -rf ${CODE_TMP} done ./service-ng/src/github.com/0000755000015600001650000000000012675036254015777 5ustar jenkinsjenkins./service-ng/src/github.com/gosexy/0000755000015600001650000000000012675036254017315 5ustar jenkinsjenkins./service-ng/src/github.com/gosexy/gettext/0000755000015600001650000000000012675036254021001 5ustar jenkinsjenkins./service-ng/src/github.com/gosexy/gettext/gettext_test.go0000644000015600001650000000435512675036254024062 0ustar jenkinsjenkinspackage gettext import ( "fmt" "os" "testing" ) /* NOTE: xgettext does not officially support Go syntax, however, you can generate a valid .pot file by forcing xgettest to use the C++ syntax: % xgettext -d example -s gettext_test.go -o example.pot -L c++ -i --keyword=NGettext:1,2 --keyword=Gettext This will generate a example.pot file. After translating the .pot file, you must generate .po and .mo files. Remember to set the UTF-8 charset. % msginit -l es_MX -o example.po -i example.pot % msgfmt -c -v -o example.mo example.po And finally, move the .mo file to an appropriate location. % mv example.mo examples/es_MX.utf8/LC_MESSAGES/example.mo */ func TestSpanishMexico(t *testing.T) { os.Setenv("LANGUAGE", "es_MX.utf8") SetLocale(LC_ALL, "") BindTextdomain("example", "./examples/") Textdomain("example") t1 := Gettext("Hello, world!") fmt.Println(t1) if t1 != "¡Hola mundo!" { t.Errorf("Failed translation.") } t2 := Sprintf(NGettext("An apple", "%d apples", 1), 1, "garbage") fmt.Println(t2) if t2 != "Una manzana" { t.Errorf("Failed translation.") } t3 := Sprintf(NGettext("An apple", "%d apples", 3), 3) fmt.Println(t3) if t3 != "3 manzanas" { t.Errorf("Failed translation.") } t4 := Gettext("Good morning") fmt.Println(t4) if t4 != "Buenos días" { t.Errorf("Failed translation.") } t5 := Gettext("Good bye!") fmt.Println(t5) if t5 != "¡Hasta luego!" { t.Errorf("Failed translation.") } } func TestGermanDeutschland(t *testing.T) { os.Setenv("LANGUAGE", "de_DE.utf8") SetLocale(LC_ALL, "") BindTextdomain("example", "./examples/") Textdomain("example") t1 := Gettext("Hello, world!") fmt.Println(t1) if t1 != "Hallo, Welt!" { t.Errorf("Failed translation.") } t2 := Sprintf(NGettext("An apple", "%d apples", 1), 1, "garbage") fmt.Println(t2) if t2 != "Ein Apfel" { t.Errorf("Failed translation.") } t3 := Sprintf(NGettext("An apple", "%d apples", 3), 3) fmt.Println(t3) if t3 != "3 Äpfel" { t.Errorf("Failed translation.") } t4 := Gettext("Good morning") fmt.Println(t4) if t4 != "Guten morgen" { t.Errorf("Failed translation.") } t5 := Gettext("Good bye!") fmt.Println(t5) if t5 != "Aufwiedersehen!" { t.Errorf("Failed translation.") } } ./service-ng/src/github.com/gosexy/gettext/LICENSE0000644000015600001650000000211212675036254022002 0ustar jenkinsjenkinsCopyright (c) 2012-2013 José Carlos Nieto, http://xiam.menteslibres.org/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ./service-ng/src/github.com/gosexy/gettext/gettext.go0000644000015600001650000001347712675036254023030 0ustar jenkinsjenkins/* Copyright (c) 2012 José Carlos Nieto, http://xiam.menteslibres.org/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package gettext /* #include #include #include */ import "C" import ( "fmt" "strings" "unsafe" ) var ( // For all of the locale. LC_ALL = uint(C.LC_ALL) // For regular expression matching (it determines the meaning of range // expressions and equivalence classes) and string collation. LC_COLATE = uint(C.LC_ALL) // For regular expression matching, character classification, conversion, // case-sensitive comparison, and wide character functions. LC_CTYPE = uint(C.LC_CTYPE) // For localizable natural-language messages. LC_MESSAGES = uint(C.LC_MESSAGES) // For monetary formatting. LC_MONETARY = uint(C.LC_MONETARY) // For number formatting (such as the decimal point and the thousands // separator). LC_NUMERIC = uint(C.LC_NUMERIC) // For time and date formatting. LC_TIME = uint(C.LC_TIME) ) // Sets or queries the program's current locale. func SetLocale(category uint, locale string) string { clocale := C.CString(locale) res := C.GoString(C.setlocale(C.int(category), clocale)) C.free(unsafe.Pointer(clocale)) return res } // Sets directory containing message catalogs. func BindTextdomain(domainname string, dirname string) string { cdirname := C.CString(dirname) cdomainname := C.CString(domainname) res := C.GoString(C.bindtextdomain(cdomainname, cdirname)) C.free(unsafe.Pointer(cdirname)) C.free(unsafe.Pointer(cdomainname)) return res } // Sets the output codeset for message catalogs for domain domainname. func BindTextdomainCodeset(domainname string, codeset string) string { cdomainname := C.CString(domainname) ccodeset := C.CString(codeset) res := C.GoString(C.bind_textdomain_codeset(cdomainname, ccodeset)) C.free(unsafe.Pointer(cdomainname)) C.free(unsafe.Pointer(ccodeset)) return res } // Sets or retrieves the current message domain. func Textdomain(domainname string) string { cdomainname := C.CString(domainname) res := C.GoString(C.textdomain(cdomainname)) C.free(unsafe.Pointer(cdomainname)) return res } // Attempt to translate a text string into the user's native language, by // looking up the translation in a message catalog. func Gettext(msgid string) string { cmsgid := C.CString(msgid) res := C.GoString(C.gettext(cmsgid)) C.free(unsafe.Pointer(cmsgid)) return res } // Like Gettext(), but looking up the message in the specified domain. func DGettext(domain string, msgid string) string { cdomain := C.CString(domain) cmsgid := C.CString(msgid) res := C.GoString(C.dgettext(cdomain, cmsgid)) C.free(unsafe.Pointer(cdomain)) C.free(unsafe.Pointer(cmsgid)) return res } // Like Gettext(), but looking up the message in the specified domain and // category. func DCGettext(domain string, msgid string, category uint) string { cdomain := C.CString(domain) cmsgid := C.CString(msgid) res := C.GoString(C.dcgettext(cdomain, cmsgid, C.int(category))) C.free(unsafe.Pointer(cdomain)) C.free(unsafe.Pointer(cmsgid)) return res } // Attempt to translate a text string into the user's native language, by // looking up the appropriate plural form of the translation in a message // catalog. func NGettext(msgid string, msgid_plural string, n uint64) string { cmsgid := C.CString(msgid) cmsgid_plural := C.CString(msgid_plural) res := C.GoString(C.ngettext(cmsgid, cmsgid_plural, C.ulong(n))) C.free(unsafe.Pointer(cmsgid)) C.free(unsafe.Pointer(cmsgid_plural)) return res } // Like fmt.Sprintf() but without %!(EXTRA) errors. func Sprintf(format string, a ...interface{}) string { expects := strings.Count(format, "%") - strings.Count(format, "%%") if expects > 0 { arguments := make([]interface{}, expects) for i := 0; i < expects; i++ { if len(a) > i { arguments[i] = a[i] } } return fmt.Sprintf(format, arguments...) } return format } // Like NGettext(), but looking up the message in the specified domain. func DNGettext(domainname string, msgid string, msgid_plural string, n uint64) string { cdomainname := C.CString(domainname) cmsgid := C.CString(msgid) cmsgid_plural := C.CString(msgid_plural) res := C.GoString(C.dngettext(cdomainname, cmsgid, cmsgid_plural, C.ulong(n))) C.free(unsafe.Pointer(cdomainname)) C.free(unsafe.Pointer(cmsgid)) C.free(unsafe.Pointer(cmsgid_plural)) return res } // Like NGettext(), but looking up the message in the specified domain and // category. func DCNGettext(domainname string, msgid string, msgid_plural string, n uint64, category uint) string { cdomainname := C.CString(domainname) cmsgid := C.CString(msgid) cmsgid_plural := C.CString(msgid_plural) res := C.GoString(C.dcngettext(cdomainname, cmsgid, cmsgid_plural, C.ulong(n), C.int(category))) C.free(unsafe.Pointer(cdomainname)) C.free(unsafe.Pointer(cmsgid)) C.free(unsafe.Pointer(cmsgid_plural)) return res } ./service-ng/src/github.com/gosexy/gettext/README.md0000644000015600001650000000355412675036254022267 0ustar jenkinsjenkins# gosexy/gettext Go bindings for [GNU gettext][1], an internationalization and localization library for writing multilingual systems. ## Requeriments The GNU C library. If you're using GNU/Linux, FreeBSD or OSX you should already have it. ## Installation Use `go get` to download and install the binding: ```sh go get github.com/gosexy/gettext ``` ## Usage ```go package main import ( "github.com/gosexy/gettext" "fmt" "os" ) func main() { gettext.BindTextdomain("example", ".") gettext.Textdomain("example") os.Setenv("LANGUAGE", "es_MX.utf8") gettext.SetLocale(gettext.LC_ALL, "") fmt.Println(gettext.Gettext("Hello, world!")) } ``` You can use `os.Setenv` to set the `LANGUAGE` environment variable or set it on a terminal: ```sh export LANGUAGE="es_MX.utf8" ./gettext-program ``` Note that `xgettext` does not officially support Go syntax yet, however, you can generate a valid `.pot` file by forcing `xgettest` to use the C++ syntax: ```sh xgettext -d example -s gettext_test.go -o example.pot -L c++ -i \ --keyword=NGettext:1,2 --keyword=Gettext ``` This will generate a `example.pot` file. After translating the `.pot` file, you must generate `.po` and `.mo` files and remember to set the UTF-8 charset. ```sh msginit -l es_MX -o example.po -i example.pot msgfmt -c -v -o example.mo example.po ``` Finally, move the `.mo` file to an appropriate location. ```sh mv example.mo examples/es_MX.utf8/LC_MESSAGES/example.mo ``` ## Documentation You can read `gosexy/gettext` documentation from a terminal ```sh go doc github.com/gosexy/gettext ``` Or you can [browse it](http://godoc.org/github.com/gosexy/gettext) online. The original gettext documentation could be very useful as well: ```sh man 3 gettext ``` Here's another [good tutorial][2] on using gettext. [1]: http://www.gnu.org/software/gettext/ [2]: http://oriya.sarovar.org/docs/gettext_single.html ./service-ng/src/github.com/gosexy/gettext/examples/0000755000015600001650000000000012675036254022617 5ustar jenkinsjenkins./service-ng/src/github.com/gosexy/gettext/examples/de_DE.utf8/0000755000015600001650000000000012675036254024444 5ustar jenkinsjenkins./service-ng/src/github.com/gosexy/gettext/examples/de_DE.utf8/LC_MESSAGES/0000755000015600001650000000000012675036254026231 5ustar jenkinsjenkins./service-ng/src/github.com/gosexy/gettext/examples/de_DE.utf8/LC_MESSAGES/example.mo0000644000015600001650000000113612675036254030222 0ustar jenkinsjenkinsDl ^ 4 D QAn apple%d applesGood bye!Good morningHello, world!Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2012-10-06 15:47-0500 PO-Revision-Date: 2012-10-06 16:03-0500 Last-Translator: Language-Team: German Language: de MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); Ein Apfel%d ÄpfelAufwiedersehen!Guten morgenHallo, Welt!./service-ng/src/github.com/gosexy/gettext/examples/de_DE.utf8/example.pot0000644000015600001650000000207012675036254026622 0ustar jenkinsjenkins# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-10-06 15:47-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: gettext_test.go:32 gettext_test.go:40 #, c-format msgid "An apple" msgid_plural "%d apples" msgstr[0] "Ein Apfel" msgstr[1] "%d Äpfel" #: gettext_test.go:56 msgid "Good bye!" msgstr "Aufwiedersehen!" #: gettext_test.go:48 msgid "Good morning" msgstr "Guten morgen" #: gettext_test.go:24 msgid "Hello, world!" msgstr "Hallo, Welt!" ./service-ng/src/github.com/gosexy/gettext/examples/es_MX.utf8/0000755000015600001650000000000012675036254024517 5ustar jenkinsjenkins./service-ng/src/github.com/gosexy/gettext/examples/es_MX.utf8/LC_MESSAGES/0000755000015600001650000000000012675036254026304 5ustar jenkinsjenkins./service-ng/src/github.com/gosexy/gettext/examples/es_MX.utf8/LC_MESSAGES/example.mo0000644000015600001650000000114612675036254030276 0ustar jenkinsjenkinsDl b$< K XAn apple%d applesGood bye!Good morningHello, world!Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2012-10-06 15:47-0500 PO-Revision-Date: 2012-10-06 15:48-0500 Last-Translator: Language-Team: Spanish Language: es_MX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); Una manzana%d manzanas¡Hasta luego!Buenos días¡Hola mundo!./service-ng/src/github.com/gosexy/gettext/examples/es_MX.utf8/example.pot0000644000015600001650000000207412675036254026701 0ustar jenkinsjenkins# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-10-06 15:47-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: gettext_test.go:32 gettext_test.go:40 #, c-format msgid "An apple" msgid_plural "%d apples" msgstr[0] "Una manzana" msgstr[1] "%d manzanas" #: gettext_test.go:56 msgid "Good bye!" msgstr "¡Hasta luego!" #: gettext_test.go:48 msgid "Good morning" msgstr "Buenos días" #: gettext_test.go:24 msgid "Hello, world!" msgstr "¡Hola mundo!" ./service-ng/src/github.com/gosexy/gettext/examples/gettext.go0000644000015600001650000000102612675036254024631 0ustar jenkinsjenkinspackage main import ( "github.com/gosexy/gettext" "fmt" "os" ) func main() { gettext.BindTextdomain("example", ".") gettext.Textdomain("example") os.Setenv("LANGUAGE", "es_MX.utf8") gettext.SetLocale(gettext.LC_ALL, "") fmt.Println(gettext.Gettext("Hello, world!")) os.Setenv("LANGUAGE", "de_DE.utf8") gettext.SetLocale(gettext.LC_ALL, "") fmt.Println(gettext.Gettext("Hello, world!")) os.Setenv("LANGUAGE", "en_US.utf8") gettext.SetLocale(gettext.LC_ALL, "") fmt.Println(gettext.Gettext("Hello, world!")) } ./service-ng/src/github.com/godbus/0000755000015600001650000000000012675036254017262 5ustar jenkinsjenkins./service-ng/src/github.com/godbus/dbus/0000755000015600001650000000000012675036254020217 5ustar jenkinsjenkins./service-ng/src/github.com/godbus/dbus/README.markdown0000644000015600001650000000234312675036254022722 0ustar jenkinsjenkinsdbus ---- dbus is a simple library that implements native Go client bindings for the D-Bus message bus system. ### Features * Complete native implementation of the D-Bus message protocol * Go-like API (channels for signals / asynchronous method calls, Goroutine-safe connections) * Subpackages that help with the introspection / property interfaces ### Installation This packages requires Go 1.1. If you installed it and set up your GOPATH, just run: ``` go get github.com/godbus/dbus ``` If you want to use the subpackages, you can install them the same way. ### Usage The complete package documentation and some simple examples are available at [godoc.org](http://godoc.org/github.com/godbus/dbus). Also, the [_examples](https://github.com/godbus/dbus/tree/master/_examples) directory gives a short overview over the basic usage. #### Projects using godbus - [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library. Please note that the API is considered unstable for now and may change without further notice. ### License go.dbus is available under the Simplified BSD License; see LICENSE for the full text. Nearly all of the credit for this library goes to github.com/guelfey/go.dbus. ./service-ng/src/github.com/godbus/dbus/doc.go0000644000015600001650000000441312675036254021315 0ustar jenkinsjenkins/* Package dbus implements bindings to the D-Bus message bus system. To use the message bus API, you first need to connect to a bus (usually the session or system bus). The acquired connection then can be used to call methods on remote objects and emit or receive signals. Using the Export method, you can arrange D-Bus methods calls to be directly translated to method calls on a Go value. Conversion Rules For outgoing messages, Go types are automatically converted to the corresponding D-Bus types. The following types are directly encoded as their respective D-Bus equivalents: Go type | D-Bus type ------------+----------- byte | BYTE bool | BOOLEAN int16 | INT16 uint16 | UINT16 int32 | INT32 uint32 | UINT32 int64 | INT64 uint64 | UINT64 float64 | DOUBLE string | STRING ObjectPath | OBJECT_PATH Signature | SIGNATURE Variant | VARIANT UnixFDIndex | UNIX_FD Slices and arrays encode as ARRAYs of their element type. Maps encode as DICTs, provided that their key type can be used as a key for a DICT. Structs other than Variant and Signature encode as a STRUCT containing their exported fields. Fields whose tags contain `dbus:"-"` and unexported fields will be skipped. Pointers encode as the value they're pointed to. Trying to encode any other type or a slice, map or struct containing an unsupported type will result in an InvalidTypeError. For incoming messages, the inverse of these rules are used, with the exception of STRUCTs. Incoming STRUCTS are represented as a slice of empty interfaces containing the struct fields in the correct order. The Store function can be used to convert such values to Go structs. Unix FD passing Handling Unix file descriptors deserves special mention. To use them, you should first check that they are supported on a connection by calling SupportsUnixFDs. If it returns true, all method of Connection will translate messages containing UnixFD's to messages that are accompanied by the given file descriptors with the UnixFD values being substituted by the correct indices. Similarily, the indices of incoming messages are automatically resolved. It shouldn't be necessary to use UnixFDIndex. */ package dbus ./service-ng/src/github.com/godbus/dbus/export_test.go0000644000015600001650000002552712675036254023141 0ustar jenkinsjenkinspackage dbus import "testing" type lowerCaseExport struct{} func (export lowerCaseExport) foo() (string, *Error) { return "bar", nil } type fooExport struct { message Message } func (export *fooExport) Foo(message Message, param string) (string, *Error) { export.message = message return "foo", nil } type barExport struct{} func (export barExport) Foo(param string) (string, *Error) { return "bar", nil } type badExport struct{} func (export badExport) Foo(param string) string { return "bar" } // Test typical Export usage. func TestExport(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } name := connection.Names()[0] connection.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") object := connection.Object(name, "/org/guelfey/DBus/Test") subtreeObject := connection.Object(name, "/org/guelfey/DBus/Test/Foo") var response int64 err = object.Call("org.guelfey.DBus.Test.Double", 0, int64(2)).Store(&response) if err != nil { t.Errorf("Unexpected error calling Double: %s", err) } if response != 4 { t.Errorf("Response was %d, expected 4", response) } // Verify that calling a subtree of a regular export does not result in a // valid method call. err = subtreeObject.Call("org.guelfey.DBus.Test.Double", 0, int64(2)).Store(&response) if err == nil { t.Error("Expected error due to no object being exported on that path") } // Now remove export connection.Export(nil, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") err = object.Call("org.guelfey.DBus.Test.Double", 0, int64(2)).Store(&response) if err == nil { t.Error("Expected an error since the export was removed") } } // Test that Exported handlers can obtain raw message. func TestExport_message(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } name := connection.Names()[0] export := &fooExport{} connection.Export(export, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") object := connection.Object(name, "/org/guelfey/DBus/Test") var response string err = object.Call("org.guelfey.DBus.Test.Foo", 0, "qux").Store(&response) if err != nil { t.Errorf("Unexpected error calling Foo: %s", err) } if response != "foo" { t.Errorf(`Response was %s, expected "foo"`, response) } if export.message.serial == 0 { t.Error("Expected a valid message to be given to handler") } } // Test Export with an invalid path. func TestExport_invalidPath(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } err = connection.Export(nil, "foo", "bar") if err == nil { t.Error("Expected an error due to exporting with an invalid path") } } // Test Export with an un-exported method. This should not panic, but rather // result in an invalid method call. func TestExport_unexportedMethod(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } name := connection.Names()[0] connection.Export(lowerCaseExport{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") object := connection.Object(name, "/org/guelfey/DBus/Test") var response string call := object.Call("org.guelfey.DBus.Test.foo", 0) err = call.Store(&response) if err == nil { t.Errorf("Expected an error due to calling unexported method") } } // Test Export with a method lacking the correct return signature. This should // result in an invalid method call. func TestExport_badSignature(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } name := connection.Names()[0] connection.Export(badExport{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") object := connection.Object(name, "/org/guelfey/DBus/Test") var response string call := object.Call("org.guelfey.DBus.Test.Foo", 0) err = call.Store(&response) if err == nil { t.Errorf("Expected an error due to the method lacking the right signature") } } // Test typical ExportWithMap usage. func TestExportWithMap(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } name := connection.Names()[0] mapping := make(map[string]string) mapping["Double"] = "double" // Export this method as lower-case connection.ExportWithMap(server{}, mapping, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") object := connection.Object(name, "/org/guelfey/DBus/Test") var response int64 err = object.Call("org.guelfey.DBus.Test.double", 0, int64(2)).Store(&response) if err != nil { t.Errorf("Unexpected error calling double: %s", err) } if response != 4 { t.Errorf("Response was %d, expected 4", response) } } // Test that ExportWithMap does not export both method alias and method. func TestExportWithMap_bypassAlias(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } name := connection.Names()[0] mapping := make(map[string]string) mapping["Double"] = "double" // Export this method as lower-case connection.ExportWithMap(server{}, mapping, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") object := connection.Object(name, "/org/guelfey/DBus/Test") var response int64 // Call upper-case Double (i.e. the real method, not the alias) err = object.Call("org.guelfey.DBus.Test.Double", 0, int64(2)).Store(&response) if err == nil { t.Error("Expected an error due to calling actual method, not alias") } } // Test typical ExportSubtree usage. func TestExportSubtree(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } name := connection.Names()[0] export := &fooExport{} connection.ExportSubtree(export, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") // Call a subpath of the exported path object := connection.Object(name, "/org/guelfey/DBus/Test/Foo") var response string err = object.Call("org.guelfey.DBus.Test.Foo", 0, "qux").Store(&response) if err != nil { t.Errorf("Unexpected error calling Foo: %s", err) } if response != "foo" { t.Errorf(`Response was %s, expected "foo"`, response) } if export.message.serial == 0 { t.Error("Expected the raw message, got an invalid one") } // Now remove export connection.Export(nil, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") err = object.Call("org.guelfey.DBus.Test.Foo", 0, "qux").Store(&response) if err == nil { t.Error("Expected an error since the export was removed") } } // Test that using ExportSubtree with exported methods that don't contain a // Message still work, they just don't get the message. func TestExportSubtree_noMessage(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } name := connection.Names()[0] connection.ExportSubtree(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") // Call a subpath of the exported path object := connection.Object(name, "/org/guelfey/DBus/Test/Foo") var response int64 err = object.Call("org.guelfey.DBus.Test.Double", 0, int64(2)).Store(&response) if err != nil { t.Errorf("Unexpected error calling Double: %s", err) } if response != 4 { t.Errorf("Response was %d, expected 4", response) } // Now remove export connection.Export(nil, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") err = object.Call("org.guelfey.DBus.Test.Double", 0, int64(2)).Store(&response) if err == nil { t.Error("Expected an error since the export was removed") } } // Test that a regular Export takes precedence over ExportSubtree. func TestExportSubtree_exportPrecedence(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } name := connection.Names()[0] // Register for the entire subtree of /org/guelfey/DBus/Test connection.ExportSubtree(&fooExport{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") // Explicitly register for /org/guelfey/DBus/Test/Foo, a subpath of above connection.Export(&barExport{}, "/org/guelfey/DBus/Test/Foo", "org.guelfey.DBus.Test") // Call the explicitly exported path object := connection.Object(name, "/org/guelfey/DBus/Test/Foo") var response string err = object.Call("org.guelfey.DBus.Test.Foo", 0, "qux").Store(&response) if err != nil { t.Errorf("Unexpected error calling Foo: %s", err) } if response != "bar" { t.Errorf(`Response was %s, expected "bar"`, response) } response = "" // Reset response so errors aren't confusing // Now remove explicit export connection.Export(nil, "/org/guelfey/DBus/Test/Foo", "org.guelfey.DBus.Test") err = object.Call("org.guelfey.DBus.Test.Foo", 0, "qux").Store(&response) if err != nil { t.Errorf("Unexpected error calling Foo: %s", err) } // Now the subtree export should handle the call if response != "foo" { t.Errorf(`Response was %s, expected "foo"`, response) } } // Test typical ExportSubtreeWithMap usage. func TestExportSubtreeWithMap(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } name := connection.Names()[0] mapping := make(map[string]string) mapping["Foo"] = "foo" // Export this method as lower-case connection.ExportSubtreeWithMap(&fooExport{}, mapping, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") // Call a subpath of the exported path object := connection.Object(name, "/org/guelfey/DBus/Test/Foo") var response string // Call the lower-case method err = object.Call("org.guelfey.DBus.Test.foo", 0, "qux").Store(&response) if err != nil { t.Errorf("Unexpected error calling Foo: %s", err) } if response != "foo" { t.Errorf(`Response was %s, expected "foo"`, response) } // Now remove export connection.Export(nil, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") err = object.Call("org.guelfey.DBus.Test.foo", 0, "qux").Store(&response) if err == nil { t.Error("Expected an error since the export was removed") } } // Test that ExportSubtreeWithMap does not export both method alias and method. func TestExportSubtreeWithMap_bypassAlias(t *testing.T) { connection, err := SessionBus() if err != nil { t.Fatalf("Unexpected error connecting to session bus: %s", err) } name := connection.Names()[0] mapping := make(map[string]string) mapping["Foo"] = "foo" // Export this method as lower-case connection.ExportSubtreeWithMap(&fooExport{}, mapping, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") object := connection.Object(name, "/org/guelfey/DBus/Test/Foo") var response string // Call upper-case Foo (i.e. the real method, not the alias) err = object.Call("org.guelfey.DBus.Test.Foo", 0, "qux").Store(&response) if err == nil { t.Error("Expected an error due to calling actual method, not alias") } } ./service-ng/src/github.com/godbus/dbus/call.go0000644000015600001650000000154312675036254021464 0ustar jenkinsjenkinspackage dbus import ( "errors" ) // Call represents a pending or completed method call. type Call struct { Destination string Path ObjectPath Method string Args []interface{} // Strobes when the call is complete. Done chan *Call // After completion, the error status. If this is non-nil, it may be an // error message from the peer (with Error as its type) or some other error. Err error // Holds the response once the call is done. Body []interface{} } var errSignature = errors.New("dbus: mismatched signature") // Store stores the body of the reply into the provided pointers. It returns // an error if the signatures of the body and retvalues don't match, or if // the error status is not nil. func (c *Call) Store(retvalues ...interface{}) error { if c.Err != nil { return c.Err } return Store(c.Body, retvalues...) } ./service-ng/src/github.com/godbus/dbus/variant_lexer.go0000644000015600001650000001140012675036254023405 0ustar jenkinsjenkinspackage dbus import ( "fmt" "strings" "unicode" "unicode/utf8" ) // Heavily inspired by the lexer from text/template. type varToken struct { typ varTokenType val string } type varTokenType byte const ( tokEOF varTokenType = iota tokError tokNumber tokString tokBool tokArrayStart tokArrayEnd tokDictStart tokDictEnd tokVariantStart tokVariantEnd tokComma tokColon tokType tokByteString ) type varLexer struct { input string start int pos int width int tokens []varToken } type lexState func(*varLexer) lexState func varLex(s string) []varToken { l := &varLexer{input: s} l.run() return l.tokens } func (l *varLexer) accept(valid string) bool { if strings.IndexRune(valid, l.next()) >= 0 { return true } l.backup() return false } func (l *varLexer) backup() { l.pos -= l.width } func (l *varLexer) emit(t varTokenType) { l.tokens = append(l.tokens, varToken{t, l.input[l.start:l.pos]}) l.start = l.pos } func (l *varLexer) errorf(format string, v ...interface{}) lexState { l.tokens = append(l.tokens, varToken{ tokError, fmt.Sprintf(format, v...), }) return nil } func (l *varLexer) ignore() { l.start = l.pos } func (l *varLexer) next() rune { var r rune if l.pos >= len(l.input) { l.width = 0 return -1 } r, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) l.pos += l.width return r } func (l *varLexer) run() { for state := varLexNormal; state != nil; { state = state(l) } } func (l *varLexer) peek() rune { r := l.next() l.backup() return r } func varLexNormal(l *varLexer) lexState { for { r := l.next() switch { case r == -1: l.emit(tokEOF) return nil case r == '[': l.emit(tokArrayStart) case r == ']': l.emit(tokArrayEnd) case r == '{': l.emit(tokDictStart) case r == '}': l.emit(tokDictEnd) case r == '<': l.emit(tokVariantStart) case r == '>': l.emit(tokVariantEnd) case r == ':': l.emit(tokColon) case r == ',': l.emit(tokComma) case r == '\'' || r == '"': l.backup() return varLexString case r == '@': l.backup() return varLexType case unicode.IsSpace(r): l.ignore() case unicode.IsNumber(r) || r == '+' || r == '-': l.backup() return varLexNumber case r == 'b': pos := l.start if n := l.peek(); n == '"' || n == '\'' { return varLexByteString } // not a byte string; try to parse it as a type or bool below l.pos = pos + 1 l.width = 1 fallthrough default: // either a bool or a type. Try bools first. l.backup() if l.pos+4 <= len(l.input) { if l.input[l.pos:l.pos+4] == "true" { l.pos += 4 l.emit(tokBool) continue } } if l.pos+5 <= len(l.input) { if l.input[l.pos:l.pos+5] == "false" { l.pos += 5 l.emit(tokBool) continue } } // must be a type. return varLexType } } } var varTypeMap = map[string]string{ "boolean": "b", "byte": "y", "int16": "n", "uint16": "q", "int32": "i", "uint32": "u", "int64": "x", "uint64": "t", "double": "f", "string": "s", "objectpath": "o", "signature": "g", } func varLexByteString(l *varLexer) lexState { q := l.next() Loop: for { switch l.next() { case '\\': if r := l.next(); r != -1 { break } fallthrough case -1: return l.errorf("unterminated bytestring") case q: break Loop } } l.emit(tokByteString) return varLexNormal } func varLexNumber(l *varLexer) lexState { l.accept("+-") digits := "0123456789" if l.accept("0") { if l.accept("x") { digits = "0123456789abcdefABCDEF" } else { digits = "01234567" } } for strings.IndexRune(digits, l.next()) >= 0 { } l.backup() if l.accept(".") { for strings.IndexRune(digits, l.next()) >= 0 { } l.backup() } if l.accept("eE") { l.accept("+-") for strings.IndexRune("0123456789", l.next()) >= 0 { } l.backup() } if r := l.peek(); unicode.IsLetter(r) { l.next() return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) } l.emit(tokNumber) return varLexNormal } func varLexString(l *varLexer) lexState { q := l.next() Loop: for { switch l.next() { case '\\': if r := l.next(); r != -1 { break } fallthrough case -1: return l.errorf("unterminated string") case q: break Loop } } l.emit(tokString) return varLexNormal } func varLexType(l *varLexer) lexState { at := l.accept("@") for { r := l.next() if r == -1 { break } if unicode.IsSpace(r) { l.backup() break } } if at { if _, err := ParseSignature(l.input[l.start+1 : l.pos]); err != nil { return l.errorf("%s", err) } } else { if _, ok := varTypeMap[l.input[l.start:l.pos]]; ok { l.emit(tokType) return varLexNormal } return l.errorf("unrecognized type %q", l.input[l.start:l.pos]) } l.emit(tokType) return varLexNormal } ./service-ng/src/github.com/godbus/dbus/transport_unix.go0000644000015600001650000001155212675036254023651 0ustar jenkinsjenkins//+build !windows package dbus import ( "bytes" "encoding/binary" "errors" "io" "net" "syscall" ) type oobReader struct { conn *net.UnixConn oob []byte buf [4096]byte } func (o *oobReader) Read(b []byte) (n int, err error) { n, oobn, flags, _, err := o.conn.ReadMsgUnix(b, o.buf[:]) if err != nil { return n, err } if flags&syscall.MSG_CTRUNC != 0 { return n, errors.New("dbus: control data truncated (too many fds received)") } o.oob = append(o.oob, o.buf[:oobn]...) return n, nil } type unixTransport struct { *net.UnixConn hasUnixFDs bool } func newUnixTransport(keys string) (transport, error) { var err error t := new(unixTransport) abstract := getKey(keys, "abstract") path := getKey(keys, "path") switch { case abstract == "" && path == "": return nil, errors.New("dbus: invalid address (neither path nor abstract set)") case abstract != "" && path == "": t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: "@" + abstract, Net: "unix"}) if err != nil { return nil, err } return t, nil case abstract == "" && path != "": t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: path, Net: "unix"}) if err != nil { return nil, err } return t, nil default: return nil, errors.New("dbus: invalid address (both path and abstract set)") } } func init() { transports["unix"] = newUnixTransport } func (t *unixTransport) EnableUnixFDs() { t.hasUnixFDs = true } func (t *unixTransport) ReadMessage() (*Message, error) { var ( blen, hlen uint32 csheader [16]byte headers []header order binary.ByteOrder unixfds uint32 ) // To be sure that all bytes of out-of-band data are read, we use a special // reader that uses ReadUnix on the underlying connection instead of Read // and gathers the out-of-band data in a buffer. rd := &oobReader{conn: t.UnixConn} // read the first 16 bytes (the part of the header that has a constant size), // from which we can figure out the length of the rest of the message if _, err := io.ReadFull(rd, csheader[:]); err != nil { return nil, err } switch csheader[0] { case 'l': order = binary.LittleEndian case 'B': order = binary.BigEndian default: return nil, InvalidMessageError("invalid byte order") } // csheader[4:8] -> length of message body, csheader[12:16] -> length of // header fields (without alignment) binary.Read(bytes.NewBuffer(csheader[4:8]), order, &blen) binary.Read(bytes.NewBuffer(csheader[12:]), order, &hlen) if hlen%8 != 0 { hlen += 8 - (hlen % 8) } // decode headers and look for unix fds headerdata := make([]byte, hlen+4) copy(headerdata, csheader[12:]) if _, err := io.ReadFull(t, headerdata[4:]); err != nil { return nil, err } dec := newDecoder(bytes.NewBuffer(headerdata), order) dec.pos = 12 vs, err := dec.Decode(Signature{"a(yv)"}) if err != nil { return nil, err } Store(vs, &headers) for _, v := range headers { if v.Field == byte(FieldUnixFDs) { unixfds, _ = v.Variant.value.(uint32) } } all := make([]byte, 16+hlen+blen) copy(all, csheader[:]) copy(all[16:], headerdata[4:]) if _, err := io.ReadFull(rd, all[16+hlen:]); err != nil { return nil, err } if unixfds != 0 { if !t.hasUnixFDs { return nil, errors.New("dbus: got unix fds on unsupported transport") } // read the fds from the OOB data scms, err := syscall.ParseSocketControlMessage(rd.oob) if err != nil { return nil, err } if len(scms) != 1 { return nil, errors.New("dbus: received more than one socket control message") } fds, err := syscall.ParseUnixRights(&scms[0]) if err != nil { return nil, err } msg, err := DecodeMessage(bytes.NewBuffer(all)) if err != nil { return nil, err } // substitute the values in the message body (which are indices for the // array receiver via OOB) with the actual values for i, v := range msg.Body { if j, ok := v.(UnixFDIndex); ok { if uint32(j) >= unixfds { return nil, InvalidMessageError("invalid index for unix fd") } msg.Body[i] = UnixFD(fds[j]) } } return msg, nil } return DecodeMessage(bytes.NewBuffer(all)) } func (t *unixTransport) SendMessage(msg *Message) error { fds := make([]int, 0) for i, v := range msg.Body { if fd, ok := v.(UnixFD); ok { msg.Body[i] = UnixFDIndex(len(fds)) fds = append(fds, int(fd)) } } if len(fds) != 0 { if !t.hasUnixFDs { return errors.New("dbus: unix fd passing not enabled") } msg.Headers[FieldUnixFDs] = MakeVariant(uint32(len(fds))) oob := syscall.UnixRights(fds...) buf := new(bytes.Buffer) msg.EncodeTo(buf, binary.LittleEndian) n, oobn, err := t.UnixConn.WriteMsgUnix(buf.Bytes(), oob, nil) if err != nil { return err } if n != buf.Len() || oobn != len(oob) { return io.ErrShortWrite } } else { if err := msg.EncodeTo(t, binary.LittleEndian); err != nil { return nil } } return nil } func (t *unixTransport) SupportsUnixFDs() bool { return true } ./service-ng/src/github.com/godbus/dbus/prop/0000755000015600001650000000000012675036254021177 5ustar jenkinsjenkins./service-ng/src/github.com/godbus/dbus/prop/prop.go0000644000015600001650000001701612675036254022513 0ustar jenkinsjenkins// Package prop provides the Properties struct which can be used to implement // org.freedesktop.DBus.Properties. package prop import ( "github.com/godbus/dbus" "github.com/godbus/dbus/introspect" "sync" ) // EmitType controls how org.freedesktop.DBus.Properties.PropertiesChanged is // emitted for a property. If it is EmitTrue, the signal is emitted. If it is // EmitInvalidates, the signal is also emitted, but the new value of the property // is not disclosed. type EmitType byte const ( EmitFalse EmitType = iota EmitTrue EmitInvalidates ) // ErrIfaceNotFound is the error returned to peers who try to access properties // on interfaces that aren't found. var ErrIfaceNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil) // ErrPropNotFound is the error returned to peers trying to access properties // that aren't found. var ErrPropNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil) // ErrReadOnly is the error returned to peers trying to set a read-only // property. var ErrReadOnly = dbus.NewError("org.freedesktop.DBus.Properties.Error.ReadOnly", nil) // ErrInvalidArg is returned to peers if the type of the property that is being // changed and the argument don't match. var ErrInvalidArg = dbus.NewError("org.freedesktop.DBus.Properties.Error.InvalidArg", nil) // The introspection data for the org.freedesktop.DBus.Properties interface. var IntrospectData = introspect.Interface{ Name: "org.freedesktop.DBus.Properties", Methods: []introspect.Method{ { Name: "Get", Args: []introspect.Arg{ {"interface", "s", "in"}, {"property", "s", "in"}, {"value", "v", "out"}, }, }, { Name: "GetAll", Args: []introspect.Arg{ {"interface", "s", "in"}, {"props", "a{sv}", "out"}, }, }, { Name: "Set", Args: []introspect.Arg{ {"interface", "s", "in"}, {"property", "s", "in"}, {"value", "v", "in"}, }, }, }, Signals: []introspect.Signal{ { Name: "PropertiesChanged", Args: []introspect.Arg{ {"interface", "s", "out"}, {"changed_properties", "a{sv}", "out"}, {"invalidates_properties", "as", "out"}, }, }, }, } // The introspection data for the org.freedesktop.DBus.Properties interface, as // a string. const IntrospectDataString = ` ` // Prop represents a single property. It is used for creating a Properties // value. type Prop struct { // Initial value. Must be a DBus-representable type. Value interface{} // If true, the value can be modified by calls to Set. Writable bool // Controls how org.freedesktop.DBus.Properties.PropertiesChanged is // emitted if this property changes. Emit EmitType // If not nil, anytime this property is changed by Set, this function is // called with an appropiate Change as its argument. If the returned error // is not nil, it is sent back to the caller of Set and the property is not // changed. Callback func(*Change) *dbus.Error } // Change represents a change of a property by a call to Set. type Change struct { Props *Properties Iface string Name string Value interface{} } // Properties is a set of values that can be made available to the message bus // using the org.freedesktop.DBus.Properties interface. It is safe for // concurrent use by multiple goroutines. type Properties struct { m map[string]map[string]*Prop mut sync.RWMutex conn *dbus.Conn path dbus.ObjectPath } // New returns a new Properties structure that manages the given properties. // The key for the first-level map of props is the name of the interface; the // second-level key is the name of the property. The returned structure will be // exported as org.freedesktop.DBus.Properties on path. func New(conn *dbus.Conn, path dbus.ObjectPath, props map[string]map[string]*Prop) *Properties { p := &Properties{m: props, conn: conn, path: path} conn.Export(p, path, "org.freedesktop.DBus.Properties") return p } // Get implements org.freedesktop.DBus.Properties.Get. func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) { p.mut.RLock() defer p.mut.RUnlock() m, ok := p.m[iface] if !ok { return dbus.Variant{}, ErrIfaceNotFound } prop, ok := m[property] if !ok { return dbus.Variant{}, ErrPropNotFound } return dbus.MakeVariant(prop.Value), nil } // GetAll implements org.freedesktop.DBus.Properties.GetAll. func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) { p.mut.RLock() defer p.mut.RUnlock() m, ok := p.m[iface] if !ok { return nil, ErrIfaceNotFound } rm := make(map[string]dbus.Variant, len(m)) for k, v := range m { rm[k] = dbus.MakeVariant(v.Value) } return rm, nil } // GetMust returns the value of the given property and panics if either the // interface or the property name are invalid. func (p *Properties) GetMust(iface, property string) interface{} { p.mut.RLock() defer p.mut.RUnlock() return p.m[iface][property].Value } // Introspection returns the introspection data that represents the properties // of iface. func (p *Properties) Introspection(iface string) []introspect.Property { p.mut.RLock() defer p.mut.RUnlock() m := p.m[iface] s := make([]introspect.Property, 0, len(m)) for k, v := range m { p := introspect.Property{Name: k, Type: dbus.SignatureOf(v.Value).String()} if v.Writable { p.Access = "readwrite" } else { p.Access = "read" } s = append(s, p) } return s } // set sets the given property and emits PropertyChanged if appropiate. p.mut // must already be locked. func (p *Properties) set(iface, property string, v interface{}) { prop := p.m[iface][property] prop.Value = v switch prop.Emit { case EmitFalse: // do nothing case EmitInvalidates: p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged", iface, map[string]dbus.Variant{}, []string{property}) case EmitTrue: p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged", iface, map[string]dbus.Variant{property: dbus.MakeVariant(v)}, []string{}) default: panic("invalid value for EmitType") } } // Set implements org.freedesktop.Properties.Set. func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error { p.mut.Lock() defer p.mut.Unlock() m, ok := p.m[iface] if !ok { return ErrIfaceNotFound } prop, ok := m[property] if !ok { return ErrPropNotFound } if !prop.Writable { return ErrReadOnly } if newv.Signature() != dbus.SignatureOf(prop.Value) { return ErrInvalidArg } if prop.Callback != nil { err := prop.Callback(&Change{p, iface, property, newv.Value()}) if err != nil { return err } } p.set(iface, property, newv.Value()) return nil } // SetMust sets the value of the given property and panics if the interface or // the property name are invalid. func (p *Properties) SetMust(iface, property string, v interface{}) { p.mut.Lock() p.set(iface, property, v) p.mut.Unlock() } ./service-ng/src/github.com/godbus/dbus/decoder.go0000644000015600001650000001176512675036254022165 0ustar jenkinsjenkinspackage dbus import ( "encoding/binary" "io" "reflect" ) type decoder struct { in io.Reader order binary.ByteOrder pos int } // newDecoder returns a new decoder that reads values from in. The input is // expected to be in the given byte order. func newDecoder(in io.Reader, order binary.ByteOrder) *decoder { dec := new(decoder) dec.in = in dec.order = order return dec } // align aligns the input to the given boundary and panics on error. func (dec *decoder) align(n int) { if dec.pos%n != 0 { newpos := (dec.pos + n - 1) & ^(n - 1) empty := make([]byte, newpos-dec.pos) if _, err := io.ReadFull(dec.in, empty); err != nil { panic(err) } dec.pos = newpos } } // Calls binary.Read(dec.in, dec.order, v) and panics on read errors. func (dec *decoder) binread(v interface{}) { if err := binary.Read(dec.in, dec.order, v); err != nil { panic(err) } } func (dec *decoder) Decode(sig Signature) (vs []interface{}, err error) { defer func() { var ok bool v := recover() if err, ok = v.(error); ok { if err == io.EOF || err == io.ErrUnexpectedEOF { err = FormatError("unexpected EOF") } } }() vs = make([]interface{}, 0) s := sig.str for s != "" { err, rem := validSingle(s, 0) if err != nil { return nil, err } v := dec.decode(s[:len(s)-len(rem)], 0) vs = append(vs, v) s = rem } return vs, nil } func (dec *decoder) decode(s string, depth int) interface{} { dec.align(alignment(typeFor(s))) switch s[0] { case 'y': var b [1]byte if _, err := dec.in.Read(b[:]); err != nil { panic(err) } dec.pos++ return b[0] case 'b': i := dec.decode("u", depth).(uint32) switch { case i == 0: return false case i == 1: return true default: panic(FormatError("invalid value for boolean")) } case 'n': var i int16 dec.binread(&i) dec.pos += 2 return i case 'i': var i int32 dec.binread(&i) dec.pos += 4 return i case 'x': var i int64 dec.binread(&i) dec.pos += 8 return i case 'q': var i uint16 dec.binread(&i) dec.pos += 2 return i case 'u': var i uint32 dec.binread(&i) dec.pos += 4 return i case 't': var i uint64 dec.binread(&i) dec.pos += 8 return i case 'd': var f float64 dec.binread(&f) dec.pos += 8 return f case 's': length := dec.decode("u", depth).(uint32) b := make([]byte, int(length)+1) if _, err := io.ReadFull(dec.in, b); err != nil { panic(err) } dec.pos += int(length) + 1 return string(b[:len(b)-1]) case 'o': return ObjectPath(dec.decode("s", depth).(string)) case 'g': length := dec.decode("y", depth).(byte) b := make([]byte, int(length)+1) if _, err := io.ReadFull(dec.in, b); err != nil { panic(err) } dec.pos += int(length) + 1 sig, err := ParseSignature(string(b[:len(b)-1])) if err != nil { panic(err) } return sig case 'v': if depth >= 64 { panic(FormatError("input exceeds container depth limit")) } var variant Variant sig := dec.decode("g", depth).(Signature) if len(sig.str) == 0 { panic(FormatError("variant signature is empty")) } err, rem := validSingle(sig.str, 0) if err != nil { panic(err) } if rem != "" { panic(FormatError("variant signature has multiple types")) } variant.sig = sig variant.value = dec.decode(sig.str, depth+1) return variant case 'h': return UnixFDIndex(dec.decode("u", depth).(uint32)) case 'a': if len(s) > 1 && s[1] == '{' { ksig := s[2:3] vsig := s[3 : len(s)-1] v := reflect.MakeMap(reflect.MapOf(typeFor(ksig), typeFor(vsig))) if depth >= 63 { panic(FormatError("input exceeds container depth limit")) } length := dec.decode("u", depth).(uint32) // Even for empty maps, the correct padding must be included dec.align(8) spos := dec.pos for dec.pos < spos+int(length) { dec.align(8) if !isKeyType(v.Type().Key()) { panic(InvalidTypeError{v.Type()}) } kv := dec.decode(ksig, depth+2) vv := dec.decode(vsig, depth+2) v.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv)) } return v.Interface() } if depth >= 64 { panic(FormatError("input exceeds container depth limit")) } length := dec.decode("u", depth).(uint32) v := reflect.MakeSlice(reflect.SliceOf(typeFor(s[1:])), 0, int(length)) // Even for empty arrays, the correct padding must be included dec.align(alignment(typeFor(s[1:]))) spos := dec.pos for dec.pos < spos+int(length) { ev := dec.decode(s[1:], depth+1) v = reflect.Append(v, reflect.ValueOf(ev)) } return v.Interface() case '(': if depth >= 64 { panic(FormatError("input exceeds container depth limit")) } dec.align(8) v := make([]interface{}, 0) s = s[1 : len(s)-1] for s != "" { err, rem := validSingle(s, 0) if err != nil { panic(err) } ev := dec.decode(s[:len(s)-len(rem)], depth+1) v = append(v, ev) s = rem } return v default: panic(SignatureError{Sig: s}) } } // A FormatError is an error in the wire format. type FormatError string func (e FormatError) Error() string { return "dbus: wire format error: " + string(e) } ./service-ng/src/github.com/godbus/dbus/auth.go0000644000015600001650000001522512675036254021514 0ustar jenkinsjenkinspackage dbus import ( "bufio" "bytes" "errors" "io" "os" "strconv" ) // AuthStatus represents the Status of an authentication mechanism. type AuthStatus byte const ( // AuthOk signals that authentication is finished; the next command // from the server should be an OK. AuthOk AuthStatus = iota // AuthContinue signals that additional data is needed; the next command // from the server should be a DATA. AuthContinue // AuthError signals an error; the server sent invalid data or some // other unexpected thing happened and the current authentication // process should be aborted. AuthError ) type authState byte const ( waitingForData authState = iota waitingForOk waitingForReject ) // Auth defines the behaviour of an authentication mechanism. type Auth interface { // Return the name of the mechnism, the argument to the first AUTH command // and the next status. FirstData() (name, resp []byte, status AuthStatus) // Process the given DATA command, and return the argument to the DATA // command and the next status. If len(resp) == 0, no DATA command is sent. HandleData(data []byte) (resp []byte, status AuthStatus) } // Auth authenticates the connection, trying the given list of authentication // mechanisms (in that order). If nil is passed, the EXTERNAL and // DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private // connections, this method must be called before sending any messages to the // bus. Auth must not be called on shared connections. func (conn *Conn) Auth(methods []Auth) error { if methods == nil { uid := strconv.Itoa(os.Getuid()) methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())} } in := bufio.NewReader(conn.transport) err := conn.transport.SendNullByte() if err != nil { return err } err = authWriteLine(conn.transport, []byte("AUTH")) if err != nil { return err } s, err := authReadLine(in) if err != nil { return err } if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) { return errors.New("dbus: authentication protocol error") } s = s[1:] for _, v := range s { for _, m := range methods { if name, data, status := m.FirstData(); bytes.Equal(v, name) { var ok bool err = authWriteLine(conn.transport, []byte("AUTH"), []byte(v), data) if err != nil { return err } switch status { case AuthOk: err, ok = conn.tryAuth(m, waitingForOk, in) case AuthContinue: err, ok = conn.tryAuth(m, waitingForData, in) default: panic("dbus: invalid authentication status") } if err != nil { return err } if ok { if conn.transport.SupportsUnixFDs() { err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD")) if err != nil { return err } line, err := authReadLine(in) if err != nil { return err } switch { case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")): conn.EnableUnixFDs() conn.unixFD = true case bytes.Equal(line[0], []byte("ERROR")): default: return errors.New("dbus: authentication protocol error") } } err = authWriteLine(conn.transport, []byte("BEGIN")) if err != nil { return err } go conn.inWorker() go conn.outWorker() return nil } } } } return errors.New("dbus: authentication failed") } // tryAuth tries to authenticate with m as the mechanism, using state as the // initial authState and in for reading input. It returns (nil, true) on // success, (nil, false) on a REJECTED and (someErr, false) if some other // error occured. func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) { for { s, err := authReadLine(in) if err != nil { return err, false } switch { case state == waitingForData && string(s[0]) == "DATA": if len(s) != 2 { err = authWriteLine(conn.transport, []byte("ERROR")) if err != nil { return err, false } continue } data, status := m.HandleData(s[1]) switch status { case AuthOk, AuthContinue: if len(data) != 0 { err = authWriteLine(conn.transport, []byte("DATA"), data) if err != nil { return err, false } } if status == AuthOk { state = waitingForOk } case AuthError: err = authWriteLine(conn.transport, []byte("ERROR")) if err != nil { return err, false } } case state == waitingForData && string(s[0]) == "REJECTED": return nil, false case state == waitingForData && string(s[0]) == "ERROR": err = authWriteLine(conn.transport, []byte("CANCEL")) if err != nil { return err, false } state = waitingForReject case state == waitingForData && string(s[0]) == "OK": if len(s) != 2 { err = authWriteLine(conn.transport, []byte("CANCEL")) if err != nil { return err, false } state = waitingForReject } conn.uuid = string(s[1]) return nil, true case state == waitingForData: err = authWriteLine(conn.transport, []byte("ERROR")) if err != nil { return err, false } case state == waitingForOk && string(s[0]) == "OK": if len(s) != 2 { err = authWriteLine(conn.transport, []byte("CANCEL")) if err != nil { return err, false } state = waitingForReject } conn.uuid = string(s[1]) return nil, true case state == waitingForOk && string(s[0]) == "REJECTED": return nil, false case state == waitingForOk && (string(s[0]) == "DATA" || string(s[0]) == "ERROR"): err = authWriteLine(conn.transport, []byte("CANCEL")) if err != nil { return err, false } state = waitingForReject case state == waitingForOk: err = authWriteLine(conn.transport, []byte("ERROR")) if err != nil { return err, false } case state == waitingForReject && string(s[0]) == "REJECTED": return nil, false case state == waitingForReject: return errors.New("dbus: authentication protocol error"), false default: panic("dbus: invalid auth state") } } } // authReadLine reads a line and separates it into its fields. func authReadLine(in *bufio.Reader) ([][]byte, error) { data, err := in.ReadBytes('\n') if err != nil { return nil, err } data = bytes.TrimSuffix(data, []byte("\r\n")) return bytes.Split(data, []byte{' '}), nil } // authWriteLine writes the given line in the authentication protocol format // (elements of data separated by a " " and terminated by "\r\n"). func authWriteLine(out io.Writer, data ...[]byte) error { buf := make([]byte, 0) for i, v := range data { buf = append(buf, v...) if i != len(data)-1 { buf = append(buf, ' ') } } buf = append(buf, '\r') buf = append(buf, '\n') n, err := out.Write(buf) if err != nil { return err } if n != len(buf) { return io.ErrUnexpectedEOF } return nil } ./service-ng/src/github.com/godbus/dbus/_examples/0000755000015600001650000000000012675036254022174 5ustar jenkinsjenkins./service-ng/src/github.com/godbus/dbus/_examples/prop.go0000644000015600001650000000301312675036254023500 0ustar jenkinsjenkinspackage main import ( "fmt" "github.com/godbus/dbus" "github.com/godbus/dbus/introspect" "github.com/godbus/dbus/prop" "os" ) type foo string func (f foo) Foo() (string, *dbus.Error) { fmt.Println(f) return string(f), nil } func main() { conn, err := dbus.SessionBus() if err != nil { panic(err) } reply, err := conn.RequestName("com.github.guelfey.Demo", dbus.NameFlagDoNotQueue) if err != nil { panic(err) } if reply != dbus.RequestNameReplyPrimaryOwner { fmt.Fprintln(os.Stderr, "name already taken") os.Exit(1) } propsSpec := map[string]map[string]*prop.Prop{ "com.github.guelfey.Demo": { "SomeInt": { int32(0), true, prop.EmitTrue, func(c *prop.Change) *dbus.Error { fmt.Println(c.Name, "changed to", c.Value) return nil }, }, }, } f := foo("Bar") conn.Export(f, "/com/github/guelfey/Demo", "com.github.guelfey.Demo") props := prop.New(conn, "/com/github/guelfey/Demo", propsSpec) n := &introspect.Node{ Name: "/com/github/guelfey/Demo", Interfaces: []introspect.Interface{ introspect.IntrospectData, prop.IntrospectData, { Name: "com.github.guelfey.Demo", Methods: introspect.Methods(f), Properties: props.Introspection("com.github.guelfey.Demo"), }, }, } conn.Export(introspect.NewIntrospectable(n), "/com/github/guelfey/Demo", "org.freedesktop.DBus.Introspectable") fmt.Println("Listening on com.github.guelfey.Demo / /com/github/guelfey/Demo ...") c := make(chan *dbus.Signal) conn.Signal(c) for _ = range c { } } ./service-ng/src/github.com/godbus/dbus/_examples/list-names.go0000644000015600001650000000077712675036254024612 0ustar jenkinsjenkinspackage main import ( "fmt" "github.com/godbus/dbus" "os" ) func main() { conn, err := dbus.SessionBus() if err != nil { fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err) os.Exit(1) } var s []string err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&s) if err != nil { fmt.Fprintln(os.Stderr, "Failed to get list of owned names:", err) os.Exit(1) } fmt.Println("Currently owned names on the session bus:") for _, v := range s { fmt.Println(v) } } ./service-ng/src/github.com/godbus/dbus/_examples/eavesdrop.go0000644000015600001650000000120012675036254024504 0ustar jenkinsjenkinspackage main import ( "fmt" "github.com/godbus/dbus" "os" ) func main() { conn, err := dbus.SessionBus() if err != nil { fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err) os.Exit(1) } for _, v := range []string{"method_call", "method_return", "error", "signal"} { call := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, "eavesdrop='true',type='"+v+"'") if call.Err != nil { fmt.Fprintln(os.Stderr, "Failed to add match:", call.Err) os.Exit(1) } } c := make(chan *dbus.Message, 10) conn.Eavesdrop(c) fmt.Println("Listening for everything") for v := range c { fmt.Println(v) } } ./service-ng/src/github.com/godbus/dbus/_examples/server.go0000644000015600001650000000177012675036254024036 0ustar jenkinsjenkinspackage main import ( "fmt" "github.com/godbus/dbus" "github.com/godbus/dbus/introspect" "os" ) const intro = ` ` + introspect.IntrospectDataString + ` ` type foo string func (f foo) Foo() (string, *dbus.Error) { fmt.Println(f) return string(f), nil } func main() { conn, err := dbus.SessionBus() if err != nil { panic(err) } reply, err := conn.RequestName("com.github.guelfey.Demo", dbus.NameFlagDoNotQueue) if err != nil { panic(err) } if reply != dbus.RequestNameReplyPrimaryOwner { fmt.Fprintln(os.Stderr, "name already taken") os.Exit(1) } f := foo("Bar!") conn.Export(f, "/com/github/guelfey/Demo", "com.github.guelfey.Demo") conn.Export(introspect.Introspectable(intro), "/com/github/guelfey/Demo", "org.freedesktop.DBus.Introspectable") fmt.Println("Listening on com.github.guelfey.Demo / /com/github/guelfey/Demo ...") select {} } ./service-ng/src/github.com/godbus/dbus/_examples/notification.go0000644000015600001650000000070112675036254025207 0ustar jenkinsjenkinspackage main import "github.com/godbus/dbus" func main() { conn, err := dbus.SessionBus() if err != nil { panic(err) } obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") call := obj.Call("org.freedesktop.Notifications.Notify", 0, "", uint32(0), "", "Test", "This is a test of the DBus bindings for go.", []string{}, map[string]dbus.Variant{}, int32(5000)) if call.Err != nil { panic(call.Err) } } ./service-ng/src/github.com/godbus/dbus/_examples/signal.go0000644000015600001650000000073412675036254024004 0ustar jenkinsjenkinspackage main import ( "fmt" "github.com/godbus/dbus" "os" ) func main() { conn, err := dbus.SessionBus() if err != nil { fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err) os.Exit(1) } conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, "type='signal',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',sender='org.freedesktop.DBus'") c := make(chan *dbus.Signal, 10) conn.Signal(c) for v := range c { fmt.Println(v) } } ./service-ng/src/github.com/godbus/dbus/_examples/introspect.go0000644000015600001650000000060612675036254024717 0ustar jenkinsjenkinspackage main import ( "encoding/json" "github.com/godbus/dbus" "github.com/godbus/dbus/introspect" "os" ) func main() { conn, err := dbus.SessionBus() if err != nil { panic(err) } node, err := introspect.Call(conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")) if err != nil { panic(err) } data, _ := json.MarshalIndent(node, "", " ") os.Stdout.Write(data) } ./service-ng/src/github.com/godbus/dbus/object.go0000644000015600001650000000671012675036254022020 0ustar jenkinsjenkinspackage dbus import ( "errors" "strings" ) // BusObject is the interface of a remote object on which methods can be // invoked. type BusObject interface { Call(method string, flags Flags, args ...interface{}) *Call Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call GetProperty(p string) (Variant, error) Destination() string Path() ObjectPath } // Object represents a remote object on which methods can be invoked. type Object struct { conn *Conn dest string path ObjectPath } // Call calls a method with (*Object).Go and waits for its reply. func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call { return <-o.Go(method, flags, make(chan *Call, 1), args...).Done } // Go calls a method with the given arguments asynchronously. It returns a // Call structure representing this method call. The passed channel will // return the same value once the call is done. If ch is nil, a new channel // will be allocated. Otherwise, ch has to be buffered or Go will panic. // // If the flags include FlagNoReplyExpected, ch is ignored and a Call structure // is returned of which only the Err member is valid. // // If the method parameter contains a dot ('.'), the part before the last dot // specifies the interface on which the method is called. func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call { iface := "" i := strings.LastIndex(method, ".") if i != -1 { iface = method[:i] } method = method[i+1:] msg := new(Message) msg.Type = TypeMethodCall msg.serial = o.conn.getSerial() msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected) msg.Headers = make(map[HeaderField]Variant) msg.Headers[FieldPath] = MakeVariant(o.path) msg.Headers[FieldDestination] = MakeVariant(o.dest) msg.Headers[FieldMember] = MakeVariant(method) if iface != "" { msg.Headers[FieldInterface] = MakeVariant(iface) } msg.Body = args if len(args) > 0 { msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...)) } if msg.Flags&FlagNoReplyExpected == 0 { if ch == nil { ch = make(chan *Call, 10) } else if cap(ch) == 0 { panic("dbus: unbuffered channel passed to (*Object).Go") } call := &Call{ Destination: o.dest, Path: o.path, Method: method, Args: args, Done: ch, } o.conn.callsLck.Lock() o.conn.calls[msg.serial] = call o.conn.callsLck.Unlock() o.conn.outLck.RLock() if o.conn.closed { call.Err = ErrClosed call.Done <- call } else { o.conn.out <- msg } o.conn.outLck.RUnlock() return call } o.conn.outLck.RLock() defer o.conn.outLck.RUnlock() if o.conn.closed { return &Call{Err: ErrClosed} } o.conn.out <- msg return &Call{Err: nil} } // GetProperty calls org.freedesktop.DBus.Properties.GetProperty on the given // object. The property name must be given in interface.member notation. func (o *Object) GetProperty(p string) (Variant, error) { idx := strings.LastIndex(p, ".") if idx == -1 || idx+1 == len(p) { return Variant{}, errors.New("dbus: invalid property " + p) } iface := p[:idx] prop := p[idx+1:] result := Variant{} err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result) if err != nil { return Variant{}, err } return result, nil } // Destination returns the destination that calls on o are sent to. func (o *Object) Destination() string { return o.dest } // Path returns the path that calls on o are sent to. func (o *Object) Path() ObjectPath { return o.path } ./service-ng/src/github.com/godbus/dbus/variant_test.go0000644000015600001650000000433112675036254023252 0ustar jenkinsjenkinspackage dbus import "reflect" import "testing" var variantFormatTests = []struct { v interface{} s string }{ {int32(1), `1`}, {"foo", `"foo"`}, {ObjectPath("/org/foo"), `@o "/org/foo"`}, {Signature{"i"}, `@g "i"`}, {[]byte{}, `@ay []`}, {[]int32{1, 2}, `[1, 2]`}, {[]int64{1, 2}, `@ax [1, 2]`}, {[][]int32{{3, 4}, {5, 6}}, `[[3, 4], [5, 6]]`}, {[]Variant{MakeVariant(int32(1)), MakeVariant(1.0)}, `[<1>, <@d 1>]`}, {map[string]int32{"one": 1, "two": 2}, `{"one": 1, "two": 2}`}, {map[int32]ObjectPath{1: "/org/foo"}, `@a{io} {1: "/org/foo"}`}, {map[string]Variant{}, `@a{sv} {}`}, } func TestFormatVariant(t *testing.T) { for i, v := range variantFormatTests { if s := MakeVariant(v.v).String(); s != v.s { t.Errorf("test %d: got %q, wanted %q", i+1, s, v.s) } } } var variantParseTests = []struct { s string v interface{} }{ {"1", int32(1)}, {"true", true}, {"false", false}, {"1.0", float64(1.0)}, {"0x10", int32(16)}, {"1e1", float64(10)}, {`"foo"`, "foo"}, {`"\a\b\f\n\r\t"`, "\x07\x08\x0c\n\r\t"}, {`"\u00e4\U0001f603"`, "\u00e4\U0001f603"}, {"[1]", []int32{1}}, {"[1, 2, 3]", []int32{1, 2, 3}}, {"@ai []", []int32{}}, {"[1, 5.0]", []float64{1, 5.0}}, {"[[1, 2], [3, 4.0]]", [][]float64{{1, 2}, {3, 4}}}, {`[@o "/org/foo", "/org/bar"]`, []ObjectPath{"/org/foo", "/org/bar"}}, {"<1>", MakeVariant(int32(1))}, {"[<1>, <2.0>]", []Variant{MakeVariant(int32(1)), MakeVariant(2.0)}}, {`[[], [""]]`, [][]string{{}, {""}}}, {`@a{ss} {}`, map[string]string{}}, {`{"foo": 1}`, map[string]int32{"foo": 1}}, {`[{}, {"foo": "bar"}]`, []map[string]string{{}, {"foo": "bar"}}}, {`{"a": <1>, "b": <"foo">}`, map[string]Variant{"a": MakeVariant(int32(1)), "b": MakeVariant("foo")}}, {`b''`, []byte{0}}, {`b"abc"`, []byte{'a', 'b', 'c', 0}}, {`b"\x01\0002\a\b\f\n\r\t"`, []byte{1, 2, 0x7, 0x8, 0xc, '\n', '\r', '\t', 0}}, {`[[0], b""]`, [][]byte{{0}, {0}}}, {"int16 0", int16(0)}, {"byte 0", byte(0)}, } func TestParseVariant(t *testing.T) { for i, v := range variantParseTests { nv, err := ParseVariant(v.s, Signature{}) if err != nil { t.Errorf("test %d: parsing failed: %s", i+1, err) continue } if !reflect.DeepEqual(nv.value, v.v) { t.Errorf("test %d: got %q, wanted %q", i+1, nv, v.v) } } } ./service-ng/src/github.com/godbus/dbus/variant.go0000644000015600001650000000634312675036254022220 0ustar jenkinsjenkinspackage dbus import ( "bytes" "fmt" "reflect" "sort" "strconv" ) // Variant represents the D-Bus variant type. type Variant struct { sig Signature value interface{} } // MakeVariant converts the given value to a Variant. It panics if v cannot be // represented as a D-Bus type. func MakeVariant(v interface{}) Variant { return Variant{SignatureOf(v), v} } // ParseVariant parses the given string as a variant as described at // https://developer.gnome.org/glib/unstable/gvariant-text.html. If sig is not // empty, it is taken to be the expected signature for the variant. func ParseVariant(s string, sig Signature) (Variant, error) { tokens := varLex(s) p := &varParser{tokens: tokens} n, err := varMakeNode(p) if err != nil { return Variant{}, err } if sig.str == "" { sig, err = varInfer(n) if err != nil { return Variant{}, err } } v, err := n.Value(sig) if err != nil { return Variant{}, err } return MakeVariant(v), nil } // format returns a formatted version of v and whether this string can be parsed // unambigously. func (v Variant) format() (string, bool) { switch v.sig.str[0] { case 'b', 'i': return fmt.Sprint(v.value), true case 'n', 'q', 'u', 'x', 't', 'd', 'h': return fmt.Sprint(v.value), false case 's': return strconv.Quote(v.value.(string)), true case 'o': return strconv.Quote(string(v.value.(ObjectPath))), false case 'g': return strconv.Quote(v.value.(Signature).str), false case 'v': s, unamb := v.value.(Variant).format() if !unamb { return "<@" + v.value.(Variant).sig.str + " " + s + ">", true } return "<" + s + ">", true case 'y': return fmt.Sprintf("%#x", v.value.(byte)), false } rv := reflect.ValueOf(v.value) switch rv.Kind() { case reflect.Slice: if rv.Len() == 0 { return "[]", false } unamb := true buf := bytes.NewBuffer([]byte("[")) for i := 0; i < rv.Len(); i++ { // TODO: slooow s, b := MakeVariant(rv.Index(i).Interface()).format() unamb = unamb && b buf.WriteString(s) if i != rv.Len()-1 { buf.WriteString(", ") } } buf.WriteByte(']') return buf.String(), unamb case reflect.Map: if rv.Len() == 0 { return "{}", false } unamb := true var buf bytes.Buffer kvs := make([]string, rv.Len()) for i, k := range rv.MapKeys() { s, b := MakeVariant(k.Interface()).format() unamb = unamb && b buf.Reset() buf.WriteString(s) buf.WriteString(": ") s, b = MakeVariant(rv.MapIndex(k).Interface()).format() unamb = unamb && b buf.WriteString(s) kvs[i] = buf.String() } buf.Reset() buf.WriteByte('{') sort.Strings(kvs) for i, kv := range kvs { if i > 0 { buf.WriteString(", ") } buf.WriteString(kv) } buf.WriteByte('}') return buf.String(), unamb } return `"INVALID"`, true } // Signature returns the D-Bus signature of the underlying value of v. func (v Variant) Signature() Signature { return v.sig } // String returns the string representation of the underlying value of v as // described at https://developer.gnome.org/glib/unstable/gvariant-text.html. func (v Variant) String() string { s, unamb := v.format() if !unamb { return "@" + v.sig.str + " " + s } return s } // Value returns the underlying value of v. func (v Variant) Value() interface{} { return v.value } ./service-ng/src/github.com/godbus/dbus/transport_generic.go0000644000015600001650000000120412675036254024273 0ustar jenkinsjenkinspackage dbus import ( "encoding/binary" "errors" "io" ) type genericTransport struct { io.ReadWriteCloser } func (t genericTransport) SendNullByte() error { _, err := t.Write([]byte{0}) return err } func (t genericTransport) SupportsUnixFDs() bool { return false } func (t genericTransport) EnableUnixFDs() {} func (t genericTransport) ReadMessage() (*Message, error) { return DecodeMessage(t) } func (t genericTransport) SendMessage(msg *Message) error { for _, v := range msg.Body { if _, ok := v.(UnixFD); ok { return errors.New("dbus: unix fd passing not enabled") } } return msg.EncodeTo(t, binary.LittleEndian) } ./service-ng/src/github.com/godbus/dbus/conn_other.go0000644000015600001650000000065412675036254022711 0ustar jenkinsjenkins// +build !darwin package dbus import ( "bytes" "errors" "os/exec" ) func sessionBusPlatform() (*Conn, error) { cmd := exec.Command("dbus-launch") b, err := cmd.CombinedOutput() if err != nil { return nil, err } i := bytes.IndexByte(b, '=') j := bytes.IndexByte(b, '\n') if i == -1 || j == -1 { return nil, errors.New("dbus: couldn't determine address of session bus") } return Dial(string(b[i+1 : j])) } ./service-ng/src/github.com/godbus/dbus/MAINTAINERS0000644000015600001650000000013112675036254021707 0ustar jenkinsjenkinsBrandon Philips (@philips) Brian Waldon (@bcwaldon) ./service-ng/src/github.com/godbus/dbus/export.go0000644000015600001650000003014412675036254022071 0ustar jenkinsjenkinspackage dbus import ( "errors" "fmt" "reflect" "strings" ) var ( errmsgInvalidArg = Error{ "org.freedesktop.DBus.Error.InvalidArgs", []interface{}{"Invalid type / number of args"}, } errmsgNoObject = Error{ "org.freedesktop.DBus.Error.NoSuchObject", []interface{}{"No such object"}, } errmsgUnknownMethod = Error{ "org.freedesktop.DBus.Error.UnknownMethod", []interface{}{"Unknown / invalid method"}, } ) // exportWithMapping represents an exported struct along with a method name // mapping to allow for exporting lower-case methods, etc. type exportWithMapping struct { export interface{} // Method name mapping; key -> struct method, value -> dbus method. mapping map[string]string // Whether or not this export is for the entire subtree includeSubtree bool } // Sender is a type which can be used in exported methods to receive the message // sender. type Sender string func exportedMethod(export exportWithMapping, name string) reflect.Value { if export.export == nil { return reflect.Value{} } // If a mapping was included in the export, check the map to see if we // should be looking for a different method in the export. if export.mapping != nil { for key, value := range export.mapping { if value == name { name = key break } // Catch the case where a method is aliased but the client is calling // the original, e.g. the "Foo" method was exported mapped to // "foo," and dbus client called the original "Foo." if key == name { return reflect.Value{} } } } value := reflect.ValueOf(export.export) m := value.MethodByName(name) // Catch the case of attempting to call an unexported method method, ok := value.Type().MethodByName(name) if !m.IsValid() || !ok || method.PkgPath != "" { return reflect.Value{} } t := m.Type() if t.NumOut() == 0 || t.Out(t.NumOut()-1) != reflect.TypeOf(&errmsgInvalidArg) { return reflect.Value{} } return m } // searchHandlers will look through all registered handlers looking for one // to handle the given path. If a verbatim one isn't found, it will check for // a subtree registration for the path as well. func (conn *Conn) searchHandlers(path ObjectPath) (map[string]exportWithMapping, bool) { conn.handlersLck.RLock() defer conn.handlersLck.RUnlock() handlers, ok := conn.handlers[path] if ok { return handlers, ok } // If handlers weren't found for this exact path, look for a matching subtree // registration handlers = make(map[string]exportWithMapping) path = path[:strings.LastIndex(string(path), "/")] for len(path) > 0 { var subtreeHandlers map[string]exportWithMapping subtreeHandlers, ok = conn.handlers[path] if ok { for iface, handler := range subtreeHandlers { // Only include this handler if it registered for the subtree if handler.includeSubtree { handlers[iface] = handler } } break } path = path[:strings.LastIndex(string(path), "/")] } return handlers, ok } // handleCall handles the given method call (i.e. looks if it's one of the // pre-implemented ones and searches for a corresponding handler if not). func (conn *Conn) handleCall(msg *Message) { name := msg.Headers[FieldMember].value.(string) path := msg.Headers[FieldPath].value.(ObjectPath) ifaceName, hasIface := msg.Headers[FieldInterface].value.(string) sender, hasSender := msg.Headers[FieldSender].value.(string) serial := msg.serial if ifaceName == "org.freedesktop.DBus.Peer" { switch name { case "Ping": conn.sendReply(sender, serial) case "GetMachineId": conn.sendReply(sender, serial, conn.uuid) default: conn.sendError(errmsgUnknownMethod, sender, serial) } return } if len(name) == 0 { conn.sendError(errmsgUnknownMethod, sender, serial) } // Find the exported handler (if any) for this path handlers, ok := conn.searchHandlers(path) if !ok { conn.sendError(errmsgNoObject, sender, serial) return } var m reflect.Value if hasIface { iface := handlers[ifaceName] m = exportedMethod(iface, name) } else { for _, v := range handlers { m = exportedMethod(v, name) if m.IsValid() { break } } } if !m.IsValid() { conn.sendError(errmsgUnknownMethod, sender, serial) return } t := m.Type() vs := msg.Body pointers := make([]interface{}, t.NumIn()) decode := make([]interface{}, 0, len(vs)) for i := 0; i < t.NumIn(); i++ { tp := t.In(i) val := reflect.New(tp) pointers[i] = val.Interface() if tp == reflect.TypeOf((*Sender)(nil)).Elem() { val.Elem().SetString(sender) } else if tp == reflect.TypeOf((*Message)(nil)).Elem() { val.Elem().Set(reflect.ValueOf(*msg)) } else { decode = append(decode, pointers[i]) } } if len(decode) != len(vs) { conn.sendError(errmsgInvalidArg, sender, serial) return } if err := Store(vs, decode...); err != nil { conn.sendError(errmsgInvalidArg, sender, serial) return } // Extract parameters params := make([]reflect.Value, len(pointers)) for i := 0; i < len(pointers); i++ { params[i] = reflect.ValueOf(pointers[i]).Elem() } // Call method ret := m.Call(params) if em := ret[t.NumOut()-1].Interface().(*Error); em != nil { conn.sendError(*em, sender, serial) return } if msg.Flags&FlagNoReplyExpected == 0 { reply := new(Message) reply.Type = TypeMethodReply reply.serial = conn.getSerial() reply.Headers = make(map[HeaderField]Variant) if hasSender { reply.Headers[FieldDestination] = msg.Headers[FieldSender] } reply.Headers[FieldReplySerial] = MakeVariant(msg.serial) reply.Body = make([]interface{}, len(ret)-1) for i := 0; i < len(ret)-1; i++ { reply.Body[i] = ret[i].Interface() } if len(ret) != 1 { reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...)) } conn.outLck.RLock() if !conn.closed { conn.out <- reply } conn.outLck.RUnlock() } } // Emit emits the given signal on the message bus. The name parameter must be // formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost". func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error { if !path.IsValid() { return errors.New("dbus: invalid object path") } i := strings.LastIndex(name, ".") if i == -1 { return errors.New("dbus: invalid method name") } iface := name[:i] member := name[i+1:] if !isValidMember(member) { return errors.New("dbus: invalid method name") } if !isValidInterface(iface) { return errors.New("dbus: invalid interface name") } msg := new(Message) msg.Type = TypeSignal msg.serial = conn.getSerial() msg.Headers = make(map[HeaderField]Variant) msg.Headers[FieldInterface] = MakeVariant(iface) msg.Headers[FieldMember] = MakeVariant(member) msg.Headers[FieldPath] = MakeVariant(path) msg.Body = values if len(values) > 0 { msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...)) } conn.outLck.RLock() defer conn.outLck.RUnlock() if conn.closed { return ErrClosed } conn.out <- msg return nil } // Export registers the given value to be exported as an object on the // message bus. // // If a method call on the given path and interface is received, an exported // method with the same name is called with v as the receiver if the // parameters match and the last return value is of type *Error. If this // *Error is not nil, it is sent back to the caller as an error. // Otherwise, a method reply is sent with the other return values as its body. // // Any parameters with the special type Sender are set to the sender of the // dbus message when the method is called. Parameters of this type do not // contribute to the dbus signature of the method (i.e. the method is exposed // as if the parameters of type Sender were not there). // // Similarly, any parameters with the type Message are set to the raw message // received on the bus. Again, parameters of this type do not contribute to the // dbus signature of the method. // // Every method call is executed in a new goroutine, so the method may be called // in multiple goroutines at once. // // Method calls on the interface org.freedesktop.DBus.Peer will be automatically // handled for every object. // // Passing nil as the first parameter will cause conn to cease handling calls on // the given combination of path and interface. // // Export returns an error if path is not a valid path name. func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error { return conn.ExportWithMap(v, nil, path, iface) } // ExportWithMap works exactly like Export but provides the ability to remap // method names (e.g. export a lower-case method). // // The keys in the map are the real method names (exported on the struct), and // the values are the method names to be exported on DBus. func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error { return conn.exportWithMap(v, mapping, path, iface, false) } // ExportSubtree works exactly like Export but registers the given value for // an entire subtree rather under the root path provided. // // In order to make this useful, one parameter in each of the value's exported // methods should be a Message, in which case it will contain the raw message // (allowing one to get access to the path that caused the method to be called). // // Note that more specific export paths take precedence over less specific. For // example, a method call using the ObjectPath /foo/bar/baz will call a method // exported on /foo/bar before a method exported on /foo. func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error { return conn.ExportSubtreeWithMap(v, nil, path, iface) } // ExportSubtreeWithMap works exactly like ExportSubtree but provides the // ability to remap method names (e.g. export a lower-case method). // // The keys in the map are the real method names (exported on the struct), and // the values are the method names to be exported on DBus. func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error { return conn.exportWithMap(v, mapping, path, iface, true) } // exportWithMap is the worker function for all exports/registrations. func (conn *Conn) exportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string, includeSubtree bool) error { if !path.IsValid() { return fmt.Errorf(`dbus: Invalid path name: "%s"`, path) } conn.handlersLck.Lock() defer conn.handlersLck.Unlock() // Remove a previous export if the interface is nil if v == nil { if _, ok := conn.handlers[path]; ok { delete(conn.handlers[path], iface) if len(conn.handlers[path]) == 0 { delete(conn.handlers, path) } } return nil } // If this is the first handler for this path, make a new map to hold all // handlers for this path. if _, ok := conn.handlers[path]; !ok { conn.handlers[path] = make(map[string]exportWithMapping) } // Finally, save this handler conn.handlers[path][iface] = exportWithMapping{export: v, mapping: mapping, includeSubtree: includeSubtree} return nil } // ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response. func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) { var r uint32 err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r) if err != nil { return 0, err } return ReleaseNameReply(r), nil } // RequestName calls org.freedesktop.DBus.RequestName and awaits a response. func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) { var r uint32 err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r) if err != nil { return 0, err } return RequestNameReply(r), nil } // ReleaseNameReply is the reply to a ReleaseName call. type ReleaseNameReply uint32 const ( ReleaseNameReplyReleased ReleaseNameReply = 1 + iota ReleaseNameReplyNonExistent ReleaseNameReplyNotOwner ) // RequestNameFlags represents the possible flags for a RequestName call. type RequestNameFlags uint32 const ( NameFlagAllowReplacement RequestNameFlags = 1 << iota NameFlagReplaceExisting NameFlagDoNotQueue ) // RequestNameReply is the reply to a RequestName call. type RequestNameReply uint32 const ( RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota RequestNameReplyInQueue RequestNameReplyExists RequestNameReplyAlreadyOwner ) ./service-ng/src/github.com/godbus/dbus/homedir.go0000644000015600001650000000050512675036254022175 0ustar jenkinsjenkinspackage dbus import ( "os" "sync" ) var ( homeDir string homeDirLock sync.Mutex ) func getHomeDir() string { homeDirLock.Lock() defer homeDirLock.Unlock() if homeDir != "" { return homeDir } homeDir = os.Getenv("HOME") if homeDir != "" { return homeDir } homeDir = lookupHomeDir() return homeDir } ./service-ng/src/github.com/godbus/dbus/dbus.go0000644000015600001650000001431512675036254021507 0ustar jenkinsjenkinspackage dbus import ( "errors" "reflect" "strings" ) var ( byteType = reflect.TypeOf(byte(0)) boolType = reflect.TypeOf(false) uint8Type = reflect.TypeOf(uint8(0)) int16Type = reflect.TypeOf(int16(0)) uint16Type = reflect.TypeOf(uint16(0)) int32Type = reflect.TypeOf(int32(0)) uint32Type = reflect.TypeOf(uint32(0)) int64Type = reflect.TypeOf(int64(0)) uint64Type = reflect.TypeOf(uint64(0)) float64Type = reflect.TypeOf(float64(0)) stringType = reflect.TypeOf("") signatureType = reflect.TypeOf(Signature{""}) objectPathType = reflect.TypeOf(ObjectPath("")) variantType = reflect.TypeOf(Variant{Signature{""}, nil}) interfacesType = reflect.TypeOf([]interface{}{}) unixFDType = reflect.TypeOf(UnixFD(0)) unixFDIndexType = reflect.TypeOf(UnixFDIndex(0)) ) // An InvalidTypeError signals that a value which cannot be represented in the // D-Bus wire format was passed to a function. type InvalidTypeError struct { Type reflect.Type } func (e InvalidTypeError) Error() string { return "dbus: invalid type " + e.Type.String() } // Store copies the values contained in src to dest, which must be a slice of // pointers. It converts slices of interfaces from src to corresponding structs // in dest. An error is returned if the lengths of src and dest or the types of // their elements don't match. func Store(src []interface{}, dest ...interface{}) error { if len(src) != len(dest) { return errors.New("dbus.Store: length mismatch") } for i := range src { if err := store(src[i], dest[i]); err != nil { return err } } return nil } func store(src, dest interface{}) error { if reflect.TypeOf(dest).Elem() == reflect.TypeOf(src) { reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(src)) return nil } else if hasStruct(dest) { rv := reflect.ValueOf(dest).Elem() switch rv.Kind() { case reflect.Struct: vs, ok := src.([]interface{}) if !ok { return errors.New("dbus.Store: type mismatch") } t := rv.Type() ndest := make([]interface{}, 0, rv.NumField()) for i := 0; i < rv.NumField(); i++ { field := t.Field(i) if field.PkgPath == "" && field.Tag.Get("dbus") != "-" { ndest = append(ndest, rv.Field(i).Addr().Interface()) } } if len(vs) != len(ndest) { return errors.New("dbus.Store: type mismatch") } err := Store(vs, ndest...) if err != nil { return errors.New("dbus.Store: type mismatch") } case reflect.Slice: sv := reflect.ValueOf(src) if sv.Kind() != reflect.Slice { return errors.New("dbus.Store: type mismatch") } rv.Set(reflect.MakeSlice(rv.Type(), sv.Len(), sv.Len())) for i := 0; i < sv.Len(); i++ { if err := store(sv.Index(i).Interface(), rv.Index(i).Addr().Interface()); err != nil { return err } } case reflect.Map: sv := reflect.ValueOf(src) if sv.Kind() != reflect.Map { return errors.New("dbus.Store: type mismatch") } keys := sv.MapKeys() rv.Set(reflect.MakeMap(sv.Type())) for _, key := range keys { v := reflect.New(sv.Type().Elem()) if err := store(v, sv.MapIndex(key).Interface()); err != nil { return err } rv.SetMapIndex(key, v.Elem()) } default: return errors.New("dbus.Store: type mismatch") } return nil } else { return errors.New("dbus.Store: type mismatch") } } func hasStruct(v interface{}) bool { t := reflect.TypeOf(v) for { switch t.Kind() { case reflect.Struct: return true case reflect.Slice, reflect.Ptr, reflect.Map: t = t.Elem() default: return false } } } // An ObjectPath is an object path as defined by the D-Bus spec. type ObjectPath string // IsValid returns whether the object path is valid. func (o ObjectPath) IsValid() bool { s := string(o) if len(s) == 0 { return false } if s[0] != '/' { return false } if s[len(s)-1] == '/' && len(s) != 1 { return false } // probably not used, but technically possible if s == "/" { return true } split := strings.Split(s[1:], "/") for _, v := range split { if len(v) == 0 { return false } for _, c := range v { if !isMemberChar(c) { return false } } } return true } // A UnixFD is a Unix file descriptor sent over the wire. See the package-level // documentation for more information about Unix file descriptor passsing. type UnixFD int32 // A UnixFDIndex is the representation of a Unix file descriptor in a message. type UnixFDIndex uint32 // alignment returns the alignment of values of type t. func alignment(t reflect.Type) int { switch t { case variantType: return 1 case objectPathType: return 4 case signatureType: return 1 case interfacesType: // sometimes used for structs return 8 } switch t.Kind() { case reflect.Uint8: return 1 case reflect.Uint16, reflect.Int16: return 2 case reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map: return 4 case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct: return 8 case reflect.Ptr: return alignment(t.Elem()) } return 1 } // isKeyType returns whether t is a valid type for a D-Bus dict. func isKeyType(t reflect.Type) bool { switch t.Kind() { case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64, reflect.String: return true } return false } // isValidInterface returns whether s is a valid name for an interface. func isValidInterface(s string) bool { if len(s) == 0 || len(s) > 255 || s[0] == '.' { return false } elem := strings.Split(s, ".") if len(elem) < 2 { return false } for _, v := range elem { if len(v) == 0 { return false } if v[0] >= '0' && v[0] <= '9' { return false } for _, c := range v { if !isMemberChar(c) { return false } } } return true } // isValidMember returns whether s is a valid name for a member. func isValidMember(s string) bool { if len(s) == 0 || len(s) > 255 { return false } i := strings.Index(s, ".") if i != -1 { return false } if s[0] >= '0' && s[0] <= '9' { return false } for _, c := range s { if !isMemberChar(c) { return false } } return true } func isMemberChar(c rune) bool { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' } ./service-ng/src/github.com/godbus/dbus/conn.go0000644000015600001650000004051412675036254021507 0ustar jenkinsjenkinspackage dbus import ( "errors" "io" "os" "reflect" "strings" "sync" ) const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket" var ( systemBus *Conn systemBusLck sync.Mutex sessionBus *Conn sessionBusLck sync.Mutex ) // ErrClosed is the error returned by calls on a closed connection. var ErrClosed = errors.New("dbus: connection closed by user") // Conn represents a connection to a message bus (usually, the system or // session bus). // // Connections are either shared or private. Shared connections // are shared between calls to the functions that return them. As a result, // the methods Close, Auth and Hello must not be called on them. // // Multiple goroutines may invoke methods on a connection simultaneously. type Conn struct { transport busObj BusObject unixFD bool uuid string names []string namesLck sync.RWMutex serialLck sync.Mutex nextSerial uint32 serialUsed map[uint32]bool calls map[uint32]*Call callsLck sync.RWMutex handlers map[ObjectPath]map[string]exportWithMapping handlersLck sync.RWMutex out chan *Message closed bool outLck sync.RWMutex signals []chan<- *Signal signalsLck sync.Mutex eavesdropped chan<- *Message eavesdroppedLck sync.Mutex } // SessionBus returns a shared connection to the session bus, connecting to it // if not already done. func SessionBus() (conn *Conn, err error) { sessionBusLck.Lock() defer sessionBusLck.Unlock() if sessionBus != nil { return sessionBus, nil } defer func() { if conn != nil { sessionBus = conn } }() conn, err = SessionBusPrivate() if err != nil { return } if err = conn.Auth(nil); err != nil { conn.Close() conn = nil return } if err = conn.Hello(); err != nil { conn.Close() conn = nil } return } // SessionBusPrivate returns a new private connection to the session bus. func SessionBusPrivate() (*Conn, error) { address := os.Getenv("DBUS_SESSION_BUS_ADDRESS") if address != "" && address != "autolaunch:" { return Dial(address) } return sessionBusPlatform() } // SystemBus returns a shared connection to the system bus, connecting to it if // not already done. func SystemBus() (conn *Conn, err error) { systemBusLck.Lock() defer systemBusLck.Unlock() if systemBus != nil { return systemBus, nil } defer func() { if conn != nil { systemBus = conn } }() conn, err = SystemBusPrivate() if err != nil { return } if err = conn.Auth(nil); err != nil { conn.Close() conn = nil return } if err = conn.Hello(); err != nil { conn.Close() conn = nil } return } // SystemBusPrivate returns a new private connection to the system bus. func SystemBusPrivate() (*Conn, error) { address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS") if address != "" { return Dial(address) } return Dial(defaultSystemBusAddress) } // Dial establishes a new private connection to the message bus specified by address. func Dial(address string) (*Conn, error) { tr, err := getTransport(address) if err != nil { return nil, err } return newConn(tr) } // NewConn creates a new private *Conn from an already established connection. func NewConn(conn io.ReadWriteCloser) (*Conn, error) { return newConn(genericTransport{conn}) } // newConn creates a new *Conn from a transport. func newConn(tr transport) (*Conn, error) { conn := new(Conn) conn.transport = tr conn.calls = make(map[uint32]*Call) conn.out = make(chan *Message, 10) conn.handlers = make(map[ObjectPath]map[string]exportWithMapping) conn.nextSerial = 1 conn.serialUsed = map[uint32]bool{0: true} conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") return conn, nil } // BusObject returns the object owned by the bus daemon which handles // administrative requests. func (conn *Conn) BusObject() BusObject { return conn.busObj } // Close closes the connection. Any blocked operations will return with errors // and the channels passed to Eavesdrop and Signal are closed. This method must // not be called on shared connections. func (conn *Conn) Close() error { conn.outLck.Lock() if conn.closed { // inWorker calls Close on read error, the read error may // be caused by another caller calling Close to shutdown the // dbus connection, a double-close scenario we prevent here. conn.outLck.Unlock() return nil } close(conn.out) conn.closed = true conn.outLck.Unlock() conn.signalsLck.Lock() for _, ch := range conn.signals { close(ch) } conn.signalsLck.Unlock() conn.eavesdroppedLck.Lock() if conn.eavesdropped != nil { close(conn.eavesdropped) } conn.eavesdroppedLck.Unlock() return conn.transport.Close() } // Eavesdrop causes conn to send all incoming messages to the given channel // without further processing. Method replies, errors and signals will not be // sent to the appropiate channels and method calls will not be handled. If nil // is passed, the normal behaviour is restored. // // The caller has to make sure that ch is sufficiently buffered; // if a message arrives when a write to ch is not possible, the message is // discarded. func (conn *Conn) Eavesdrop(ch chan<- *Message) { conn.eavesdroppedLck.Lock() conn.eavesdropped = ch conn.eavesdroppedLck.Unlock() } // getSerial returns an unused serial. func (conn *Conn) getSerial() uint32 { conn.serialLck.Lock() defer conn.serialLck.Unlock() n := conn.nextSerial for conn.serialUsed[n] { n++ } conn.serialUsed[n] = true conn.nextSerial = n + 1 return n } // Hello sends the initial org.freedesktop.DBus.Hello call. This method must be // called after authentication, but before sending any other messages to the // bus. Hello must not be called for shared connections. func (conn *Conn) Hello() error { var s string err := conn.busObj.Call("org.freedesktop.DBus.Hello", 0).Store(&s) if err != nil { return err } conn.namesLck.Lock() conn.names = make([]string, 1) conn.names[0] = s conn.namesLck.Unlock() return nil } // inWorker runs in an own goroutine, reading incoming messages from the // transport and dispatching them appropiately. func (conn *Conn) inWorker() { for { msg, err := conn.ReadMessage() if err == nil { conn.eavesdroppedLck.Lock() if conn.eavesdropped != nil { select { case conn.eavesdropped <- msg: default: } conn.eavesdroppedLck.Unlock() continue } conn.eavesdroppedLck.Unlock() dest, _ := msg.Headers[FieldDestination].value.(string) found := false if dest == "" { found = true } else { conn.namesLck.RLock() if len(conn.names) == 0 { found = true } for _, v := range conn.names { if dest == v { found = true break } } conn.namesLck.RUnlock() } if !found { // Eavesdropped a message, but no channel for it is registered. // Ignore it. continue } switch msg.Type { case TypeMethodReply, TypeError: serial := msg.Headers[FieldReplySerial].value.(uint32) conn.callsLck.Lock() if c, ok := conn.calls[serial]; ok { if msg.Type == TypeError { name, _ := msg.Headers[FieldErrorName].value.(string) c.Err = Error{name, msg.Body} } else { c.Body = msg.Body } c.Done <- c conn.serialLck.Lock() delete(conn.serialUsed, serial) conn.serialLck.Unlock() delete(conn.calls, serial) } conn.callsLck.Unlock() case TypeSignal: iface := msg.Headers[FieldInterface].value.(string) member := msg.Headers[FieldMember].value.(string) // as per http://dbus.freedesktop.org/doc/dbus-specification.html , // sender is optional for signals. sender, _ := msg.Headers[FieldSender].value.(string) if iface == "org.freedesktop.DBus" && sender == "org.freedesktop.DBus" { if member == "NameLost" { // If we lost the name on the bus, remove it from our // tracking list. name, ok := msg.Body[0].(string) if !ok { panic("Unable to read the lost name") } conn.namesLck.Lock() for i, v := range conn.names { if v == name { conn.names = append(conn.names[:i], conn.names[i+1:]...) } } conn.namesLck.Unlock() } else if member == "NameAcquired" { // If we acquired the name on the bus, add it to our // tracking list. name, ok := msg.Body[0].(string) if !ok { panic("Unable to read the acquired name") } conn.namesLck.Lock() conn.names = append(conn.names, name) conn.namesLck.Unlock() } } signal := &Signal{ Sender: sender, Path: msg.Headers[FieldPath].value.(ObjectPath), Name: iface + "." + member, Body: msg.Body, } conn.signalsLck.Lock() for _, ch := range conn.signals { ch <- signal } conn.signalsLck.Unlock() case TypeMethodCall: go conn.handleCall(msg) } } else if _, ok := err.(InvalidMessageError); !ok { // Some read error occured (usually EOF); we can't really do // anything but to shut down all stuff and returns errors to all // pending replies. conn.Close() conn.callsLck.RLock() for _, v := range conn.calls { v.Err = err v.Done <- v } conn.callsLck.RUnlock() return } // invalid messages are ignored } } // Names returns the list of all names that are currently owned by this // connection. The slice is always at least one element long, the first element // being the unique name of the connection. func (conn *Conn) Names() []string { conn.namesLck.RLock() // copy the slice so it can't be modified s := make([]string, len(conn.names)) copy(s, conn.names) conn.namesLck.RUnlock() return s } // Object returns the object identified by the given destination name and path. func (conn *Conn) Object(dest string, path ObjectPath) BusObject { return &Object{conn, dest, path} } // outWorker runs in an own goroutine, encoding and sending messages that are // sent to conn.out. func (conn *Conn) outWorker() { for msg := range conn.out { err := conn.SendMessage(msg) conn.callsLck.RLock() if err != nil { if c := conn.calls[msg.serial]; c != nil { c.Err = err c.Done <- c } conn.serialLck.Lock() delete(conn.serialUsed, msg.serial) conn.serialLck.Unlock() } else if msg.Type != TypeMethodCall { conn.serialLck.Lock() delete(conn.serialUsed, msg.serial) conn.serialLck.Unlock() } conn.callsLck.RUnlock() } } // Send sends the given message to the message bus. You usually don't need to // use this; use the higher-level equivalents (Call / Go, Emit and Export) // instead. If msg is a method call and NoReplyExpected is not set, a non-nil // call is returned and the same value is sent to ch (which must be buffered) // once the call is complete. Otherwise, ch is ignored and a Call structure is // returned of which only the Err member is valid. func (conn *Conn) Send(msg *Message, ch chan *Call) *Call { var call *Call msg.serial = conn.getSerial() if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 { if ch == nil { ch = make(chan *Call, 5) } else if cap(ch) == 0 { panic("dbus: unbuffered channel passed to (*Conn).Send") } call = new(Call) call.Destination, _ = msg.Headers[FieldDestination].value.(string) call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath) iface, _ := msg.Headers[FieldInterface].value.(string) member, _ := msg.Headers[FieldMember].value.(string) call.Method = iface + "." + member call.Args = msg.Body call.Done = ch conn.callsLck.Lock() conn.calls[msg.serial] = call conn.callsLck.Unlock() conn.outLck.RLock() if conn.closed { call.Err = ErrClosed call.Done <- call } else { conn.out <- msg } conn.outLck.RUnlock() } else { conn.outLck.RLock() if conn.closed { call = &Call{Err: ErrClosed} } else { conn.out <- msg call = &Call{Err: nil} } conn.outLck.RUnlock() } return call } // sendError creates an error message corresponding to the parameters and sends // it to conn.out. func (conn *Conn) sendError(e Error, dest string, serial uint32) { msg := new(Message) msg.Type = TypeError msg.serial = conn.getSerial() msg.Headers = make(map[HeaderField]Variant) if dest != "" { msg.Headers[FieldDestination] = MakeVariant(dest) } msg.Headers[FieldErrorName] = MakeVariant(e.Name) msg.Headers[FieldReplySerial] = MakeVariant(serial) msg.Body = e.Body if len(e.Body) > 0 { msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...)) } conn.outLck.RLock() if !conn.closed { conn.out <- msg } conn.outLck.RUnlock() } // sendReply creates a method reply message corresponding to the parameters and // sends it to conn.out. func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) { msg := new(Message) msg.Type = TypeMethodReply msg.serial = conn.getSerial() msg.Headers = make(map[HeaderField]Variant) if dest != "" { msg.Headers[FieldDestination] = MakeVariant(dest) } msg.Headers[FieldReplySerial] = MakeVariant(serial) msg.Body = values if len(values) > 0 { msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...)) } conn.outLck.RLock() if !conn.closed { conn.out <- msg } conn.outLck.RUnlock() } // Signal registers the given channel to be passed all received signal messages. // The caller has to make sure that ch is sufficiently buffered; if a message // arrives when a write to c is not possible, it is discarded. // // Multiple of these channels can be registered at the same time. Passing a // channel that already is registered will remove it from the list of the // registered channels. // // These channels are "overwritten" by Eavesdrop; i.e., if there currently is a // channel for eavesdropped messages, this channel receives all signals, and // none of the channels passed to Signal will receive any signals. func (conn *Conn) Signal(ch chan<- *Signal) { conn.signalsLck.Lock() conn.signals = append(conn.signals, ch) conn.signalsLck.Unlock() } // SupportsUnixFDs returns whether the underlying transport supports passing of // unix file descriptors. If this is false, method calls containing unix file // descriptors will return an error and emitted signals containing them will // not be sent. func (conn *Conn) SupportsUnixFDs() bool { return conn.unixFD } // Error represents a D-Bus message of type Error. type Error struct { Name string Body []interface{} } func NewError(name string, body []interface{}) *Error { return &Error{name, body} } func (e Error) Error() string { if len(e.Body) >= 1 { s, ok := e.Body[0].(string) if ok { return s } } return e.Name } // Signal represents a D-Bus message of type Signal. The name member is given in // "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost. type Signal struct { Sender string Path ObjectPath Name string Body []interface{} } // transport is a D-Bus transport. type transport interface { // Read and Write raw data (for example, for the authentication protocol). io.ReadWriteCloser // Send the initial null byte used for the EXTERNAL mechanism. SendNullByte() error // Returns whether this transport supports passing Unix FDs. SupportsUnixFDs() bool // Signal the transport that Unix FD passing is enabled for this connection. EnableUnixFDs() // Read / send a message, handling things like Unix FDs. ReadMessage() (*Message, error) SendMessage(*Message) error } var ( transports map[string]func(string) (transport, error) = make(map[string]func(string) (transport, error)) ) func getTransport(address string) (transport, error) { var err error var t transport addresses := strings.Split(address, ";") for _, v := range addresses { i := strings.IndexRune(v, ':') if i == -1 { err = errors.New("dbus: invalid bus address (no transport)") continue } f := transports[v[:i]] if f == nil { err = errors.New("dbus: invalid bus address (invalid or unsupported transport)") } t, err = f(v[i+1:]) if err == nil { return t, nil } } return nil, err } // dereferenceAll returns a slice that, assuming that vs is a slice of pointers // of arbitrary types, containes the values that are obtained from dereferencing // all elements in vs. func dereferenceAll(vs []interface{}) []interface{} { for i := range vs { v := reflect.ValueOf(vs[i]) v = v.Elem() vs[i] = v.Interface() } return vs } // getKey gets a key from a the list of keys. Returns "" on error / not found... func getKey(s, key string) string { i := strings.Index(s, key) if i == -1 { return "" } if i+len(key)+1 >= len(s) || s[i+len(key)] != '=' { return "" } j := strings.Index(s, ",") if j == -1 { j = len(s) } return s[i+len(key)+1 : j] } ./service-ng/src/github.com/godbus/dbus/LICENSE0000644000015600001650000000246012675036254021226 0ustar jenkinsjenkinsCopyright (c) 2013, Georg Reinke (), Google All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ./service-ng/src/github.com/godbus/dbus/auth_external.go0000644000015600001650000000110612675036254023407 0ustar jenkinsjenkinspackage dbus import ( "encoding/hex" ) // AuthExternal returns an Auth that authenticates as the given user with the // EXTERNAL mechanism. func AuthExternal(user string) Auth { return authExternal{user} } // AuthExternal implements the EXTERNAL authentication mechanism. type authExternal struct { user string } func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) { b := make([]byte, 2*len(a.user)) hex.Encode(b, []byte(a.user)) return []byte("EXTERNAL"), b, AuthOk } func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) { return nil, AuthError } ./service-ng/src/github.com/godbus/dbus/conn_test.go0000644000015600001650000000737112675036254022552 0ustar jenkinsjenkinspackage dbus import "testing" func TestSessionBus(t *testing.T) { _, err := SessionBus() if err != nil { t.Error(err) } } func TestSystemBus(t *testing.T) { _, err := SystemBus() if err != nil { t.Error(err) } } func TestSend(t *testing.T) { bus, err := SessionBus() if err != nil { t.Error(err) } ch := make(chan *Call, 1) msg := &Message{ Type: TypeMethodCall, Flags: 0, Headers: map[HeaderField]Variant{ FieldDestination: MakeVariant(bus.Names()[0]), FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")), FieldInterface: MakeVariant("org.freedesktop.DBus.Peer"), FieldMember: MakeVariant("Ping"), }, } call := bus.Send(msg, ch) <-ch if call.Err != nil { t.Error(call.Err) } } type server struct{} func (server) Double(i int64) (int64, *Error) { return 2 * i, nil } func BenchmarkCall(b *testing.B) { b.StopTimer() var s string bus, err := SessionBus() if err != nil { b.Fatal(err) } name := bus.Names()[0] obj := bus.BusObject() b.StartTimer() for i := 0; i < b.N; i++ { err := obj.Call("org.freedesktop.DBus.GetNameOwner", 0, name).Store(&s) if err != nil { b.Fatal(err) } if s != name { b.Errorf("got %s, wanted %s", s, name) } } } func BenchmarkCallAsync(b *testing.B) { b.StopTimer() bus, err := SessionBus() if err != nil { b.Fatal(err) } name := bus.Names()[0] obj := bus.BusObject() c := make(chan *Call, 50) done := make(chan struct{}) go func() { for i := 0; i < b.N; i++ { v := <-c if v.Err != nil { b.Error(v.Err) } s := v.Body[0].(string) if s != name { b.Errorf("got %s, wanted %s", s, name) } } close(done) }() b.StartTimer() for i := 0; i < b.N; i++ { obj.Go("org.freedesktop.DBus.GetNameOwner", 0, c, name) } <-done } func BenchmarkServe(b *testing.B) { b.StopTimer() srv, err := SessionBus() if err != nil { b.Fatal(err) } cli, err := SessionBusPrivate() if err != nil { b.Fatal(err) } if err = cli.Auth(nil); err != nil { b.Fatal(err) } if err = cli.Hello(); err != nil { b.Fatal(err) } benchmarkServe(b, srv, cli) } func BenchmarkServeAsync(b *testing.B) { b.StopTimer() srv, err := SessionBus() if err != nil { b.Fatal(err) } cli, err := SessionBusPrivate() if err != nil { b.Fatal(err) } if err = cli.Auth(nil); err != nil { b.Fatal(err) } if err = cli.Hello(); err != nil { b.Fatal(err) } benchmarkServeAsync(b, srv, cli) } func BenchmarkServeSameConn(b *testing.B) { b.StopTimer() bus, err := SessionBus() if err != nil { b.Fatal(err) } benchmarkServe(b, bus, bus) } func BenchmarkServeSameConnAsync(b *testing.B) { b.StopTimer() bus, err := SessionBus() if err != nil { b.Fatal(err) } benchmarkServeAsync(b, bus, bus) } func benchmarkServe(b *testing.B, srv, cli *Conn) { var r int64 var err error dest := srv.Names()[0] srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") obj := cli.Object(dest, "/org/guelfey/DBus/Test") b.StartTimer() for i := 0; i < b.N; i++ { err = obj.Call("org.guelfey.DBus.Test.Double", 0, int64(i)).Store(&r) if err != nil { b.Fatal(err) } if r != 2*int64(i) { b.Errorf("got %d, wanted %d", r, 2*int64(i)) } } } func benchmarkServeAsync(b *testing.B, srv, cli *Conn) { dest := srv.Names()[0] srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") obj := cli.Object(dest, "/org/guelfey/DBus/Test") c := make(chan *Call, 50) done := make(chan struct{}) go func() { for i := 0; i < b.N; i++ { v := <-c if v.Err != nil { b.Fatal(v.Err) } i, r := v.Args[0].(int64), v.Body[0].(int64) if 2*i != r { b.Errorf("got %d, wanted %d", r, 2*i) } } close(done) }() b.StartTimer() for i := 0; i < b.N; i++ { obj.Go("org.guelfey.DBus.Test.Double", 0, c, int64(i)) } <-done } ./service-ng/src/github.com/godbus/dbus/encoder.go0000644000015600001650000001246412675036254022174 0ustar jenkinsjenkinspackage dbus import ( "bytes" "encoding/binary" "io" "reflect" ) // An encoder encodes values to the D-Bus wire format. type encoder struct { out io.Writer order binary.ByteOrder pos int } // NewEncoder returns a new encoder that writes to out in the given byte order. func newEncoder(out io.Writer, order binary.ByteOrder) *encoder { return newEncoderAtOffset(out, 0, order) } // newEncoderAtOffset returns a new encoder that writes to out in the given // byte order. Specify the offset to initialize pos for proper alignment // computation. func newEncoderAtOffset(out io.Writer, offset int, order binary.ByteOrder) *encoder { enc := new(encoder) enc.out = out enc.order = order enc.pos = offset return enc } // Aligns the next output to be on a multiple of n. Panics on write errors. func (enc *encoder) align(n int) { pad := enc.padding(0, n) if pad > 0 { empty := make([]byte, pad) if _, err := enc.out.Write(empty); err != nil { panic(err) } enc.pos += pad } } // pad returns the number of bytes of padding, based on current position and additional offset. // and alignment. func (enc *encoder) padding(offset, algn int) int { abs := enc.pos + offset if abs%algn != 0 { newabs := (abs + algn - 1) & ^(algn - 1) return newabs - abs } return 0 } // Calls binary.Write(enc.out, enc.order, v) and panics on write errors. func (enc *encoder) binwrite(v interface{}) { if err := binary.Write(enc.out, enc.order, v); err != nil { panic(err) } } // Encode encodes the given values to the underyling reader. All written values // are aligned properly as required by the D-Bus spec. func (enc *encoder) Encode(vs ...interface{}) (err error) { defer func() { err, _ = recover().(error) }() for _, v := range vs { enc.encode(reflect.ValueOf(v), 0) } return nil } // encode encodes the given value to the writer and panics on error. depth holds // the depth of the container nesting. func (enc *encoder) encode(v reflect.Value, depth int) { enc.align(alignment(v.Type())) switch v.Kind() { case reflect.Uint8: var b [1]byte b[0] = byte(v.Uint()) if _, err := enc.out.Write(b[:]); err != nil { panic(err) } enc.pos++ case reflect.Bool: if v.Bool() { enc.encode(reflect.ValueOf(uint32(1)), depth) } else { enc.encode(reflect.ValueOf(uint32(0)), depth) } case reflect.Int16: enc.binwrite(int16(v.Int())) enc.pos += 2 case reflect.Uint16: enc.binwrite(uint16(v.Uint())) enc.pos += 2 case reflect.Int32: enc.binwrite(int32(v.Int())) enc.pos += 4 case reflect.Uint32: enc.binwrite(uint32(v.Uint())) enc.pos += 4 case reflect.Int64: enc.binwrite(v.Int()) enc.pos += 8 case reflect.Uint64: enc.binwrite(v.Uint()) enc.pos += 8 case reflect.Float64: enc.binwrite(v.Float()) enc.pos += 8 case reflect.String: enc.encode(reflect.ValueOf(uint32(len(v.String()))), depth) b := make([]byte, v.Len()+1) copy(b, v.String()) b[len(b)-1] = 0 n, err := enc.out.Write(b) if err != nil { panic(err) } enc.pos += n case reflect.Ptr: enc.encode(v.Elem(), depth) case reflect.Slice, reflect.Array: if depth >= 64 { panic(FormatError("input exceeds container depth limit")) } // Lookahead offset: 4 bytes for uint32 length (with alignment), // plus alignment for elements. n := enc.padding(0, 4) + 4 offset := enc.pos + n + enc.padding(n, alignment(v.Type().Elem())) var buf bytes.Buffer bufenc := newEncoderAtOffset(&buf, offset, enc.order) for i := 0; i < v.Len(); i++ { bufenc.encode(v.Index(i), depth+1) } enc.encode(reflect.ValueOf(uint32(buf.Len())), depth) length := buf.Len() enc.align(alignment(v.Type().Elem())) if _, err := buf.WriteTo(enc.out); err != nil { panic(err) } enc.pos += length case reflect.Struct: if depth >= 64 && v.Type() != signatureType { panic(FormatError("input exceeds container depth limit")) } switch t := v.Type(); t { case signatureType: str := v.Field(0) enc.encode(reflect.ValueOf(byte(str.Len())), depth+1) b := make([]byte, str.Len()+1) copy(b, str.String()) b[len(b)-1] = 0 n, err := enc.out.Write(b) if err != nil { panic(err) } enc.pos += n case variantType: variant := v.Interface().(Variant) enc.encode(reflect.ValueOf(variant.sig), depth+1) enc.encode(reflect.ValueOf(variant.value), depth+1) default: for i := 0; i < v.Type().NumField(); i++ { field := t.Field(i) if field.PkgPath == "" && field.Tag.Get("dbus") != "-" { enc.encode(v.Field(i), depth+1) } } } case reflect.Map: // Maps are arrays of structures, so they actually increase the depth by // 2. if depth >= 63 { panic(FormatError("input exceeds container depth limit")) } if !isKeyType(v.Type().Key()) { panic(InvalidTypeError{v.Type()}) } keys := v.MapKeys() // Lookahead offset: 4 bytes for uint32 length (with alignment), // plus 8-byte alignment n := enc.padding(0, 4) + 4 offset := enc.pos + n + enc.padding(n, 8) var buf bytes.Buffer bufenc := newEncoderAtOffset(&buf, offset, enc.order) for _, k := range keys { bufenc.align(8) bufenc.encode(k, depth+2) bufenc.encode(v.MapIndex(k), depth+2) } enc.encode(reflect.ValueOf(uint32(buf.Len())), depth) length := buf.Len() enc.align(8) if _, err := buf.WriteTo(enc.out); err != nil { panic(err) } enc.pos += length default: panic(InvalidTypeError{v.Type()}) } } ./service-ng/src/github.com/godbus/dbus/transport_unixcred_linux.go0000644000015600001650000000117512675036254025726 0ustar jenkinsjenkins// The UnixCredentials system call is currently only implemented on Linux // http://golang.org/src/pkg/syscall/sockcmsg_linux.go // https://golang.org/s/go1.4-syscall // http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys package dbus import ( "io" "os" "syscall" ) func (t *unixTransport) SendNullByte() error { ucred := &syscall.Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())} b := syscall.UnixCredentials(ucred) _, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil) if err != nil { return err } if oobn != len(b) { return io.ErrShortWrite } return nil } ./service-ng/src/github.com/godbus/dbus/transport_unixcred_dragonfly.go0000644000015600001650000000537612675036254026563 0ustar jenkinsjenkins// The UnixCredentials system call is currently only implemented on Linux // http://golang.org/src/pkg/syscall/sockcmsg_linux.go // https://golang.org/s/go1.4-syscall // http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys // Local implementation of the UnixCredentials system call for DragonFly BSD package dbus /* #include */ import "C" import ( "io" "os" "syscall" "unsafe" ) // http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go // http://golang.org/src/pkg/syscall/ztypes_dragonfly_amd64.go type Ucred struct { Pid int32 Uid uint32 Gid uint32 } // http://golang.org/src/pkg/syscall/types_linux.go // http://golang.org/src/pkg/syscall/types_dragonfly.go // https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/sys/sys/ucred.h const ( SizeofUcred = C.sizeof_struct_ucred ) // http://golang.org/src/pkg/syscall/sockcmsg_unix.go func cmsgAlignOf(salen int) int { // From http://golang.org/src/pkg/syscall/sockcmsg_unix.go //salign := sizeofPtr // NOTE: It seems like 64-bit Darwin and DragonFly BSD kernels // still require 32-bit aligned access to network subsystem. //if darwin64Bit || dragonfly64Bit { // salign = 4 //} salign := 4 return (salen + salign - 1) & ^(salign - 1) } // http://golang.org/src/pkg/syscall/sockcmsg_unix.go func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer { return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr))) } // http://golang.org/src/pkg/syscall/sockcmsg_linux.go // UnixCredentials encodes credentials into a socket control message // for sending to another process. This can be used for // authentication. func UnixCredentials(ucred *Ucred) []byte { b := make([]byte, syscall.CmsgSpace(SizeofUcred)) h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0])) h.Level = syscall.SOL_SOCKET h.Type = syscall.SCM_CREDS h.SetLen(syscall.CmsgLen(SizeofUcred)) *((*Ucred)(cmsgData(h))) = *ucred return b } // http://golang.org/src/pkg/syscall/sockcmsg_linux.go // ParseUnixCredentials decodes a socket control message that contains // credentials in a Ucred structure. To receive such a message, the // SO_PASSCRED option must be enabled on the socket. func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) { if m.Header.Level != syscall.SOL_SOCKET { return nil, syscall.EINVAL } if m.Header.Type != syscall.SCM_CREDS { return nil, syscall.EINVAL } ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0])) return &ucred, nil } func (t *unixTransport) SendNullByte() error { ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())} b := UnixCredentials(ucred) _, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil) if err != nil { return err } if oobn != len(b) { return io.ErrShortWrite } return nil } ./service-ng/src/github.com/godbus/dbus/auth_sha1.go0000644000015600001650000000461412675036254022430 0ustar jenkinsjenkinspackage dbus import ( "bufio" "bytes" "crypto/rand" "crypto/sha1" "encoding/hex" "os" ) // AuthCookieSha1 returns an Auth that authenticates as the given user with the // DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home // directory of the user. func AuthCookieSha1(user, home string) Auth { return authCookieSha1{user, home} } type authCookieSha1 struct { user, home string } func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) { b := make([]byte, 2*len(a.user)) hex.Encode(b, []byte(a.user)) return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue } func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) { challenge := make([]byte, len(data)/2) _, err := hex.Decode(challenge, data) if err != nil { return nil, AuthError } b := bytes.Split(challenge, []byte{' '}) if len(b) != 3 { return nil, AuthError } context := b[0] id := b[1] svchallenge := b[2] cookie := a.getCookie(context, id) if cookie == nil { return nil, AuthError } clchallenge := a.generateChallenge() if clchallenge == nil { return nil, AuthError } hash := sha1.New() hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'})) hexhash := make([]byte, 2*hash.Size()) hex.Encode(hexhash, hash.Sum(nil)) data = append(clchallenge, ' ') data = append(data, hexhash...) resp := make([]byte, 2*len(data)) hex.Encode(resp, data) return resp, AuthOk } // getCookie searches for the cookie identified by id in context and returns // the cookie content or nil. (Since HandleData can't return a specific error, // but only whether an error occured, this function also doesn't bother to // return an error.) func (a authCookieSha1) getCookie(context, id []byte) []byte { file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context)) if err != nil { return nil } defer file.Close() rd := bufio.NewReader(file) for { line, err := rd.ReadBytes('\n') if err != nil { return nil } line = line[:len(line)-1] b := bytes.Split(line, []byte{' '}) if len(b) != 3 { return nil } if bytes.Equal(b[0], id) { return b[2] } } } // generateChallenge returns a random, hex-encoded challenge, or nil on error // (see above). func (a authCookieSha1) generateChallenge() []byte { b := make([]byte, 16) n, err := rand.Read(b) if err != nil { return nil } if n != 16 { return nil } enc := make([]byte, 32) hex.Encode(enc, b) return enc } ./service-ng/src/github.com/godbus/dbus/transport_darwin.go0000644000015600001650000000015112675036254024143 0ustar jenkinsjenkinspackage dbus func (t *unixTransport) SendNullByte() error { _, err := t.Write([]byte{0}) return err } ./service-ng/src/github.com/godbus/dbus/CONTRIBUTING.md0000644000015600001650000000271412675036254022454 0ustar jenkinsjenkins# How to Contribute ## Getting Started - Fork the repository on GitHub - Read the [README](README.markdown) for build and test instructions - Play with the project, submit bugs, submit patches! ## Contribution Flow This is a rough outline of what a contributor's workflow looks like: - Create a topic branch from where you want to base your work (usually master). - Make commits of logical units. - Make sure your commit messages are in the proper format (see below). - Push your changes to a topic branch in your fork of the repository. - Make sure the tests pass, and add any new tests as appropriate. - Submit a pull request to the original repository. Thanks for your contributions! ### Format of the Commit Message We follow a rough convention for commit messages that is designed to answer two questions: what changed and why. The subject line should feature the what and the body of the commit should describe the why. ``` scripts: add the test-cluster command this uses tmux to setup a test cluster that you can easily kill and start for debugging. Fixes #38 ``` The format can be described more formally as follows: ``` :