diff --git a/src/qgis_geonode/apiclient/base.py b/src/qgis_geonode/apiclient/base.py index 9fea4481..28c5438a 100644 --- a/src/qgis_geonode/apiclient/base.py +++ b/src/qgis_geonode/apiclient/base.py @@ -21,143 +21,10 @@ from .models import GeonodeApiSearchParameters -@dataclasses.dataclass() -class EventLoopResult: - result: typing.Optional[bool] - - -@contextmanager -def wait_for_signal( - signal, timeout: int = 10000 -) -> typing.ContextManager[EventLoopResult]: - """Fire up a custom event loop and wait for the input signal to be emitted - - This function allows running QT async code in a blocking fashion. It works by - spawning a Qt event loop. This custom loop has its `quit()` slot bound to the - input `signal`. The event loop is `exec_`'ed, thus blocking the current - thread until the the input `signal` is emitted. - - """ - - loop = QtCore.QEventLoop() - signal.connect(loop.quit) - loop_result = EventLoopResult(result=None) - yield loop_result - QtCore.QTimer.singleShot(timeout, partial(_forcibly_terminate_loop, loop)) - log(f"About to start custom event loop...") - loop_result.result = not bool(loop.exec_()) - log(f"Custom event loop ended, resuming...") - - -def _forcibly_terminate_loop(loop: QtCore.QEventLoop): - log("Forcibly ending event loop...") - loop.exit(1) - - -def reply_matches( - qgis_reply: qgis.core.QgsNetworkReplyContent, qt_reply: QtNetwork.QNetworkReply -) -> bool: - reply_id = int(qt_reply.property("requestId")) - return qgis_reply.requestId() == reply_id - - -class NetworkFetcherTask(qgis.core.QgsTask): - authcfg: typing.Optional[str] - description: str - request: QtNetwork.QNetworkRequest - request_payload: typing.Optional[str] - reply_content: typing.Optional[QtCore.QByteArray] - parsed_reply: typing.Optional[ParsedNetworkReply] - redirect_policy: QtNetwork.QNetworkRequest.RedirectPolicy - _reply: typing.Optional[QtNetwork.QNetworkReply] - - request_finished = QtCore.pyqtSignal() - request_parsed = QtCore.pyqtSignal() - - def __init__( - self, - request: QtNetwork.QNetworkRequest, - request_payload: typing.Optional[str] = None, - authcfg: typing.Optional[str] = None, - description: typing.Optional[str] = "MyNetworkfetcherTask", - redirect_policy: typing.Optional[QtNetwork.QNetworkRequest.RedirectPolicy] = ( - QtNetwork.QNetworkRequest.NoLessSafeRedirectPolicy - ), - ): - """ - Custom QgsTask that performs network requests - - This class is able to perform both GET and POST HTTP requests. - - It is needed because: - - - QgsNetworkContentFetcherTask only performs GET requests - - QgsNetworkAcessManager.blockingPost() does not seem to handle redirects - correctly - - Implementation is based on QgsNetworkContentFetcher. The run() method performs - a normal async request using QtNetworkAccessManager's get() or post() methods. - The resulting QNetworkReply instance has its `finished` signal be connected to - a custom handler. The request is executed in scope of a custom Qt event loop, - which blocks the current thread while the request is being processed. - - """ - - super().__init__(description) - self.authcfg = authcfg - self.request = request - self.request_payload = request_payload - self.reply_content = None - self.parsed_reply = None - self.redirect_policy = redirect_policy - self.network_access_manager = qgis.core.QgsNetworkAccessManager.instance() - self.network_access_manager.setRedirectPolicy(self.redirect_policy) - self.network_access_manager.finished.connect(self._request_done) - self._reply = None - - def run(self): - if self.authcfg is not None: - auth_manager = qgis.core.QgsApplication.authManager() - auth_manager.updateNetworkRequest(self.request, self.authcfg) - with wait_for_signal(self.request_parsed): - if self.request_payload is None: - self._reply = self.network_access_manager.get(self.request) - else: - self._reply = self.network_access_manager.post( - self.request, - QtCore.QByteArray(self.request_payload.encode("utf-8")), - ) - try: - result = self.parsed_reply.qt_error is None - self._reply.deleteLater() - self._reply = None - except AttributeError: - result = False - self.network_access_manager.finished.disconnect(self._request_done) - self.request_finished.emit() - return result - - def _request_done(self, qgis_reply: qgis.core.QgsNetworkReplyContent): - log(f"requested_url: {qgis_reply.request().url().toString()}") - if self._reply is None: - log( - "Some other request was completed, probably authentication, " - "ignoring..." - ) - elif reply_matches(qgis_reply, self._reply): - self.reply_content = self._reply.readAll() - self.parsed_reply = parse_network_reply(qgis_reply) - log(f"http_status_code: {self.parsed_reply.http_status_code}") - log(f"qt_error: {self.parsed_reply.qt_error}") - self.request_parsed.emit() - else: - log(f"qgis_reply did not match the original reply id, ignoring...") - - class BaseGeonodeClient(QtCore.QObject): auth_config: str base_url: str - network_fetcher_task: typing.Optional[NetworkFetcherTask] + network_fetcher_task: typing.Optional["NetworkFetcherTask"] capabilities: typing.List[models.ApiClientCapability] layer_list_received = QtCore.pyqtSignal(list, models.GeoNodePaginationInfo) @@ -266,6 +133,7 @@ def get_layers( search_params if search_params is not None else GeonodeApiSearchParameters() ) self.network_fetcher_task = NetworkFetcherTask( + self, QtNetwork.QNetworkRequest(self.get_layers_url_endpoint(params)), request_payload=self.get_layers_request_payload(params), authcfg=self.auth_config, @@ -282,6 +150,7 @@ def get_layer_detail_from_brief_resource( def get_layer_detail(self, id_: typing.Union[int, uuid.UUID]): self.network_fetcher_task = NetworkFetcherTask( + self, QtNetwork.QNetworkRequest(self.get_layer_detail_url_endpoint(id_)), authcfg=self.auth_config, ) @@ -293,7 +162,7 @@ def get_layer_styles(self, layer_id: int): self.get_layer_styles_url_endpoint(layer_id) ) self.network_fetcher_task = NetworkFetcherTask( - request, authcfg=self.auth_config + self, request, authcfg=self.auth_config ) self.network_fetcher_task.request_finished.connect(self.handle_layer_style_list) qgis.core.QgsApplication.taskManager().addTask(self.network_fetcher_task) @@ -307,7 +176,9 @@ def get_layer_style( style_details = [i for i in layer.styles if i.name == style_name][0] style_url = style_details.sld_url self.network_fetcher_task = NetworkFetcherTask( - QtNetwork.QNetworkRequest(QtCore.QUrl(style_url)), authcfg=self.auth_config + self, + QtNetwork.QNetworkRequest(QtCore.QUrl(style_url)), + authcfg=self.auth_config, ) self.network_fetcher_task.request_finished.connect( self.handle_layer_style_detail @@ -319,6 +190,7 @@ def get_maps(self, search_params: GeonodeApiSearchParameters): request_payload = self.get_maps_request_payload(search_params) log(f"URL: {url.toString()}") self.network_fetcher_task = NetworkFetcherTask( + self, QtNetwork.QNetworkRequest(url), request_payload=request_payload, authcfg=self.auth_config, @@ -327,3 +199,153 @@ def get_maps(self, search_params: GeonodeApiSearchParameters): partial(self.handle_map_list, search_params) ) qgis.core.QgsApplication.taskManager().addTask(self.network_fetcher_task) + + +class NetworkFetcherTask(qgis.core.QgsTask): + api_client: BaseGeonodeClient + authcfg: typing.Optional[str] + description: str + request: QtNetwork.QNetworkRequest + request_payload: typing.Optional[str] + reply_content: typing.Optional[QtCore.QByteArray] + parsed_reply: typing.Optional[ParsedNetworkReply] + redirect_policy: QtNetwork.QNetworkRequest.RedirectPolicy + _reply: typing.Optional[QtNetwork.QNetworkReply] + + request_finished = QtCore.pyqtSignal() + request_parsed = QtCore.pyqtSignal() + + def __init__( + self, + api_client: BaseGeonodeClient, + request: QtNetwork.QNetworkRequest, + request_payload: typing.Optional[str] = None, + authcfg: typing.Optional[str] = None, + description: typing.Optional[str] = "MyNetworkfetcherTask", + redirect_policy: typing.Optional[QtNetwork.QNetworkRequest.RedirectPolicy] = ( + QtNetwork.QNetworkRequest.NoLessSafeRedirectPolicy + ), + ): + """ + Custom QgsTask that performs network requests + + This class is able to perform both GET and POST HTTP requests. + + It is needed because: + + - QgsNetworkContentFetcherTask only performs GET requests + - QgsNetworkAcessManager.blockingPost() does not seem to handle redirects + correctly + + Implementation is based on QgsNetworkContentFetcher. The run() method performs + a normal async request using QtNetworkAccessManager's get() or post() methods. + The resulting QNetworkReply instance has its `finished` signal be connected to + a custom handler. The request is executed in scope of a custom Qt event loop, + which blocks the current thread while the request is being processed. + + """ + + super().__init__(description) + self.api_client = api_client + self.authcfg = authcfg + self.request = request + self.request_payload = request_payload + self.reply_content = None + self.parsed_reply = None + self.redirect_policy = redirect_policy + self.network_access_manager = qgis.core.QgsNetworkAccessManager.instance() + self.network_access_manager.setRedirectPolicy(self.redirect_policy) + self.network_access_manager.finished.connect(self._request_done) + self._reply = None + + def run(self): + if self.authcfg is not None: + auth_manager = qgis.core.QgsApplication.authManager() + auth_manager.updateNetworkRequest(self.request, self.authcfg) + with wait_for_signal(self.request_parsed) as loop_result: + if self.request_payload is None: + self._reply = self.network_access_manager.get(self.request) + else: + self._reply = self.network_access_manager.post( + self.request, + QtCore.QByteArray(self.request_payload.encode("utf-8")), + ) + try: + if loop_result.result: + result = self.parsed_reply.qt_error is None + else: + result = False + self._reply.deleteLater() + self._reply = None + except AttributeError: + result = False + # self.network_access_manager.finished.disconnect(self._request_done) + # self.request_finished.emit() + return result + + def finished(self, result: bool): + self.network_access_manager.finished.disconnect(self._request_done) + log(f"Inside finished. Result: {result}") + self.request_finished.emit() + if not result: + self.api_client.error_received.emit( + self.parsed_reply.qt_error, + self.parsed_reply.http_status_code, + self.parsed_reply.http_status_reason, + ) + + def _request_done(self, qgis_reply: qgis.core.QgsNetworkReplyContent): + log(f"requested_url: {qgis_reply.request().url().toString()}") + if self._reply is None: + log( + "Some other request was completed, probably authentication, " + "ignoring..." + ) + elif reply_matches(qgis_reply, self._reply): + self.reply_content = self._reply.readAll() + self.parsed_reply = parse_network_reply(qgis_reply) + log(f"http_status_code: {self.parsed_reply.http_status_code}") + log(f"qt_error: {self.parsed_reply.qt_error}") + self.request_parsed.emit() + else: + log(f"qgis_reply did not match the original reply id, ignoring...") + + +@dataclasses.dataclass() +class EventLoopResult: + result: typing.Optional[bool] + + +@contextmanager +def wait_for_signal( + signal, timeout: int = 10000 +) -> typing.ContextManager[EventLoopResult]: + """Fire up a custom event loop and wait for the input signal to be emitted + + This function allows running QT async code in a blocking fashion. It works by + spawning a Qt event loop. This custom loop has its `quit()` slot bound to the + input `signal`. The event loop is `exec_`'ed, thus blocking the current + thread until the the input `signal` is emitted. + + """ + + loop = QtCore.QEventLoop() + signal.connect(loop.quit) + loop_result = EventLoopResult(result=None) + yield loop_result + QtCore.QTimer.singleShot(timeout, partial(_forcibly_terminate_loop, loop)) + log(f"About to start custom event loop...") + loop_result.result = not bool(loop.exec_()) + log(f"Custom event loop ended, resuming...") + + +def _forcibly_terminate_loop(loop: QtCore.QEventLoop): + log("Forcibly ending event loop...") + loop.exit(1) + + +def reply_matches( + qgis_reply: qgis.core.QgsNetworkReplyContent, qt_reply: QtNetwork.QNetworkReply +) -> bool: + reply_id = int(qt_reply.property("requestId")) + return qgis_reply.requestId() == reply_id diff --git a/src/qgis_geonode/apiclient/csw.py b/src/qgis_geonode/apiclient/csw.py index 262a20f3..dc0566b7 100644 --- a/src/qgis_geonode/apiclient/csw.py +++ b/src/qgis_geonode/apiclient/csw.py @@ -98,8 +98,8 @@ def run(self) -> bool: self.parsed_reply = parse_network_reply(self._final_reply) self.reply_content = self._final_reply.readAll() self._blocking_logout() - self.network_access_manager.finished.disconnect(self._request_done) - self.request_finished.emit() + # self.network_access_manager.finished.disconnect(self._request_done) + # self.request_finished.emit() result = self.parsed_reply.qt_error is None else: result = False @@ -252,8 +252,150 @@ def _blocking_logout(self) -> bool: return result +class GeonodeLayerDetailFetcherMixin: + TIMEOUT: int + base_url: str + authcfg: str + network_access_manager: qgis.core.QgsNetworkAccessManager + + layer_detail_api_v1_parsed: QtCore.pyqtSignal + layer_style_parsed: QtCore.pyqtSignal + + def _blocking_get_layer_detail_v1_api( + self, layer_title: str + ) -> typing.Optional[typing.Dict]: + layer_detail_url = "?".join( + ( + f"{self.base_url}/api/layers/", + urllib.parse.urlencode({"title": layer_title}), + ) + ) + request = QtNetwork.QNetworkRequest(QtCore.QUrl(layer_detail_url)) + auth_manager = qgis.core.QgsApplication.authManager() + auth_manager.updateNetworkRequest(request, self.authcfg) + with base.wait_for_signal(self.layer_detail_api_v1_parsed, self.TIMEOUT): + self._layer_detail_api_v1_reply = self.network_access_manager.get(request) + if self._layer_detail_api_v1_reply.error() == QtNetwork.QNetworkReply.NoError: + raw_layer_detail = self._layer_detail_api_v1_reply.readAll() + layer_detail_response = json.loads(raw_layer_detail.data().decode()) + try: + result = layer_detail_response["objects"][0] + except (KeyError, IndexError): + raise IOError(f"Received unexpected API response for {layer_title!r}") + else: + result = None + return result + + def _blocking_get_style_detail(self, style_uri: str) -> models.BriefGeonodeStyle: + request = QtNetwork.QNetworkRequest(QtCore.QUrl(f"{self.base_url}{style_uri}")) + auth_manager = qgis.core.QgsApplication.authManager() + auth_manager.updateNetworkRequest(request, self.authcfg) + with base.wait_for_signal(self.layer_style_parsed, self.TIMEOUT): + self._layer_style_reply = self.network_access_manager.get(request) + if self._layer_style_reply.error() == QtNetwork.QNetworkReply.NoError: + raw_style_detail = self._layer_style_reply.readAll() + style_detail = json.loads(raw_style_detail.data().decode()) + sld_path = urllib.parse.urlparse(style_detail["sld_url"]).path + result = models.BriefGeonodeStyle( + name=style_detail["name"], + sld_url=f"{self.base_url}{sld_path}", + ) + else: + parsed_reply = parse_network_reply(self._layer_style_reply) + msg = ( + f"Received an error retrieving style detail: {parsed_reply.qt_error} - " + f"{parsed_reply.http_status_code} - {parsed_reply.http_status_reason} " + f"- {self._layer_style_reply.readAll()}" + ) + raise RuntimeError(msg) + return result + + +class GeoNodeLegacyLayerDetailFetcher( + GeonodeLayerDetailFetcherMixin, base.NetworkFetcherTask +): + TIMEOUT: int = 10000 + base_url: str + reply_content = GeoNodeCswLayerDetail + _layer_detail_api_v1_reply: typing.Optional[QtNetwork.QNetworkReply] + _layer_style_reply: typing.Optional[QtNetwork.QNetworkReply] + + layer_detail_api_v1_parsed = QtCore.pyqtSignal() + layer_style_parsed = QtCore.pyqtSignal() + + def __init__(self, base_url: str, *args, **kwargs): + """Fetch layer details from GeoNode using CSW API with anonymous access.""" + super().__init__(*args, **kwargs) + self.base_url = base_url + self.reply_content = GeoNodeCswLayerDetail(None, None, None) + self._layer_detail_api_v1_reply = None + self._layer_style_reply = None + + def run(self): + record = self._blocking_get_reply() + if record is not None: + self.reply_content.parsed_csw_record = record + layer_title = _extract_layer_title(record) + layer_detail = self._blocking_get_layer_detail_v1_api(layer_title) + if layer_detail is not None: + self.reply_content.parsed_layer_detail = layer_detail + style_uri = layer_detail["default_style"] + try: + brief_style = self._blocking_get_style_detail(style_uri) + self.reply_content.brief_style = brief_style + result = brief_style is not None + except RuntimeError as exc: + log(str(exc)) + result = False + else: + result = False + else: + result = False + return result + + def _request_done(self, qgis_reply: qgis.core.QgsNetworkReplyContent): + self.parsed_reply = parse_network_reply(qgis_reply) + log(f"requested_url: {qgis_reply.request().url().toString()}") + log(f"http_status_code: {self.parsed_reply.http_status_code}") + log(f"qt_error: {self.parsed_reply.qt_error}") + found_matched_reply = False + if self._final_reply is not None: + if base.reply_matches(qgis_reply, self._final_reply): + found_matched_reply = True + self.request_parsed.emit() + if self._layer_detail_api_v1_reply is not None: + if base.reply_matches(qgis_reply, self._layer_detail_api_v1_reply): + found_matched_reply = True + self.layer_detail_api_v1_parsed.emit() + if self._layer_style_reply is not None: + if base.reply_matches(qgis_reply, self._layer_style_reply): + found_matched_reply = True + self.layer_style_parsed.emit() + if not found_matched_reply: + log("Could not match this reply with a previous one, ignoring...") + + def _blocking_get_reply( + self, + ) -> typing.Optional[ET.Element]: + with base.wait_for_signal(self.request_parsed, self.TIMEOUT) as loop_result: + if self.request_payload is None: + self._final_reply = self.network_access_manager.get(self.request) + else: + self._final_reply = self.network_access_manager.post( + self.request, + QtCore.QByteArray(self.request_payload.encode("utf-8")), + ) + if loop_result.result: + decoded = self._final_reply.readAll().data().decode("utf-8") + decoded_element = ET.fromstring(decoded) + record = decoded_element.find(f"{{{Csw202Namespace.GMD.value}}}MD_Metadata") + else: + record = None + return record + + class GeoNodeLegacyAuthenticatedLayerDetailFetcherTask( - GeoNodeLegacyAuthenticatedRecordSearcherTask + GeonodeLayerDetailFetcherMixin, GeoNodeLegacyAuthenticatedRecordSearcherTask ): reply_content: GeoNodeCswLayerDetail @@ -300,10 +442,9 @@ def run(self): except RuntimeError as exc: log(str(exc)) self._blocking_logout() - self.network_access_manager.finished.disconnect(self._request_done) - self.request_finished.emit() - # TODO: Define self.parsed_reply - self.parsed_reply = parse_network_reply(self._final_reply) + # self.network_access_manager.finished.disconnect(self._request_done) + # self.request_finished.emit() + # self.parsed_reply = parse_network_reply(self._final_reply) result = self.parsed_reply.qt_error is None else: result = False @@ -317,6 +458,17 @@ def run(self): result = False return result + # def finished(self, result: bool): + # self.network_access_manager.finished.disconnect(self._request_done) + # self.parsed_reply = parse_network_reply(self._final_reply) + # if not result: + # self.api_client.error_received.emit( + # self.parsed_reply.qt_error, + # self.parsed_reply.http_status_code, + # self.parsed_reply.http_status_reason + # ) + # self.request_finished.emit() + def _request_done(self, qgis_reply: qgis.core.QgsNetworkReplyContent): """Handle finished network requests @@ -332,10 +484,10 @@ def _request_done(self, qgis_reply: qgis.core.QgsNetworkReplyContent): """ - parsed_reply = parse_network_reply(qgis_reply) + self.parsed_reply = parse_network_reply(qgis_reply) log(f"requested_url: {qgis_reply.request().url().toString()}") - log(f"http_status_code: {parsed_reply.http_status_code}") - log(f"qt_error: {parsed_reply.qt_error}") + log(f"http_status_code: {self.parsed_reply.http_status_code}") + log(f"qt_error: {self.parsed_reply.qt_error}") found_matched_reply = False if self._first_login_reply is not None: if base.reply_matches(qgis_reply, self._first_login_reply): @@ -368,7 +520,6 @@ def _blocking_get_authenticated_reply( self, ) -> typing.Optional[ET.Element]: result = super()._blocking_get_authenticated_reply() - self.parsed_reply = parse_network_reply(self._final_reply) if result: decoded = self._final_reply.readAll().data().decode("utf-8") decoded_element = ET.fromstring(decoded) @@ -377,54 +528,54 @@ def _blocking_get_authenticated_reply( record = None return record - def _blocking_get_layer_detail_v1_api( - self, layer_title: str - ) -> typing.Optional[typing.Dict]: - layer_detail_url = "?".join( - ( - f"{self.base_url}/api/layers/", - urllib.parse.urlencode({"title": layer_title}), - ) - ) - request = QtNetwork.QNetworkRequest(QtCore.QUrl(layer_detail_url)) - auth_manager = qgis.core.QgsApplication.authManager() - auth_manager.updateNetworkRequest(request, self.authcfg) - with base.wait_for_signal(self.layer_detail_api_v1_parsed, self.TIMEOUT): - self._layer_detail_api_v1_reply = self.network_access_manager.get(request) - if self._layer_detail_api_v1_reply.error() == QtNetwork.QNetworkReply.NoError: - raw_layer_detail = self._layer_detail_api_v1_reply.readAll() - layer_detail_response = json.loads(raw_layer_detail.data().decode()) - try: - result = layer_detail_response["objects"][0] - except (KeyError, IndexError): - raise IOError(f"Received unexpected API response for {layer_title!r}") - else: - result = None - return result - - def _blocking_get_style_detail(self, style_uri: str) -> models.BriefGeonodeStyle: - request = QtNetwork.QNetworkRequest(QtCore.QUrl(f"{self.base_url}{style_uri}")) - auth_manager = qgis.core.QgsApplication.authManager() - auth_manager.updateNetworkRequest(request, self.authcfg) - with base.wait_for_signal(self.layer_style_parsed, self.TIMEOUT): - self._layer_style_reply = self.network_access_manager.get(request) - if self._layer_style_reply.error() == QtNetwork.QNetworkReply.NoError: - raw_style_detail = self._layer_style_reply.readAll() - style_detail = json.loads(raw_style_detail.data().decode()) - sld_path = urllib.parse.urlparse(style_detail["sld_url"]).path - result = models.BriefGeonodeStyle( - name=style_detail["name"], - sld_url=f"{self.base_url}{sld_path}", - ) - else: - parsed_reply = parse_network_reply(self._layer_style_reply) - msg = ( - f"Received an error retrieving style detail: {parsed_reply.qt_error} - " - f"{parsed_reply.http_status_code} - {parsed_reply.http_status_reason} " - f"- {self._layer_style_reply.readAll()}" - ) - raise RuntimeError(msg) - return result + # def _blocking_get_layer_detail_v1_api( + # self, layer_title: str + # ) -> typing.Optional[typing.Dict]: + # layer_detail_url = "?".join( + # ( + # f"{self.base_url}/api/layers/", + # urllib.parse.urlencode({"title": layer_title}), + # ) + # ) + # request = QtNetwork.QNetworkRequest(QtCore.QUrl(layer_detail_url)) + # auth_manager = qgis.core.QgsApplication.authManager() + # auth_manager.updateNetworkRequest(request, self.authcfg) + # with base.wait_for_signal(self.layer_detail_api_v1_parsed, self.TIMEOUT): + # self._layer_detail_api_v1_reply = self.network_access_manager.get(request) + # if self._layer_detail_api_v1_reply.error() == QtNetwork.QNetworkReply.NoError: + # raw_layer_detail = self._layer_detail_api_v1_reply.readAll() + # layer_detail_response = json.loads(raw_layer_detail.data().decode()) + # try: + # result = layer_detail_response["objects"][0] + # except (KeyError, IndexError): + # raise IOError(f"Received unexpected API response for {layer_title!r}") + # else: + # result = None + # return result + + # def _blocking_get_style_detail(self, style_uri: str) -> models.BriefGeonodeStyle: + # request = QtNetwork.QNetworkRequest(QtCore.QUrl(f"{self.base_url}{style_uri}")) + # auth_manager = qgis.core.QgsApplication.authManager() + # auth_manager.updateNetworkRequest(request, self.authcfg) + # with base.wait_for_signal(self.layer_style_parsed, self.TIMEOUT): + # self._layer_style_reply = self.network_access_manager.get(request) + # if self._layer_style_reply.error() == QtNetwork.QNetworkReply.NoError: + # raw_style_detail = self._layer_style_reply.readAll() + # style_detail = json.loads(raw_style_detail.data().decode()) + # sld_path = urllib.parse.urlparse(style_detail["sld_url"]).path + # result = models.BriefGeonodeStyle( + # name=style_detail["name"], + # sld_url=f"{self.base_url}{sld_path}", + # ) + # else: + # parsed_reply = parse_network_reply(self._layer_style_reply) + # msg = ( + # f"Received an error retrieving style detail: {parsed_reply.qt_error} - " + # f"{parsed_reply.http_status_code} - {parsed_reply.http_status_reason} " + # f"- {self._layer_style_reply.readAll()}" + # ) + # raise RuntimeError(msg) + # return result class Csw202Namespace(enum.Enum): @@ -592,13 +743,14 @@ def get_layers( self.base_url, self.username, self.password, + self, request=request, request_payload=request_payload, authcfg=self.auth_config, ) else: self.network_fetcher_task = base.NetworkFetcherTask( - request, request_payload=request_payload, authcfg=self.auth_config + self, request, request_payload=request_payload, authcfg=self.auth_config ) self.network_fetcher_task.request_finished.connect( partial(self.handle_layer_list, params) @@ -606,13 +758,22 @@ def get_layers( qgis.core.QgsApplication.taskManager().addTask(self.network_fetcher_task) def get_layer_detail(self, id_: typing.Union[int, uuid.UUID]): - self.network_fetcher_task = GeoNodeLegacyAuthenticatedLayerDetailFetcherTask( - self.base_url, - self.username, - self.password, - QtNetwork.QNetworkRequest(self.get_layer_detail_url_endpoint(id_)), - authcfg=self.auth_config, - ) + request = QtNetwork.QNetworkRequest(self.get_layer_detail_url_endpoint(id_)) + if self.username is not None: + self.network_fetcher_task = ( + GeoNodeLegacyAuthenticatedLayerDetailFetcherTask( + self, + self.base_url, + self.username, + self.password, + request, + authcfg=self.auth_config, + ) + ) + else: + self.network_fetcher_task = GeoNodeLegacyLayerDetailFetcher( + self.base_url, self, request + ) self.network_fetcher_task.request_finished.connect( partial(self.handle_layer_detail) ) @@ -627,13 +788,16 @@ def handle_layer_list( original_search_params: models.GeonodeApiSearchParameters, ): log(f"inside handle_layer_list") - deserialized = self.deserialize_response_contents( - self.network_fetcher_task.reply_content - ) layers = [] - search_results = deserialized.find( - f"{{{Csw202Namespace.CSW.value}}}SearchResults" - ) + if self.network_fetcher_task.parsed_reply.qt_error is None: + deserialized = self.deserialize_response_contents( + self.network_fetcher_task.reply_content + ) + search_results = deserialized.find( + f"{{{Csw202Namespace.CSW.value}}}SearchResults" + ) + else: + search_results = None if search_results is not None: total = int(search_results.attrib["numberOfRecordsMatched"]) next_record = int(search_results.attrib["nextRecord"]) @@ -692,7 +856,10 @@ def handle_layer_detail(self): """ - self.network_fetcher_task: GeoNodeLegacyAuthenticatedLayerDetailFetcherTask + self.network_fetcher_task: typing.Union[ + GeoNodeLegacyLayerDetailFetcher, + GeoNodeLegacyAuthenticatedLayerDetailFetcherTask, + ] layer = get_geonode_resource( self.network_fetcher_task.reply_content.parsed_csw_record, self.base_url, diff --git a/src/qgis_geonode/gui/search_result_widget.py b/src/qgis_geonode/gui/search_result_widget.py index 2282fe25..f28ad998 100644 --- a/src/qgis_geonode/gui/search_result_widget.py +++ b/src/qgis_geonode/gui/search_result_widget.py @@ -134,7 +134,8 @@ def load_thumbnail(self): log(f"thumbnail URL: {self.brief_resource.thumbnail_url}") self.thumbnail_fetcher_task = base.NetworkFetcherTask( - QtNetwork.QNetworkRequest(QtCore.QUrl(self.brief_resource.thumbnail_url)) + self.api_client, + QtNetwork.QNetworkRequest(QtCore.QUrl(self.brief_resource.thumbnail_url)), ) self.thumbnail_fetcher_task.request_finished.connect( self.handle_thumbnail_response