From 76cc8a77bd0a618530d55d5d3344b3e1e746963f Mon Sep 17 00:00:00 2001
From: Insoleet <insomniak.fr@gmail.com>
Date: Tue, 15 Sep 2015 12:33:58 +0200
Subject: [PATCH] Proxy for aiohttp + Diverses fixes :

- Pressing Ok in tests
- Handling timeout errors
---
 lib/ucoinpy/api/bma/__init__.py               | 10 ++-
 setup.py                                      |  4 +-
 src/cutecoin/core/app.py                      | 72 ++++++++++---------
 src/cutecoin/core/net/api/bma/access.py       | 29 ++++++--
 src/cutecoin/core/net/node.py                 |  5 ++
 src/cutecoin/gui/homescreen.py                |  3 +
 src/cutecoin/gui/mainwindow.py                |  2 +-
 src/cutecoin/gui/network_tab.py               |  2 +-
 .../gui/certification/test_certification.py   |  8 ++-
 .../tests/gui/transfer/test_transfer.py       |  9 ++-
 10 files changed, 95 insertions(+), 49 deletions(-)

diff --git a/lib/ucoinpy/api/bma/__init__.py b/lib/ucoinpy/api/bma/__init__.py
index b83875b7..821cce8c 100644
--- a/lib/ucoinpy/api/bma/__init__.py
+++ b/lib/ucoinpy/api/bma/__init__.py
@@ -25,7 +25,7 @@ __nonsense__    = 'uCoin'
 
 PROTOCOL_VERSION = "1"
 
-import aiohttp, requests, asyncio, logging, json
+import aiohttp, asyncio, logging, json
 
 logger = logging.getLogger("ucoin")
 
@@ -42,6 +42,7 @@ class ConnectionHandler(object):
 
         self.server = server
         self.port = port
+        self.connector = None
 
     def __str__(self):
         return 'connection info: %s:%d' % (self.server, self.port)
@@ -50,6 +51,8 @@ class ConnectionHandler(object):
 class API(object):
     """APIRequest is a class used as an interface. The intermediate derivated classes are the modules and the leaf classes are the API requests."""
 
+    aiohttp_connector = None
+
     def __init__(self, connection_handler, module):
         """
         Asks a module in order to create the url used then by derivated classes.
@@ -110,7 +113,7 @@ class API(object):
         """
         logging.debug("Request : {0}".format(self.reverse_url(path)))
         response = yield from asyncio.wait_for(aiohttp.get(self.reverse_url(path), params=kwargs,
-                                headers=self.headers), 15)
+                                headers=self.headers, connector=API.aiohttp_connector), timeout=15)
 
         if response.status != 200:
             raise ValueError('status code != 200 => %d (%s)' % (response.status, (yield from response.text())))
@@ -129,7 +132,8 @@ class API(object):
 
         logging.debug("POST : {0}".format(kwargs))
         response = yield from asyncio.wait_for(
-            aiohttp.post(self.reverse_url(path), data=kwargs, headers=self.headers),
+            aiohttp.post(self.reverse_url(path), data=kwargs, headers=self.headers,
+                         connector=API.aiohttp_connector),
                                  timeout=15)
         return response
 
diff --git a/setup.py b/setup.py
index 8381c99f..8fc5952a 100644
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@ print(sys.path)
 includes = ["sip", "re", "json", "logging",
             "hashlib", "os", "urllib",
             "ucoinpy", "pylibscrypt"]
-excludes = ['.git']
+exclude = ['.git']
 packages = ["libnacl", "encodings"]
 
 includefiles = []
@@ -62,7 +62,7 @@ else:
 options = {"path": sys.path,
            "includes": includes,
            "include_files": includefiles,
-           "excludes": excludes,
+           "excludes": exclude,
            "packages": packages,
            }
 
diff --git a/src/cutecoin/core/app.py b/src/cutecoin/core/app.py
index 5c473575..4b9bb871 100644
--- a/src/cutecoin/core/app.py
+++ b/src/cutecoin/core/app.py
@@ -10,17 +10,19 @@ import tarfile
 import shutil
 import json
 import datetime
-import i18n_rc
+import asyncio
+import aiohttp
 
 from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, \
 QUrl, QTranslator, QCoreApplication, QLocale
-from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest, QNetworkProxy
-
+from ucoinpy.api.bma import API
+from aiohttp.connector import ProxyConnector
 from . import config
 from .account import Account
 from .registry.identities import IdentitiesRegistry
 from .. import __version__
 from ..tools.exceptions import NameAlreadyExists, BadAccountFile
+from ..tools.decorators import asyncify
 
 
 class Application(QObject):
@@ -73,12 +75,9 @@ class Application(QObject):
         app.load()
         app.switch_language()
         if app.preferences['enable_proxy'] is True:
-            proxytypes = {"HTTP": QNetworkProxy.HttpProxy,
-                          "SOCKS5": QNetworkProxy.Socks5Proxy}
-            qtproxy = QNetworkProxy(proxytypes[app.preferences.get('proxy_type', "HTTP")],
+            API.aiohttp_connector = ProxyConnector("http://{0}:{1}".format(
                                     app.preferences['proxy_address'],
-                                    app.preferences['proxy_port'])
-            #network_manager.setProxy(qtproxy)
+                                    app.preferences['proxy_port']))
 
         if app.preferences["account"] != "":
             account = app.get_account(app.preferences["account"])
@@ -456,32 +455,35 @@ class Application(QObject):
 
         self.save_registries()
 
+    @asyncify
+    @asyncio.coroutine
     def get_last_version(self):
-        url = QUrl("https://api.github.com/repos/ucoin-io/cutecoin/releases")
-        """request = QNetworkRequest(url)
-        reply = self._network_manager.get(request)
-        reply.finished.connect(self.read_available_version)"""
-
-    @pyqtSlot(QNetworkReply)
-    def read_available_version(self):
-        latest = None
-        reply = self.sender()
-        releases = reply.readAll().data().decode('utf-8')
-        logging.debug(releases)
-        if reply.error() == QNetworkReply.NoError:
-            for r in json.loads(releases):
-                if not latest:
-                    latest = r
-                else:
-                    latest_date = datetime.datetime.strptime(latest['published_at'], "%Y-%m-%dT%H:%M:%SZ")
-                    date = datetime.datetime.strptime(r['published_at'], "%Y-%m-%dT%H:%M:%SZ")
-                    if latest_date < date:
+        if self.preferences['enable_proxy'] is True:
+            connector = ProxyConnector("http://{0}:{1}".format(
+                                    self.preferences['proxy_address'],
+                                    self.preferences['proxy_port']))
+        else:
+            connector = None
+        try:
+            response = yield from asyncio.wait_for(aiohttp.get("https://api.github.com/repos/ucoin-io/cutecoin/releases",
+                                                               connector=connector), timeout=15)
+            if response.status == 200:
+                releases = yield from response.json()
+                for r in releases:
+                    if not latest:
                         latest = r
-            latest_version = latest["tag_name"]
-            version = (__version__ == latest_version,
-                       latest_version,
-                       latest["html_url"])
-            logging.debug("Found version : {0}".format(latest_version))
-            logging.debug("Current version : {0}".format(__version__))
-            self.available_version = version
-        self.version_requested.emit()
+                    else:
+                        latest_date = datetime.datetime.strptime(latest['published_at'], "%Y-%m-%dT%H:%M:%SZ")
+                        date = datetime.datetime.strptime(r['published_at'], "%Y-%m-%dT%H:%M:%SZ")
+                        if latest_date < date:
+                            latest = r
+                latest_version = latest["tag_name"]
+                version = (__version__ == latest_version,
+                           latest_version,
+                           latest["html_url"])
+                logging.debug("Found version : {0}".format(latest_version))
+                logging.debug("Current version : {0}".format(__version__))
+                self.available_version = version
+            self.version_requested.emit()
+        except aiohttp.errors.ClientError as e:
+            logging.debug("Could not connect to github : {0}".format(str(e)))
diff --git a/src/cutecoin/core/net/api/bma/access.py b/src/cutecoin/core/net/api/bma/access.py
index 8700fc1f..cb830c18 100644
--- a/src/cutecoin/core/net/api/bma/access.py
+++ b/src/cutecoin/core/net/api/bma/access.py
@@ -169,6 +169,8 @@ class BmaAccess(QObject):
                     tries += 1
                 except ClientError:
                     tries += 1
+                except TimeoutError:
+                    tries += 1
         if len(nodes) == 0 or json_data is None:
             raise NoPeerAvailable("", len(nodes))
         return json_data
@@ -186,8 +188,19 @@ class BmaAccess(QObject):
         if len(nodes) > 0:
             node = random.choice(nodes)
             req = request(node.endpoint.conn_handler(), **req_args)
-            json_data = yield from req.get(**get_args)
-            return json_data
+            tries = 0
+            while tries < 3:
+                try:
+                    json_data = yield from req.get(**get_args)
+                    return json_data
+                except ValueError as e:
+                    if '404' in str(e) or '400' in str(e):
+                        raise
+                    tries += 1
+                except ClientError:
+                    tries += 1
+                except TimeoutError:
+                    tries += 1
         else:
             raise NoPeerAvailable("", len(nodes))
 
@@ -212,8 +225,16 @@ class BmaAccess(QObject):
                 logging.debug("Trying to connect to : " + node.pubkey)
                 conn_handler = node.endpoint.conn_handler()
                 req = request(conn_handler, **req_args)
-                reply = yield from req.post(**post_args)
-                replies.append(reply)
+                try:
+                    reply = yield from req.post(**post_args)
+                    replies.append(reply)
+                except ValueError as e:
+                    if '404' in str(e) or '400' in str(e):
+                        raise
+                except ClientError:
+                    pass
+                except TimeoutError:
+                    pass
         else:
             raise NoPeerAvailable("", len(nodes))
         return tuple(replies)
diff --git a/src/cutecoin/core/net/node.py b/src/cutecoin/core/net/node.py
index 4c25fdbf..f534cc8e 100644
--- a/src/cutecoin/core/net/node.py
+++ b/src/cutecoin/core/net/node.py
@@ -296,6 +296,7 @@ class Node(QObject):
         try:
             block_data = yield from bma.blockchain.Current(conn_handler).get()
             block_hash = block_data['hash']
+            self.state = Node.ONLINE
 
             if not self.block or block_hash != self.block['hash']:
                 self.set_block(block_data)
@@ -324,6 +325,7 @@ class Node(QObject):
             logging.debug(peering_data)
             node_pubkey = peering_data["pubkey"]
             node_currency = peering_data["currency"]
+            self.state = Node.ONLINE
 
             change = False
             if node_pubkey != self.pubkey:
@@ -354,6 +356,7 @@ class Node(QObject):
             summary_data = yield from bma.node.Summary(conn_handler).get()
             self.software = summary_data["ucoin"]["software"]
             self.version = summary_data["ucoin"]["version"]
+            self.state = Node.ONLINE
             if "forkWindowSize" in summary_data["ucoin"]:
                 self.fork_window = summary_data["ucoin"]["forkWindowSize"]
             else:
@@ -372,6 +375,7 @@ class Node(QObject):
         conn_handler = self.endpoint.conn_handler()
         try:
             data = yield from bma.wot.Lookup(conn_handler, self.pubkey).get()
+            self.state = Node.ONLINE
             timestamp = 0
             for result in data['results']:
                 if result["pubkey"] == self.pubkey:
@@ -402,6 +406,7 @@ class Node(QObject):
 
         try:
             peers_data = yield from bma.network.peering.Peers(conn_handler).get(leaves='true')
+            self.state = Node.ONLINE
             if peers_data['root'] != self._last_merkle['root']:
                 leaves = [leaf for leaf in peers_data['leaves']
                           if leaf not in self._last_merkle['leaves']]
diff --git a/src/cutecoin/gui/homescreen.py b/src/cutecoin/gui/homescreen.py
index 85f21a16..aea3ad0f 100644
--- a/src/cutecoin/gui/homescreen.py
+++ b/src/cutecoin/gui/homescreen.py
@@ -84,6 +84,9 @@ class HomeScreenWidget(QWidget, Ui_HomescreenWidget):
         :param QShowEvent:
         :return:
         """
+        for tile in self.frame_communities:
+            tile.refresh()
+
         self.status_label.setText("")
 
     def changeEvent(self, event):
diff --git a/src/cutecoin/gui/mainwindow.py b/src/cutecoin/gui/mainwindow.py
index c59870d3..a9e704aa 100644
--- a/src/cutecoin/gui/mainwindow.py
+++ b/src/cutecoin/gui/mainwindow.py
@@ -86,7 +86,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
 
     def startup(self):
         self.update_time()
-        self.app.get_last_version()
+        # FIXME : Need python 3.5 self.app.get_last_version()
         if self.app.preferences['maximized']:
             self.showMaximized()
         else:
diff --git a/src/cutecoin/gui/network_tab.py b/src/cutecoin/gui/network_tab.py
index a1dce6c3..8805a2d5 100644
--- a/src/cutecoin/gui/network_tab.py
+++ b/src/cutecoin/gui/network_tab.py
@@ -11,7 +11,7 @@ from PyQt5.QtGui import QCursor, QDesktopServices
 from PyQt5.QtWidgets import QWidget, QMenu, QAction
 from PyQt5.QtCore import Qt, QModelIndex, pyqtSlot, QUrl, QEvent
 from ..models.network import NetworkTableModel, NetworkFilterProxyModel
-from ..core.net.api import bma as bma
+from ucoinpy.api import bma
 from ..gen_resources.network_tab_uic import Ui_NetworkTabWidget
 
 
diff --git a/src/cutecoin/tests/gui/certification/test_certification.py b/src/cutecoin/tests/gui/certification/test_certification.py
index 3440e678..a2e38286 100644
--- a/src/cutecoin/tests/gui/certification/test_certification.py
+++ b/src/cutecoin/tests/gui/certification/test_certification.py
@@ -5,7 +5,8 @@ import quamash
 import time
 import logging
 from ucoinpy.documents.peer import BMAEndpoint
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox
+from quamash import QApplication
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox
 from PyQt5.QtCore import QLocale, Qt
 from PyQt5.QtTest import QTest
 from ucoinpy.api.bma import API
@@ -85,6 +86,11 @@ class TestCertificationDialog(unittest.TestCase):
             QTest.mouseClick(certification_dialog.radio_pubkey, Qt.LeftButton)
             QTest.keyClicks(certification_dialog.edit_pubkey, "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn")
             QTest.mouseClick(certification_dialog.button_box.button(QDialogButtonBox.Ok), Qt.LeftButton)
+            yield from asyncio.sleep(1)
+            topWidgets = QApplication.topLevelWidgets()
+            for w in topWidgets:
+                if type(w) is QMessageBox:
+                    QTest.keyClick(w, Qt.Key_Enter)
 
         self.lp.call_later(15, close_dialog)
         asyncio.async(exec_test())
diff --git a/src/cutecoin/tests/gui/transfer/test_transfer.py b/src/cutecoin/tests/gui/transfer/test_transfer.py
index fc69bd58..dcb83313 100644
--- a/src/cutecoin/tests/gui/transfer/test_transfer.py
+++ b/src/cutecoin/tests/gui/transfer/test_transfer.py
@@ -4,8 +4,8 @@ import asyncio
 import quamash
 import time
 import logging
-from ucoinpy.documents.peer import BMAEndpoint as PyBMAEndpoint
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox
+from quamash import QApplication
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox
 from PyQt5.QtCore import QLocale, Qt
 from PyQt5.QtTest import QTest
 from ucoinpy.api.bma import API
@@ -85,6 +85,11 @@ class TestTransferDialog(unittest.TestCase):
             QTest.mouseClick(transfer_dialog.radio_pubkey, Qt.LeftButton)
             QTest.keyClicks(transfer_dialog.edit_pubkey, "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn")
             QTest.mouseClick(transfer_dialog.button_box.button(QDialogButtonBox.Cancel), Qt.LeftButton)
+            yield from asyncio.sleep(1)
+            topWidgets = QApplication.topLevelWidgets()
+            for w in topWidgets:
+                if type(w) is QMessageBox:
+                    QTest.keyClick(w, Qt.Key_Enter)
 
         self.lp.call_later(15, close_dialog)
         asyncio.async(exec_test())
-- 
GitLab