<div dir="ltr"><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">> Of course it looks great, I just want to have a try tomorrow on a modem to double check it.<br><br></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">Double checked, all green. Thanks a lot!<br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On 4 September 2017 at 18:41, Carlo Lobrano <span dir="ltr"><<a href="mailto:c.lobrano@gmail.com" target="_blank">c.lobrano@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">Of course it looks great, I just want to have a try tomorrow on a modem to double check it.<br></div></div><div class="HOEnZb"><div class="h5"><div class="gmail_extra"><br><div class="gmail_quote">On 4 September 2017 at 18:29, Carlo Lobrano <span dir="ltr"><<a href="mailto:c.lobrano@gmail.com" target="_blank">c.lobrano@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><span><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">> Using
its own GTask ends up requiring the extra _ready() method, but that<br>
> makes the flow much more clear and consistent I think.</div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><br></div></span><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">yeah, I agree too</div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><br></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><span>> I've pushed your patch to the "clobrano/qss-unsolicited-powe<wbr>r" branch<br>> in my github repo:<br>> <a href="https://github.com/aleksander0m/ModemManager/commits/clobrano/qss-unsolicited-power" target="_blank">https://github.com/aleksander0<wbr>m/ModemManager/commits/<wbr>clobrano/qss-unsolicited-power</a><br>> <br>> I've fixed up several whitespace issues in your patch, and then pushed<br>> several other commits on top. Could you give them a look to make sure<br>> they're ok?<br><br></span>sure, I'm going to check it, thanks for the explanation and the fixes!<br><br><br>
</div></div><div class="m_-3560426087727553143HOEnZb"><div class="m_-3560426087727553143h5"><div class="gmail_extra"><br><div class="gmail_quote">On 4 September 2017 at 18:09, Aleksander Morgado <span dir="ltr"><<a href="mailto:aleksander@aleksander.es" target="_blank">aleksander@aleksander.es</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hey!<br>
<span><br>
On Mon, Sep 4, 2017 at 8:13 AM, Carlo Lobrano <<a href="mailto:c.lobrano@gmail.com" target="_blank">c.lobrano@gmail.com</a>> wrote:<br>
> When transitioning between power-low and power-on modes, Telit modems<br>
> switch the SIM off/on, which leads to the emission of #QSS unsolicited not<br>
> related to actual SIM swaps.<br>
><br>
> To handle this #QSS unsolicited, this patch:<br>
><br>
> * disables reacting on #QSS unsolicited when modem_power_down is received<br>
> * implements modem_after_power_up that:<br>
> - checks whether the SIM has been changed, matching cached SIM<br>
> Identifier with the value in the current SIM. If SIM Identifier,<br>
> is different, sim hot swap ports detected is called.<br>
> - re-enables reacting on #QSS unsolicited<br>
><br>
> ---<br>
><br>
> Hi Aleksander,<br>
><br>
> I should have fixed all the problems, but I'm still not sure about<br>
> base-sim's implementation of mm_base_sim_load_sim_identifie<wbr>r and<br>
> mm_base_sim_load_sim_identifie<wbr>r_finish.<br>
><br>
> I saw that, generally, similar functions have their own ..._ready counterpart,<br>
> and maybe a GTask that takes care of caller's callback,<br>
> but I think I shouldn't need it and let the caller's callback do all the<br>
> job. However, as I said, I'm not really sure this is right.<br>
><br>
<br>
</span>What you did is technically right, although I always prefer to avoid<br>
putting much logic in the finish() method (as it's really duplicating<br>
logic), so I always have the async method manage its own GTask. In<br>
this way, the finish() method doesn't need to do extra checks like "do<br>
I need to do special things based on how the GTask was created?" Using<br>
its own GTask ends up requiring the extra _ready() method, but that<br>
makes the flow much more clear and consistent I think.<br>
<br>
I've pushed your patch to the "clobrano/qss-unsolicited-powe<wbr>r" branch<br>
in my github repo:<br>
<a href="https://github.com/aleksander0m/ModemManager/commits/clobrano/qss-unsolicited-power" rel="noreferrer" target="_blank">https://github.com/aleksander0<wbr>m/ModemManager/commits/clobran<wbr>o/qss-unsolicited-power</a><br>
<br>
I've fixed up several whitespace issues in your patch, and then pushed<br>
several other commits on top. Could you give them a look to make sure<br>
they're ok? The last patch does the own-GTask think discussed above.<br>
<div class="m_-3560426087727553143m_4760795883497439400HOEnZb"><div class="m_-3560426087727553143m_4760795883497439400h5"><br>
<br>
> ---<br>
> plugins/telit/mm-broadband-mod<wbr>em-telit.c | 181 ++++++++++++++++++++++++++++++<wbr>-<br>
> src/mm-base-sim.c | 51 +++++++++<br>
> src/mm-base-sim.h | 80 +++++++-------<br>
> 3 files changed, 271 insertions(+), 41 deletions(-)<br>
><br>
> diff --git a/plugins/telit/mm-broadband-m<wbr>odem-telit.c b/plugins/telit/mm-broadband-m<wbr>odem-telit.c<br>
> index a9fc5f0..b30e7c9 100644<br>
> --- a/plugins/telit/mm-broadband-m<wbr>odem-telit.c<br>
> +++ b/plugins/telit/mm-broadband-m<wbr>odem-telit.c<br>
> @@ -58,6 +58,7 @@ struct _MMBroadbandModemTelitPrivate {<br>
> MMTelitCsimLockState csim_lock_state;<br>
> GTask *csim_lock_task;<br>
> guint csim_lock_timeout_id;<br>
> + gboolean parse_qss;<br>
> };<br>
><br>
> /*****************************<wbr>******************************<wbr>******************/<br>
> @@ -151,10 +152,15 @@ telit_qss_unsolicited_handler (MMPortSerialAt *port,<br>
> }<br>
><br>
> if (cur_qss_status != prev_qss_status)<br>
> - mm_dbg ("QSS handler: status changed '%s -> %s",<br>
> + mm_dbg ("QSS handler: status changed '%s -> %s'",<br>
> mm_telit_qss_status_get_string (prev_qss_status),<br>
> mm_telit_qss_status_get_string (cur_qss_status));<br>
><br>
> + if (self->priv->parse_qss == FALSE) {<br>
> + mm_dbg ("QSS: message ignored");<br>
> + return;<br>
> + }<br>
> +<br>
> if ((prev_qss_status == QSS_STATUS_SIM_REMOVED && cur_qss_status != QSS_STATUS_SIM_REMOVED) ||<br>
> (prev_qss_status > QSS_STATUS_SIM_REMOVED && cur_qss_status == QSS_STATUS_SIM_REMOVED)) {<br>
> mm_info ("QSS handler: SIM swap detected");<br>
> @@ -903,14 +909,175 @@ modem_load_unlock_retries (MMIfaceModem *self,<br>
> }<br>
><br>
> /*****************************<wbr>******************************<wbr>******************/<br>
> +/* Modem after power up (Modem interface) */<br>
> +typedef enum {<br>
> + AFTER_POWER_UP_STEP_FIRST,<br>
> + AFTER_POWER_UP_STEP_GET_SIM_ID<wbr>ENTIFIER,<br>
> + AFTER_POWER_UP_STEP_ENABLE_QSS<wbr>_PARSING,<br>
> + AFTER_POWER_UP_STEP_LAST<br>
> +} ModemAfterPowerUpStep;<br>
> +<br>
> +typedef struct {<br>
> + gboolean has_sim_changed;<br>
> + guint retries;<br>
> + ModemAfterPowerUpStep step;<br>
> +} AfterPowerUpContext;<br>
> +<br>
> +static void after_power_up_step (GTask *task);<br>
> +<br>
> +static gboolean<br>
> +modem_after_power_up_finish (MMIfaceModem *self,<br>
> + GAsyncResult *res,<br>
> + GError **error)<br>
> +{<br>
> + return g_task_propagate_boolean (G_TASK (res), error);<br>
> +}<br>
> +<br>
> +static void<br>
> +load_sim_identifier_ready (MMBaseSim *sim,<br>
> + GAsyncResult *res,<br>
> + GTask *task)<br>
> +{<br>
> + AfterPowerUpContext *ctx;<br>
> + GError *error = NULL;<br>
> + gchar *current_simid;<br>
> + gchar *cached_simid;<br>
> +<br>
> + ctx = g_task_get_task_data (task);<br>
> +<br>
> + cached_simid = (gchar *)mm_gdbus_sim_get_sim_identif<wbr>ier (MM_GDBUS_SIM (sim));<br>
> + current_simid = mm_base_sim_load_sim_identifie<wbr>r_finish (sim, res, &error);<br>
> +<br>
> + if (error) {<br>
> + if (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) {<br>
> + mm_warn ("could not load SIM identifier: %s", error->message);<br>
> + g_clear_error (&error);<br>
> + goto out;<br>
> + }<br>
> +<br>
> + if (ctx->retries-- > 0) {<br>
> + mm_warn ("could not load SIM identifier: %s (%d retries left)",<br>
> + error->message, ctx->retries);<br>
> + g_clear_error (&error);<br>
> + g_timeout_add_seconds (1, (GSourceFunc) after_power_up_step, task);<br>
> + return;<br>
> + }<br>
> +<br>
> + mm_warn ("could not load SIM identifier: %s", error->message);<br>
> + g_clear_error (&error);<br>
> + goto out;<br>
> + }<br>
> +<br>
> + if (g_strcmp0 (current_simid, cached_simid) != 0) {<br>
> + mm_warn ("sim identifier has changed: possible SIM swap during power down/low");<br>
> + ctx->has_sim_changed = TRUE;<br>
> + }<br>
> +<br>
> +out:<br>
> + g_free (current_simid);<br>
> + ctx->step++;<br>
> + after_power_up_step (task);<br>
> +}<br>
> +<br>
> +static void<br>
> +after_power_up_step (GTask *task)<br>
> +{<br>
> + AfterPowerUpContext *ctx;<br>
> + MMBroadbandModemTelit *self;<br>
> + MMBaseSim *sim = NULL;<br>
> +<br>
> + ctx = g_task_get_task_data (task);<br>
> + self = g_task_get_source_object (task);<br>
> +<br>
> + g_object_get (MM_BROADBAND_MODEM_TELIT (self),<br>
> + MM_IFACE_MODEM_SIM, &sim,<br>
> + NULL);<br>
> + if (!sim) {<br>
> + g_task_return_new_error (task,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_FAILED,<br>
> + "could not acquire sim object");<br>
> + return;<br>
> + }<br>
> +<br>
> + switch (ctx->step) {<br>
> + case AFTER_POWER_UP_STEP_FIRST:<br>
> + /* Fall back on next step */<br>
> + ctx->step++;<br>
> + case AFTER_POWER_UP_STEP_GET_SIM_ID<wbr>ENTIFIER:<br>
> + mm_base_sim_load_sim_identifie<wbr>r (sim,<br>
> + (GAsyncReadyCallback)load_sim<wbr>_identifier_ready,<br>
> + task);<br>
> + g_object_unref (sim);<br>
> + return;<br>
> + case AFTER_POWER_UP_STEP_ENABLE_QSS<wbr>_PARSING:<br>
> + mm_dbg ("Stop ignoring #QSS");<br>
> + self->priv->parse_qss = TRUE;<br>
> +<br>
> + /* Fall back on next step */<br>
> + ctx->step++;<br>
> + case AFTER_POWER_UP_STEP_LAST:<br>
> + if (ctx->has_sim_changed)<br>
> + mm_broadband_modem_update_sim_<wbr>hot_swap_detected (MM_BROADBAND_MODEM (self));<br>
> +<br>
> + g_task_return_boolean (task, TRUE);<br>
> + g_object_unref (task);<br>
> + break;<br>
> + default:<br>
> + g_assert_not_reached ();<br>
> + }<br>
> +}<br>
> +<br>
> +static void<br>
> +modem_after_power_up (MMIfaceModem *self,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data)<br>
> +{<br>
> + GTask *task;<br>
> + AfterPowerUpContext *ctx;<br>
> +<br>
> + ctx = g_new0 (AfterPowerUpContext, 1);<br>
> + ctx->step = AFTER_POWER_UP_STEP_FIRST;<br>
> + ctx->has_sim_changed = FALSE;<br>
> + ctx->retries = 3;<br>
> +<br>
> + task = g_task_new (self, NULL, callback, user_data);<br>
> + g_task_set_task_data (task, ctx, (GDestroyNotify) g_free);<br>
> +<br>
> + after_power_up_step (task);<br>
> +}<br>
> +<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> /* Modem power down (Modem interface) */<br>
><br>
> +static void<br>
> +telit_modem_power_down_ready (MMBaseModem *self,<br>
> + GAsyncResult *res,<br>
> + GTask *task)<br>
> +{<br>
> + GError *error = NULL;<br>
> +<br>
> + if (mm_base_modem_at_command_fini<wbr>sh (self, res, &error)) {<br>
> + mm_dbg ("Ignore #QSS unsolicited during power down/low");<br>
> + MM_BROADBAND_MODEM_TELIT (self)->priv->parse_qss = FALSE;<br>
> + }<br>
> +<br>
> + if (error) {<br>
> + mm_err ("modem power down: %s", error->message);<br>
> + g_clear_error (&error);<br>
> + }<br>
> +<br>
> + g_task_return_boolean (task, TRUE);<br>
> + g_object_unref (task);<br>
> +}<br>
> +<br>
> static gboolean<br>
> modem_power_down_finish (MMIfaceModem *self,<br>
> GAsyncResult *res,<br>
> GError **error)<br>
> {<br>
> - return !!mm_base_modem_at_command_fin<wbr>ish (MM_BASE_MODEM (self), res, error);<br>
> + return g_task_propagate_boolean (G_TASK (res), error);<br>
> }<br>
><br>
> static void<br>
> @@ -918,12 +1085,15 @@ modem_power_down (MMIfaceModem *self,<br>
> GAsyncReadyCallback callback,<br>
> gpointer user_data)<br>
> {<br>
> + GTask *task;<br>
> +<br>
> + task = g_task_new (self, NULL, callback, user_data);<br>
> mm_base_modem_at_command (MM_BASE_MODEM (self),<br>
> "+CFUN=4",<br>
> 20,<br>
> FALSE,<br>
> - callback,<br>
> - user_data);<br>
> + (GAsyncReadyCallback) telit_modem_power_down_ready,<br>
> + task);<br>
> }<br>
><br>
> /*****************************<wbr>******************************<wbr>******************/<br>
> @@ -1457,6 +1627,7 @@ mm_broadband_modem_telit_init (MMBroadbandModemTelit *self)<br>
> self->priv->csim_lock_support = FEATURE_SUPPORT_UNKNOWN;<br>
> self->priv->csim_lock_state = CSIM_LOCK_STATE_UNKNOWN;<br>
> self->priv->qss_status = QSS_STATUS_UNKNOWN;<br>
> + self->priv->parse_qss = TRUE;<br>
> }<br>
><br>
> static void<br>
> @@ -1474,6 +1645,8 @@ iface_modem_init (MMIfaceModem *iface)<br>
> iface->load_unlock_retries = modem_load_unlock_retries;<br>
> iface->reset = modem_reset;<br>
> iface->reset_finish = modem_reset_finish;<br>
> + iface->modem_after_power_up = modem_after_power_up;<br>
> + iface->modem_after_power_up_fi<wbr>nish = modem_after_power_up_finish;<br>
> iface->modem_power_down = modem_power_down;<br>
> iface->modem_power_down_finish = modem_power_down_finish;<br>
> iface->load_access_technologie<wbr>s = load_access_technologies;<br>
> diff --git a/src/mm-base-sim.c b/src/mm-base-sim.c<br>
> index f3525e0..d03f342 100644<br>
> --- a/src/mm-base-sim.c<br>
> +++ b/src/mm-base-sim.c<br>
> @@ -701,6 +701,57 @@ mm_base_sim_send_puk (MMBaseSim *self,<br>
> }<br>
><br>
> /*****************************<wbr>******************************<wbr>******************/<br>
> +/* LOAD SIM IDENTIFIER */<br>
> +gchar *<br>
> +mm_base_sim_load_sim_identifi<wbr>er_finish (MMBaseSim *self,<br>
> + GAsyncResult *res,<br>
> + GError **error) {<br>
> + GError *inner_error = NULL;<br>
> + gchar *simid;<br>
> +<br>
> + if (g_async_result_is_tagged (res, mm_base_sim_load_sim_identifie<wbr>r) ||<br>
> + !MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier_fi<wbr>nish) {<br>
> + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,<br>
> + "not implemented");<br>
> + g_propagate_error (error, inner_error);<br>
> + return NULL;<br>
> + }<br>
> +<br>
> + simid = MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier_fi<wbr>nish (self, res, &inner_error);<br>
> + if (inner_error) {<br>
> + g_propagate_error (error, inner_error);<br>
> + return NULL;<br>
> + }<br>
> +<br>
> + return simid;<br>
> +}<br>
> +<br>
> +void<br>
> +mm_base_sim_load_sim_identifi<wbr>er (MMBaseSim *self,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data)<br>
> +{<br>
> + if (!MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier &&<br>
> + !MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier_fi<wbr>nish) {<br>
> + g_task_report_new_error (self,<br>
> + callback,<br>
> + user_data,<br>
> + mm_base_sim_load_sim_identifi<wbr>er,<br>
> + MM_CORE_ERROR,<br>
> + MM_CORE_ERROR_UNSUPPORTED,<br>
> + "not implemented");<br>
> + return;<br>
> + }<br>
> +<br>
> + MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier (<br>
> + self,<br>
> + (GAsyncReadyCallback)callback,<br>
> + user_data);<br>
> +<br>
> + return;<br>
> +}<br>
> +<br>
> +/****************************<wbr>******************************<wbr>*******************/<br>
> /* SEND PIN (DBus call handling) */<br>
><br>
> typedef struct {<br>
> diff --git a/src/mm-base-sim.h b/src/mm-base-sim.h<br>
> index 1903994..22f02c8 100644<br>
> --- a/src/mm-base-sim.h<br>
> +++ b/src/mm-base-sim.h<br>
> @@ -50,8 +50,7 @@ struct _MMBaseSim {<br>
><br>
> struct _MMBaseSimClass {<br>
> MmGdbusSimSkeletonClass parent;<br>
> -<br>
> - /* Load SIM identifier (async) */<br>
> +/* Load SIM identifier (async) */<br>
> void (* load_sim_identifier) (MMBaseSim *self,<br>
> GAsyncReadyCallback callback,<br>
> gpointer user_data);<br>
> @@ -129,40 +128,47 @@ struct _MMBaseSimClass {<br>
><br>
> GType mm_base_sim_get_type (void);<br>
><br>
> -void mm_base_sim_new (MMBaseModem *modem,<br>
> - GCancellable *cancellable,<br>
> - GAsyncReadyCallback callback,<br>
> - gpointer user_data);<br>
> -MMBaseSim *mm_base_sim_new_finish (GAsyncResult *res,<br>
> - GError **error);<br>
> -<br>
> -void mm_base_sim_initialize (MMBaseSim *self,<br>
> - GCancellable *cancellable,<br>
> - GAsyncReadyCallback callback,<br>
> - gpointer user_data);<br>
> -gboolean mm_base_sim_initialize_finish (MMBaseSim *self,<br>
> - GAsyncResult *result,<br>
> - GError **error);<br>
> -<br>
> -void mm_base_sim_send_pin (MMBaseSim *self,<br>
> - const gchar *pin,<br>
> - GAsyncReadyCallback callback,<br>
> - gpointer user_data);<br>
> -gboolean mm_base_sim_send_pin_finish (MMBaseSim *self,<br>
> - GAsyncResult *res,<br>
> - GError **error);<br>
> -<br>
> -void mm_base_sim_send_puk (MMBaseSim *self,<br>
> - const gchar *puk,<br>
> - const gchar *new_pin,<br>
> - GAsyncReadyCallback callback,<br>
> - gpointer user_data);<br>
> -gboolean mm_base_sim_send_puk_finish (MMBaseSim *self,<br>
> - GAsyncResult *res,<br>
> - GError **error);<br>
> -<br>
> -void mm_base_sim_export (MMBaseSim *self);<br>
> -<br>
> -const gchar *mm_base_sim_get_path (MMBaseSim *sim);<br>
> +void mm_base_sim_new (MMBaseModem *modem,<br>
> + GCancellable *cancellable,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data);<br>
> +MMBaseSim *mm_base_sim_new_finish (GAsyncResult *res,<br>
> + GError **error);<br>
> +<br>
> +void mm_base_sim_initialize (MMBaseSim *self,<br>
> + GCancellable *cancellable,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data);<br>
> +gboolean mm_base_sim_initialize_finish<wbr> (MMBaseSim *self,<br>
> + GAsyncResult *result,<br>
> + GError **error);<br>
> +<br>
> +void mm_base_sim_send_pin (MMBaseSim *self,<br>
> + const gchar *pin,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data);<br>
> +gboolean mm_base_sim_send_pin_finish (MMBaseSim *self,<br>
> + GAsyncResult *res,<br>
> + GError **error);<br>
> +<br>
> +void mm_base_sim_send_puk (MMBaseSim *self,<br>
> + const gchar *puk,<br>
> + const gchar *new_pin,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data);<br>
> +gboolean mm_base_sim_send_puk_finish (MMBaseSim *self,<br>
> + GAsyncResult *res,<br>
> + GError **error);<br>
> +<br>
> +void mm_base_sim_export (MMBaseSim *self);<br>
> +<br>
> +const gchar *mm_base_sim_get_path (MMBaseSim *sim);<br>
> +<br>
> +void mm_base_sim_load_sim_identifie<wbr>r (MMBaseSim *self,<br>
> + GAsyncReadyCallback callback,<br>
> + gpointer user_data);<br>
> +gchar *mm_base_sim_load_sim_identif<wbr>ier_finish (MMBaseSim *self,<br>
> + GAsyncResult *res,<br>
> + GError **error);<br>
><br>
> #endif /* MM_BASE_SIM_H */<br>
> --<br>
> 2.9.3<br>
><br>
</div></div><div class="m_-3560426087727553143m_4760795883497439400HOEnZb"><div class="m_-3560426087727553143m_4760795883497439400h5">> ______________________________<wbr>_________________<br>
> ModemManager-devel mailing list<br>
> <a href="mailto:ModemManager-devel@lists.freedesktop.org" target="_blank">ModemManager-devel@lists.freed<wbr>esktop.org</a><br>
> <a href="https://lists.freedesktop.org/mailman/listinfo/modemmanager-devel" rel="noreferrer" target="_blank">https://lists.freedesktop.org/<wbr>mailman/listinfo/modemmanager-<wbr>devel</a><br>
<br>
<br>
<br>
--<br>
Aleksander<br>
<a href="https://aleksander.es" rel="noreferrer" target="_blank">https://aleksander.es</a><br>
</div></div></blockquote></div><br></div>
</div></div></blockquote></div><br></div>
</div></div></blockquote></div><br></div>