Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial libsoup async implementation #236

Merged
merged 17 commits into from
Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dialect/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ sources = [
'window.py',
'preferences.py',
'lang_selector.py',
'session.py',
'settings.py',
'shortcuts.py',
]
Expand Down
120 changes: 68 additions & 52 deletions dialect/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
import threading
from gettext import gettext as _

from gi.repository import Adw, Gio, GLib, GObject, Gtk
from gi.repository import Adw, Gio, GLib, GObject, Gtk, Soup

from dialect.define import RES_PATH
from dialect.session import Session
from dialect.settings import Settings
from dialect.translators import TRANSLATORS
from dialect.tts import TTS
Expand Down Expand Up @@ -161,7 +162,7 @@ def _on_settings_changed(self, _settings, key):
if key == 'instance-url' and TRANSLATORS[backend].supported_features['change-instance']:
Settings.get().reset_src_langs()
Settings.get().reset_dest_langs()
self.parent.change_backends(backend)
self.parent.reload_backends()

def _toggle_dark_mode(self, switch, _active):
active = switch.get_active()
Expand Down Expand Up @@ -197,7 +198,7 @@ def _switch_backends(self, row, _value):
backend = self.backend_model[row.get_selected()].name
Settings.get().active_translator = backend
self.__check_instance_or_api_key_support()
self.parent.change_backends(backend)
self.parent.reload_backends()

def _on_backend_loading(self, window, _value):
self.backend.set_sensitive(not window.get_property('backend-loading'))
Expand All @@ -209,19 +210,75 @@ def _on_edit_backend_instance(self, _button):
self.backend_instance.set_text(Settings.get().instance_url)

def _on_save_backend_instance(self, _button):
def on_finished():
self.backend.set_sensitive(True)
self.backend_instance_row.set_sensitive(True)
self.api_key_row.set_sensitive(True)
self.backend_instance_save.set_child(self.instance_save_image)
self.backend_instance_label.set_label(Settings.get().instance_url)
self.instance_save_spinner.stop()

def on_validation_response(data):
backend = Settings.get().active_translator
valid = TRANSLATORS[backend].validate_instance(data)
if valid:
Settings.get().instance_url = self.new_instance_url
self.backend_instance.get_style_context().remove_class('error')
self.backend_instance_stack.set_visible_child_name('view')
# self.error_popover.popdown()
else:
self.backend_instance.get_style_context().add_class('error')
error_text = _('Not a valid {backend} instance')
error_text = error_text.format(backend=TRANSLATORS[backend].prettyname)
self.error_label.set_label(error_text)
# self.error_popover.popup()
self.api_key_row.set_visible(False)
self.api_key_label.set_label('None')

def on_settings_response(data):
backend = Settings.get().active_translator
settings = TRANSLATORS[backend].get_instance_settings(data)
if settings['api-key-supported']:
Settings.get().reset_api_key()
self.api_key_row.set_visible(True)
self.api_key_label.set_label(Settings.get().api_key or 'None')
else:
self.api_key_row.set_visible(False)

old_value = Settings.get().instance_url
new_value = self.backend_instance.get_text()

url = re.compile(r'https?://(www\.)?')
new_value = url.sub('', new_value).strip().strip('/')
self.new_instance_url = url.sub('', new_value).strip().strip('/')

if new_value != old_value:
# Validate
threading.Thread(
target=self.__validate_new_backend_instance,
args=[new_value],
daemon=True
).start()
# Validate
if self.new_instance_url != old_value:
# Progress feedback
self.backend.set_sensitive(False)
self.backend_instance_row.set_sensitive(False)
self.api_key_row.set_sensitive(False)
self.backend_instance_save.set_child(self.instance_save_spinner)
self.instance_save_spinner.start()

backend = Settings.get().active_translator
validation_url = TRANSLATORS[backend].format_instance_url(
rafaelmardojai marked this conversation as resolved.
Show resolved Hide resolved
self.new_instance_url,
TRANSLATORS[backend].validation_path
)
settings_url = TRANSLATORS[backend].format_instance_url(
self.new_instance_url,
TRANSLATORS[backend].settings_path
)
validation_message = Soup.Message.new('GET', validation_url)
settings_message = Soup.Message.new('GET', settings_url)

Session.get().multiple(
[
[validation_message, on_validation_response],
[settings_message, on_settings_response]
],
on_finished
)
else:
self.backend_instance_stack.set_visible_child_name('view')

Expand Down Expand Up @@ -271,47 +328,6 @@ def __check_instance_or_api_key_support(self):
else:
self.api_key_row.set_visible(False)

def __validate_new_backend_instance(self, url):
def spinner_start():
self.backend.set_sensitive(False)
self.backend_instance_row.set_sensitive(False)
self.api_key_row.set_sensitive(False)
self.backend_instance_save.set_child(self.instance_save_spinner)
self.instance_save_spinner.start()

def spinner_end():
self.backend.set_sensitive(True)
self.backend_instance_row.set_sensitive(True)
self.api_key_row.set_sensitive(True)
self.backend_instance_save.set_child(self.instance_save_image)
self.backend_instance_label.set_label(Settings.get().instance_url)
self.instance_save_spinner.stop()

GLib.idle_add(spinner_start)
backend = Settings.get().active_translator
result = TRANSLATORS[backend].validate_instance_url(url)
if result['validation-success']:
Settings.get().instance_url = url
GLib.idle_add(self.backend_instance.get_style_context().remove_class, 'error')
GLib.idle_add(self.backend_instance_stack.set_visible_child_name, 'view')
# GLib.idle_add(self.error_popover.popdown)
if result['api-key-supported']:
Settings.get().reset_api_key()
self.api_key_row.set_visible(True)
self.api_key_label.set_label(Settings.get().api_key or 'None')
else:
self.api_key_row.set_visible(False)
else:
GLib.idle_add(self.backend_instance.get_style_context().add_class, 'error')
error_text = _('Not a valid {backend} instance')
error_text = error_text.format(backend=TRANSLATORS[backend].prettyname)
GLib.idle_add(self.error_label.set_label, error_text)
# GLib.idle_add(self.error_popover.popup)
self.api_key_row.set_visible(False)
self.api_key_label.set_label('None')

GLib.idle_add(spinner_end)

def __validate_new_api_key(self, api_key):
def spinner_start():
self.backend.set_sensitive(False)
Expand Down
70 changes: 70 additions & 0 deletions dialect/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright 2021 Mufeed Ali
# Copyright 2021 Rafael Mardojai CM
# SPDX-License-Identifier: GPL-3.0-or-later

import json
import logging

from gi.repository import GLib, Soup

class Session(Soup.Session):
"""
Dialect soup sessions handler
"""

instance = None

def __init__(self):
Soup.Session.__init__(self)

@staticmethod
def new():
"""Create a new instance of Session."""
s_session = Soup.Session()
s_session.__class__ = Session
return s_session

@staticmethod
def get():
"""Return an active instance of Session."""
if Session.instance is None:
Session.instance = Session.new()
return Session.instance

@staticmethod
def read_response(response):
response_data = None
try:
response_data = json.loads(
response.get_data()
) if response else {}
except Exception as exc:
logging.warning(type(exc))
return response_data

@staticmethod
def encode_data(data):
data_glib_bytes = None
try:
data_bytes = json.dumps(data).encode('utf-8')
data_glib_bytes = GLib.Bytes.new(data_bytes)
except Exception as exc:
logging.warning(type(exc))
return data_glib_bytes

def multiple(self, messages, callback):
"""Keep track of multiple async operations."""
def on_task_response(session, result, message_callback):
response = session.send_and_read_finish(result)
data = Session.get().read_response(response)
messages.pop()
message_callback(data)

# If all tasks are done, run main callback
if len(messages) == 0:
callback()

for msg in messages:
# msg[0]: Soup.Message
# msg[1]: message callback
self.send_and_read_async(msg[0], 0, None, on_task_response, msg[1])
29 changes: 25 additions & 4 deletions dialect/translators/basetrans.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,43 @@ class TranslatorBase:
src_langs = ['en', 'fr', 'es', 'de']
dest_langs = ['fr', 'es', 'de', 'en']

validation_path = ''
settings_path = ''

@staticmethod
def format_instance_url(url, path, http=False):
protocol = 'https://'
if url.startswith('localhost:') or http:
protocol = 'http://'

return protocol + url + path

@staticmethod
def validate_instance_url(url):
def validate_instance(data):
pass

@staticmethod
def get_instance_settings(data):
pass

@staticmethod
def validate_api_key(api_key, url=None):
pass

def detect(self, src_text):
def format_detection(self, text):
pass

def get_detect(self, data):
return None

def suggest(self, suggestion):
pass

def translate(self, src_text, src, dest):
return False
def format_translation(self, text, src, dest):
pass

def get_translation(self, data):
pass


class TranslationError(Exception):
Expand Down
Loading