Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • appimage
  • check_uniq_node_by_endpoints
  • dev
  • feature/agent_architecture
  • feature/backend
  • fix_ci
  • fix_ci_osx
  • fix_dbus_error
  • fix_gitlab
  • fix_travis#1105
  • fixappveyor
  • gitlab
  • landscape
  • master
  • pyinstaller
  • pyqt5.6
  • qt5.7
  • qtwebengine
  • sakia020
  • translations
  • 0.1.0
  • 0.10.0
  • 0.10.1
  • 0.10.2
  • 0.11.0
  • 0.11.1
  • 0.11.2
  • 0.11.3
  • 0.11.4
  • 0.11.4.post1
  • 0.11.5
  • 0.12.0
  • 0.12.1dev1
  • 0.12.1dev2
  • 0.12.1dev3
  • 0.12.1dev4
  • 0.12.dev1
  • 0.12.dev2
  • 0.12.dev3
  • 0.12.dev4
  • 0.12.dev5
  • 0.12rc1
  • 0.2.0
  • 0.2.1
  • 0.20.0
  • 0.20.0dev1
  • 0.20.0dev10
  • 0.20.0dev12
  • 0.20.0dev13
  • 0.20.0dev14
  • 0.20.0dev15
  • 0.20.0dev16
  • 0.20.0dev2
  • 0.20.0dev3
  • 0.20.0dev3-test1
  • 0.20.0dev4
  • 0.20.0dev5
  • 0.20.0dev6
  • 0.20.0dev7
  • 0.20.0dev8
  • 0.20.0dev9
  • 0.20.1
  • 0.20.10
  • 0.20.11
  • 0.20.13
  • 0.20.2
  • 0.20.3
  • 0.20.4
  • 0.20.5
  • 0.20.6
  • 0.20.7
  • 0.20.8
  • 0.20.9
  • 0.3.0
  • 0.30.0
  • 0.30.0beta
  • 0.30.0beta1
  • 0.30.0beta2
  • 0.30.0beta3
  • 0.30.0beta4
  • 0.30.0beta5
  • 0.30.0beta6
  • 0.30.0beta7
  • 0.30.0beta8
  • 0.30.0beta9
  • 0.30.1
  • 0.30.10
  • 0.30.11
  • 0.30.12
  • 0.30.13
  • 0.30.14
  • 0.30.2
  • 0.30.3
  • 0.30.4
  • 0.30.5
  • 0.30.6
  • 0.30.7
  • 0.30.8
  • 0.30.9
  • 0.31.0
  • 0.31.0.post1
  • 0.31.1
  • 0.31.2
  • 0.31.3
  • 0.31.4
  • 0.31.5
  • 0.31.6
  • 0.32.0
  • 0.32.0-linux
  • 0.32.0RC1
  • 0.32.0RC2
  • 0.32.0RC3
  • 0.32.0RC4
  • 0.32.0RC5
  • 0.32.0RC6
  • 0.32.1
  • 0.32.10
  • 0.32.10post1
  • 0.32.2
  • 0.32.3
120 results

Target

Select target project
  • cebash/sakia
  • santiago/sakia
  • jonas/sakia
3 results
Select Git revision
  • Docker
  • Docker-debian9
  • check_uniq_node_by_endpoints
  • dev
  • feature/agent_architecture
  • feature/backend
  • fix_ci
  • fix_ci_osx
  • fix_dbus_error
  • fix_travis#1105
  • fixappveyor
  • gitlab
  • landscape
  • master
  • pyinstaller
  • pyqt5.6
  • qt5.7
  • qtwebengine
  • sakia020
  • translations
  • 0.1.0
  • 0.10.0
  • 0.10.1
  • 0.10.2
  • 0.11.0
  • 0.11.1
  • 0.11.2
  • 0.11.3
  • 0.11.4
  • 0.11.4.post1
  • 0.11.5
  • 0.12.0
  • 0.12.1dev1
  • 0.12.1dev2
  • 0.12.1dev3
  • 0.12.1dev4
  • 0.12.dev1
  • 0.12.dev2
  • 0.12.dev3
  • 0.12.dev4
  • 0.12.dev5
  • 0.12rc1
  • 0.2.0
  • 0.2.1
  • 0.20.0
  • 0.20.0dev1
  • 0.20.0dev10
  • 0.20.0dev12
  • 0.20.0dev13
  • 0.20.0dev14
  • 0.20.0dev15
  • 0.20.0dev16
  • 0.20.0dev2
  • 0.20.0dev3
  • 0.20.0dev3-test1
  • 0.20.0dev4
  • 0.20.0dev5
  • 0.20.0dev6
  • 0.20.0dev7
  • 0.20.0dev8
  • 0.20.0dev9
  • 0.20.1
  • 0.20.10
  • 0.20.11
  • 0.20.13
  • 0.20.2
  • 0.20.3
  • 0.20.4
  • 0.20.5
  • 0.20.6
  • 0.20.7
  • 0.20.8
  • 0.20.9
  • 0.3.0
  • 0.30.0
  • 0.30.0beta
  • 0.30.0beta1
  • 0.30.0beta2
  • 0.30.0beta3
  • 0.30.0beta4
  • 0.30.0beta5
  • 0.30.0beta6
  • 0.30.0beta7
  • 0.30.0beta8
  • 0.30.0beta9
  • 0.30.1
  • 0.30.10
  • 0.30.11
  • 0.30.12
  • 0.30.13
  • 0.30.14
  • 0.30.2
  • 0.30.3
  • 0.30.4
  • 0.30.5
  • 0.30.6
  • 0.30.7
  • 0.30.8
  • 0.30.9
  • 0.31.0
  • 0.31.0.post1
  • 0.31.1
  • 0.31.2
  • 0.31.3
  • 0.31.4
  • 0.31.5
  • 0.31.6
  • 0.32.0
  • 0.32.0-linux
  • 0.32.0RC1
  • 0.32.0RC2
  • 0.32.0RC3
  • 0.32.0RC4
  • 0.32.0RC5
  • 0.32.0RC6
  • 0.32.1
  • 0.32.10
  • 0.32.10post1
  • 0.32.2
  • 0.32.3
120 results
Show changes
Showing
with 985 additions and 382 deletions
......@@ -6,8 +6,8 @@ from ..entities import Node
@attr.s(frozen=True)
class NodesRepo:
"""The repository for Communities entities.
"""
"""The repository for Communities entities."""
_conn = attr.ib() # :type sqlite3.Connection
_primary_keys = (attr.fields(Node).currency, attr.fields(Node).pubkey)
......@@ -19,7 +19,7 @@ class NodesRepo:
node_tuple = attr.astuple(node, tuple_factory=list)
node_tuple[2] = "\n".join([str(n) for n in node_tuple[2]])
node_tuple[12] = "\n".join([str(n) for n in node_tuple[12]])
values = ",".join(['?'] * len(node_tuple))
values = ",".join(["?"] * len(node_tuple))
self._conn.execute("INSERT INTO nodes VALUES ({0})".format(values), node_tuple)
def update(self, node):
......@@ -27,13 +27,20 @@ class NodesRepo:
Update an existing node in the database
:param sakia.data.entities.Node node: the node to update
"""
updated_fields = attr.astuple(node, tuple_factory=list,
filter=attr.filters.exclude(*NodesRepo._primary_keys))
updated_fields = attr.astuple(
node,
tuple_factory=list,
filter=attr.filters.exclude(*NodesRepo._primary_keys),
)
updated_fields[0] = "\n".join([str(n) for n in updated_fields[0]])
updated_fields[10] = "\n".join([str(n) for n in updated_fields[9]])
where_fields = attr.astuple(node, tuple_factory=list,
filter=attr.filters.include(*NodesRepo._primary_keys))
self._conn.execute("""UPDATE nodes SET
where_fields = attr.astuple(
node,
tuple_factory=list,
filter=attr.filters.include(*NodesRepo._primary_keys),
)
self._conn.execute(
"""UPDATE nodes SET
endpoints=?,
peer_buid=?,
uid=?,
......@@ -51,7 +58,8 @@ class NodesRepo:
WHERE
currency=? AND
pubkey=?""",
updated_fields + where_fields)
updated_fields + where_fields,
)
def get_one(self, **search):
"""
......@@ -67,7 +75,9 @@ class NodesRepo:
filters.append("{k}=?".format(k=k))
values.append(v)
request = "SELECT * FROM nodes WHERE {filters}".format(filters=" AND ".join(filters))
request = "SELECT * FROM nodes WHERE {filters}".format(
filters=" AND ".join(filters)
)
c = self._conn.execute(request, tuple(values))
data = c.fetchone()
......@@ -91,7 +101,9 @@ class NodesRepo:
values.append(value)
if filters:
request = "SELECT * FROM nodes WHERE {filters}".format(filters=" AND ".join(filters))
request = "SELECT * FROM nodes WHERE {filters}".format(
filters=" AND ".join(filters)
)
else:
request = "SELECT * FROM nodes"
......@@ -106,30 +118,110 @@ class NodesRepo:
Drop an existing node from the database
:param sakia.data.entities.Node node: the node to update
"""
where_fields = attr.astuple(node, filter=attr.filters.include(*NodesRepo._primary_keys))
self._conn.execute("""DELETE FROM nodes
where_fields = attr.astuple(
node, filter=attr.filters.include(*NodesRepo._primary_keys)
)
self._conn.execute(
"""DELETE FROM nodes
WHERE
currency=? AND pubkey=?""", where_fields)
currency=? AND pubkey=?""",
where_fields,
)
def get_offline_nodes(self, currency):
c = self._conn.execute(
"SELECT * FROM nodes WHERE currency == ? AND state > ?;",
(currency, Node.FAILURE_THRESHOLD),
)
datas = c.fetchall()
if datas:
return [Node(*data) for data in datas]
return []
def get_synced_nodes(self, currency, current_buid):
c = self._conn.execute(
"SELECT * FROM nodes "
"WHERE currency == ? "
"AND state <= ?"
"AND current_buid == ?",
(currency, Node.FAILURE_THRESHOLD, current_buid),
)
datas = c.fetchall()
if datas:
return [Node(*data) for data in datas]
return []
def get_synced_members_nodes(self, currency, current_buid):
c = self._conn.execute(
"SELECT * FROM nodes "
"WHERE currency == ? "
"AND state <= ?"
"AND current_buid == ?"
"AND member == 1",
(currency, Node.FAILURE_THRESHOLD, current_buid),
)
datas = c.fetchall()
if datas:
return [Node(*data) for data in datas]
return []
def get_online_nodes(self, currency):
c = self._conn.execute(
"SELECT * FROM nodes WHERE currency == ? AND state <= ?;",
(currency, Node.FAILURE_THRESHOLD),
)
datas = c.fetchall()
if datas:
return [Node(*data) for data in datas]
return []
def get_offline_synced_nodes(self, currency, current_buid):
c = self._conn.execute(
"SELECT * FROM nodes "
"WHERE currency == ? "
"AND state > ?"
"AND current_buid == ?",
(currency, Node.FAILURE_THRESHOLD, current_buid),
)
datas = c.fetchall()
if datas:
return [Node(*data) for data in datas]
return []
def current_buid(self, currency):
c = self._conn.execute("""SELECT `current_buid`,
c = self._conn.execute(
"""SELECT COUNT(`uid`)
FROM `nodes`
WHERE member == 1 AND currency == ?
LIMIT 1;""",
(currency,),
)
data = c.fetchone()
if data and data[0] > 3:
c = self._conn.execute(
"""SELECT `current_buid`,
COUNT(`current_buid`) AS `value_occurrence`
FROM `nodes`
WHERE state == 1 AND member == 1 AND currency == ?
WHERE member == 1 AND currency == ?
GROUP BY `current_buid`
ORDER BY `value_occurrence` DESC
LIMIT 1;""", (currency,))
LIMIT 1;""",
(currency,),
)
data = c.fetchone()
if data:
return block_uid(data[0])
else:
c = self._conn.execute("""SELECT `current_buid`,
c = self._conn.execute(
"""SELECT `current_buid`,
COUNT(`current_buid`) AS `value_occurrence`
FROM `nodes`
WHERE state == 1 AND currency == ?
WHERE currency == ?
GROUP BY `current_buid`
ORDER BY `value_occurrence` DESC
LIMIT 1;""", (currency,))
LIMIT 1;""",
(currency,),
)
data = c.fetchone()
if data:
return block_uid(data[0])
......
......@@ -5,10 +5,15 @@ from ..entities import Source
@attr.s(frozen=True)
class SourcesRepo:
"""The repository for Communities entities.
"""
"""The repository for Communities entities."""
_conn = attr.ib() # :type sqlite3.Connection
_primary_keys = (attr.fields(Source).currency, attr.fields(Source).pubkey, attr.fields(Source).identifier, attr.fields(Source).noffset)
_primary_keys = (
attr.fields(Source).currency,
attr.fields(Source).pubkey,
attr.fields(Source).identifier,
attr.fields(Source).noffset,
)
def insert(self, source):
"""
......@@ -16,13 +21,16 @@ class SourcesRepo:
:param sakia.data.entities.Source source: the source to commit
"""
source_tuple = attr.astuple(source)
values = ",".join(['?'] * len(source_tuple))
self._conn.execute("INSERT INTO sources VALUES ({0})".format(values), source_tuple)
values = ",".join(["?"] * len(source_tuple))
self._conn.execute(
"INSERT INTO sources VALUES ({0})".format(values), source_tuple
)
def get_one(self, **search):
"""
Get an existing source in the database
:param dict search: the criterions of the lookup
Get an existing not consumed source in the database
:param ** search: the criterions of the lookup
:rtype: sakia.data.entities.Source
"""
filters = []
......@@ -31,7 +39,9 @@ class SourcesRepo:
filters.append("{k}=?".format(k=k))
values.append(v)
request = "SELECT * FROM sources WHERE {filters}".format(filters=" AND ".join(filters))
request = "SELECT * FROM sources WHERE used_by is NULL AND {filters}".format(
filters=" AND ".join(filters)
)
c = self._conn.execute(request, tuple(values))
data = c.fetchone()
......@@ -40,9 +50,10 @@ class SourcesRepo:
def get_all(self, **search):
"""
Get all existing source in the database corresponding to the search
:param dict search: the criterions of the lookup
:rtype: sakia.data.entities.Source
Get all existing not consumed source in the database corresponding to the search
:param ** search: the criterions of the lookup
:rtype: [sakia.data.entities.Source]
"""
filters = []
values = []
......@@ -51,7 +62,9 @@ class SourcesRepo:
filters.append("{key} = ?".format(key=k))
values.append(value)
request = "SELECT * FROM sources WHERE {filters}".format(filters=" AND ".join(filters))
request = "SELECT * FROM sources WHERE used_by is NULL AND {filters}".format(
filters=" AND ".join(filters)
)
c = self._conn.execute(request, tuple(values))
datas = c.fetchall()
......@@ -64,13 +77,18 @@ class SourcesRepo:
Drop an existing source from the database
:param sakia.data.entities.Source source: the source to update
"""
where_fields = attr.astuple(source, filter=attr.filters.include(*SourcesRepo._primary_keys))
self._conn.execute("""DELETE FROM sources
where_fields = attr.astuple(
source, filter=attr.filters.include(*SourcesRepo._primary_keys)
)
self._conn.execute(
"""DELETE FROM sources
WHERE
currency=? AND
pubkey=? AND
identifier=? AND
noffset=?""", where_fields)
noffset=?""",
where_fields,
)
def drop_all(self, **filter):
filters = []
......@@ -80,5 +98,44 @@ class SourcesRepo:
filters.append("{key} = ?".format(key=k))
values.append(value)
request = "DELETE FROM sources WHERE {filters}".format(filters=" AND ".join(filters))
request = "DELETE FROM sources WHERE {filters}".format(
filters=" AND ".join(filters)
)
self._conn.execute(request, tuple(values))
def consume(self, source, tx_hash):
"""
Consume a source by setting the used_by column with the tx hash
:param Source source: Source instance to consume
:param str tx_hash: Hash of tx
:return:
"""
where_fields = attr.astuple(
source, filter=attr.filters.include(*SourcesRepo._primary_keys)
)
fields = (tx_hash,) + where_fields
self._conn.execute(
"""UPDATE sources SET used_by=?
WHERE
currency=? AND
pubkey=? AND
identifier=? AND
noffset=?""",
fields,
)
def restore_all(self, tx_hash):
"""
Restore all sources released by tx_hash setting the used_by column with null
:param Source source: Source instance to consume
:param str tx_hash: Hash of tx
:return:
"""
self._conn.execute(
"""UPDATE sources SET used_by=NULL
WHERE
used_by=?""",
(tx_hash,),
)
......@@ -5,10 +5,14 @@ from ..entities import Transaction, Dividend
@attr.s(frozen=True)
class TransactionsRepo:
"""The repository for Communities entities.
"""
"""The repository for Communities entities."""
_conn = attr.ib() # :type sqlite3.Connection
_primary_keys = (attr.fields(Transaction).currency, attr.fields(Transaction).pubkey, attr.fields(Transaction).sha_hash,)
_primary_keys = (
attr.fields(Transaction).currency,
attr.fields(Transaction).pubkey,
attr.fields(Transaction).sha_hash,
)
def insert(self, transaction):
"""
......@@ -21,23 +25,32 @@ class TransactionsRepo:
transaction_tuple[7] = "\n".join([str(n) for n in transaction_tuple[7]])
transaction_tuple[8] = "\n".join([str(n) for n in transaction_tuple[8]])
values = ",".join(['?'] * len(transaction_tuple))
self._conn.execute("INSERT INTO transactions VALUES ({0})".format(values), transaction_tuple)
values = ",".join(["?"] * len(transaction_tuple))
self._conn.execute(
"INSERT INTO transactions VALUES ({0})".format(values), transaction_tuple
)
def update(self, transaction):
"""
Update an existing transaction in the database
:param sakia.data.entities.Transaction transaction: the transaction to update
"""
updated_fields = attr.astuple(transaction, filter=attr.filters.exclude(*TransactionsRepo._primary_keys),
tuple_factory=list)
updated_fields = attr.astuple(
transaction,
filter=attr.filters.exclude(*TransactionsRepo._primary_keys),
tuple_factory=list,
)
updated_fields[3] = "\n".join([str(n) for n in updated_fields[3]])
updated_fields[4] = "\n".join([str(n) for n in updated_fields[4]])
updated_fields[5] = "\n".join([str(n) for n in updated_fields[5]])
where_fields = attr.astuple(transaction, filter=attr.filters.include(*TransactionsRepo._primary_keys),
tuple_factory=list)
self._conn.execute("""UPDATE transactions SET
where_fields = attr.astuple(
transaction,
filter=attr.filters.include(*TransactionsRepo._primary_keys),
tuple_factory=list,
)
self._conn.execute(
"""UPDATE transactions SET
written_on=?,
blockstamp=?,
ts=?,
......@@ -55,12 +68,13 @@ class TransactionsRepo:
currency=? AND
pubkey=? AND
sha_hash=?""",
updated_fields + where_fields)
updated_fields + where_fields,
)
def get_one(self, **search):
"""
Get an existing transaction in the database
:param dict search: the criterions of the lookup
:param ** search: the criterions of the lookup
:rtype: sakia.data.entities.Transaction
"""
filters = []
......@@ -69,7 +83,9 @@ class TransactionsRepo:
filters.append("{k}=?".format(k=k))
values.append(v)
request = "SELECT * FROM transactions WHERE {filters}".format(filters=" AND ".join(filters))
request = "SELECT * FROM transactions WHERE {filters}".format(
filters=" AND ".join(filters)
)
c = self._conn.execute(request, tuple(values))
data = c.fetchone()
......@@ -89,7 +105,9 @@ class TransactionsRepo:
filters.append("{key} = ?".format(key=k))
values.append(value)
request = "SELECT * FROM transactions WHERE {filters}".format(filters=" AND ".join(filters))
request = "SELECT * FROM transactions WHERE {filters}".format(
filters=" AND ".join(filters)
)
c = self._conn.execute(request, tuple(values))
datas = c.fetchall()
......@@ -97,7 +115,15 @@ class TransactionsRepo:
return [Transaction(*data) for data in datas]
return []
def get_transfers(self, currency, pubkey, offset=0, limit=1000, sort_by="currency", sort_order="ASC"):
def get_transfers(
self,
currency,
pubkey,
offset=0,
limit=1000,
sort_by="currency",
sort_order="ASC",
):
"""
Get all transfers in the database on a given currency from or to a pubkey
......@@ -107,11 +133,8 @@ class TransactionsRepo:
request = """SELECT * FROM transactions
WHERE currency=? AND pubkey=?
ORDER BY {sort_by} {sort_order}
LIMIT {limit} OFFSET {offset}""" \
.format(offset=offset,
limit=limit,
sort_by=sort_by,
sort_order=sort_order
LIMIT {limit} OFFSET {offset}""".format(
offset=offset, limit=limit, sort_by=sort_by, sort_order=sort_order
)
c = self._conn.execute(request, (currency, pubkey))
datas = c.fetchall()
......@@ -124,9 +147,14 @@ class TransactionsRepo:
Drop an existing transaction from the database
:param sakia.data.entities.Transaction transaction: the transaction to update
"""
where_fields = attr.astuple(transaction, filter=attr.filters.include(*TransactionsRepo._primary_keys))
self._conn.execute("""DELETE FROM transactions
where_fields = attr.astuple(
transaction, filter=attr.filters.include(*TransactionsRepo._primary_keys)
)
self._conn.execute(
"""DELETE FROM transactions
WHERE
currency=? AND
pubkey=? AND
sha_hash=?""", where_fields)
sha_hash=?""",
where_fields,
)
......@@ -19,9 +19,11 @@ def once_at_a_time(fn):
func_call = args[0].__tasks[fn.__name__]
args[0].__tasks.pop(fn.__name__)
if getattr(func_call, "_next_task", None):
start_task(func_call._next_task[0],
start_task(
func_call._next_task[0],
*func_call._next_task[1],
**func_call._next_task[2])
**func_call._next_task[2]
)
except KeyError:
logging.debug("Task {0} already removed".format(fn.__name__))
......@@ -39,6 +41,7 @@ def once_at_a_time(fn):
start_task(fn, *args, **kwargs)
return args[0].__tasks[fn.__name__]
return wrapper
......@@ -49,6 +52,7 @@ def asyncify(fn):
:return: the task
:rtype: asyncio.Task
"""
@functools.wraps(fn)
def wrapper(*args, **kwargs):
return asyncio.ensure_future(asyncio.coroutine(fn)(*args, **kwargs))
......
......@@ -6,7 +6,6 @@ Created on 9 févr. 2014
class Error(Exception):
def __init__(self, message):
"""
Constructor
......@@ -29,11 +28,10 @@ class NotEnoughChangeError(Error):
Constructor
"""
super().__init__(
"Only {0} {1} available in {2} sources, needs {3}"
.format(available,
currency,
nb_inputs,
requested))
"Only {0} {1} available in {2} sources, needs {3}".format(
available, currency, nb_inputs, requested
)
)
class NoPeerAvailable(Error):
......@@ -41,23 +39,29 @@ class NoPeerAvailable(Error):
Exception raised when a community doesn't have any
peer available.
"""
def __init__(self, currency, nbpeers):
"""
Constructor
"""
super().__init__(
"No peer answered in {0} community ({1} peers available)"
.format(currency, nbpeers))
"No peer answered in {0} community ({1} peers available)".format(
currency, nbpeers
)
)
class InvalidNodeCurrency(Error):
"""
Exception raised when a node doesn't use the intended currency
"""
def __init__(self, currency, node_currency):
"""
Constructor
"""
super().__init__(
"Node is working for {0} currency, but should be {1}"
.format(node_currency, currency))
"Node is working for {0} currency, but should be {1}".format(
node_currency, currency
)
)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="generator" content="pandoc" />
<title></title>
<style type="text/css">code{white-space: pre;}</style>
</head>
<body>
<h1 id="license-ğ1---v0.2.3">License Ğ1 - v0.2.3</h1>
<p><strong>Money licensing and liability commitment.</strong></p>
<p>Any certification operation of a new member of Ğ1 must first be accompanied by the transmission of this license of the currency Ğ1 whose certifier must ensure that it has been studied, understood and accepted by the person who will be certified.</p>
<h2 id="money-ğ1">Money Ğ1</h2>
<p>Ğ1 occurs via a Universal Dividend (DU) for any human member, which is of the form:</p>
<ul>
<li>1 DU per person per day</li>
</ul>
<p>The amount of DU is identical each day until the next equinox, where the DU will then be reevaluated according to the formula:</p>
<ul>
<li>DU <sub>day</sub> (the following equinox) = DU <day>(equinox) + c² (M / N) (equinox) / (15778800 seconds)</day></li>
</ul>
<p>With as parameters:</p>
<ul>
<li>c = 4.88% / equinox</li>
<li>UD (0) = 10.00 Ğ1</li>
</ul>
<p>And as variables:</p>
<ul>
<li><em>M</em> the total monetary mass at the equinox</li>
<li><em>N</em> the number of members at the equinox</li>
</ul>
<h2 id="web-of-trust-ğ1-wot-ğ1">Web of Trust Ğ1 (WoT Ğ1)</h2>
<p><strong>Warning:</strong> Certifying is not just about making sure you've met the person, it's ensuring that the community Ğ1 knows the certified person well enough and Duplicate account made by a person certified by you, or other types of problems (disappearance ...), by cross-checking that will reveal the problem if necessary.</p>
<p>When you are a member of Ğ1 and you are about to certify a new account:</p>
<p><strong>You are assured:</strong></p>
<p>1°) The person who declares to manage this public key (new account) and to have personally checked with him that this is the public key is sufficiently well known (not only to know this person visually) that you are about to certify.</p>
<p>2a°) To meet her physically to make sure that it is this person you know who manages this public key.</p>
<p>2b°) Remotely verify the public person / key link by contacting the person via several different means of communication, such as social network + forum + mail + video conference + phone (acknowledge voice).</p>
<p>Because if you can hack an email account or a forum account, it will be much harder to imagine hacking four distinct means of communication, and mimic the appearance (video) as well as the voice of the person .</p>
<p>However, the 2 °) is preferable to 3 °, whereas the 1 °) is always indispensable in all cases.</p>
<p>3 °) To have verified with the person concerned that he has indeed generated his Duniter account revocation document, which will enable him, if necessary, to cancel his account (in case of account theft, ID, an incorrectly created account, etc.).</p>
<p><strong>Abbreviated WoT rules:</strong></p>
<p>Each member has a stock of 100 possible certifications, which can only be issued at the rate of 1 certification / 5 days.</p>
<p>Valid for 2 months, certification for a new member is definitively adopted only if the certified has at least 4 other certifications after these 2 months, otherwise the entry process will have to be relaunched.</p>
<p>To become a new member of WoT Ğ1 therefore 5 certifications must be obtained at a distance&gt; 5 of 80% of the WoT sentinels.</p>
<p>A member of the TdC Ğ1 is sentinel when he has received and issued at least Y [N] certifications where N is the number of members of the TdC and Y [N] = ceiling N ^ (1/5). Examples:</p>
<ul>
<li>For 1024 &lt; N ≤ 3125 we have Y [N] = 5</li>
<li>For 7776 &lt; N ≤ 16807 we have Y [N] = 7</li>
<li>For 59049 &lt; N ≤ 100 000 we have Y [N] = 10</li>
</ul>
<p>Once the new member is part of the WoT Ğ1 his certifications remain valid for 2 years.</p>
<p>To remain a member, you must renew your agreement regularly with your private key (every 12 months) and make sure you have at least 5 certifications valid after 2 years.</p>
<h2 id="software-ğ1-and-license-ğ1">Software Ğ1 and license Ğ1</h2>
<p>The software Ğ1 allowing users to manage their use of Ğ1 must transmit this license with the software and all the technical parameters of the currency Ğ1 and TdC Ğ1 which are entered in block 0 of Ğ1.</p>
<p>For more details in the technical details it is possible to consult directly the code of Duniter which is a free software and also the data of the blockchain Ğ1 by retrieving it via a Duniter instance or node Ğ1.</p>
<p>More information on the Duniter Team website <a href="https://www.duniter.org" class="uri">https://www.duniter.org</a></p>
</body>
</html>
<!DOCTYPE html>
<!--Converted via md-to-html-->
<html>
<head>
</head>
<body>
<p>
License Ğ1 - v0.2.5
</p>
<p>
===================
</p>
<p>
<strong>
Money licensing and liability commitment.
</strong>
</p>
<p>
Any certification operation of a new member of Ğ1 must first be accompanied by the transmission of this license of the currency Ğ1 whose certifier must ensure that it has been studied, understood and accepted by the person who will be certified.
</p>
<p>
Money Ğ1
</p>
<hr/>
<p>
Ğ1 occurs via a Universal Dividend (DU) for any human member, which is of the form:
</p>
<ul>
<li>
1 DU per person per day
</li>
</ul>
<p>
The amount of DU is identical each day until the next equinox, where the DU will then be reevaluated according to the formula:
</p>
<ul>
<li>
DU &lt;sub&gt;day&lt;/sub&gt; (the following equinox) = DU &lt;day&gt;(equinox) + c² (M / N) (equinox) / (15778800 seconds)&lt;/day&gt;
</li>
</ul>
<p>
With as parameters:
</p>
<ul>
<li>
c = 4.88% / equinox
</li>
</ul>
<ul>
<li>
UD (0) = 10.00 Ğ1
</li>
</ul>
<p>
And as variables:
</p>
<ul>
<li>
<em>
M
</em>
the total monetary mass at the equinox
</li>
</ul>
<ul>
<li>
<em>
N
</em>
the number of members at the equinox
</li>
</ul>
<p>
Web of Trust Ğ1 (WoT Ğ1)
</p>
<hr/>
<p>
<strong>
Warning:
</strong>
Certifying is not just about making sure you've met the person, it's ensuring that the community Ğ1 knows the certified person well enough and Duplicate account made by a person certified by you, or other types of problems (disappearance ...), by cross-checking that will reveal the problem if necessary.
</p>
<p>
When you are a member of Ğ1 and you are about to certify a new account:
</p>
<p>
<strong>
You are assured:
</strong>
</p>
<p>
1°) The person who declares to manage this public key (new account) and to have personally checked with him that this is the public key is sufficiently well known (not only to know this person visually) that you are about to certify.
</p>
<p>
2a°) To meet her physically to make sure that it is this person you know who manages this public key.
</p>
<p>
2b°) Remotely verify the public person / key link by contacting the person via several different means of communication, such as social network + forum + mail + video conference + phone (acknowledge voice).
</p>
<p>
Because if you can hack an email account or a forum account, it will be much harder to imagine hacking four distinct means of communication, and mimic the appearance (video) as well as the voice of the person .
</p>
<p>
However, the 2 °) is preferable to 3 °, whereas the 1 °) is always indispensable in all cases.
</p>
<p>
3 °) To have verified with the person concerned that he has indeed generated his Duniter account revocation document, which will enable him, if necessary, to cancel his account (in case of account theft, ID, an incorrectly created account, etc.).
</p>
<p>
<strong>
Abbreviated WoT rules:
</strong>
</p>
<p>
Each member has a stock of 100 possible certifications, which can only be issued at the rate of 1 certification / 5 days.
</p>
<p>
Valid for 2 months, certification for a new member is definitively adopted only if the certified has at least 4 other certifications after these 2 months, otherwise the entry process will have to be relaunched.
</p>
<p>
To become a new member of WoT Ğ1 therefore 5 certifications must be obtained at a distance &lt; 5 of 80% of the WoT sentinels.
</p>
<p>
A member of the TdC Ğ1 is sentinel when he has received and issued at least Y [N] certifications where N is the number of members of the TdC and Y [N] = ceiling N ^ (1/5). Examples:
</p>
<ul>
<li>
For 1024 &lt; N ≤ 3125 we have Y [N] = 5
</li>
</ul>
<ul>
<li>
For 7776 &lt; N ≤ 16807 we have Y [N] = 7
</li>
</ul>
<ul>
<li>
For 59049 &lt; N ≤ 100 000 we have Y [N] = 10
</li>
</ul>
<p>
Once the new member is part of the WoT Ğ1 his certifications remain valid for 2 years.
</p>
<p>
To remain a member, you must renew your agreement regularly with your private key (every 12 months) and make sure you have at least 5 certifications valid after 2 years.
</p>
<p>
Software Ğ1 and license Ğ1
</p>
<hr/>
<p>
The software Ğ1 allowing users to manage their use of Ğ1 must transmit this license with the software and all the technical parameters of the currency Ğ1 and TdC Ğ1 which are entered in block 0 of Ğ1.
</p>
<p>
For more details in the technical details it is possible to consult directly the code of Duniter which is a free software and also the data of the blockchain Ğ1 by retrieving it via a Duniter instance or node Ğ1.
</p>
<p>
More information on the Duniter Team website
<a href="https://www.duniter.org">
https://www.duniter.org
</a>
</p>
</body>
</html>
\ No newline at end of file
......@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Add a connection</string>
<string>Add an account</string>
</property>
<property name="modal">
<bool>true</bool>
......@@ -30,13 +30,13 @@
<number>6</number>
</property>
<item>
<widget class="QPushButton" name="button_register">
<widget class="QPushButton" name="button_pubkey">
<property name="text">
<string>Register a new identity</string>
<string>Add using a public key (quick)</string>
</property>
<property name="icon">
<iconset resource="../../../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/new_membership</normaloff>:/icons/new_membership</iconset>
<normaloff>:/icons/duniter_info_icon</normaloff>:/icons/duniter_info_icon</iconset>
</property>
<property name="iconSize">
<size>
......@@ -47,47 +47,47 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="button_connect">
<widget class="QPushButton" name="button_wallet">
<property name="text">
<string>Connect with an existing identity</string>
<string>Add a wallet</string>
</property>
<property name="icon">
<iconset resource="../../../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/connect_icon</normaloff>:/icons/connect_icon</iconset>
<normaloff>:/icons/wallet_icon</normaloff>:/icons/wallet_icon</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<width>50</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_wallet">
<widget class="QPushButton" name="button_connect">
<property name="text">
<string>Connect a wallet</string>
<string>Add an existing member account</string>
</property>
<property name="icon">
<iconset resource="../../../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/wallet_icon</normaloff>:/icons/wallet_icon</iconset>
<normaloff>:/icons/connect_icon</normaloff>:/icons/connect_icon</iconset>
</property>
<property name="iconSize">
<size>
<width>50</width>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_pubkey">
<widget class="QPushButton" name="button_register">
<property name="text">
<string>Connect using a public key</string>
<string>Create a new member account</string>
</property>
<property name="icon">
<iconset resource="../../../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/duniter_info_icon</normaloff>:/icons/duniter_info_icon</iconset>
<normaloff>:/icons/new_membership</normaloff>:/icons/new_membership</iconset>
</property>
<property name="iconSize">
<size>
......@@ -119,7 +119,7 @@
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;h3&gt;License&lt;/h3&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:large; font-weight:600;&quot;&gt;Licence&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
......@@ -129,26 +129,26 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack';&quot;&gt; This program is free software: you can redistribute it and/or modify&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack';&quot;&gt; it under the terms of the GNU General Public License as published by&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack';&quot;&gt; the Free Software Foundation, either version 3 of the License, or&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack';&quot;&gt; (at your option) any later version.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Hack';&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack';&quot;&gt; This program is distributed in the hope that it will be useful,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack';&quot;&gt; but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack';&quot;&gt; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack';&quot;&gt; GNU General Public License for more details.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Hack';&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack';&quot;&gt; You should have received a copy of the GNU General Public License&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack';&quot;&gt; along with this program. If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;. &lt;/span&gt;&lt;a name=&quot;TransNote1-rev&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://www.gnu.org/licenses/gpl-howto.fr.html#TransNote1&quot;&gt;&lt;span style=&quot; font-family:'Hack'; text-decoration: underline; color:#2980b9; vertical-align:super;&quot;&gt;1&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack'; font-size:10pt;&quot;&gt; This program is free software: you can redistribute it and/or modify&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack'; font-size:10pt;&quot;&gt; it under the terms of the GNU General Public License as published by&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack'; font-size:10pt;&quot;&gt; the Free Software Foundation, either version 3 of the License, or&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack'; font-size:10pt;&quot;&gt; (at your option) any later version.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Hack'; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack'; font-size:10pt;&quot;&gt; This program is distributed in the hope that it will be useful,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack'; font-size:10pt;&quot;&gt; but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack'; font-size:10pt;&quot;&gt; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack'; font-size:10pt;&quot;&gt; GNU General Public License for more details.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Hack'; font-size:10pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack'; font-size:10pt;&quot;&gt; You should have received a copy of the GNU General Public License&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Hack'; font-size:10pt;&quot;&gt; along with this program. If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;. &lt;/span&gt;&lt;a name=&quot;TransNote1-rev&quot;&gt;&lt;/a&gt;&lt;a href=&quot;https://www.gnu.org/licenses/gpl-howto.fr.html#TransNote1&quot;&gt;&lt;span style=&quot; font-family:'Hack'; font-size:10pt; text-decoration: underline; color:#2980b9; vertical-align:super;&quot;&gt;1&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>By going throught the process of creating a wallet, you accept the license above.</string>
<string>By going throught the process of creating a wallet, you accept the licence above.</string>
</property>
</widget>
</item>
......@@ -186,7 +186,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Connection parameters</string>
<string>Account parameters</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
......@@ -194,7 +194,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QLabel" name="label_action">
<property name="text">
<string>Account name (uid)</string>
<string>Identity name (UID)</string>
</property>
</widget>
</item>
......@@ -231,7 +231,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QGroupBox" name="groupbox_key">
<property name="title">
<string>Key parameters</string>
<string>Credentials</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
......@@ -370,7 +370,7 @@ p, li { white-space: pre-wrap; }
</sizepolicy>
</property>
<property name="text">
<string>N :</string>
<string>N</string>
</property>
</widget>
</item>
......@@ -386,7 +386,7 @@ p, li { white-space: pre-wrap; }
</sizepolicy>
</property>
<property name="text">
<string>r :</string>
<string>r</string>
</property>
</widget>
</item>
......@@ -396,7 +396,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>p :</string>
<string>p</string>
</property>
</widget>
</item>
......
import asyncio
import logging
from PyQt5.QtCore import QObject, Qt
from PyQt5.QtCore import QObject, Qt, QCoreApplication
from aiohttp import ClientError
from asyncio import TimeoutError
......@@ -43,16 +43,24 @@ class ConnectionConfigController(QObject):
self.step_licence = asyncio.Future()
self.step_key = asyncio.Future()
self.view.button_connect.clicked.connect(
lambda: self.step_node.set_result(ConnectionConfigController.CONNECT))
lambda: self.step_node.set_result(ConnectionConfigController.CONNECT)
)
self.view.button_register.clicked.connect(
lambda: self.step_node.set_result(ConnectionConfigController.REGISTER))
lambda: self.step_node.set_result(ConnectionConfigController.REGISTER)
)
self.view.button_wallet.clicked.connect(
lambda: self.step_node.set_result(ConnectionConfigController.WALLET))
lambda: self.step_node.set_result(ConnectionConfigController.WALLET)
)
self.view.button_pubkey.clicked.connect(
lambda: self.step_node.set_result(ConnectionConfigController.PUBKEY))
self.view.values_changed.connect(lambda: self.view.button_next.setEnabled(self.check_key()))
self.view.values_changed.connect(lambda: self.view.button_generate.setEnabled(self.check_key()))
self._logger = logging.getLogger('sakia')
lambda: self.step_node.set_result(ConnectionConfigController.PUBKEY)
)
self.view.values_changed.connect(
lambda: self.view.button_next.setEnabled(self.check_key())
)
self.view.values_changed.connect(
lambda: self.view.button_generate.setEnabled(self.check_key())
)
self._logger = logging.getLogger("sakia")
@classmethod
def create(cls, parent, app):
......@@ -63,9 +71,10 @@ class ConnectionConfigController(QObject):
:return: a new AccountConfigController controller
:rtype: AccountConfigController
"""
view = ConnectionConfigView(parent.view if parent else None)
model = ConnectionConfigModel(None, app, None,
IdentitiesProcessor.instanciate(app))
view = ConnectionConfigView(parent.view if parent else None, app)
model = ConnectionConfigModel(
None, app, None, IdentitiesProcessor.instanciate(app)
)
account_cfg = cls(parent, view, model)
model.setParent(account_cfg)
view.set_license(app.currency)
......@@ -91,7 +100,9 @@ class ConnectionConfigController(QObject):
self.view.set_nodes_model(model)
self.view.button_previous.setEnabled(False)
self.view.button_next.setText(self.config_dialog.tr("Ok"))
self.view.button_next.setText(
QCoreApplication.translate("ConnectionConfigController", "Ok")
)
def init_name_page(self):
"""
......@@ -113,22 +124,35 @@ class ConnectionConfigController(QObject):
else:
while not self.model.connection:
self.mode = await self.step_node
self._logger.debug("Create connection")
self._logger.debug("Create account")
try:
self.view.button_connect.setEnabled(False)
self.view.button_register.setEnabled(False)
await self.model.create_connection()
except (ClientError, MalformedDocumentError, ValueError, TimeoutError) as e:
except (
ClientError,
MalformedDocumentError,
ValueError,
TimeoutError,
) as e:
self._logger.debug(str(e))
self.view.display_info(self.tr("Could not connect. Check hostname, ip address or port : <br/>"
+ str(e)))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"Could not connect. Check hostname, IP address or port: <br/>"
+ str(e),
)
)
self.step_node = asyncio.Future()
self.view.button_connect.setEnabled(True)
self.view.button_register.setEnabled(True)
if self.mode == ConnectionConfigController.REGISTER:
self._logger.debug("Licence step")
self.view.stacked_pages.setCurrentWidget(self.view.page_licence)
self.view.button_accept.clicked.connect(lambda: self.step_licence.set_result(True))
self.view.button_accept.clicked.connect(
lambda: self.step_licence.set_result(True)
)
await self.step_licence
self.view.button_accept.disconnect()
self._logger.debug("Key step")
......@@ -144,14 +168,18 @@ class ConnectionConfigController(QObject):
connection_identity = await self.step_key
elif self.mode == ConnectionConfigController.CONNECT:
self._logger.debug("Connect mode")
self.view.button_next.setText(self.tr("Next"))
self.view.button_next.setText(
QCoreApplication.translate("ConnectionConfigController", "Next")
)
self.view.groupbox_pubkey.hide()
self.view.button_next.clicked.connect(self.check_connect)
self.view.stacked_pages.setCurrentWidget(self.view.page_connection)
connection_identity = await self.step_key
elif self.mode == ConnectionConfigController.WALLET:
self._logger.debug("Wallet mode")
self.view.button_next.setText(self.tr("Next"))
self.view.button_next.setText(
QCoreApplication.translate("ConnectionConfigController", "Next")
)
self.view.button_next.clicked.connect(self.check_wallet)
self.view.edit_uid.hide()
self.view.label_action.hide()
......@@ -160,10 +188,19 @@ class ConnectionConfigController(QObject):
connection_identity = await self.step_key
elif self.mode == ConnectionConfigController.PUBKEY:
self._logger.debug("Pubkey mode")
self.view.button_next.setText(self.tr("Next"))
self.view.button_next.setText(
QCoreApplication.translate("ConnectionConfigController", "Next")
)
self.view.button_next.clicked.connect(self.check_pubkey)
if not self.view.label_action.text().endswith(self.tr(" (Optional)")):
self.view.label_action.setText(self.view.label_action.text() + self.tr(" (Optional)"))
if not self.view.label_action.text().endswith(
QCoreApplication.translate("ConnectionConfigController", " (Optional)")
):
self.view.label_action.setText(
self.view.label_action.text()
+ QCoreApplication.translate(
"ConnectionConfigController", " (Optional)"
)
)
self.view.groupbox_key.hide()
self.view.stacked_pages.setCurrentWidget(self.view.page_connection)
connection_identity = await self.step_key
......@@ -172,7 +209,11 @@ class ConnectionConfigController(QObject):
self.view.set_progress_steps(6)
try:
if self.mode == ConnectionConfigController.REGISTER:
self.view.display_info(self.tr("Broadcasting identity..."))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController", "Broadcasting identity..."
)
)
self.view.stream_log("Broadcasting identity...")
result = await self.model.publish_selfcert(connection_identity)
if result[0]:
......@@ -183,38 +224,53 @@ class ConnectionConfigController(QObject):
self.view.set_step(1)
if self.mode in (ConnectionConfigController.REGISTER,
if (
self.mode
in (
ConnectionConfigController.REGISTER,
ConnectionConfigController.CONNECT,
ConnectionConfigController.PUBKEY) and connection_identity:
ConnectionConfigController.PUBKEY,
)
and connection_identity
):
self.view.stream_log("Saving identity...")
self.model.connection.blockstamp = connection_identity.blockstamp
self.model.insert_or_update_connection()
self.model.insert_or_update_identity(connection_identity)
self.view.stream_log("Initializing identity informations...")
await self.model.initialize_identity(connection_identity,
await self.model.initialize_identity(
connection_identity,
log_stream=self.view.stream_log,
progress=self.view.progress)
progress=self.view.progress,
)
self.view.stream_log("Initializing certifications informations...")
self.view.set_step(2)
await self.model.initialize_certifications(connection_identity,
await self.model.initialize_certifications(
connection_identity,
log_stream=self.view.stream_log,
progress=self.view.progress)
progress=self.view.progress,
)
self.view.set_step(3)
self.view.stream_log("Initializing transactions history...")
transactions = await self.model.initialize_transactions(self.model.connection,
transactions = await self.model.initialize_transactions(
self.model.connection,
log_stream=self.view.stream_log,
progress=self.view.progress)
progress=self.view.progress,
)
self.view.set_step(4)
self.view.stream_log("Initializing dividends history...")
dividends = await self.model.initialize_dividends(self.model.connection, transactions,
dividends = await self.model.initialize_dividends(
self.model.connection,
transactions,
log_stream=self.view.stream_log,
progress=self.view.progress)
progress=self.view.progress,
)
self.view.set_step(5)
await self.model.initialize_sources(transactions, dividends,
log_stream=self.view.stream_log,
progress=self.view.progress)
await self.model.initialize_sources(
log_stream=self.view.stream_log, progress=self.view.progress
)
self.view.set_step(6)
self._logger.debug("Validate changes")
......@@ -222,7 +278,9 @@ class ConnectionConfigController(QObject):
self.model.app.db.commit()
if self.mode == ConnectionConfigController.REGISTER:
await self.view.show_register_message(self.model.blockchain_parameters())
await self.view.show_register_message(
self.model.blockchain_parameters()
)
except (NoPeerAvailable, DuniterError, StopIteration) as e:
if not isinstance(e, StopIteration):
self.view.show_error(self.model.notification(), str(e))
......@@ -235,47 +293,79 @@ class ConnectionConfigController(QObject):
self.view.edit_uid.show()
asyncio.ensure_future(self.process())
return
self.accept()
await self.accept()
def check_key(self):
if self.mode == ConnectionConfigController.PUBKEY:
if len(self.view.edit_pubkey.text()) < 42:
self.view.label_info.setText(self.tr("Forbidden : pubkey is too short"))
self.view.label_info.setText(
QCoreApplication.translate(
"ConnectionConfigController", "Forbidden: pubkey is too short"
)
)
return False
if len(self.view.edit_pubkey.text()) > 45:
self.view.label_info.setText(self.tr("Forbidden : pubkey is too long"))
self.view.label_info.setText(
QCoreApplication.translate(
"ConnectionConfigController", "Forbidden: pubkey is too long"
)
)
return False
else:
if self.view.edit_password.text() != \
self.view.edit_password_repeat.text():
self.view.label_info.setText(self.tr("Error : passwords are different"))
if self.view.edit_password.text() != self.view.edit_password_repeat.text():
self.view.label_info.setText(
QCoreApplication.translate(
"ConnectionConfigController", "Error: passwords are different"
)
)
return False
if self.view.edit_salt.text() != \
self.view.edit_salt_bis.text():
self.view.label_info.setText(self.tr("Error : secret keys are different"))
if self.view.edit_salt.text() != self.view.edit_salt_bis.text():
self.view.label_info.setText(
QCoreApplication.translate(
"ConnectionConfigController", "Error: salts are different"
)
)
return False
if detect_non_printable(self.view.edit_salt.text()):
self.view.label_info.setText(self.tr("Forbidden : Invalid characters in salt field"))
self.view.label_info.setText(
QCoreApplication.translate(
"ConnectionConfigController",
"Forbidden: invalid characters in salt",
)
)
return False
if detect_non_printable(self.view.edit_password.text()):
self.view.label_info.setText(
self.tr("Forbidden : Invalid characters in password field"))
QCoreApplication.translate(
"ConnectionConfigController",
"Forbidden: invalid characters in password",
)
)
return False
if self.model.app.parameters.expert_mode:
self.view.label_info.setText(
self.tr(""))
QCoreApplication.translate("ConnectionConfigController", "")
)
return True
if len(self.view.edit_salt.text()) < 6:
self.view.label_info.setText(self.tr("Forbidden : salt is too short"))
self.view.label_info.setText(
QCoreApplication.translate(
"ConnectionConfigController", "Forbidden: salt is too short"
)
)
return False
if len(self.view.edit_password.text()) < 6:
self.view.label_info.setText(self.tr("Forbidden : password is too short"))
self.view.label_info.setText(
QCoreApplication.translate(
"ConnectionConfigController", "Forbidden: password is too short"
)
)
return False
self.view.label_info.setText("")
......@@ -284,19 +374,38 @@ class ConnectionConfigController(QObject):
async def action_save_revocation(self):
raw_document, identity = self.model.generate_revocation()
# Testable way of using a QFileDialog
selected_files = await QAsyncFileDialog.get_save_filename(self.view, self.tr("Save a revocation document"),
"", self.tr("All text files (*.txt)"))
selected_files = await QAsyncFileDialog.get_save_filename(
self.view,
QCoreApplication.translate(
"ConnectionConfigController", "Save a revocation document"
),
"revocation-{uid}-{pubkey}-{currency}.txt".format(
uid=identity.uid, pubkey=identity.pubkey[:8], currency=identity.currency
),
QCoreApplication.translate(
"ConnectionConfigController", "All text files (*.txt)"
),
)
if selected_files:
path = selected_files[0]
if not path.endswith('.txt'):
if not path.endswith(".txt"):
path = "{0}.txt".format(path)
with open(path, 'w') as save_file:
with open(path, "w") as save_file:
save_file.write(raw_document)
dialog = QMessageBox(QMessageBox.Information, self.tr("Revokation file"),
self.tr("""<div>Your revocation document has been saved.</div>
dialog = QMessageBox(
QMessageBox.Information,
QCoreApplication.translate(
"ConnectionConfigController", "Revocation file"
),
QCoreApplication.translate(
"ConnectionConfigController",
"""<div>Your revocation document has been saved.</div>
<div><b>Please keep it in a safe place.</b></div>
The publication of this document will remove your identity from the network.</p>"""), QMessageBox.Ok)
The publication of this document will revoke your identity on the network.</p>""",
),
QMessageBox.Ok,
)
dialog.setTextFormat(Qt.RichText)
return True, identity
return False, identity
......@@ -304,7 +413,10 @@ The publication of this document will remove your identity from the network.</p>
@asyncify
async def check_pubkey(self, checked=False):
self._logger.debug("Is valid ? ")
self.view.display_info(self.tr("connecting..."))
self.view.display_info(
QCoreApplication.translate("ConnectionConfigController", "connecting...")
)
self.view.button_next.setDisabled(True)
try:
self.model.set_pubkey(self.view.edit_pubkey.text(), self.view.scrypt_params)
self.model.set_uid(self.view.edit_uid.text())
......@@ -315,10 +427,22 @@ The publication of this document will remove your identity from the network.</p>
self.view.button_register.setEnabled(True)
if self.view.edit_uid.text():
if registered[0] is False and registered[2] is None:
self.view.display_info(self.tr("Could not find your identity on the network."))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"Could not find your identity on the network.",
)
)
elif registered[0] is False and registered[2]:
self.view.display_info(self.tr("""Your pubkey or UID is different on the network.
Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"""Your pubkey or UID is different on the network.
Yours: {0}, the network: {1}""".format(
registered[1], registered[2]
),
)
)
else:
self.step_key.set_result(found_identity)
else:
......@@ -326,18 +450,32 @@ Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
except DuniterError as e:
self.view.display_info(e.message)
self.step_key.set_result(None)
except NoPeerAvailable as e:
self.view.display_info(str(e))
self.step_key.set_result(None)
else:
self.view.display_info(self.tr("A connection already exists using this key."))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"An account already exists using this key.",
)
)
except NoPeerAvailable:
self.config_dialog.label_error.setText(self.tr("Could not connect. Check node peering entry"))
self.config_dialog.label_error.setText(
QCoreApplication.translate(
"ConnectionConfigController",
"Could not connect. Check node peering entry",
)
)
@asyncify
async def check_wallet(self, checked=False):
self._logger.debug("Is valid ? ")
self.view.display_info(self.tr("connecting..."))
self.view.display_info(
QCoreApplication.translate("ConnectionConfigController", "connecting...")
)
try:
salt = self.view.edit_salt.text()
password = self.view.edit_password.text()
......@@ -351,22 +489,41 @@ Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
if registered[0] is False and registered[2] is None:
self.step_key.set_result(None)
elif registered[2]:
self.view.display_info(self.tr("""Your pubkey is associated to an identity.
Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"""Your pubkey is associated to an identity.
Yours: {0}, the network: {1}""".format(
registered[1], registered[2]
),
)
)
except DuniterError as e:
self.view.display_info(e.message)
except NoPeerAvailable as e:
self.view.display_info(str(e))
else:
self.view.display_info(self.tr("A connection already exists using this key."))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"An account already exists using this key.",
)
)
except NoPeerAvailable:
self.config_dialog.label_error.setText(self.tr("Could not connect. Check node peering entry"))
self.config_dialog.label_error.setText(
QCoreApplication.translate(
"ConnectionConfigController",
"Could not connect. Check node peering entry",
)
)
@asyncify
async def check_connect(self, checked=False):
self._logger.debug("Is valid ? ")
self.view.display_info(self.tr("connecting..."))
self.view.display_info(
QCoreApplication.translate("ConnectionConfigController", "connecting...")
)
try:
salt = self.view.edit_salt.text()
password = self.view.edit_password.text()
......@@ -378,10 +535,22 @@ Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
self.view.button_connect.setEnabled(True)
self.view.button_register.setEnabled(True)
if registered[0] is False and registered[2] is None:
self.view.display_info(self.tr("Could not find your identity on the network."))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"Could not find your identity on the network.",
)
)
elif registered[0] is False and registered[2]:
self.view.display_info(self.tr("""Your pubkey or UID is different on the network.
Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"""Your pubkey or UID is different on the network.
Yours: {0}, the network: {1}""".format(
registered[1], registered[2]
),
)
)
else:
self.step_key.set_result(found_identity)
except DuniterError as e:
......@@ -389,15 +558,27 @@ Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
except NoPeerAvailable as e:
self.view.display_info(str(e))
else:
self.view.display_info(self.tr("A connection already exists using this key."))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"An account already exists using this key.",
)
)
except NoPeerAvailable:
self.config_dialog.label_error.setText(self.tr("Could not connect. Check node peering entry"))
self.config_dialog.label_error.setText(
QCoreApplication.translate(
"ConnectionConfigController",
"Could not connect. Check node peering entry",
)
)
@asyncify
async def check_register(self, checked=False):
self._logger.debug("Is valid ? ")
self.view.display_info(self.tr("connecting..."))
self.view.display_info(
QCoreApplication.translate("ConnectionConfigController", "connecting...")
)
try:
salt = self.view.edit_salt.text()
password = self.view.edit_password.text()
......@@ -411,20 +592,41 @@ Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
if result:
self.step_key.set_result(identity)
else:
self.view.display_info("Saving your revocation document on your disk is mandatory.")
self.view.display_info(
"Saving your revocation document on your disk is mandatory."
)
elif registered[0] is False and registered[2]:
self.view.display_info(self.tr("""Your pubkey or UID was already found on the network.
Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"""Your pubkey or UID was already found on the network.
Yours: {0}, the network: {1}""".format(
registered[1], registered[2]
),
)
)
else:
self.view.display_info("Your account already exists on the network")
self.view.display_info(
"Your account already exists on the network"
)
except DuniterError as e:
self.view.display_info(e.message)
except NoPeerAvailable as e:
self.view.display_info(str(e))
else:
self.view.display_info(self.tr("A connection already exists using this key."))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"An account already exists using this key.",
)
)
except NoPeerAvailable:
self.view.display_info(self.tr("Could not connect. Check node peering entry"))
self.view.display_info(
QCoreApplication.translate(
"ConnectionConfigController",
"Could not connect. Check node peering entry",
)
)
@asyncify
async def accept(self):
......
......@@ -2,8 +2,14 @@ import aiohttp
from PyQt5.QtCore import QObject
from duniterpy.key import SigningKey
from sakia.data.entities import Connection
from sakia.data.processors import ConnectionsProcessor, BlockchainProcessor, \
SourcesProcessor, TransactionsProcessor, DividendsProcessor, IdentitiesProcessor
from sakia.data.processors import (
ConnectionsProcessor,
BlockchainProcessor,
SourcesProcessor,
TransactionsProcessor,
DividendsProcessor,
IdentitiesProcessor,
)
class ConnectionConfigModel(QObject):
......@@ -11,7 +17,9 @@ class ConnectionConfigModel(QObject):
The model of AccountConfig component
"""
def __init__(self, parent, app, connection, identities_processor, node_connector=None):
def __init__(
self, parent, app, connection, identities_processor, node_connector=None
):
"""
:param sakia.gui.dialogs.account_cfg.controller.AccountConfigController parent:
......@@ -40,7 +48,9 @@ class ConnectionConfigModel(QObject):
self.connection.scrypt_r = scrypt_params.r
self.connection.scrypt_p = scrypt_params.p
self.connection.password = password
self.connection.pubkey = SigningKey(self.connection.salt, password, scrypt_params).pubkey
self.connection.pubkey = SigningKey.from_credentials(
self.connection.salt, password, scrypt_params
).pubkey
def set_pubkey(self, pubkey, scrypt_params):
self.connection.salt = ""
......@@ -51,15 +61,17 @@ class ConnectionConfigModel(QObject):
self.connection.pubkey = pubkey
def insert_or_update_connection(self):
ConnectionsProcessor(self.app.db.connections_repo).commit_connection(self.connection)
ConnectionsProcessor(self.app.db.connections_repo).commit_connection(
self.connection
)
def insert_or_update_identity(self, identity):
self.identities_processor.insert_or_update_identity(identity)
def generate_revocation(self):
return self.app.documents_service.generate_revocation(self.connection,
self.connection.salt,
self.connection.password)
return self.app.documents_service.generate_revocation(
self.connection, self.connection.salt, self.connection.password
)
def generate_identity(self):
return self.app.documents_service.generate_identity(self.connection)
......@@ -73,7 +85,7 @@ class ConnectionConfigModel(QObject):
blockchain_processor = BlockchainProcessor.instanciate(self.app)
await blockchain_processor.initialize_blockchain(self.app.currency)
async def initialize_sources(self, transactions, dividends, log_stream, progress):
async def initialize_sources(self, log_stream, progress):
"""
Download sources information locally
:param function log_stream: a method to log data in the screen
......@@ -81,8 +93,9 @@ class ConnectionConfigModel(QObject):
:return:
"""
log_stream("Parsing sources...")
await self.app.sources_service.refresh_sources_of_pubkey(self.connection.pubkey, transactions, dividends,
None, log_stream, progress)
await self.app.sources_service.initialize_sources(
self.connection.pubkey, log_stream, progress
)
log_stream("Sources parsed succefully !")
async def initialize_identity(self, identity, log_stream, progress):
......@@ -92,7 +105,9 @@ class ConnectionConfigModel(QObject):
:param function log_stream: a method to log data in the screen
:return:
"""
await self.identities_processor.initialize_identity(identity, log_stream, progress)
await self.identities_processor.initialize_identity(
identity, log_stream, progress
)
async def initialize_certifications(self, identity, log_stream, progress):
"""
......@@ -101,7 +116,9 @@ class ConnectionConfigModel(QObject):
:param function log_stream: a method to log data in the screen
:return:
"""
await self.app.identities_service.initialize_certifications(identity, log_stream, progress)
await self.app.identities_service.initialize_certifications(
identity, log_stream, progress
)
async def initialize_transactions(self, identity, log_stream, progress):
"""
......@@ -111,7 +128,9 @@ class ConnectionConfigModel(QObject):
:return:
"""
transactions_processor = TransactionsProcessor.instanciate(self.app)
return await transactions_processor.initialize_transactions(identity, log_stream, progress)
return await transactions_processor.initialize_transactions(
identity, log_stream, progress
)
async def initialize_dividends(self, identity, transactions, log_stream, progress):
"""
......@@ -122,13 +141,17 @@ class ConnectionConfigModel(QObject):
:return:
"""
dividends_processor = DividendsProcessor.instanciate(self.app)
return await dividends_processor.initialize_dividends(identity, transactions, log_stream, progress)
return await dividends_processor.initialize_dividends(
identity, transactions, log_stream, progress
)
async def publish_selfcert(self, identity):
""" "
Publish the self certification of the connection identity
"""
result = await self.app.documents_service.broadcast_identity(self.connection, identity.document())
result = await self.app.documents_service.broadcast_identity(
self.connection, identity.document()
)
return result
async def check_registered(self):
......@@ -136,7 +159,10 @@ class ConnectionConfigModel(QObject):
return await identities_processor.check_registered(self.connection)
def key_exists(self):
return self.connection.pubkey in ConnectionsProcessor.instanciate(self.app).pubkeys()
return (
self.connection.pubkey
in ConnectionsProcessor.instanciate(self.app).pubkeys()
)
def blockchain_parameters(self):
blockchain_processor = BlockchainProcessor.instanciate(self.app)
......
import asyncio
from PyQt5.QtWidgets import QDialog
from PyQt5.QtCore import pyqtSignal, Qt, QElapsedTimer, QDateTime, QCoreApplication
from .connection_cfg_uic import Ui_ConnectionConfigurationDialog
from .congratulation_uic import Ui_CongratulationPopup
from duniterpy.key import SigningKey, ScryptParams
from duniterpy.key import SigningKey
from duniterpy.key.scrypt_params import ScryptParams
from math import ceil, log
from sakia.gui.widgets import toast
from sakia.decorators import asyncify
from sakia.helpers import timestamp_to_dhms
from sakia.gui.widgets.dialogs import dialog_async_exec, QAsyncMessageBox
from sakia.constants import ROOT_SERVERS, G1_LICENCE
from sakia.constants import G1_LICENSE
from sakia.app import Application
class ConnectionConfigView(QDialog, Ui_ConnectionConfigurationDialog):
"""
Connection config view
"""
values_changed = pyqtSignal()
def __init__(self, parent):
def __init__(self, parent, app: Application):
"""
Constructor
Init ConnectionConfigView
:param parent: Parent QObject
:param app: Application instance
"""
super().__init__(parent)
self.app = app
self.setupUi(self)
self.last_speed = 0.1
self.average_speed = 1
......@@ -66,20 +70,26 @@ class ConnectionConfigView(QDialog, Ui_ConnectionConfigurationDialog):
self.spin_p.blockSignals(False)
def set_license(self, currency):
license_text = self.tr(G1_LICENCE)
license_text = QCoreApplication.translate("ConnectionConfigView", G1_LICENSE)
self.text_license.setText(license_text)
def handle_n_change(self, value):
spinbox = self.sender()
self.scrypt_params.N = ConnectionConfigView.compute_power_of_2(spinbox, value, self.scrypt_params.N)
self.scrypt_params.N = ConnectionConfigView.compute_power_of_2(
spinbox, value, self.scrypt_params.N
)
def handle_r_change(self, value):
spinbox = self.sender()
self.scrypt_params.r = ConnectionConfigView.compute_power_of_2(spinbox, value, self.scrypt_params.r)
self.scrypt_params.r = ConnectionConfigView.compute_power_of_2(
spinbox, value, self.scrypt_params.r
)
def handle_p_change(self, value):
spinbox = self.sender()
self.scrypt_params.p = ConnectionConfigView.compute_power_of_2(spinbox, value, self.scrypt_params.p)
self.scrypt_params.p = ConnectionConfigView.compute_power_of_2(
spinbox, value, self.scrypt_params.p
)
@staticmethod
def compute_power_of_2(spinbox, value, param):
......@@ -111,15 +121,32 @@ class ConnectionConfigView(QDialog, Ui_ConnectionConfigurationDialog):
async def show_success(self, notification):
if notification:
toast.display(self.tr("UID broadcast"), self.tr("Identity broadcasted to the network"))
toast.display(
QCoreApplication.translate("ConnectionConfigView", "UID broadcast"),
QCoreApplication.translate(
"ConnectionConfigView", "Identity broadcasted to the network"
),
)
else:
await QAsyncMessageBox.information(self, self.tr("UID broadcast"),
self.tr("Identity broadcasted to the network"))
await QAsyncMessageBox.information(
self,
QCoreApplication.translate("ConnectionConfigView", "UID broadcast"),
QCoreApplication.translate(
"ConnectionConfigView", "Identity broadcasted to the network"
),
)
def show_error(self, notification, error_txt):
if notification:
toast.display(self.tr("UID broadcast"), error_txt)
self.label_info.setText(self.tr("Error") + " " + error_txt)
toast.display(
QCoreApplication.translate("ConnectionConfigView", "UID broadcast"),
error_txt,
)
self.label_info.setText(
QCoreApplication.translate("ConnectionConfigView", "Error")
+ " "
+ error_txt
)
def set_nodes_model(self, model):
self.tree_peers.setModel(model)
......@@ -128,12 +155,16 @@ class ConnectionConfigView(QDialog, Ui_ConnectionConfigurationDialog):
"""
Hide unecessary buttons and display correct title
"""
self.setWindowTitle(self.tr("New connection to {0} network").format(ROOT_SERVERS[currency]["display"]))
self.setWindowTitle(
QCoreApplication.translate(
"ConnectionConfigView", "New sakia account on {0} network"
).format(self.app.root_servers[currency]["display"])
)
def action_show_pubkey(self):
salt = self.edit_salt.text()
password = self.edit_password.text()
pubkey = SigningKey(salt, password, self.scrypt_params).pubkey
pubkey = SigningKey.from_credentials(salt, password, self.scrypt_params).pubkey
self.label_info.setText(pubkey)
def account_name(self):
......@@ -164,11 +195,21 @@ class ConnectionConfigView(QDialog, Ui_ConnectionConfigurationDialog):
value = self.progress_bar.value()
next_value = value + 1000000 * step_ratio
speed_percent_by_ms = (next_value - value) / self.timer.elapsed()
self.average_speed = SMOOTHING_FACTOR * self.last_speed + (1 - SMOOTHING_FACTOR) * self.average_speed
self.average_speed = (
SMOOTHING_FACTOR * self.last_speed
+ (1 - SMOOTHING_FACTOR) * self.average_speed
)
remaining = (self.progress_bar.maximum() - next_value) / self.average_speed
self.last_speed = speed_percent_by_ms
displayed_remaining_time = QDateTime.fromTime_t(remaining).toUTC().toString("hh:mm:ss")
self.progress_bar.setFormat(self.tr("{0} remaining...".format(displayed_remaining_time)))
displayed_remaining_time = (
QDateTime.fromTime_t(remaining).toUTC().toString("hh:mm:ss")
)
self.progress_bar.setFormat(
QCoreApplication.translate(
"ConnectionConfigView",
"{0} remaining...".format(displayed_remaining_time),
)
)
self.progress_bar.setValue(next_value)
self.timer.restart()
......@@ -186,16 +227,21 @@ class ConnectionConfigView(QDialog, Ui_ConnectionConfigurationDialog):
:param sakia.data.entities.BlockchainParameters blockchain_parameters:
:return:
"""
days, hours, minutes, seconds = timestamp_to_dhms(blockchain_parameters.idty_window)
expiration_time_str = self.tr("{days} days, {hours}h and {min}min").format(days=days,
hours=hours,
min=minutes)
days, hours, minutes, seconds = timestamp_to_dhms(
blockchain_parameters.idty_window
)
expiration_time_str = QCoreApplication.translate(
"ConnectionConfigView", "{days} days, {hours}h and {min}min"
).format(days=days, hours=hours, min=minutes)
dialog = QDialog(self)
about_dialog = Ui_CongratulationPopup()
about_dialog.setupUi(dialog)
dialog.setWindowTitle("Registration")
about_dialog.label.setText(self.tr("""
dialog.setWindowTitle("Identity registration")
about_dialog.label.setText(
QCoreApplication.translate(
"ConnectionConfigView",
"""
<p><b>Congratulations!</b><br>
<br>
You just published your identity to the network.<br>
......@@ -208,6 +254,11 @@ Please notice that your identity document <br>
<b>will expire in {expiration_time_str}.</b><br>
If you failed to get {certs} certifications before this time, <br>
the process will have to be restarted from scratch.</p>
""".format(certs=blockchain_parameters.sig_qty, expiration_time_str=expiration_time_str)))
""".format(
certs=blockchain_parameters.sig_qty,
expiration_time_str=expiration_time_str,
),
)
)
await dialog_async_exec(dialog)
import asyncio
from PyQt5.QtCore import Qt, QObject
from PyQt5.QtWidgets import QApplication
from sakia.constants import ROOT_SERVERS
from sakia.decorators import asyncify
from sakia.gui.sub.search_user.controller import SearchUserController
from sakia.gui.sub.user_information.controller import UserInformationController
from sakia.gui.sub.password_input import PasswordInputController
from sakia.gui.widgets import dialogs
from PyQt5.QtCore import QObject
from .model import ContactModel
from .view import ContactView
import attr
......@@ -52,8 +44,6 @@ class ContactController(QObject):
Certify and identity
:param sakia.gui.component.controller.ComponentController parent: the parent
:param sakia.core.Application app: the application
:param sakia.core.Account account: the account certifying the identity
:param sakia.core.Community community: the community
:return:
"""
dialog = cls.create(parent, app)
......
from PyQt5.QtCore import QObject, Qt
from sakia.data.entities import Contact
from sakia.data.processors import IdentitiesProcessor, ContactsProcessor, \
BlockchainProcessor, ConnectionsProcessor
from sakia.data.processors import (
IdentitiesProcessor,
ContactsProcessor,
BlockchainProcessor,
ConnectionsProcessor,
)
from sakia.helpers import timestamp_to_dhms
from .table_model import ContactsTableModel, ContactsFilterProxyModel
import attr
......
from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel,\
QModelIndex, QT_TRANSLATE_NOOP
from PyQt5.QtCore import (
QAbstractTableModel,
Qt,
QVariant,
QSortFilterProxyModel,
QModelIndex,
QCoreApplication,
QT_TRANSLATE_NOOP,
)
from sakia.data.processors import ContactsProcessor
......@@ -28,7 +34,7 @@ class ContactsFilterProxyModel(QSortFilterProxyModel):
left_data = source_model.data(left, Qt.DisplayRole)
right_data = source_model.data(right, Qt.DisplayRole)
if left_data == right_data:
txid_col = source_model.columns_types.index('contact_id')
txid_col = source_model.columns_types.index("contact_id")
txid_left = source_model.index(left.row(), txid_col)
txid_right = source_model.index(right.row(), txid_col)
return txid_left < txid_right
......@@ -43,8 +49,10 @@ class ContactsFilterProxyModel(QSortFilterProxyModel):
"""
if index.isValid() and index.row() < self.rowCount(QModelIndex()):
source_index = self.mapToSource(index)
contact_id_col = ContactsTableModel.columns_types.index('contact_id')
contact_id = self.sourceModel().contacts_data[source_index.row()][contact_id_col]
contact_id_col = ContactsTableModel.columns_types.index("contact_id")
contact_id = self.sourceModel().contacts_data[source_index.row()][
contact_id_col
]
return contact_id
return -1
......@@ -60,15 +68,11 @@ class ContactsTableModel(QAbstractTableModel):
A Qt abstract item model to display contacts in a table view
"""
columns_types = (
'name',
'pubkey',
'contact_id'
)
columns_types = ("name", "pubkey", "contact_id")
# mark strings as translatable string
columns_headers = (
QT_TRANSLATE_NOOP("ContactsTableModel", 'Name'),
QT_TRANSLATE_NOOP("ContactsTableModel", 'Public key'),
QT_TRANSLATE_NOOP("ContactsTableModel", "Name"),
QT_TRANSLATE_NOOP("ContactsTableModel", "Public key"),
)
def __init__(self, parent, app):
......@@ -85,18 +89,29 @@ class ContactsTableModel(QAbstractTableModel):
def add_or_edit_contact(self, contact):
for i, data in enumerate(self.contacts_data):
if data[ContactsTableModel.columns_types.index('contact_id')] == contact.contact_id:
if (
data[ContactsTableModel.columns_types.index("contact_id")]
== contact.contact_id
):
self.contacts_data[i] = self.data_contact(contact)
self.dataChanged.emit(self.index(i, 0), self.index(i, len(ContactsTableModel.columns_types)))
self.dataChanged.emit(
self.index(i, 0),
self.index(i, len(ContactsTableModel.columns_types)),
)
return
else:
self.beginInsertRows(QModelIndex(), len(self.contacts_data), len(self.contacts_data))
self.beginInsertRows(
QModelIndex(), len(self.contacts_data), len(self.contacts_data)
)
self.contacts_data.append(self.data_contact(contact))
self.endInsertRows()
def remove_contact(self, contact):
for i, data in enumerate(self.contacts_data):
if data[ContactsTableModel.columns_types.index('contact_id')] == contact.contact_id:
if (
data[ContactsTableModel.columns_types.index("contact_id")]
== contact.contact_id
):
self.beginRemoveRows(QModelIndex(), i, i)
self.contacts_data.pop(i)
self.endRemoveRows()
......@@ -126,7 +141,9 @@ class ContactsTableModel(QAbstractTableModel):
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return ContactsTableModel.columns_headers[section]
return QCoreApplication.translate(
"ContactsTableModel", ContactsTableModel.columns_headers[section]
)
def data(self, index, role):
row = index.row()
......@@ -140,4 +157,3 @@ class ContactsTableModel(QAbstractTableModel):
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox, QAbstractItemView, QHeaderView
from PyQt5.QtWidgets import (
QDialog,
QDialogButtonBox,
QMessageBox,
QAbstractItemView,
QHeaderView,
)
from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, QModelIndex
from .contact_uic import Ui_ContactDialog
from duniterpy.documents.constants import pubkey_regex
from duniterpy.constants import PUBKEY_REGEX
import re
......@@ -32,9 +38,13 @@ class ContactView(QDialog, Ui_ContactDialog):
self.table_contacts.setModel(model)
self.table_contacts.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table_contacts.setSortingEnabled(True)
self.table_contacts.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
self.table_contacts.horizontalHeader().setSectionResizeMode(
QHeaderView.Interactive
)
self.table_contacts.resizeRowsToContents()
self.table_contacts.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
self.table_contacts.verticalHeader().setSectionResizeMode(
QHeaderView.ResizeToContents
)
def selected_contact_index(self):
indexes = self.table_contacts.selectedIndexes()
......@@ -44,7 +54,7 @@ class ContactView(QDialog, Ui_ContactDialog):
def check_pubkey(self):
text = self.edit_pubkey.text()
re_pubkey = re.compile(pubkey_regex)
re_pubkey = re.compile(PUBKEY_REGEX)
result = re_pubkey.match(text)
if result:
self.edit_pubkey.setStyleSheet("")
......
import asyncio
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtCore import QObject
from PyQt5.QtCore import QObject, QCoreApplication
from .model import PluginsManagerModel
from .view import PluginsManagerView
import attr
......@@ -57,8 +57,14 @@ class PluginsManagerController(QObject):
def install_plugin(self):
filename = QFileDialog.getOpenFileName(self.view, self.tr("Open File"),"",
self.tr("Sakia module (*.zip)"))
filename = QFileDialog.getOpenFileName(
self.view,
QCoreApplication.translate("PluginsManagerController", "Open File"),
"",
QCoreApplication.translate(
"PluginsManagerController", "Sakia module (*.zip)"
),
)
if filename[0]:
self.model.install_plugin(filename[0])
......
......@@ -37,7 +37,9 @@ class PluginsManagerModel(QObject):
plugin_name = self._proxy.plugin_name(index)
if plugin_name:
try:
return next(p for p in self.app.plugins_dir.plugins if p.name == plugin_name)
return next(
p for p in self.app.plugins_dir.plugins if p.name == plugin_name
)
except StopIteration:
pass
return None
......
from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel,\
QModelIndex, QT_TRANSLATE_NOOP
from PyQt5.QtCore import (
QAbstractTableModel,
Qt,
QVariant,
QSortFilterProxyModel,
QModelIndex,
QT_TRANSLATE_NOOP,
QCoreApplication,
)
from sakia.data.entities import Plugin
......@@ -36,8 +43,10 @@ class PluginsFilterProxyModel(QSortFilterProxyModel):
"""
if index.isValid() and index.row() < self.rowCount(QModelIndex()):
source_index = self.mapToSource(index)
plugin_name_col = PluginsTableModel.columns_types.index('name')
plugin_name = self.sourceModel().plugins_data[source_index.row()][plugin_name_col]
plugin_name_col = PluginsTableModel.columns_types.index("name")
plugin_name = self.sourceModel().plugins_data[source_index.row()][
plugin_name_col
]
return plugin_name
return None
......@@ -53,18 +62,13 @@ class PluginsTableModel(QAbstractTableModel):
A Qt abstract item model to display plugins in a table view
"""
columns_types = (
'name',
'description',
'version',
'imported'
)
columns_types = ("name", "description", "version", "imported")
columns_headers = (
QT_TRANSLATE_NOOP("PluginsTableModel", 'Name'),
QT_TRANSLATE_NOOP("PluginsTableModel", 'Description'),
QT_TRANSLATE_NOOP("PluginsTableModel", 'Version'),
QT_TRANSLATE_NOOP("PluginsTableModel", 'Imported'),
QT_TRANSLATE_NOOP("PluginsTableModel", "Name"),
QT_TRANSLATE_NOOP("PluginsTableModel", "Description"),
QT_TRANSLATE_NOOP("PluginsTableModel", "Version"),
QT_TRANSLATE_NOOP("PluginsTableModel", "Imported"),
)
def __init__(self, parent, app):
......@@ -78,13 +82,15 @@ class PluginsTableModel(QAbstractTableModel):
self.plugins_data = []
def add_plugin(self, plugin):
self.beginInsertRows(QModelIndex(), len(self.plugins_data), len(self.plugins_data))
self.beginInsertRows(
QModelIndex(), len(self.plugins_data), len(self.plugins_data)
)
self.plugins_data.append(self.data_plugin(plugin))
self.endInsertRows()
def remove_plugin(self, plugin):
for i, data in enumerate(self.plugins_data):
if data[PluginsTableModel.columns_types.index('name')] == plugin.name:
if data[PluginsTableModel.columns_types.index("name")] == plugin.name:
self.beginRemoveRows(QModelIndex(), i, i)
self.plugins_data.pop(i)
self.endRemoveRows()
......@@ -113,7 +119,9 @@ class PluginsTableModel(QAbstractTableModel):
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return PluginsTableModel.columns_headers[section]
return QCoreApplication.translate(
"PluginsTableModel", PluginsTableModel.columns_headers[section]
)
def data(self, index, role):
row = index.row()
......@@ -127,4 +135,3 @@ class PluginsTableModel(QAbstractTableModel):
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled