Skip to content
Snippets Groups Projects
Select Git revision
  • 81f6f40b3a0ab8b663fcb16ad31b754abf85ce17
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/windowsReleaseTest
  • release/releaseTest
  • release/releaseWindowsTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 4.0.0
  • 2.2.0
  • 2.1.0
  • 2.0.1
  • 2.0.0
  • 1.4.1
  • 1.4.0
  • 1.3.0
  • 1.2.0
  • 1.1.0
31 results

fileutils.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    utils.cpp 31.39 KiB
    /*
     * Copyright (C) 2015-2020 by Savoir-faire Linux
     * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>
     * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
     * Author: Isa Nanic <isa.nanic@savoirfairelinux.com
     * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
     * Author: Aline Gondim Santos   <aline.gondimsantos@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, see <http://www.gnu.org/licenses/>.
     */
    
    #include "utils.h"
    
    #ifdef Q_OS_WIN
    #include <lmcons.h>
    #include <shlguid.h>
    #include <shlobj.h>
    #include <shlwapi.h>
    #include <shobjidl.h>
    #include <windows.h>
    #endif
    
    #include "globalsystemtray.h"
    #include "jamiavatartheme.h"
    #include "lrcinstance.h"
    #include "pixbufmanipulator.h"
    #include "version.h"
    
    #include <globalinstances.h>
    #include <qrencode.h>
    
    #include <QApplication>
    #include <QBitmap>
    #include <QErrorMessage>
    #include <QFile>
    #include <QMessageBox>
    #include <QObject>
    #include <QPainter>
    #include <QPropertyAnimation>
    #include <QScreen>
    #include <QStackedWidget>
    #include <QSvgRenderer>
    #include <QTranslator>
    #include <QtConcurrent/QtConcurrent>
    
    bool
    Utils::CreateStartupLink(const std::wstring &wstrAppName)
    {
    #ifdef Q_OS_WIN
        TCHAR szPath[MAX_PATH];
        GetModuleFileName(NULL, szPath, MAX_PATH);
    
        std::wstring programPath(szPath);
    
        TCHAR startupPath[MAX_PATH];
        SHGetFolderPathW(NULL, CSIDL_STARTUP, NULL, 0, startupPath);
    
        std::wstring linkPath(startupPath);
        linkPath += std::wstring(TEXT("\\") + wstrAppName + TEXT(".lnk"));
    
        return Utils::CreateLink(programPath.c_str(), linkPath.c_str());
    #else
        return true;
    #endif
    }
    
    bool
    Utils::CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszPathLink)
    {
    #ifdef Q_OS_WIN
        HRESULT hres;
        IShellLink *psl;
    
        hres = CoCreateInstance(CLSID_ShellLink,
                                NULL,
                                CLSCTX_INPROC_SERVER,
                                IID_IShellLink,
                                (LPVOID *) &psl);
        if (SUCCEEDED(hres)) {
            IPersistFile *ppf;
            psl->SetPath(lpszPathObj);
            psl->SetArguments(TEXT("--minimized"));
    
            hres = psl->QueryInterface(IID_IPersistFile, (LPVOID *) &ppf);
            if (SUCCEEDED(hres)) {
                hres = ppf->Save(lpszPathLink, TRUE);
                ppf->Release();
            }
            psl->Release();
        }
        return hres;
    #else
        Q_UNUSED(lpszPathObj)
        Q_UNUSED(lpszPathLink)
        return true;
    #endif
    }
    
    void
    Utils::DeleteStartupLink(const std::wstring &wstrAppName)
    {
    #ifdef Q_OS_WIN
        TCHAR startupPath[MAX_PATH];
        SHGetFolderPathW(NULL, CSIDL_STARTUP, NULL, 0, startupPath);
    
        std::wstring linkPath(startupPath);
        linkPath += std::wstring(TEXT("\\") + wstrAppName + TEXT(".lnk"));
    
        DeleteFile(linkPath.c_str());
    #endif
    }
    
    bool
    Utils::CheckStartupLink(const std::wstring &wstrAppName)
    {
    #ifdef Q_OS_WIN
        TCHAR startupPath[MAX_PATH];
        SHGetFolderPathW(NULL, CSIDL_STARTUP, NULL, 0, startupPath);
    
        std::wstring linkPath(startupPath);
        linkPath += std::wstring(TEXT("\\") + wstrAppName + TEXT(".lnk"));
        return PathFileExists(linkPath.c_str());
    #else
        return true;
    #endif
    }
    
    const char *
    Utils::WinGetEnv(const char *name)
    {
    #ifdef Q_OS_WIN
        const DWORD buffSize = 65535;
        static char buffer[buffSize];
        if (GetEnvironmentVariableA(name, buffer, buffSize)) {
            return buffer;
        } else {
            return 0;
        }
    #else
        return 0;
    #endif
    }
    
    void
    Utils::removeOldVersions()
    {
    #ifdef Q_OS_WIN
        /*
         * As per: https://git.jami.net/savoirfairelinux/ring-client-windows/issues/429
         * NB: As only the 64-bit version of this application is distributed, we will only
         * remove 1. the configuration reg keys for Ring-x64, 2. the startup links for Ring,
         * 3. the winsparkle reg keys. The NSIS uninstall reg keys for Jami-x64 are removed
         * by the MSI installer.
         * Uninstallation of Ring, either 32 or 64 bit, is left to the user.
         * The current version of Jami will attempt to kill Ring.exe upon start if a startup
         * link is found.
         */
        QString node64 = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node";
        QString hkcuSoftwareKey = "HKEY_CURRENT_USER\\Software\\";
        QString uninstKey = "\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";
        QString company = "Savoir-Faire Linux";
    
        /*
         * 1. Configuration reg keys for Ring-x64.
         */
        QSettings(hkcuSoftwareKey + "jami.net\\Ring", QSettings::NativeFormat).remove("");
        QSettings(hkcuSoftwareKey + "ring.cx", QSettings::NativeFormat).remove("");
        /*
         * 2. Unset Ring as a startup application.
         */
        if (Utils::CheckStartupLink(TEXT("Ring"))) {
            qDebug() << "Found startup link for Ring. Removing it and killing Ring.exe.";
            Utils::DeleteStartupLink(TEXT("Ring"));
            QProcess::execute("taskkill /im Ring.exe /f");
        }
        /*
         * 3. Remove registry entries for winsparkle(both Jami-x64 and Ring-x64).
         */
        QSettings(hkcuSoftwareKey + company, QSettings::NativeFormat).remove("");
    #else
        return;
    #endif
    }
    
    QString
    Utils::GetRingtonePath()
    {
    #ifdef Q_OS_WIN
        return QCoreApplication::applicationDirPath() + "\\ringtones\\default.opus";
    #else
        return QString("/usr/local");
    #endif
    }
    
    QString
    Utils::GenGUID()
    {
    #ifdef Q_OS_WIN
        GUID gidReference;
        wchar_t *str;
        HRESULT hCreateGuid = CoCreateGuid(&gidReference);
        if (hCreateGuid == S_OK) {
            StringFromCLSID(gidReference, &str);
            auto gStr = QString::fromWCharArray(str);
            return gStr.remove("{").remove("}").toLower();
        } else
            return QString();
    #else
        return QString("");
    #endif
    }
    
    QString
    Utils::GetISODate()
    {
    #ifdef Q_OS_WIN
        SYSTEMTIME lt;
        GetSystemTime(&lt);
        return QString("%1-%2-%3T%4:%5:%6Z")
            .arg(lt.wYear)
            .arg(lt.wMonth, 2, 10, QChar('0'))
            .arg(lt.wDay, 2, 10, QChar('0'))
            .arg(lt.wHour, 2, 10, QChar('0'))
            .arg(lt.wMinute, 2, 10, QChar('0'))
            .arg(lt.wSecond, 2, 10, QChar('0'));
    #else
        return QString();
    #endif
    }
    
    void
    Utils::InvokeMailto(const QString &subject, const QString &body, const QString &attachement)
    {
    #ifdef Q_OS_WIN
        HKEY hKey;
        LONG lRes = RegOpenKeyExW(HKEY_CLASSES_ROOT, L"mailto", 0, KEY_READ, &hKey);
        if (lRes != ERROR_FILE_NOT_FOUND) {
            auto addr = QString("mailto:?subject=%1&body=%2").arg(subject).arg(body);
            if (not attachement.isEmpty())
                addr += QString("&attachement=%1").arg(attachement);
            ShellExecute(nullptr, L"open", addr.toStdWString().c_str(), NULL, NULL, SW_SHOWNORMAL);
        } else {
            QErrorMessage errorMessage;
            errorMessage.showMessage(QObject::tr("No default mail client found"));
        }
    #endif
    }
    
    QString
    Utils::getContactImageString(const QString &accountId, const QString &uid)
    {
        return QString::fromLatin1(
            Utils::QImageToByteArray(
                Utils::conversationPhoto(uid, LRCInstance::getAccountInfo(accountId)))
                .toBase64()
                .data());
    }
    
    QImage
    Utils::getCirclePhoto(const QImage original, int sizePhoto)
    {
        QImage target(sizePhoto, sizePhoto, QImage::Format_ARGB32_Premultiplied);
        target.fill(Qt::transparent);
    
        QPainter painter(&target);
        painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
        painter.setBrush(QBrush(Qt::white));
        auto scaledPhoto = original
                               .scaled(sizePhoto,
                                       sizePhoto,
                                       Qt::KeepAspectRatioByExpanding,
                                       Qt::SmoothTransformation)
                               .convertToFormat(QImage::Format_ARGB32_Premultiplied);
        int margin = 0;
        if (scaledPhoto.width() > sizePhoto) {
            margin = (scaledPhoto.width() - sizePhoto) / 2;
        }
        painter.drawEllipse(0, 0, sizePhoto, sizePhoto);
        painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
        painter.drawImage(0, 0, scaledPhoto, margin, 0);
        return target;
    }
    
    void
    Utils::setStackWidget(QStackedWidget *stack, QWidget *widget)
    {
        if (stack->indexOf(widget) != -1 && stack->currentWidget() != widget) {
            stack->setCurrentWidget(widget);
        }
    }
    
    void
    Utils::showSystemNotification(QWidget *widget,
                                  const QString &message,
                                  long delay,
                                  const QString &triggeredAccountId)
    {
        QSettings settings("jami.net", "Jami");
        if (settings.value(SettingsKey::enableNotifications).toBool()) {
            GlobalSystemTray::instance().setTriggeredAccountId(triggeredAccountId);
            GlobalSystemTray::instance().showMessage(message, "", QIcon(":images/jami.png"));
            QApplication::alert(widget, delay);
        }
    }
    
    void
    Utils::showSystemNotification(QWidget *widget,
                                  const QString &sender,
                                  const QString &message,
                                  long delay,
                                  const QString &triggeredAccountId)
    {
        QSettings settings("jami.net", "Jami");
        if (settings.value(SettingsKey::enableNotifications).toBool()) {
            GlobalSystemTray::instance().setTriggeredAccountId(triggeredAccountId);
            GlobalSystemTray::instance().showMessage(sender, message, QIcon(":images/jami.png"));
            QApplication::alert(widget, delay);
        }
    }
    
    QSize
    Utils::getRealSize(QScreen *screen)
    {
    #ifdef Q_OS_WIN
        DEVMODE dmThisScreen;
        ZeroMemory(&dmThisScreen, sizeof(dmThisScreen));
        EnumDisplaySettings((const wchar_t *) screen->name().utf16(),
                            ENUM_CURRENT_SETTINGS,
                            (DEVMODE *) &dmThisScreen);
        return QSize(dmThisScreen.dmPelsWidth, dmThisScreen.dmPelsHeight);
    #else
        return {};
    #endif
    }
    
    void
    Utils::forceDeleteAsync(const QString &path)
    {
        /*
         * Keep deleting file until the process holding it let go,
         * or the file itself does not exist anymore.
         */
        QtConcurrent::run([path] {
            QFile file(path);
            if (!QFile::exists(path))
                return;
            int retries{0};
            while (!file.remove() && retries < 5) {
                qDebug().noquote() << "\n" << file.errorString() << "\n";
                QThread::msleep(10);
                ++retries;
            }
        });
    }
    
    UtilsAdapter &
    UtilsAdapter::instance()
    {
        static auto instance = new UtilsAdapter;
        return *instance;
    }
    
    QString
    Utils::getChangeLog()
    {
        QString logs;
        QFile changeLogFile(":/changelog.html");
        if (!changeLogFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
            qDebug().noquote() << " Change log file failed to load";
            return {};
        }
        QTextStream in(&changeLogFile);
        in.setCodec("UTF-8");
        while (!in.atEnd()) {
            logs += in.readLine();
        }
        return logs;
    }
    
    QString
    Utils::getProjectCredits()
    {
        QString credits;
        QFile projectCreditsFile(":/projectcredits.html");
        if (!projectCreditsFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
            qDebug().noquote() << " Project Credits failed to load";
            return {};
        }
        QTextStream in(&projectCreditsFile);
        in.setCodec("UTF-8");
        while (!in.atEnd()) {
            QString currentLine = in.readLine();
            if (credits.isEmpty()) {
                credits += "<h3 align=\"center\" style=\" margin-top:0px; "
                           + QString("margin-bottom:0px; margin-left:0px; margin-right:0px; ")
                           + "-qt-block-indent:0; text-indent:0px;\"><span style=\" font-weight:600;\">"
                           + UtilsAdapter::tr("Created by:") + "</span></h3>";
            } else if (currentLine.contains("Marianne Forget")) {
                credits
                    += "<h3 align=\"center\" style=\" margin-top:0px; margin-bottom:0px; "
                       + QString(
                           "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">")
                       + "<span style=\" font-weight:600;\">" + UtilsAdapter::tr("Artwork by:")
                       + "</span></h3>";
            }
            credits += currentLine;
        }
        credits += "<p align=\"center\" style=\" margin-top:0px; margin-bottom:0px; "
                   + QString(
                       "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">")
                   + UtilsAdapter::tr("Based on the SFLPhone project") + "</p>";
    
        return credits;
    }
    
    void
    Utils::cleanUpdateFiles()
    {
        /*
         * Delete all logs and msi in the %TEMP% directory before launching.
         */
        QString dir = QString(Utils::WinGetEnv("TEMP"));
        QDir log_dir(dir, {"jami*.log"});
        for (const QString &filename : log_dir.entryList()) {
            log_dir.remove(filename);
        }
        QDir msi_dir(dir, {"jami*.msi"});
        for (const QString &filename : msi_dir.entryList()) {
            msi_dir.remove(filename);
        }
        QDir version_dir(dir, {"version"});
        for (const QString &filename : version_dir.entryList()) {
            version_dir.remove(filename);
        }
    }
    
    void
    Utils::checkForUpdates(bool withUI, QWidget *parent)
    {
        Q_UNUSED(withUI)
        Q_UNUSED(parent)
        /*
         * TODO: check update logic.
         */
    }
    
    void
    Utils::applyUpdates(bool updateToBeta, QWidget *parent)
    {
        Q_UNUSED(updateToBeta)
        Q_UNUSED(parent)
        /*
         * TODO: update logic.
         */
    }
    
    inline QString
    removeEndlines(const QString &str)
    {
        QString trimmed(str);
        trimmed.remove(QChar('\n'));
        trimmed.remove(QChar('\r'));
        return trimmed;
    }
    
    QString
    Utils::bestIdForConversation(const lrc::api::conversation::Info &conv,
                                 const lrc::api::ConversationModel &model)
    {
        auto contact = model.owner.contactModel->getContact(conv.participants[0]);
        if (!contact.registeredName.isEmpty()) {
            return removeEndlines(contact.registeredName);
        }
        return removeEndlines(contact.profileInfo.uri);
    }
    
    QString
    Utils::bestIdForAccount(const lrc::api::account::Info &account)
    {
        if (!account.registeredName.isEmpty()) {
            return removeEndlines(account.registeredName);
        }
        return removeEndlines(account.profileInfo.uri);
    }
    
    QString
    Utils::bestNameForAccount(const lrc::api::account::Info &account)
    {
        if (account.profileInfo.alias.isEmpty()) {
            return bestIdForAccount(account);
        }
        return account.profileInfo.alias;
    }
    
    QString
    Utils::bestIdForContact(const lrc::api::contact::Info &contact)
    {
        if (!contact.registeredName.isEmpty()) {
            return removeEndlines(contact.registeredName);
        }
        return removeEndlines(contact.profileInfo.uri);
    }
    
    QString
    Utils::bestNameForContact(const lrc::api::contact::Info &contact)
    {
        auto alias = removeEndlines(contact.profileInfo.alias);
        if (alias.length() == 0) {
            return bestIdForContact(contact);
        }
        return alias;
    }
    
    QString
    Utils::bestNameForConversation(const lrc::api::conversation::Info &conv,
                                   const lrc::api::ConversationModel &model)
    {
        try {
            auto contact = model.owner.contactModel->getContact(conv.participants[0]);
            auto alias = removeEndlines(contact.profileInfo.alias);
            if (alias.length() == 0) {
                return bestIdForConversation(conv, model);
            }
            return alias;
        } catch (...) {
        }
        return {};
    }
    
    /*
     * Returns empty string if only infoHash is available, second best identifier otherwise.
     */
    QString
    Utils::secondBestNameForAccount(const lrc::api::account::Info &account)
    {
        auto alias = removeEndlines(account.profileInfo.alias);
        auto registeredName = removeEndlines(account.registeredName);
        auto infoHash = account.profileInfo.uri;
    
        if (!alias.length() == 0) {
            /*
             * If alias exists.
             */
            if (!registeredName.length() == 0) {
                /*
                 * If registeredName exists.
                 */
                return registeredName;
            } else {
                return infoHash;
            }
        } else {
            if (!registeredName.length() == 0) {
                /*
                 * If registeredName exists.
                 */
                return infoHash;
            } else {
                return "";
            }
        }
    }
    
    lrc::api::profile::Type
    Utils::profileType(const lrc::api::conversation::Info &conv,
                       const lrc::api::ConversationModel &model)
    {
        try {
            auto contact = model.owner.contactModel->getContact(conv.participants[0]);
            return contact.profileInfo.type;
        } catch (...) {
            return lrc::api::profile::Type::INVALID;
        }
    }
    
    std::string
    Utils::formatTimeString(const std::time_t &timestamp)
    {
        std::time_t now = std::time(nullptr);
        char interactionDay[64];
        char nowDay[64];
        std::strftime(interactionDay, sizeof(interactionDay), "%D", std::localtime(&timestamp));
        std::strftime(nowDay, sizeof(nowDay), "%D", std::localtime(&now));
        if (std::string(interactionDay) == std::string(nowDay)) {
            char interactionTime[64];
            std::strftime(interactionTime, sizeof(interactionTime), "%R", std::localtime(&timestamp));
            return interactionTime;
        } else {
            return interactionDay;
        }
    }
    
    bool
    Utils::isInteractionGenerated(const lrc::api::interaction::Type &type)
    {
        return type == lrc::api::interaction::Type::CALL
               || type == lrc::api::interaction::Type::CONTACT;
    }
    
    bool
    Utils::isContactValid(const QString &contactUid, const lrc::api::ConversationModel &model)
    {
        const auto contact = model.owner.contactModel->getContact(contactUid);
        return (contact.profileInfo.type == lrc::api::profile::Type::PENDING
                || contact.profileInfo.type == lrc::api::profile::Type::TEMPORARY
                || contact.profileInfo.type == lrc::api::profile::Type::RING
                || contact.profileInfo.type == lrc::api::profile::Type::SIP)
               && !contact.profileInfo.uri.isEmpty();
    }
    
    bool
    Utils::getReplyMessageBox(QWidget *widget, const QString &title, const QString &text)
    {
        if (QMessageBox::question(widget, title, text, QMessageBox::Yes | QMessageBox::No)
            == QMessageBox::Yes)
            return true;
        return false;
    }
    
    QImage
    Utils::conversationPhoto(const QString &convUid,
                             const lrc::api::account::Info &accountInfo,
                             bool filtered)
    {
        auto* convModel = LRCInstance::getCurrentConversationModel();
        const auto convInfo = convModel->getConversationForUID(convUid);
        if (!convInfo.uid.isEmpty()) {
            return GlobalInstances::pixmapManipulator()
                .decorationRole(convInfo, accountInfo)
                .value<QImage>();
        }
        return QImage();
    }
    
    QColor
    Utils::getAvatarColor(const QString &canonicalUri)
    {
        if (canonicalUri.isEmpty()) {
            return JamiAvatarTheme::defaultAvatarColor_;
        }
        auto h = QString(
            QCryptographicHash::hash(canonicalUri.toLocal8Bit(), QCryptographicHash::Md5).toHex());
        if (h.isEmpty() || h.isNull()) {
            return JamiAvatarTheme::defaultAvatarColor_;
        }
        auto colorIndex = std::string("0123456789abcdef").find(h.at(0).toLatin1());
        return JamiAvatarTheme::avatarColors_[colorIndex];
    }
    
    /* Generate a QImage representing a dummy user avatar, when user doesn't provide it.
     * Current rendering is a flat colored circle with a centered letter.
     * The color of the letter is computed from the circle color to be visible whaterver be the circle color.
     */
    QImage
    Utils::fallbackAvatar(const QSize size, const QString &canonicalUriStr, const QString &letterStr)
    {
        /*
         * We start with a transparent avatar.
         */
        QImage avatar(size, QImage::Format_ARGB32);
        avatar.fill(Qt::transparent);
    
        /*
         * We pick a color based on the passed character.
         */
        QColor avColor = getAvatarColor(canonicalUriStr);
    
        /*
         * We draw a circle with this color.
         */
        QPainter painter(&avatar);
        painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
        painter.setPen(Qt::transparent);
        painter.setBrush(avColor.lighter(110));
        painter.drawEllipse(avatar.rect());
    
        /*
         * If a letter was passed, then we paint a letter in the circle,
         * otherwise we draw the default avatar icon.
         */
        QString letterStrCleaned(letterStr);
        letterStrCleaned.remove(QRegExp("[\\n\\t\\r]"));
        if (!letterStr.isEmpty()) {
            auto unicode = letterStr.toUcs4().at(0);
            if (unicode >= 0x1F000 && unicode <= 0x1FFFF) {
                /*
                 * Is Emoticon.
                 */
                auto letter = QString::fromUcs4(&unicode, 1);
                QFont font(QStringLiteral("Segoe UI Emoji"), avatar.height() / 2.66667, QFont::Medium);
                painter.setFont(font);
                QRect emojiRect(avatar.rect());
                emojiRect.moveTop(-6);
                painter.drawText(emojiRect, letter, QTextOption(Qt::AlignCenter));
            } else if (unicode >= 0x0000 && unicode <= 0x00FF) {
                /*
                 * Is Basic Latin.
                 */
                auto letter = letterStr.at(0).toUpper();
                QFont font("Arial", avatar.height() / 2.66667, QFont::Medium);
                painter.setFont(font);
                painter.setPen(Qt::white);
                painter.drawText(avatar.rect(), QString(letter), QTextOption(Qt::AlignCenter));
            } else {
                auto letter = QString::fromUcs4(&unicode, 1);
                QFont font("Arial", avatar.height() / 2.66667, QFont::Medium);
                painter.setFont(font);
                painter.setPen(Qt::white);
                painter.drawText(avatar.rect(), QString(letter), QTextOption(Qt::AlignCenter));
            }
        } else {
            QRect overlayRect = avatar.rect();
            qreal margin = (0.05 * overlayRect.width());
            overlayRect.moveLeft(overlayRect.left() + margin * 0.5);
            overlayRect.moveTop(overlayRect.top() + margin * 0.5);
            overlayRect.setWidth(overlayRect.width() - margin);
            overlayRect.setHeight(overlayRect.height() - margin);
            painter.drawPixmap(overlayRect, QPixmap(":/images/default_avatar_overlay.svg"));
        }
    
        return avatar;
    }
    
    QImage
    Utils::fallbackAvatar(const QSize size, const std::string &alias, const std::string &uri)
    {
        return fallbackAvatar(size, QString::fromStdString(uri), QString::fromStdString(alias));
    }
    
    QByteArray
    Utils::QImageToByteArray(QImage image)
    {
        QByteArray ba;
        QBuffer buffer(&ba);
        buffer.open(QIODevice::WriteOnly);
        image.save(&buffer, "PNG");
        return ba;
    }
    
    QImage
    Utils::cropImage(const QImage &img)
    {
        QRect rect;
        auto w = img.width();
        auto h = img.height();
        if (w > h) {
            return img.copy({(w - h) / 2, 0, h, h});
        }
        return img.copy({0, (h - w) / 2, w, w});
    }
    
    QPixmap
    Utils::pixmapFromSvg(const QString &svg_resource, const QSize &size)
    {
        QSvgRenderer svgRenderer(svg_resource);
        QPixmap pixmap(size);
        pixmap.fill(Qt::transparent);
        QPainter pixPainter(&pixmap);
        svgRenderer.render(&pixPainter);
        return pixmap;
    }
    
    QImage
    Utils::setupQRCode(QString ringID, int margin)
    {
        auto rcode = QRcode_encodeString(ringID.toStdString().c_str(),
                                         0,            // Let the version be decided by libqrencode
                                         QR_ECLEVEL_L, // Lowest level of error correction
                                         QR_MODE_8,    // 8-bit data mode
                                         1);
        if (not rcode) {
            qWarning() << "Failed to generate QR code: " << strerror(errno);
            return QImage();
        }
    
        int qrwidth = rcode->width + margin * 2;
        QImage result(QSize(qrwidth, qrwidth), QImage::Format_Mono);
        QPainter painter;
        painter.begin(&result);
        painter.setClipRect(QRect(0, 0, qrwidth, qrwidth));
        painter.setPen(QPen(Qt::black, 0.1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
        painter.setBrush(Qt::black);
        painter.fillRect(QRect(0, 0, qrwidth, qrwidth), Qt::white);
        unsigned char *p;
        p = rcode->data;
        for (int y = 0; y < rcode->width; y++) {
            unsigned char *row = (p + (y * rcode->width));
            for (int x = 0; x < rcode->width; x++) {
                if (*(row + x) & 0x1) {
                    painter.drawRect(margin + x, margin + y, 1, 1);
                }
            }
        }
        painter.end();
        QRcode_free(rcode);
        return result;
    }
    
    float
    Utils::getCurrentScalingRatio()
    {
        return CURRENT_SCALING_RATIO;
    }
    
    void
    Utils::setCurrentScalingRatio(float ratio)
    {
        CURRENT_SCALING_RATIO = ratio;
    }
    
    QString
    Utils::formattedTime(int duration)
    {
        if (duration == 0)
            return {};
        std::string formattedString;
        auto minutes = duration / 60;
        auto seconds = duration % 60;
        if (minutes > 0) {
            formattedString += std::to_string(minutes) + ":";
            if (formattedString.length() == 2) {
                formattedString = "0" + formattedString;
            }
        } else {
            formattedString += "00:";
        }
        if (seconds < 10)
            formattedString += "0";
        formattedString += std::to_string(seconds);
        return QString::fromStdString(formattedString);
    }
    
    QByteArray
    Utils::QByteArrayFromFile(const QString &filename)
    {
        QFile file(filename);
        if (file.open(QIODevice::ReadOnly)) {
            return file.readAll();
        } else {
            qDebug() << "can't open file";
            return QByteArray();
        }
    }
    
    QPixmap
    Utils::generateTintedPixmap(const QString &filename, QColor color)
    {
        QPixmap px(filename);
        QImage tmpImage = px.toImage();
        for (int y = 0; y < tmpImage.height(); y++) {
            for (int x = 0; x < tmpImage.width(); x++) {
                color.setAlpha(tmpImage.pixelColor(x, y).alpha());
                tmpImage.setPixelColor(x, y, color);
            }
        }
        return QPixmap::fromImage(tmpImage);
    }
    
    QPixmap
    Utils::generateTintedPixmap(const QPixmap &pix, QColor color)
    {
        QPixmap px = pix;
        QImage tmpImage = px.toImage();
        for (int y = 0; y < tmpImage.height(); y++) {
            for (int x = 0; x < tmpImage.width(); x++) {
                color.setAlpha(tmpImage.pixelColor(x, y).alpha());
                tmpImage.setPixelColor(x, y, color);
            }
        }
        return QPixmap::fromImage(tmpImage);
    }
    
    QImage
    Utils::scaleAndFrame(const QImage photo, const QSize &size)
    {
        return photo.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
    }
    
    QImage
    Utils::accountPhoto(const lrc::api::account::Info &accountInfo, const QSize &size)
    {
        QImage photo;
        if (!accountInfo.profileInfo.avatar.isEmpty()) {
            QByteArray ba = accountInfo.profileInfo.avatar.toLocal8Bit();
            photo = GlobalInstances::pixmapManipulator().personPhoto(ba, nullptr).value<QImage>();
        } else {
            auto bestId = bestIdForAccount(accountInfo);
            auto bestName = bestNameForAccount(accountInfo);
            QString letterStr = bestId == bestName ? QString() : bestName;
            QString prefix = accountInfo.profileInfo.type == lrc::api::profile::Type::RING ? "ring:"
                                                                                           : "sip:";
            photo = fallbackAvatar(size, prefix + accountInfo.profileInfo.uri, letterStr);
        }
        return scaleAndFrame(photo, size);
    }
    
    QString
    Utils::humanFileSize(qint64 fileSize)
    {
        float fileSizeF = static_cast<float>(fileSize);
        float thresh = 1024;
    
        if (abs(fileSizeF) < thresh) {
            return QString::number(fileSizeF) + " B";
        }
        QString units[] = {"kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
        int unit_position = -1;
        do {
            fileSizeF /= thresh;
            ++unit_position;
        } while (abs(fileSizeF) >= thresh && unit_position < units->size() - 1);
        /*
         * Round up to two decimal.
         */
        fileSizeF = roundf(fileSizeF * 100) / 100;
        return QString::number(fileSizeF) + " " + units[unit_position];
    }
    
    const QString
    UtilsAdapter::getBestName(const QString &accountId, const QString &uid)
    {
        auto* convModel = LRCInstance::getAccountInfo(accountId).conversationModel.get();
        return Utils::bestNameForConversation(convModel->getConversationForUID(uid), *convModel);
    }
    
    const QString
    UtilsAdapter::getBestId(const QString &accountId, const QString &uid)
    {
        auto* convModel = LRCInstance::getAccountInfo(accountId).conversationModel.get();
        return Utils::bestIdForConversation(convModel->getConversationForUID(uid), *convModel);
    }
    
    int
    UtilsAdapter::getTotalUnreadMessages()
    {
        int totalUnreadMessages{0};
        if (LRCInstance::getCurrentAccountInfo().profileInfo.type != lrc::api::profile::Type::SIP) {
            auto* convModel = LRCInstance::getCurrentConversationModel();
            auto ringConversations = convModel->getFilteredConversations(lrc::api::profile::Type::RING);
            std::for_each(ringConversations.begin(),
                          ringConversations.end(),
                          [&totalUnreadMessages](const auto &conversation) {
                              totalUnreadMessages += conversation.unreadMessages;
                          });
        }
        return totalUnreadMessages;
    }
    
    int
    UtilsAdapter::getTotalPendingRequest()
    {
        auto &accountInfo = LRCInstance::getCurrentAccountInfo();
        return accountInfo.contactModel->pendingRequestCount();
    }
    
    void
    UtilsAdapter::setConversationFilter(const QString &filter)
    {
        LRCInstance::getCurrentConversationModel()->setFilter(filter);
    }
    
    void
    UtilsAdapter::clearConversationHistory(const QString &accountId, const QString &uid)
    {
        LRCInstance::getAccountInfo(accountId).conversationModel->clearHistory(uid);
    }
    
    void
    UtilsAdapter::removeConversation(const QString &accountId, const QString &uid, bool banContact)
    {
        LRCInstance::getAccountInfo(accountId).conversationModel->removeConversation(uid, banContact);
    }
    
    const QString
    UtilsAdapter::getCurrAccId()
    {
        return LRCInstance::getCurrAccId();
    }
    
    const QStringList
    UtilsAdapter::getCurrAccList()
    {
        return LRCInstance::accountModel().getAccountList();
    }
    
    int
    UtilsAdapter::getAccountListSize()
    {
        return getCurrAccList().size();
    }
    
    void
    UtilsAdapter::setCurrentCall(const QString &accountId, const QString &convUid)
    {
        auto &accInfo = LRCInstance::getAccountInfo(accountId);
        const auto convInfo = accInfo.conversationModel->getConversationForUID(convUid);
        accInfo.callModel->setCurrentCall(convInfo.callId);
    }
    
    void
    UtilsAdapter::startPreviewing(bool force)
    {
        LRCInstance::renderer()->startPreviewing(force);
    }
    
    void
    UtilsAdapter::stopPreviewing()
    {
        if (!LRCInstance::hasVideoCall()) {
            LRCInstance::renderer()->stopPreviewing();
        }
    }
    
    bool
    UtilsAdapter::hasVideoCall()
    {
        return LRCInstance::hasVideoCall();
    }
    
    const QString
    UtilsAdapter::getCallId(const QString &accountId, const QString &convUid)
    {
        auto &accInfo = LRCInstance::getAccountInfo(accountId);
        const auto convInfo = accInfo.conversationModel->getConversationForUID(convUid);
    
        if (convInfo.uid.isEmpty()) {
            return "";
        }
    
        auto call = LRCInstance::getCallInfoForConversation(convInfo, false);
        if (!call) {
            return "";
        }
    
        return call->id;
    }
    
    // returns true if name is valid registered name
    bool
    UtilsAdapter::validateRegNameForm(const QString &regName)
    {
        QRegularExpression regExp(" ");
    
        if (regName.size() > 2 && !regName.contains(regExp)) {
            return true;
    
        } else {
            return false;
        }
    }
    
    QString
    UtilsAdapter::getStringUTF8(QString string)
    {
        return string.toUtf8();
    }
    
    QString
    UtilsAdapter::getRecordQualityString(int value)
    {
        return value ? QString::number(static_cast<float>(value) / 100, 'f', 1) + " Mbps" : "Default";
    }
    
    QString
    UtilsAdapter::getCurrentPath()
    {
        return QDir::currentPath();
    }
    
    bool
    UtilsAdapter::checkShowPluginsButton()
    {
        return LRCInstance::pluginModel().getPluginsEnabled()
               && (LRCInstance::pluginModel().listLoadedPlugins().size() > 0);
    }