usernameregistrationbox.cpp 20.6 KB
Newer Older
1
/*
2
 *  Copyright (C) 2016-2019 Savoir-faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *  Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */

// GTK+ related
#include <gtk/gtk.h>
#include <glib/gi18n.h>

// LRC
25
#include <api/newaccountmodel.h>
26
#include <namedirectory.h>
27

28
// Jami  Client
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
#include "usernameregistrationbox.h"
#include "utils/models.h"

struct _UsernameRegistrationBox
{
    GtkGrid parent;
};

struct _UsernameRegistrationBoxClass
{
    GtkGridClass parent_class;
};

typedef struct _UsernameRegistrationBoxPrivate UsernameRegistrationBoxPrivate;

struct _UsernameRegistrationBoxPrivate
{
46 47
    AccountInfoPointer const *accountInfo_ = nullptr;
    bool withAccount;
48 49

    //Widgets
50 51
    GtkWidget *label_username;
    GtkWidget *frame_username;
52 53 54 55 56 57 58 59 60 61
    GtkWidget *entry_username;
    GtkWidget *spinner;
    GtkWidget *button_register_username;

    QMetaObject::Connection name_registration_ended;

    //Lookup variables
    QMetaObject::Connection registered_name_found;
    QString* username_waiting_for_lookup_result;
    gint lookup_timeout;
62
    gulong entry_changed;
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96

    gboolean use_blockchain;
    gboolean show_register_button;
};

G_DEFINE_TYPE_WITH_PRIVATE(UsernameRegistrationBox, username_registration_box, GTK_TYPE_GRID);

#define USERNAME_REGISTRATION_BOX_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), USERNAME_REGISTRATION_BOX_TYPE, UsernameRegistrationBoxPrivate))

/* signals */
enum {
    USERNAME_AVAILABILITY_CHANGED,
    USERNAME_REGISTRATION_COMPLETED,
    LAST_SIGNAL
};

static guint username_registration_box_signals[LAST_SIGNAL] = { 0 };

static void
username_registration_box_dispose(GObject *object)
{
    auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(object);

    QObject::disconnect(priv->registered_name_found);
    QObject::disconnect(priv->name_registration_ended);

    if (priv->lookup_timeout) {
        g_source_remove(priv->lookup_timeout);
        priv->lookup_timeout = 0;
    }

    G_OBJECT_CLASS(username_registration_box_parent_class)->dispose(object);
}

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
static void
show_error(UsernameRegistrationBox* view, bool show_error, gchar* tooltip)
{
    auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
    g_return_if_fail(priv);

    GtkStyleContext* context_box;
    if (show_error) {
        context_box = gtk_widget_get_style_context(GTK_WIDGET(priv->entry_username));
        gtk_style_context_add_class(context_box, "box_error");
        context_box = gtk_widget_get_style_context(GTK_WIDGET(priv->spinner));
        gtk_style_context_add_class(context_box, "box_error");
        context_box = gtk_widget_get_style_context(GTK_WIDGET(priv->button_register_username));
        gtk_style_context_add_class(context_box, "box_error");
    } else {
        context_box = gtk_widget_get_style_context(GTK_WIDGET(priv->entry_username));
        gtk_style_context_remove_class(context_box, "box_error");
        context_box = gtk_widget_get_style_context(GTK_WIDGET(priv->spinner));
        gtk_style_context_remove_class(context_box, "box_error");
        context_box = gtk_widget_get_style_context(GTK_WIDGET(priv->button_register_username));
        gtk_style_context_remove_class(context_box, "box_error");
    }

    auto* image = show_error ?
        gtk_image_new_from_icon_name("dialog-error-symbolic", GTK_ICON_SIZE_BUTTON)
        : gtk_image_new_from_icon_name("emblem-ok-symbolic", GTK_ICON_SIZE_BUTTON);
    gtk_button_set_image(GTK_BUTTON(priv->button_register_username), image);
    gtk_widget_set_tooltip_text(priv->button_register_username, tooltip);
}

127 128 129 130 131 132 133 134 135 136
static void
username_registration_box_init(UsernameRegistrationBox *view)
{
    gtk_widget_init_template(GTK_WIDGET(view));

    auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);

    priv->registered_name_found = QObject::connect(
        &NameDirectory::instance(),
        &NameDirectory::registeredNameFound,
137
        [=] (NameDirectory::LookupStatus status, const QString&, const QString& name) {
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
            // g_debug("Name lookup ended");

            if (!priv->use_blockchain)
                return;

            // compare to the current username entry
            const auto username_lookup = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
            if (name.compare(username_lookup) != 0) {
                // assume result is for older lookup
                return;
            }

            //We may now stop the spinner
            gtk_spinner_stop(GTK_SPINNER(priv->spinner));
            gtk_widget_hide(priv->spinner);

154 155 156 157 158 159
            if (priv->show_register_button)
                gtk_widget_show(priv->button_register_username);
            else
                gtk_widget_hide(priv->button_register_username);


160 161 162
            // We don't want to display any icon/label in case of empty lookup
            if (!username_lookup || !*username_lookup) {
                gtk_widget_set_sensitive(priv->button_register_username, FALSE);
163
                show_error(view, false, _("Register this username on the name server"));
164 165 166
                return;
            }

167 168 169 170 171
            switch(status)
            {
                case NameDirectory::LookupStatus::SUCCESS:
                {
                    gtk_widget_set_sensitive(priv->button_register_username, FALSE);
172
                    show_error(view, true, _("Username already taken"));
173 174 175 176 177 178
                    g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
                    break;
                }
                case NameDirectory::LookupStatus::INVALID_NAME:
                {
                    gtk_widget_set_sensitive(priv->button_register_username, FALSE);
179
                    show_error(view, true, _("Invalid username"));
180 181 182 183 184 185
                    g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
                    break;
                }
                case NameDirectory::LookupStatus::NOT_FOUND:
                {
                    gtk_widget_set_sensitive(priv->button_register_username, TRUE);
186
                    show_error(view, false, _("Register this username on the name server"));
187 188 189 190 191 192
                    g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, TRUE);
                    break;
                }
                case NameDirectory::LookupStatus::ERROR:
                {
                    gtk_widget_set_sensitive(priv->button_register_username, FALSE);
193
                    show_error(view, true, _("Lookup unavailable, check your network connection"));
194 195 196 197
                    g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
                    break;
                }
            }
198
        });
199 200 201 202 203 204 205 206
}

static void
username_registration_box_class_init(UsernameRegistrationBoxClass *klass)
{
    G_OBJECT_CLASS(klass)->dispose = username_registration_box_dispose;

    gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
207
                                                "/net/jami/JamiGnome/usernameregistrationbox.ui");
208

209 210
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, label_username);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, frame_username);
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, entry_username);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, spinner);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, button_register_username);

    /* add signals */
    username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED] = g_signal_new("username-availability-changed",
                 G_TYPE_FROM_CLASS(klass),
                 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
                 0,
                 nullptr,
                 nullptr,
                 g_cclosure_marshal_VOID__BOOLEAN,
                 G_TYPE_NONE,
                 1, G_TYPE_BOOLEAN);
    username_registration_box_signals[USERNAME_REGISTRATION_COMPLETED] = g_signal_new("username-registration-completed",
                 G_TYPE_FROM_CLASS(klass),
                 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
                 0,
                 nullptr,
                 nullptr,
                 g_cclosure_marshal_VOID__VOID,
                 G_TYPE_NONE, 0);
}

static gboolean
lookup_username(UsernameRegistrationBox *view)
{
    g_return_val_if_fail(IS_USERNAME_REGISTRATION_BOX(view), G_SOURCE_REMOVE);

    auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);

    const auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));

244 245
    if (priv->accountInfo_) {
        auto prop = (*priv->accountInfo_)->accountModel->getAccountConfig((*priv->accountInfo_)->id);
246
        NameDirectory::instance().lookupName(prop.RingNS.uri.c_str(), username);
247
    } else {
248
        NameDirectory::instance().lookupName(QString(), username);
249
    }
250

251

252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
    priv->lookup_timeout = 0;
    return G_SOURCE_REMOVE;
}

static void
entry_username_changed(UsernameRegistrationBox *view)
{
    g_return_if_fail(IS_USERNAME_REGISTRATION_BOX(view));
    UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);

    auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));

    // cancel any queued lookup
    if (priv->lookup_timeout) {
        g_source_remove(priv->lookup_timeout);
        priv->lookup_timeout = 0;
    }
    gtk_widget_set_sensitive(priv->button_register_username, FALSE);

271
    if (priv->use_blockchain) {
272

273
        show_error(view, false, _("Performing lookup…"));
274 275 276 277 278 279 280 281 282 283
        if (strlen(username) == 0) {
            gtk_widget_hide(priv->spinner);
            gtk_spinner_stop(GTK_SPINNER(priv->spinner));
        } else {
            gtk_widget_show(priv->spinner);
            gtk_spinner_start(GTK_SPINNER(priv->spinner));

            // queue lookup with a 500ms delay
            priv->lookup_timeout = g_timeout_add(500, (GSourceFunc)lookup_username, view);
        }
284
    } else {
285
        // not using blockchain, so don't care about username validity
286 287
        gtk_widget_hide(priv->spinner);
        gtk_spinner_stop(GTK_SPINNER(priv->spinner));
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
    }
}

static void
button_register_username_clicked(G_GNUC_UNUSED GtkButton* button, UsernameRegistrationBox *view)
{
    g_return_if_fail(IS_USERNAME_REGISTRATION_BOX(view));
    UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);

    if (!priv->use_blockchain)
        return;

    const auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
    if (strlen(username) == 0)
    {
        //Error message should be displayed already.
        return;
    }

307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
    auto show_password = true;
    if (*priv->accountInfo_) {
        std::string accountId = (*priv->accountInfo_)->id;
        lrc::api::account::ConfProperties_t props;
        props = (*priv->accountInfo_)->accountModel->getAccountConfig(accountId);
        show_password = props.archiveHasPassword;
    }

    std::string password;
    gint result = GTK_RESPONSE_OK;

    if (show_password) {
        GtkWidget* password_dialog = gtk_message_dialog_new(
            GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))),
            (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
            GTK_MESSAGE_QUESTION,
            GTK_BUTTONS_OK_CANCEL,
            _("Enter the password of your Jami account")
        );

        GtkWidget* entry_password = gtk_entry_new();
        gtk_entry_set_visibility(GTK_ENTRY(entry_password), FALSE);
        gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(password_dialog))), entry_password, FALSE, FALSE, 0);
        gtk_widget_show(entry_password);

        result = gtk_dialog_run(GTK_DIALOG(password_dialog));
        password = gtk_entry_get_text(GTK_ENTRY(entry_password));
        gtk_widget_destroy(password_dialog);
    }
336 337 338 339 340 341 342 343 344 345 346 347 348

    switch(result)
    {
        case GTK_RESPONSE_OK:
        {
            // Show the spinner
            gtk_widget_show(priv->spinner);
            gtk_spinner_start(GTK_SPINNER(priv->spinner));

            //Disable the entry
            gtk_widget_set_sensitive(priv->entry_username, FALSE);
            gtk_widget_set_sensitive(priv->button_register_username, FALSE);

349
            if (!(*priv->accountInfo_)->accountModel->registerName((*priv->accountInfo_)->id, password, username))
350
            {
351 352 353 354 355
                gtk_spinner_stop(GTK_SPINNER(priv->spinner));
                gtk_widget_hide(priv->spinner);
                gtk_widget_set_sensitive(priv->entry_username, TRUE);

                gtk_widget_set_sensitive(priv->button_register_username, TRUE);
356
                show_error(view, false, _("Registration in progress…"));
357
            }
358 359 360 361 362 363 364 365 366
            break;
        }
        case GTK_RESPONSE_CANCEL:
        {
            break;
        }
    }
}

367 368 369 370 371 372 373 374 375 376 377 378 379
static void
username_registration_dialog_error(const char* msg)
{
    g_warning("%s", msg);
    GtkWidget *dialog = gtk_message_dialog_new(NULL,
                            (GtkDialogFlags)(GTK_DIALOG_MODAL),
                            GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
                            "%s", msg);
    gtk_window_set_title(GTK_WINDOW(dialog), _("Name registration Error"));
    gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);
}

380 381 382 383 384
static void
build_view(UsernameRegistrationBox *view, gboolean register_button)
{
    UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);

385 386 387
    std::string registered_name = {};
    if(priv->withAccount) {
        registered_name = (*priv->accountInfo_)->registeredName;
388 389
    }

390
    if (registered_name.empty())
391 392 393
    {
        //Make the entry editable
        g_object_set(G_OBJECT(priv->entry_username), "editable", TRUE, NULL);
394
        priv->entry_changed = g_signal_connect_swapped(priv->entry_username, "changed", G_CALLBACK(entry_username_changed), view);
395 396

        //Show the register button
397
        if (register_button && priv->withAccount)
398 399 400 401 402 403
        {
            gtk_widget_show(priv->button_register_username);
            gtk_widget_set_sensitive(priv->button_register_username, FALSE);
            g_signal_connect(priv->button_register_username, "clicked", G_CALLBACK(button_register_username_clicked), view);
        }

404
        if (priv->withAccount) {
405
            priv->name_registration_ended = QObject::connect(
406 407 408 409
                (*priv->accountInfo_)->accountModel,
                &lrc::api::NewAccountModel::nameRegistrationEnded,
                [=] (const std::string& accountId, lrc::api::account::RegisterNameStatus status, const std::string& name) {
                    if (accountId != (*priv->accountInfo_)->id) return;
410
                    if (name == "") return;
411 412 413 414 415 416
                    gtk_spinner_stop(GTK_SPINNER(priv->spinner));
                    gtk_widget_hide(priv->spinner);
                    gtk_widget_set_sensitive(priv->entry_username, TRUE);

                    switch(status)
                    {
417
                    case lrc::api::account::RegisterNameStatus::SUCCESS:
418 419 420 421 422 423 424
                        {
                            // update the entry to what was registered, just to be sure
                            // don't do more lookups
                            if (priv->entry_changed != 0) {
                                g_signal_handler_disconnect(priv->entry_username, priv->entry_changed);
                                priv->entry_changed = 0;
                            }
425 426 427
                            gtk_label_set_text(GTK_LABEL(priv->label_username), name.c_str());
                            gtk_widget_hide(priv->frame_username);
                            gtk_widget_show(priv->label_username);
428
                            gtk_widget_set_can_focus(priv->label_username, true);
429 430 431
                            g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_REGISTRATION_COMPLETED], 0);
                            break;
                        }
432
                    case lrc::api::account::RegisterNameStatus::INVALID_NAME:
433 434 435
                        gtk_widget_set_sensitive(priv->button_register_username, TRUE);
                        username_registration_dialog_error(_("Unable to register name (Invalid name). Your username should contains between 3 and 32 alphanumerics characters (or underscore)."));
                        break;
436
                    case lrc::api::account::RegisterNameStatus::WRONG_PASSWORD:
437 438 439
                        gtk_widget_set_sensitive(priv->button_register_username, TRUE);
                        username_registration_dialog_error(_("Unable to register name (Wrong password)."));
                        break;
440
                    case lrc::api::account::RegisterNameStatus::ALREADY_TAKEN:
441 442 443
                        gtk_widget_set_sensitive(priv->button_register_username, TRUE);
                        username_registration_dialog_error(_("Unable to register name (Username already taken)."));
                        break;
444
                    case lrc::api::account::RegisterNameStatus::NETWORK_ERROR:
445 446 447
                        gtk_widget_set_sensitive(priv->button_register_username, TRUE);
                        username_registration_dialog_error(_("Unable to register name (Network error) - check your connection."));
                        break;
448 449 450 451
                    }
                }
            );
        }
452 453 454 455 456 457
        gtk_widget_show(priv->frame_username);
        gtk_widget_hide(priv->label_username);
    } else {
        gtk_widget_hide(priv->frame_username);
        gtk_label_set_text(GTK_LABEL(priv->label_username), registered_name.c_str());
        gtk_widget_show(priv->label_username);
458
    }
459 460 461 462 463 464 465 466 467 468 469 470 471

    if (priv->show_register_button)
        gtk_widget_show(priv->button_register_username);
    else
        gtk_widget_hide(priv->button_register_username);

    // CSS styles
    auto provider = gtk_css_provider_new();
    std::string css = ".box_error { background: #de8484; }";
    gtk_css_provider_load_from_data(provider, css.c_str(), -1, nullptr);
    gtk_style_context_add_provider_for_screen(gdk_display_get_default_screen(gdk_display_get_default()),
                                              GTK_STYLE_PROVIDER(provider),
                                              GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
472 473 474
}

GtkWidget *
475
username_registration_box_new_empty(bool register_button)
476 477 478 479
{
    gpointer view = g_object_new(USERNAME_REGISTRATION_BOX_TYPE, NULL);

    UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
    priv->accountInfo_ = nullptr;
    priv->withAccount = false;
    priv->use_blockchain = true;
    priv->show_register_button = register_button;

    build_view(USERNAME_REGISTRATION_BOX(view), register_button);

    return (GtkWidget *)view;
}

GtkWidget *
username_registration_box_new(AccountInfoPointer const & accountInfo, bool register_button)
{
    gpointer view = g_object_new(USERNAME_REGISTRATION_BOX_TYPE, NULL);

    UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
    priv->accountInfo_ = &accountInfo;
    priv->withAccount = true;
    priv->use_blockchain = true;
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
    priv->show_register_button = register_button;

    build_view(USERNAME_REGISTRATION_BOX(view), register_button);

    return (GtkWidget *)view;
}

GtkEntry*
username_registration_box_get_entry(UsernameRegistrationBox* view)
{
    UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
    return GTK_ENTRY(priv->entry_username);
}


void
username_registration_box_set_use_blockchain(UsernameRegistrationBox* view, gboolean use_blockchain)
{
    auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);

    if (priv->use_blockchain == use_blockchain)
        return;

    priv->use_blockchain = use_blockchain;

524
    entry_username_changed(view);
525

526
    if (use_blockchain) {
527 528
        if (priv->show_register_button)
            gtk_widget_show(priv->button_register_username);
529 530
        else
            gtk_widget_hide(priv->button_register_username);
531 532 533 534
    } else {
        gtk_widget_hide(priv->button_register_username);
    }
}