From 1c9ad9f953a052bca61afff9ad8e02622a0aa777 Mon Sep 17 00:00:00 2001 From: LoveIsGrief Date: Sat, 13 Apr 2019 00:00:25 +0200 Subject: [PATCH 1/5] i2p: Use augeas for editing the router.config It's cleaner and less hacky, however we still overwrite the default favs because they aren't written to the file by i2p until a change is made manually in the frontend. We still need to recreate the list of default and add them manually. Reviewed-by: Sunil Mohan Adapa --- actions/i2p | 35 ++----- plinth/modules/i2p/__init__.py | 4 + plinth/modules/i2p/helpers.py | 92 +++++++++++++++++++ plinth/modules/i2p/tests/__init__.py | 18 ++++ plinth/modules/i2p/tests/data/router.config | 23 +++++ .../modules/i2p/tests/test_router_editor.py | 86 +++++++++++++++++ 6 files changed, 229 insertions(+), 29 deletions(-) create mode 100644 plinth/modules/i2p/tests/data/router.config create mode 100644 plinth/modules/i2p/tests/test_router_editor.py diff --git a/actions/i2p b/actions/i2p index 5ac069fd1..5e34ab98d 100755 --- a/actions/i2p +++ b/actions/i2p @@ -23,7 +23,7 @@ import argparse import os from plinth import action_utils, cfg -from plinth.modules.i2p.helpers import TunnelEditor +from plinth.modules.i2p.helpers import RouterEditor, TunnelEditor cfg.read() module_config_path = os.path.join(cfg.config_dir, 'modules-enabled') @@ -87,36 +87,13 @@ def subcommand_add_favorite(arguments): :param arguments: :type arguments: """ - router_config_path = os.path.join(I2P_CONF_DIR, 'router.config') - # Read config - with open(router_config_path) as config_file: - config_lines = config_file.readlines() - - found_favorites = False url = arguments.url - new_favorite = '{name},{description},{url},{icon},'.format( - name=arguments.name, description='', url=arguments.url, - icon='/themes/console/images/eepsite.png') - for i in range(len(config_lines)): - line = config_lines[i] - # Find favorites line - if line.startswith('routerconsole.favorites'): - found_favorites = True - if url in line: - print('URL already in favorites') - exit(0) - - # Append favorite - config_lines[i] = line.strip() + new_favorite + '\n' - break - - if not found_favorites: - config_lines.append('routerconsole.favorites=' + new_favorite + '\n') - - # Update config - with open(router_config_path, mode='w') as config_file: - config_file.writelines(config_lines) + editor = RouterEditor() + editor.read_conf().add_favorite( + arguments.name, + url + ).write_conf() print('Added {} to favorites'.format(url)) diff --git a/plinth/modules/i2p/__init__.py b/plinth/modules/i2p/__init__.py index f2ec2bb34..0ab07b132 100644 --- a/plinth/modules/i2p/__init__.py +++ b/plinth/modules/i2p/__init__.py @@ -59,6 +59,10 @@ proxies_service = None manual_page = 'I2P' +# TODO: Add all default favorites +# the router.config favorites is empty until a change is made +# in the front-end, but we currently do not have a method of +# doing that, so we need to add the favorites ourselves additional_favorites = [ ('Searx instance', 'http://ransack.i2p'), ('Torrent tracker', 'http://tracker2.postman.i2p'), diff --git a/plinth/modules/i2p/helpers.py b/plinth/modules/i2p/helpers.py index ac052e2cb..0897efa19 100644 --- a/plinth/modules/i2p/helpers.py +++ b/plinth/modules/i2p/helpers.py @@ -17,6 +17,7 @@ Various helpers for the I2P app. """ +from collections import OrderedDict import os import re @@ -25,6 +26,7 @@ import augeas I2P_CONF_DIR = '/var/lib/i2p/i2p-config' FILE_TUNNEL_CONF = os.path.join(I2P_CONF_DIR, 'i2ptunnel.config') TUNNEL_IDX_REGEX = re.compile(r'tunnel.(\d+).name$') +I2P_ROUTER_CONF = os.path.join(I2P_CONF_DIR, 'router.config') class TunnelEditor(): @@ -133,3 +135,93 @@ class TunnelEditor(): def __setitem__(self, tunnel_prop, value): self.aug.set(self.calc_prop_path(tunnel_prop), value) + + +class RouterEditor(object): + """ + + :type aug: augeas.Augeas + """ + + FAVORITE_PROP = 'routerconsole.favorites' + FAVORITE_TUPLE_SIZE = 4 + + def __init__(self, filename=None): + self.conf_filename = filename or I2P_ROUTER_CONF + self.aug = None + + def read_conf(self): + self.aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + + augeas.Augeas.NO_MODL_AUTOLOAD) + self.aug.set('/augeas/load/Properties/lens', 'Properties.lns') + self.aug.set('/augeas/load/Properties/incl[last() + 1]', self.conf_filename) + self.aug.load() + return self + + def write_conf(self): + self.aug.save() + return self + + @property + def favorite_property(self): + return '/files{filename}/{prop}'.format(filename=self.conf_filename, prop=self.FAVORITE_PROP) + + def add_favorite(self, name, url, description='', icon='/themes/console/images/eepsite.png'): + """ + Adds a favorite to the router.config + + Favorites are in a single string and separated by ','. + none of the incoming params can therefore use commas. + I2P replaces the commas by dots. + + That's ok for the name and description, + but not for the url and icon + + :type name: basestring + :type url: basestring + :type description: basestring + :type icon: basestring + """ + if ',' in url: + raise ValueError('URL cannot contain commas') + if ',' in icon: + raise ValueError('Icon cannot contain commas') + + name = name.replace(',', '.') + description = description.replace(',', '.') + + prop = self.favorite_property + favorites = self.aug.get(prop) or '' + new_favorite = '{name},{description},{url},{icon},'.format( + name=name, description=description, url=url, + icon=icon + ) + self.aug.set(prop, favorites + new_favorite) + return self + + def get_favorites(self): + favs_string = self.aug.get(self.favorite_property) or '' + favs_split = favs_string.split(',') + + # There's a trailing comma --> 1 extra + favs_len = len(favs_split) + if favs_len > 0: + favs_split = favs_split[:-1] + favs_len = len(favs_split) + + if favs_len % self.FAVORITE_TUPLE_SIZE: + raise SyntaxError("Invalid number of fields in favorite line") + + favs = OrderedDict() + i = 0 + while i < favs_len: + next_idx = i + self.FAVORITE_TUPLE_SIZE + t = favs_split[i:next_idx] + name, description, url, icon = t + favs[url] = { + "name": name, + "description": description, + "icon": icon + } + i = next_idx + return favs diff --git a/plinth/modules/i2p/tests/__init__.py b/plinth/modules/i2p/tests/__init__.py index e69de29bb..52393bed3 100644 --- a/plinth/modules/i2p/tests/__init__.py +++ b/plinth/modules/i2p/tests/__init__.py @@ -0,0 +1,18 @@ +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +from pathlib import Path + +DATA_DIR = Path(__file__).parent / 'data' diff --git a/plinth/modules/i2p/tests/data/router.config b/plinth/modules/i2p/tests/data/router.config new file mode 100644 index 000000000..8661f7f28 --- /dev/null +++ b/plinth/modules/i2p/tests/data/router.config @@ -0,0 +1,23 @@ +# NOTE: This I2P config file must use UTF-8 encoding +i2np.lastIPChange=1555091394049 +i2np.ntcp2.iv=i3yLM2tPW6QH5h4YYZ8EWQ== +i2np.ntcp2.sp=j1K18jMDa5SPH23R2cHMJ-dUyPdo~uooZp6Uz06qP0k= +i2np.udp.internalPort=18778 +i2np.udp.port=18778 +jbigi.lastProcessor=Haswell Celeron/Pentium w/ AVX model 60/64 +router.blocklistVersion=1523108115000 +router.firstInstalled=1555091372597 +router.firstVersion=0.9.38 +router.inboundPool.randomKey=c1BGKzCBpxYfsH0AiEMIS39zYWzyBuWO9lYTqeA91dk= +router.outboundPool.randomKey=Of0jkHuGeUAHr~NoIAQbY930fbYacb4NyX3CjbVKKFI= +router.passwordManager.migrated=true +router.previousVersion=0.9.38 +router.startup.jetty9.migrated=true +router.updateDisabled=true +router.updateLastInstalled=1555091372597 +routerconsole.country= +routerconsole.favorites=anoncoin.i2p,The Anoncoin project,http://anoncoin.i2p/,/themes/console/images/anoncoin_32.png,Dev Builds,Development builds of I2P,http://bobthebuilder.i2p/,/themes/console/images/script_gear.png,Dev Forum,Development forum,http://zzz.i2p/,/themes/console/images/group_gear.png,echelon.i2p,I2P Applications,http://echelon.i2p/,/themes/console/images/box_open.png,exchanged.i2p,Anonymous cryptocurrency exchange,http://exchanged.i2p/,/themes/console/images/exchanged.png,I2P Bug Reports,Bug tracker,http://trac.i2p2.i2p/report/1,/themes/console/images/bug.png,I2P FAQ,Frequently Asked Questions,http://i2p-projekt.i2p/faq,/themes/console/images/question.png,I2P Forum,Community forum,http://i2pforum.i2p/,/themes/console/images/group.png,I2P Plugins,Add-on directory,http://i2pwiki.i2p/index.php?title=Plugins,/themes/console/images/info/plugin_link.png,I2P Technical Docs,Technical documentation,http://i2p-projekt.i2p/how,/themes/console/images/education.png,I2P Wiki,Anonymous wiki - share the knowledge,http://i2pwiki.i2p/,/themes/console/images/i2pwiki_logo.png,Planet I2P,I2P News,http://planet.i2p/,/themes/console/images/world.png,PrivateBin,Encrypted I2P Pastebin,http://paste.crypthost.i2p/,/themes/console/images/paste_plain.png,Project Website,I2P home page,http://i2p-projekt.i2p/,/themes/console/images/info_rhombus.png,stats.i2p,I2P Network Statistics,http://stats.i2p/cgi-bin/dashboard.cgi,/themes/console/images/chart_line.png,The Tin Hat,Privacy guides and tutorials,http://secure.thetinhat.i2p/,/themes/console/images/thetinhat.png,Trac Wiki,,http://trac.i2p2.i2p/,/themes/console/images/billiard_marker.png, +routerconsole.lang=en +routerconsole.newsLastChecked=1555093599462 +routerconsole.newsLastUpdated=1553195540000 +routerconsole.welcomeWizardComplete=true diff --git a/plinth/modules/i2p/tests/test_router_editor.py b/plinth/modules/i2p/tests/test_router_editor.py new file mode 100644 index 000000000..10feb1c02 --- /dev/null +++ b/plinth/modules/i2p/tests/test_router_editor.py @@ -0,0 +1,86 @@ +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +import unittest + +from plinth.modules.i2p.helpers import RouterEditor +from plinth.modules.i2p.tests import DATA_DIR + +ROUTER_CONF_PATH = str(DATA_DIR / 'router.config') + + +class RouterEditingTests(unittest.TestCase): + + def setUp(self): + self.editor = RouterEditor(ROUTER_CONF_PATH) + + def test_count_favs(self): + self.editor.read_conf() + favs = self.editor.get_favorites() + self.assertEqual(len(favs.keys()), 17) + + def test_add_normal_favorite(self): + self.editor.read_conf() + name = 'Somewhere' + url = 'http://somewhere-again.i2p' + description = "Just somewhere else" + self.editor.add_favorite( + name, url, description + ) + + favs = self.editor.get_favorites() + self.assertIn(url, favs) + favorite = favs[url] + self.assertEqual(favorite['name'], name) + self.assertEqual(favorite['description'], description) + + self.assertEqual(len(favs), 18) + + def test_add_favorite_with_comma(self): + self.editor.read_conf() + name = 'Name,with,comma' + expected_name = name.replace(',', '.') + url = 'http://url-without-comma.i2p' + description = "Another,comma,to,play,with" + expected_description = description.replace(',', '.') + + self.editor.add_favorite( + name, url, description + ) + + favs = self.editor.get_favorites() + self.assertIn(url, favs) + favorite = favs[url] + self.assertEqual(favorite['name'], expected_name) + self.assertEqual(favorite['description'], expected_description) + + self.assertEqual(len(favs), 18) + + def test_add_fav_to_empty_config(self): + self.editor.conf_filename = '/tmp/inexistent.conf' + self.editor.read_conf() + self.assertEqual(len(self.editor.get_favorites()), 0) + + name = 'Test Favorite' + url = 'http://test-fav.i2p' + self.editor.add_favorite( + name, url + ) + self.assertEqual(len(self.editor.get_favorites()), 1) + + +if __name__ == '__main__': + unittest.main() From f7d9c9eff5707305fef6f845afab3ef465c00e3c Mon Sep 17 00:00:00 2001 From: LoveIsGrief Date: Sat, 13 Apr 2019 19:39:57 +0200 Subject: [PATCH 2/5] i2p: Include default favorites after installation The default favorites might change and we might have to update the list but for now they were extracted from a clean router.config saved by the i2p daemon. 1528 - augeas for router.config Reviewed-by: Sunil Mohan Adapa --- actions/i2p | 6 +- plinth/modules/i2p/__init__.py | 33 ++++---- plinth/modules/i2p/helpers.py | 7 +- plinth/modules/i2p/resources.py | 140 ++++++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 plinth/modules/i2p/resources.py diff --git a/actions/i2p b/actions/i2p index 5e34ab98d..c170aadf0 100755 --- a/actions/i2p +++ b/actions/i2p @@ -43,6 +43,8 @@ def parse_arguments(): 'add-favorite', help='Add an eepsite to the list of favorites') subparser.add_argument('--name', help='Name of the entry', required=True) subparser.add_argument('--url', help='URL of the entry', required=True) + subparser.add_argument('--description', help='short description', required=False) + subparser.add_argument('--icon', help='URL to icon', required=False) subparser = subparsers.add_parser('set-tunnel-property', help='Modify configuration of a tunnel') @@ -92,7 +94,9 @@ def subcommand_add_favorite(arguments): editor = RouterEditor() editor.read_conf().add_favorite( arguments.name, - url + url, + arguments.description, + arguments.icon ).write_conf() print('Added {} to favorites'.format(url)) diff --git a/plinth/modules/i2p/__init__.py b/plinth/modules/i2p/__init__.py index 0ab07b132..8cfc59e58 100644 --- a/plinth/modules/i2p/__init__.py +++ b/plinth/modules/i2p/__init__.py @@ -23,8 +23,8 @@ from django.utils.translation import ugettext_lazy as _ from plinth import action_utils, actions, frontpage from plinth import service as service_module from plinth.menu import main_menu +from plinth.modules.i2p.resources import FAVORITES from plinth.modules.users import register_group - from .manifest import backup, clients version = 1 @@ -59,17 +59,6 @@ proxies_service = None manual_page = 'I2P' -# TODO: Add all default favorites -# the router.config favorites is empty until a change is made -# in the front-end, but we currently do not have a method of -# doing that, so we need to add the favorites ourselves -additional_favorites = [ - ('Searx instance', 'http://ransack.i2p'), - ('Torrent tracker', 'http://tracker2.postman.i2p'), - ('YaCy Legwork', 'http://legwork.i2p'), - ('YaCy Seeker', 'http://seeker.i2p'), -] - tunnels_to_manage = { 'I2P HTTP Proxy': 'i2p-http-proxy-freedombox', 'I2P HTTPS Proxy': 'i2p-https-proxy-freedombox', @@ -105,14 +94,20 @@ def setup(helper, old_version=None): helper.call('post', disable) # Add favorites to the configuration - for fav_name, fav_url in additional_favorites: - helper.call('post', actions.superuser_run, 'i2p', [ + for fav in FAVORITES: + args = [ 'add-favorite', - '--name', - fav_name, - '--url', - fav_url, - ]) + '--name', fav.get('name'), + '--url', fav.get('url'), + ] + if 'icon' in fav: + args.extend(['--icon', fav.get('icon')]) + + if 'description' in fav: + args.extend(['--description', fav.get('description')]) + + helper.call('post', actions.superuser_run, 'i2p', args) + # Tunnels to all interfaces for tunnel in tunnels_to_manage: diff --git a/plinth/modules/i2p/helpers.py b/plinth/modules/i2p/helpers.py index 0897efa19..883db10b8 100644 --- a/plinth/modules/i2p/helpers.py +++ b/plinth/modules/i2p/helpers.py @@ -166,7 +166,7 @@ class RouterEditor(object): def favorite_property(self): return '/files{filename}/{prop}'.format(filename=self.conf_filename, prop=self.FAVORITE_PROP) - def add_favorite(self, name, url, description='', icon='/themes/console/images/eepsite.png'): + def add_favorite(self, name, url, description=None, icon=None): """ Adds a favorite to the router.config @@ -182,6 +182,11 @@ class RouterEditor(object): :type description: basestring :type icon: basestring """ + if not description: + description = '' + if not icon: + icon = '/themes/console/images/eepsite.png' + if ',' in url: raise ValueError('URL cannot contain commas') if ',' in icon: diff --git a/plinth/modules/i2p/resources.py b/plinth/modules/i2p/resources.py new file mode 100644 index 000000000..ae63ebece --- /dev/null +++ b/plinth/modules/i2p/resources.py @@ -0,0 +1,140 @@ +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +DEFAULT_FAVORITES = [ + { + 'name': 'anoncoin.i2p', + 'description': 'The Anoncoin project', + 'icon': '/themes/console/images/anoncoin_32.png', + 'url': 'http://anoncoin.i2p/' + }, + { + 'name': 'Dev Builds', + 'description': 'Development builds of I2P', + 'icon': '/themes/console/images/script_gear.png', + 'url': 'http://bobthebuilder.i2p/' + }, + { + 'name': 'Dev Forum', + 'description': 'Development forum', + 'icon': '/themes/console/images/group_gear.png', + 'url': 'http://zzz.i2p/' + }, + { + 'name': 'echelon.i2p', + 'description': 'I2P Applications', + 'icon': '/themes/console/images/box_open.png', + 'url': 'http://echelon.i2p/' + }, + { + 'name': 'exchanged.i2p', + 'description': 'Anonymous cryptocurrency exchange', + 'icon': '/themes/console/images/exchanged.png', + 'url': 'http://exchanged.i2p/' + }, + { + 'name': 'I2P Bug Reports', + 'description': 'Bug tracker', + 'icon': '/themes/console/images/bug.png', + 'url': 'http://trac.i2p2.i2p/report/1' + }, + { + 'name': 'I2P FAQ', + 'description': 'Frequently Asked Questions', + 'icon': '/themes/console/images/question.png', + 'url': 'http://i2p-projekt.i2p/faq' + }, + { + 'name': 'I2P Forum', + 'description': 'Community forum', + 'icon': '/themes/console/images/group.png', + 'url': 'http://i2pforum.i2p/' + }, + { + 'name': 'I2P Plugins', + 'description': 'Add-on directory', + 'icon': '/themes/console/images/info/plugin_link.png', + 'url': 'http://i2pwiki.i2p/index.php?title=Plugins' + }, + { + 'name': 'I2P Technical Docs', + 'description': 'Technical documentation', + 'icon': '/themes/console/images/education.png', + 'url': 'http://i2p-projekt.i2p/how' + }, + { + 'name': 'I2P Wiki', + 'description': 'Anonymous wiki - share the knowledge', + 'icon': '/themes/console/images/i2pwiki_logo.png', + 'url': 'http://i2pwiki.i2p/' + }, + { + 'name': 'Planet I2P', + 'description': 'I2P News', + 'icon': '/themes/console/images/world.png', + 'url': 'http://planet.i2p/' + }, + { + 'name': 'PrivateBin', + 'description': 'Encrypted I2P Pastebin', + 'icon': '/themes/console/images/paste_plain.png', + 'url': 'http://paste.crypthost.i2p/' + }, + { + 'name': 'Project Website', + 'description': 'I2P home page', + 'icon': '/themes/console/images/info_rhombus.png', + 'url': 'http://i2p-projekt.i2p/' + }, + { + 'name': 'stats.i2p', + 'description': 'I2P Network Statistics', + 'icon': '/themes/console/images/chart_line.png', + 'url': 'http://stats.i2p/cgi-bin/dashboard.cgi' + }, + { + 'name': 'The Tin Hat', + 'description': 'Privacy guides and tutorials', + 'icon': '/themes/console/images/thetinhat.png', + 'url': 'http://secure.thetinhat.i2p/' + }, + { + 'name': 'Trac Wiki', + 'description': '', + 'icon': '/themes/console/images/billiard_marker.png', + 'url': 'http://trac.i2p2.i2p/' + } +] +ADDITIONAL_FAVORITES = [ + { + 'name': 'Searx instance', + 'url': 'http://ransack.i2p' + }, + { + 'name': 'Torrent tracker', + 'url': 'http://tracker2.postman.i2p' + }, + { + 'name': 'YaCy Legwork', + 'url': 'http://legwork.i2p' + }, + { + 'name': 'YaCy Seeker', + 'url': 'http://seeker.i2p' + }, +] + +FAVORITES = DEFAULT_FAVORITES + ADDITIONAL_FAVORITES From dca91da571b7e1038f07ed83b745ad7300a30297 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Mon, 29 Apr 2019 16:33:06 -0700 Subject: [PATCH 3/5] i2p: Update license headers for consistent formatting Signed-off-by: Sunil Mohan Adapa --- plinth/modules/i2p/resources.py | 1 + plinth/modules/i2p/tests/__init__.py | 1 + plinth/modules/i2p/tests/test_helpers.py | 23 ++++++++++--------- .../modules/i2p/tests/test_router_editor.py | 23 ++++++++++--------- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/plinth/modules/i2p/resources.py b/plinth/modules/i2p/resources.py index ae63ebece..1efe5783b 100644 --- a/plinth/modules/i2p/resources.py +++ b/plinth/modules/i2p/resources.py @@ -1,3 +1,4 @@ +# # This file is part of FreedomBox. # # This program is free software: you can redistribute it and/or modify diff --git a/plinth/modules/i2p/tests/__init__.py b/plinth/modules/i2p/tests/__init__.py index 52393bed3..5d1e441e2 100644 --- a/plinth/modules/i2p/tests/__init__.py +++ b/plinth/modules/i2p/tests/__init__.py @@ -1,3 +1,4 @@ +# # This file is part of FreedomBox. # # This program is free software: you can redistribute it and/or modify diff --git a/plinth/modules/i2p/tests/test_helpers.py b/plinth/modules/i2p/tests/test_helpers.py index c9181161a..fdc21bc39 100644 --- a/plinth/modules/i2p/tests/test_helpers.py +++ b/plinth/modules/i2p/tests/test_helpers.py @@ -1,17 +1,18 @@ -# This file is part of FreedomBox. # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. +# This file is part of FreedomBox. # -# 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 Affero General Public License for more details. +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. # -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . # """ Unit tests for helpers of I2P application. diff --git a/plinth/modules/i2p/tests/test_router_editor.py b/plinth/modules/i2p/tests/test_router_editor.py index 10feb1c02..bd689c924 100644 --- a/plinth/modules/i2p/tests/test_router_editor.py +++ b/plinth/modules/i2p/tests/test_router_editor.py @@ -1,17 +1,18 @@ -# This file is part of FreedomBox. # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. +# This file is part of FreedomBox. # -# 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 Affero General Public License for more details. +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. # -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . # import unittest From 97ed7fe144d76f99c0b1f4e5637597c4e8f33f59 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Mon, 29 Apr 2019 16:35:15 -0700 Subject: [PATCH 4/5] i2p: Minor flake8 and yapf fixes Signed-off-by: Sunil Mohan Adapa --- actions/i2p | 11 ++--- plinth/modules/i2p/__init__.py | 7 ++- plinth/modules/i2p/helpers.py | 64 +++++++++++++++++----------- plinth/modules/i2p/manifest.py | 3 ++ plinth/modules/i2p/resources.py | 3 ++ plinth/modules/i2p/tests/__init__.py | 4 ++ 6 files changed, 57 insertions(+), 35 deletions(-) diff --git a/actions/i2p b/actions/i2p index c170aadf0..2ee857510 100755 --- a/actions/i2p +++ b/actions/i2p @@ -43,7 +43,8 @@ def parse_arguments(): 'add-favorite', help='Add an eepsite to the list of favorites') subparser.add_argument('--name', help='Name of the entry', required=True) subparser.add_argument('--url', help='URL of the entry', required=True) - subparser.add_argument('--description', help='short description', required=False) + subparser.add_argument('--description', help='Short description', + required=False) subparser.add_argument('--icon', help='URL to icon', required=False) subparser = subparsers.add_parser('set-tunnel-property', @@ -92,12 +93,8 @@ def subcommand_add_favorite(arguments): url = arguments.url editor = RouterEditor() - editor.read_conf().add_favorite( - arguments.name, - url, - arguments.description, - arguments.icon - ).write_conf() + editor.read_conf().add_favorite(arguments.name, url, arguments.description, + arguments.icon).write_conf() print('Added {} to favorites'.format(url)) diff --git a/plinth/modules/i2p/__init__.py b/plinth/modules/i2p/__init__.py index 8cfc59e58..a0334e14b 100644 --- a/plinth/modules/i2p/__init__.py +++ b/plinth/modules/i2p/__init__.py @@ -25,6 +25,7 @@ from plinth import service as service_module from plinth.menu import main_menu from plinth.modules.i2p.resources import FAVORITES from plinth.modules.users import register_group + from .manifest import backup, clients version = 1 @@ -97,8 +98,10 @@ def setup(helper, old_version=None): for fav in FAVORITES: args = [ 'add-favorite', - '--name', fav.get('name'), - '--url', fav.get('url'), + '--name', + fav.get('name'), + '--url', + fav.get('url'), ] if 'icon' in fav: args.extend(['--icon', fav.get('icon')]) diff --git a/plinth/modules/i2p/helpers.py b/plinth/modules/i2p/helpers.py index 883db10b8..6374fc9f2 100644 --- a/plinth/modules/i2p/helpers.py +++ b/plinth/modules/i2p/helpers.py @@ -17,9 +17,9 @@ Various helpers for the I2P app. """ -from collections import OrderedDict import os import re +from collections import OrderedDict import augeas @@ -137,10 +137,11 @@ class TunnelEditor(): self.aug.set(self.calc_prop_path(tunnel_prop), value) -class RouterEditor(object): - """ +class RouterEditor(): + """Helper to edit I2P router configuration file using augeas. :type aug: augeas.Augeas + """ FAVORITE_PROP = 'routerconsole.favorites' @@ -151,44 +152,58 @@ class RouterEditor(object): self.aug = None def read_conf(self): - self.aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + - augeas.Augeas.NO_MODL_AUTOLOAD) + """Load an instance of Augeaus for processing APT configuration. + + Chainable method. + + """ + self.aug = augeas.Augeas( + flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD) self.aug.set('/augeas/load/Properties/lens', 'Properties.lns') - self.aug.set('/augeas/load/Properties/incl[last() + 1]', self.conf_filename) + self.aug.set('/augeas/load/Properties/incl[last() + 1]', + self.conf_filename) self.aug.load() return self def write_conf(self): + """Write changes to the configuration file to disk. + + Chainable method. + + """ self.aug.save() return self @property def favorite_property(self): - return '/files{filename}/{prop}'.format(filename=self.conf_filename, prop=self.FAVORITE_PROP) + """Return the favourites property from configuration file.""" + return '/files{filename}/{prop}'.format(filename=self.conf_filename, + prop=self.FAVORITE_PROP) def add_favorite(self, name, url, description=None, icon=None): - """ - Adds a favorite to the router.config + """Add a favorite to the router configuration file. - Favorites are in a single string and separated by ','. - none of the incoming params can therefore use commas. - I2P replaces the commas by dots. + Favorites are in a single string and separated by ','. none of the + incoming params can therefore use commas. I2P replaces the commas by + dots. - That's ok for the name and description, - but not for the url and icon + That's ok for the name and description, but not for the url and icon. :type name: basestring :type url: basestring :type description: basestring :type icon: basestring + """ if not description: description = '' + if not icon: icon = '/themes/console/images/eepsite.png' if ',' in url: raise ValueError('URL cannot contain commas') + if ',' in icon: raise ValueError('Icon cannot contain commas') @@ -198,13 +213,12 @@ class RouterEditor(object): prop = self.favorite_property favorites = self.aug.get(prop) or '' new_favorite = '{name},{description},{url},{icon},'.format( - name=name, description=description, url=url, - icon=icon - ) + name=name, description=description, url=url, icon=icon) self.aug.set(prop, favorites + new_favorite) return self def get_favorites(self): + """Return list of favorites.""" favs_string = self.aug.get(self.favorite_property) or '' favs_split = favs_string.split(',') @@ -218,15 +232,13 @@ class RouterEditor(object): raise SyntaxError("Invalid number of fields in favorite line") favs = OrderedDict() - i = 0 - while i < favs_len: - next_idx = i + self.FAVORITE_TUPLE_SIZE - t = favs_split[i:next_idx] - name, description, url, icon = t + for index in range(0, favs_len, self.FAVORITE_TUPLE_SIZE): + next_index = index + self.FAVORITE_TUPLE_SIZE + name, description, url, icon = favs_split[index:next_index] favs[url] = { - "name": name, - "description": description, - "icon": icon + 'name': name, + 'description': description, + 'icon': icon } - i = next_idx + return favs diff --git a/plinth/modules/i2p/manifest.py b/plinth/modules/i2p/manifest.py index c65bdebe7..472b9e016 100644 --- a/plinth/modules/i2p/manifest.py +++ b/plinth/modules/i2p/manifest.py @@ -14,6 +14,9 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # +""" +Application manifest for I2P. +""" from django.utils.translation import ugettext_lazy as _ diff --git a/plinth/modules/i2p/resources.py b/plinth/modules/i2p/resources.py index 1efe5783b..b6b9df7fd 100644 --- a/plinth/modules/i2p/resources.py +++ b/plinth/modules/i2p/resources.py @@ -14,6 +14,9 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # +""" +Pre-defined list of favorites for I2P and some additional favorites. +""" DEFAULT_FAVORITES = [ { diff --git a/plinth/modules/i2p/tests/__init__.py b/plinth/modules/i2p/tests/__init__.py index 5d1e441e2..1bf534884 100644 --- a/plinth/modules/i2p/tests/__init__.py +++ b/plinth/modules/i2p/tests/__init__.py @@ -14,6 +14,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # +""" +Common parts for all I2P tests. +""" + from pathlib import Path DATA_DIR = Path(__file__).parent / 'data' From 56d511368d0c12ca7b214648baf65418f89a336e Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Mon, 29 Apr 2019 16:36:00 -0700 Subject: [PATCH 5/5] i2p: Convert router configuration tests to pytest style Signed-off-by: Sunil Mohan Adapa --- .../modules/i2p/tests/test_router_editor.py | 123 +++++++++--------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/plinth/modules/i2p/tests/test_router_editor.py b/plinth/modules/i2p/tests/test_router_editor.py index bd689c924..cbf188d3a 100644 --- a/plinth/modules/i2p/tests/test_router_editor.py +++ b/plinth/modules/i2p/tests/test_router_editor.py @@ -14,8 +14,11 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # +""" +Test I2P router configuration editing helper. +""" -import unittest +import pytest from plinth.modules.i2p.helpers import RouterEditor from plinth.modules.i2p.tests import DATA_DIR @@ -23,65 +26,63 @@ from plinth.modules.i2p.tests import DATA_DIR ROUTER_CONF_PATH = str(DATA_DIR / 'router.config') -class RouterEditingTests(unittest.TestCase): - - def setUp(self): - self.editor = RouterEditor(ROUTER_CONF_PATH) - - def test_count_favs(self): - self.editor.read_conf() - favs = self.editor.get_favorites() - self.assertEqual(len(favs.keys()), 17) - - def test_add_normal_favorite(self): - self.editor.read_conf() - name = 'Somewhere' - url = 'http://somewhere-again.i2p' - description = "Just somewhere else" - self.editor.add_favorite( - name, url, description - ) - - favs = self.editor.get_favorites() - self.assertIn(url, favs) - favorite = favs[url] - self.assertEqual(favorite['name'], name) - self.assertEqual(favorite['description'], description) - - self.assertEqual(len(favs), 18) - - def test_add_favorite_with_comma(self): - self.editor.read_conf() - name = 'Name,with,comma' - expected_name = name.replace(',', '.') - url = 'http://url-without-comma.i2p' - description = "Another,comma,to,play,with" - expected_description = description.replace(',', '.') - - self.editor.add_favorite( - name, url, description - ) - - favs = self.editor.get_favorites() - self.assertIn(url, favs) - favorite = favs[url] - self.assertEqual(favorite['name'], expected_name) - self.assertEqual(favorite['description'], expected_description) - - self.assertEqual(len(favs), 18) - - def test_add_fav_to_empty_config(self): - self.editor.conf_filename = '/tmp/inexistent.conf' - self.editor.read_conf() - self.assertEqual(len(self.editor.get_favorites()), 0) - - name = 'Test Favorite' - url = 'http://test-fav.i2p' - self.editor.add_favorite( - name, url - ) - self.assertEqual(len(self.editor.get_favorites()), 1) +@pytest.fixture +def editor(): + """Return editor instance object for each test.""" + return RouterEditor(ROUTER_CONF_PATH) -if __name__ == '__main__': - unittest.main() +def test_count_favorites(editor): + """Test counting favorites.""" + editor.read_conf() + favorites = editor.get_favorites() + assert len(favorites.keys()) == 17 + + +def test_add_normal_favorite(editor): + """Test adding a normal favorite.""" + editor.read_conf() + name = 'Somewhere' + url = 'http://somewhere-again.i2p' + description = "Just somewhere else" + editor.add_favorite(name, url, description) + + favorites = editor.get_favorites() + assert url in favorites + favorite = favorites[url] + assert favorite['name'] == name + assert favorite['description'] == description + + assert len(favorites) == 18 + + +def test_add_favorite_with_comma(editor): + """Test adding a favorite with common in its name.""" + editor.read_conf() + name = 'Name,with,comma' + expected_name = name.replace(',', '.') + url = 'http://url-without-comma.i2p' + description = "Another,comma,to,play,with" + expected_description = description.replace(',', '.') + + editor.add_favorite(name, url, description) + + favorites = editor.get_favorites() + assert url in favorites + favorite = favorites[url] + assert favorite['name'] == expected_name + assert favorite['description'] == expected_description + + assert len(favorites) == 18 + + +def test_add_fav_to_empty_config(editor): + """Test adding favorite to empty configuration.""" + editor.conf_filename = '/tmp/inexistent.conf' + editor.read_conf() + assert not editor.get_favorites() + + name = 'Test Favorite' + url = 'http://test-fav.i2p' + editor.add_favorite(name, url) + assert len(editor.get_favorites()) == 1