diff options
author | Walter Bender <walter@sugarlabs.org> | 2013-06-09 10:55:49 (GMT) |
---|---|---|
committer | Daniel Narvaez <dwnarvaez@gmail.com> | 2013-06-10 13:42:19 (GMT) |
commit | 14659c2ef2948c2d52908e9f2ac476ab72369613 (patch) | |
tree | d8b7f4b4f6c64b477d2756c4ca205f26c13b7796 | |
parent | de87d97df7dd48288bda14c8ffc467d635d30ee4 (diff) |
Add online service support
This patch adds a new section to sugar/src/jarabe to manage web services.
The relevant feature request is [1].
Adds a new web section of jarabe is for a prototype of a new online
account management class that manages the services described above.
The services themselves are intended to be kept in subdirectors of
sugar/extensions/web and are the subject of a separate patch. Also
in a separate patch will be a UI extention to the Journal toolbar and
palette, adding new menuitems.
(Includes a unit test)
[1] http://wiki.sugarlabs.org/go/Features/Web_services
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | src/jarabe/Makefile.am | 3 | ||||
-rw-r--r-- | src/jarabe/web/Makefile.am | 5 | ||||
-rw-r--r-- | src/jarabe/web/__init__.py | 0 | ||||
-rw-r--r-- | src/jarabe/web/account.py | 113 | ||||
-rw-r--r-- | src/jarabe/web/accountsmanager.py | 101 | ||||
-rw-r--r-- | tests/extensions/web/mock/__init__.py | 0 | ||||
-rw-r--r-- | tests/extensions/web/mock/account.py | 62 | ||||
-rw-r--r-- | tests/extensions/web/mock/icons/mock.svg | 25 | ||||
-rw-r--r-- | tests/test_webaccounts.py | 100 |
10 files changed, 409 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac index 0ff82d6..84e2555 100644 --- a/configure.ac +++ b/configure.ac @@ -77,6 +77,7 @@ src/jarabe/model/Makefile src/jarabe/util/Makefile src/jarabe/util/telepathy/Makefile src/jarabe/view/Makefile +src/jarabe/web/Makefile src/Makefile ]) diff --git a/src/jarabe/Makefile.am b/src/jarabe/Makefile.am index 2bdf412..f51cb90 100644 --- a/src/jarabe/Makefile.am +++ b/src/jarabe/Makefile.am @@ -6,7 +6,8 @@ SUBDIRS = \ model \ view \ intro \ - util + util \ + web sugardir = $(pythondir)/jarabe sugar_PYTHON = \ diff --git a/src/jarabe/web/Makefile.am b/src/jarabe/web/Makefile.am new file mode 100644 index 0000000..b51b5a9 --- /dev/null +++ b/src/jarabe/web/Makefile.am @@ -0,0 +1,5 @@ +sugardir = $(pythondir)/jarabe/web +sugar_PYTHON = \ + __init__.py \ + account.py \ + accountsmanager.py diff --git a/src/jarabe/web/__init__.py b/src/jarabe/web/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/jarabe/web/__init__.py diff --git a/src/jarabe/web/account.py b/src/jarabe/web/account.py new file mode 100644 index 0000000..f94c7c2 --- /dev/null +++ b/src/jarabe/web/account.py @@ -0,0 +1,113 @@ +# Copyright (c) 2013 Walter Bender, Raul Gutierrez Segales +# Copyright (c) 2013 SugarLabs +# +# This library 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 2 of the License, or (at your option) any later version. +# +# This library 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 library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import logging + +from gi.repository import GObject + + +class Account(): + ''' Account is a prototype class for online accounts. It provides + stubs for public methods that are used by online services. + ''' + + STATE_NONE = 0 + STATE_VALID = 1 + STATE_EXPIRED = 2 + + def get_description(self): + ''' get_description returns a brief description of the online + service. The description is used in palette menuitems and on + the webservices control panel. + + :returns: online-account name + :rtype: string + ''' + raise NotImplementedError + + def get_token_state(self): + ''' get_token_state returns an enum to describe the state of + the online service: + State.NONE means there is no token, e.g., the service is not + configured. + State.VALID means there is a valid token, e.g., the service is + available for use. + State.EXPIRED means the token is no longer valid. + + :returns: token state + :rtype: enum + ''' + raise NotImplementedError + + def get_shared_journal_entry(self): + ''' get_shared_journal_entry returns a class used to + intermediate between the online service and the Sugar UI + elements. + + :returns: SharedJournalEntry() + :rtype: SharedJournalEntry + ''' + return NotImplemented + + +class SharedJournalEntry(): + ''' SharedJournalEntry is a class used to intermediate between the + online service and the Sugar UI elements (MenuItems used in the + Journal UI) for online accounts. It provides stubs for public + methods that are used by online services. + + The comments-changed signal is emitted by the online service if + changes to the 'comments' metadata have been made. + + :emits: metadata['comments'] + :type: string + ''' + + __gsignals__ = { + 'comments-changed': (GObject.SignalFlags.RUN_FIRST, None, ([str])) + } + + def get_share_menu(self, metadata): + ''' get_share_menu returns a menu item used on the Copy To + palette in the Journal and on the Journal detail-view toolbar. + + :param: journal_entry_metadata + :type: dict + :returns: MenuItem + :rtype: MenuItem + ''' + raise NotImplementedError + + def get_refresh_menu(self): + ''' get_refresh_menu returns a menu item used on the Journal + detail-view toolbar. + + :returns: MenuItem + :rtype: MenuItem + ''' + raise NotImplementedError + + def set_metadata(self, metadata): + ''' The online account uses this method to set metadata in the + Sugar journal and provide a means of updating menuitem status, + e.g., enabling the refresh menu after a successful transfer. + + :param: journal_entry_metadata + :type: dict + ''' + raise NotImplementedError diff --git a/src/jarabe/web/accountsmanager.py b/src/jarabe/web/accountsmanager.py new file mode 100644 index 0000000..90cb94e --- /dev/null +++ b/src/jarabe/web/accountsmanager.py @@ -0,0 +1,101 @@ +# Copyright (c) 2013 Walter Bender +# +# This library 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 2 of the License, or (at your option) any later version. +# +# This library 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 library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import os +import logging + +from gi.repository import Gtk + +from jarabe import config +from sugar3.web.account import Account + +_accounts = [] + + +def get_all_accounts(): + ''' Returns a list of all installed online account managers ''' + global _accounts + if len(_accounts) > 0: + return _accounts + + web_path = os.path.join(config.ext_path, 'web') + try: + web_path_dirs = os.listdir(web_path) + except OSError, e: + web_path_dirs = [] + logging.warning('listdir: %s: %s' % (web_path, e)) + + for d in web_path_dirs: + dir_path = os.path.join(web_path, d) + module = _load_module(dir_path) + if module is not None: + _accounts.append(module) + _extend_icon_theme_search_path(dir_path) + + return _accounts + + +def _load_module(dir_path): + module = None + if os.path.isdir(dir_path): + for f in os.listdir(dir_path): + if f == 'account.py': + module_name = f[:-3] + logging.debug('OnlineAccountsManager loading %s' % + (module_name)) + module_path = 'web.%s.%s' % (os.path.basename(dir_path), + module_name) + try: + mod = __import__(module_path, globals(), locals(), + [module_name]) + if hasattr(mod, 'get_account'): + module = mod.get_account() + + except Exception as e: + logging.exception('Exception while loading %s: %s' % + (module_name, str(e))) + + return module + + +def _extend_icon_theme_search_path(dir_path): + icon_theme = Gtk.IconTheme.get_default() + icon_search_path = icon_theme.get_search_path() + + try: + icon_path_dirs = os.listdir(dir_path) + except OSError, e: + icon_path_dirs = [] + logging.warning('listdir: %s: %s' % (dir_path, e)) + + for f in icon_path_dirs: + if f == 'icons': + icon_path = os.path.join(dir_path, f) + if os.path.isdir(icon_path) and \ + icon_path not in icon_search_path: + icon_theme.append_search_path(icon_path) + + +def get_configured_accounts(): + return [a for a in get_all_accounts() + if a.get_token_state() in (Account.STATE_VALID, + Account.STATE_EXPIRED)] + + +def get_active_accounts(): + return [a for a in get_all_accounts() + if a.get_token_state() == Account.STATE_VALID] diff --git a/tests/extensions/web/mock/__init__.py b/tests/extensions/web/mock/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/extensions/web/mock/__init__.py diff --git a/tests/extensions/web/mock/account.py b/tests/extensions/web/mock/account.py new file mode 100644 index 0000000..176af5f --- /dev/null +++ b/tests/extensions/web/mock/account.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# Copyright (c) 2013 Walter Bender + +#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. + +import os + +from sugar3.graphics.menuitem import MenuItem +from jarabe.web import account + +ACCOUNT_NAME = 'mock' + + +class MockAccount(account.Account): + def __init__(self): + return + + def get_description(self): + return ACCOUNT_NAME + + def get_shared_journal_entry(): + return MockSharedJournalEntry() + + def get_token_state(self): + return os.environ["MOCK_ACCOUNT_STATE"] + + +class MockSharedJournalEnrty(account.SharedJournalEntry): + def __init__(self): + return + + def get_share_menu(self, journal_entry_metadata): + share_menu = MenuItem() + return share_menu + + def get_refresh_menu(self): + refresh_menu = MenuItem() + return refresh_menu + + def set_metadata(self, metadata): + return + + +def get_account(): + return MockAccount() diff --git a/tests/extensions/web/mock/icons/mock.svg b/tests/extensions/web/mock/icons/mock.svg new file mode 100644 index 0000000..7a9434c --- /dev/null +++ b/tests/extensions/web/mock/icons/mock.svg @@ -0,0 +1,25 @@ +<?xml version="1.0" ?><!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) --><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#A0A0A0"> + <!ENTITY fill_color "#282828"> +]><svg enable-background="new 0 0 55.125 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"> + <rect + width="54" + height="54" + ry="3.5" + x="0.5" + y="0.5" + style="fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:#FFFFFF;stroke-width:1.0;stroke-opacity:1" /> + <rect + width="46.25" + height="7.75" + ry="0" + x="4.5" + y="42" + style="fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none" /> +<text> + <tspan + x="6" + y="45" + id="tspan2839" + style="font-size:44px;font-style:normal;font-variant:normal;font-weight:bold;fill:#ffffff;font-family:DejaVu Sans;">M</tspan></text> +</svg> diff --git a/tests/test_webaccounts.py b/tests/test_webaccounts.py new file mode 100644 index 0000000..a64662b --- /dev/null +++ b/tests/test_webaccounts.py @@ -0,0 +1,100 @@ +# Copyright (C) 2013, Walter Bender +# +# 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 2 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, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import sys +import unittest + +from gi.repository import Gtk + +from jarabe import config + +from sugar3.web.account import Account +from sugar3.web import accountsmanager + +ACCOUNT_NAME = 'mock' + +tests_dir = os.path.dirname(__file__) +base_dir = os.path.dirname(tests_dir) +extension_dir = os.path.join(tests_dir, 'extensions') +web_extension_dir = os.path.join(extensions_dir, 'web') + + +class TestWebAccounts(unittest.TestCase): + def setUp(self): + os.environ["MOCK_ACCOUNT_STATE"] = Account.STATE_NONE + self.save_ext_path = config.ext_path + config.ext_path = extension_dir + + def test_get_description(self): + accounts = accountsmanager.get_all_accounts() + found_mock_account = False + for account in accounts: + if account.get_description() == ACCOUNT_NAME: + found_mock_account = True + break + self.assertTrue(found_mock_account) + + def test_icon_theme(self): + icon_theme = Gtk.IconTheme.get_default() + icon_search_path = icon_theme.get_search_path() + icon_path = os.path.join(web_extension_dir, ACCOUNT_NAME, 'icons') + self.assertTrue(icon_path in icon_search_path) + + def test_get_all_accounts(self): + accounts = accountsmanager.get_all_accounts() + self.assertTrue(len(self.accounts) > 0) + + def test_get_configured_accounts(self): + os.environ["MOCK_ACCOUNT_STATE"] = Account.STATE_VALID + accounts = accountsmanager.get_configured_accounts() + count = len(accounts) + self.assertTrue(count > 0) + + os.environ["MOCK_ACCOUNT_STATE"] = Account.STATE_NONE + accounts = accountsmanager.get_configured_accounts() + self.assertTrue(len(self.accounts) == count - 1) + + os.environ["MOCK_ACCOUNT_STATE"] = Account.STATE_EXPIRED + accounts = accountsmanager.get_active_accounts() + self.assertTrue(len(self.accounts) == count) + + def test_get_active_accounts(self): + os.environ["MOCK_ACCOUNT_STATE"] = Account.STATE_VALID + accounts = accountsmanager.get_active_accounts() + count = len(accounts) + self.assertTrue(count > 0) + + os.environ["MOCK_ACCOUNT_STATE"] = Account.STATE_EXPIRED + accounts = accountsmanager.get_active_accounts() + self.assertTrue(len(self.accounts) == count - 1) + + def test_share_menu(self): + accounts = accountsmanager.get_all_accounts() + for account in accounts: + shared_journal_entry = account.get_shared_journal_entry() + share_menu = shared_journal_entry.get_shared_menu() + self.assertIsNotNone(share_menu) + + def test_refresh_menu(self): + accounts = accountsmanager.get_all_accounts() + for account in accounts: + shared_journal_entry = account.get_shared_journal_entry() + refresh_menu = shared_journal_entry.get_refresh_menu() + self.assertIsNotNone(refresh_menu) + + def tearDown(self): + config.ext_path = self.save_ext_path |