diff --git a/client.qrc b/client.qrc index e71b14b30..f726c633a 100644 --- a/client.qrc +++ b/client.qrc @@ -163,5 +163,6 @@ resources/icons/actions/light-down-arrow.svg resources/pictures/en-endpoint-security.svg resources/pictures/fr-endpoint-security.svg + resources/icons/actions/messages-bubble-square-typing.svg diff --git a/config.h.in b/config.h.in index 974203a23..aab95a2d3 100644 --- a/config.h.in +++ b/config.h.in @@ -15,6 +15,11 @@ #cmakedefine APPLICATION_STORAGE_URL "@APPLICATION_STORAGE_URL@" #cmakedefine APPLICATION_HELP_URL "@APPLICATION_HELP_URL@" #cmakedefine APPLICATION_CONFLICT_HELP_URL "@APPLICATION_CONFLICT_HELP_URL@" +#cmakedefine FEEDBACK_EN_URL "@FEEDBACK_EN_URL@" +#cmakedefine FEEDBACK_FR_URL "@FEEDBACK_FR_URL@" +#cmakedefine FEEDBACK_DE_URL "@FEEDBACK_DE_URL@" +#cmakedefine FEEDBACK_ES_URL "@FEEDBACK_ES_URL@" +#cmakedefine FEEDBACK_IT_URL "@FEEDBACK_IT_URL@" #cmakedefine APPLICATION_ICON_NAME "@APPLICATION_ICON_NAME@" #cmakedefine APPLICATION_VIRTUALFILE_SUFFIX "@APPLICATION_VIRTUALFILE_SUFFIX@" #define APPLICATION_DOTVIRTUALFILE_SUFFIX "." APPLICATION_VIRTUALFILE_SUFFIX diff --git a/infomaniak/kDrive.cmake b/infomaniak/kDrive.cmake index 522c9cb36..191274f9a 100644 --- a/infomaniak/kDrive.cmake +++ b/infomaniak/kDrive.cmake @@ -16,6 +16,11 @@ endif() set( APPLICATION_HELP_URL "https://support.infomaniak.com/" CACHE STRING "URL for the help menu" ) set( APPLICATION_CONFLICT_HELP_URL "https://www.infomaniak.com/en/support/faq/2403/resolve-a-kdrive-sync-conflict" ) +set( FEEDBACK_FR_URL "https://feedback.userreport.com/fe6ca4b6-5812-4f39-8ca6-5f2300aecda6" ) +set( FEEDBACK_EN_URL "https://feedback.userreport.com/652ad8f0-84c8-4a21-9e31-7a8bd7134f46" ) +set( FEEDBACK_DE_URL "https://feedback.userreport.com/074f5c5a-372b-40a6-b82f-9157fdb3d2d7" ) +set( FEEDBACK_ES_URL "https://feedback.userreport.com/e1304b1e-ebd0-4ffe-9234-a1a91730e651" ) +set( FEEDBACK_IT_URL "https://feedback.userreport.com/191a0beb-797d-4ec1-b1ff-31889a0012ee" ) if( APPLE ) set( APPLICATION_ICON_NAME "kdrive-mac" ) diff --git a/resources/icons/actions/messages-bubble-square-typing.svg b/resources/icons/actions/messages-bubble-square-typing.svg new file mode 100644 index 000000000..648e21dd5 --- /dev/null +++ b/resources/icons/actions/messages-bubble-square-typing.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index dd03ddc08..a0b8074f3 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -80,6 +80,7 @@ set(client_SRCS customtreewidgetitem.h customtreewidgetitem.cpp customsystembar.h customsystembar.cpp customwordwraplabel.h customwordwraplabel.cpp + taglabel.h taglabel.cpp debuggingdialog.h debuggingdialog.cpp disabledoverlay.h disabledoverlay.cpp drivepreferenceswidget.h drivepreferenceswidget.cpp @@ -116,6 +117,7 @@ set(client_SRCS preferencesmenubarwidget.h preferencesmenubarwidget.cpp preferenceswidget.h preferenceswidget.cpp versionwidget.h versionwidget.cpp + betaprogramdialog.h betaprogramdialog.cpp progressbarwidget.h progressbarwidget.cpp proxyserverdialog.h proxyserverdialog.cpp guirequests.h guirequests.cpp diff --git a/src/gui/betaprogramdialog.cpp b/src/gui/betaprogramdialog.cpp new file mode 100644 index 000000000..dd62e1e2e --- /dev/null +++ b/src/gui/betaprogramdialog.cpp @@ -0,0 +1,233 @@ +/* + * Infomaniak kDrive - Desktop + * Copyright (C) 2023-2024 Infomaniak Network SA + * + * 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 . + */ + +#include "betaprogramdialog.h" + +#include "adddriveconfirmationwidget.h" +#include "customcombobox.h" +#include "guirequests.h" +#include "parameterscache.h" +#include "utility/utility.h" + +#include +#include + +static constexpr int mainLayoutHMargin = 40; +static constexpr int mainLayoutSpacing = 24; +static constexpr int titleBoxVSpacing = 14; +static constexpr int subLayoutSpacing = 8; +static constexpr int iconSize = 16; +static constexpr auto iconColor = QColor(239, 139, 52); +static constexpr int indexNo = 0; +static constexpr int indexBeta = 1; +static constexpr int indexInternal = 2; + +namespace KDC { + +BetaProgramDialog::BetaProgramDialog(const bool isQuit, const bool isStaff, QWidget *parent /*= nullptr*/) : + CustomDialog(true, parent), _isQuit(isQuit && !isStaff), _isStaff(isStaff) { + setObjectName("BetaProgramDialog"); + + /* + * |-------------------------------------------------------| + * | layout | + * | | + * | |---------------------------------------------| | + * | | acknowledmentLayout | | + * | | | | + * | |---------------------------------------------| | + * | | + * | |---------------------------------------------| | + * | | buttonLayout | | + * | |---------------------------------------------| | + * |-------------------------------------------------------| + */ + + auto *layout = new QVBoxLayout(this); + layout->setContentsMargins(mainLayoutHMargin, 0, mainLayoutHMargin, 0); + layout->setSpacing(mainLayoutSpacing); + mainLayout()->addLayout(layout); + + // Title + auto *titleLabel = new QLabel(this); + titleLabel->setObjectName("titleLabel"); + titleLabel->setText(_isQuit ? tr("Quit the beta program") : tr("Join the beta program")); + layout->addWidget(titleLabel); + layout->addSpacing(titleBoxVSpacing); + + // Main text box + if (!_isQuit) { + auto *mainTextBox = new QLabel(this); + mainTextBox->setObjectName("largeNormalBoldTextLabel"); + mainTextBox->setText(tr( + R"(Get early access to new versions of the application before they are released to the general public, and take )" + R"(part in improving the application by sending us your comments.)")); + mainTextBox->setWordWrap(true); + layout->addWidget(mainTextBox); + } + + if (_isStaff) { + auto *staffLabel = new QLabel(this); + staffLabel->setObjectName("largeMediumBoldTextLabel"); + staffLabel->setText(tr("Benefit from application beta updates")); + layout->addWidget(staffLabel); + + _staffSelectionBox = new CustomComboBox(this); + _staffSelectionBox->insertItem(indexNo, tr("No")); + _staffSelectionBox->insertItem(indexBeta, tr("Public beta version")); + _staffSelectionBox->insertItem(indexInternal, tr("Internal beta version")); + + switch (ParametersCache::instance()->parametersInfo().distributionChannel()) { + case DistributionChannel::Prod: + _initialIndex = indexNo; + break; + case DistributionChannel::Beta: + _initialIndex = indexBeta; + break; + case DistributionChannel::Internal: + _initialIndex = indexInternal; + break; + default: + break; + } + _staffSelectionBox->setCurrentIndex(_initialIndex); + layout->addWidget(_staffSelectionBox); + + connect(_staffSelectionBox, &CustomComboBox::currentIndexChanged, this, &BetaProgramDialog::onChannelChange); + } + + // Acknowledgment + _acknowledgmentFrame = new QFrame(this); + _acknowledgmentFrame->setStyleSheet("QFrame {border-radius: 8px; background-color: #F4F6FC;}"); + _acknowledgmentFrame->setVisible(!_isStaff); + layout->addWidget(_acknowledgmentFrame); + + auto *acknowledgmentLayout = new QGridLayout(this); + _acknowledgmentFrame->setLayout(acknowledgmentLayout); + acknowledgmentLayout->setSpacing(subLayoutSpacing); + + auto *warningIcon = new QLabel(this); + warningIcon->setPixmap(GuiUtility::getIconWithColor(":/client/resources/icons/actions/warning.svg", iconColor) + .pixmap(QSize(iconSize, iconSize))); + warningIcon->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + acknowledgmentLayout->addWidget(warningIcon, 0, 0); + + _acknowledgmentLabel = new QLabel(this); + _acknowledgmentLabel->setObjectName("largeNormalTextLabel"); + if (_isQuit) { + setTooRecentMessage(); + } else { + setInstabilityMessage(); + } + _acknowledgmentLabel->setWordWrap(true); + _acknowledgmentLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + acknowledgmentLayout->addWidget(_acknowledgmentLabel, 0, 1); + + _acknowledgmentCheckbox = new QCheckBox(tr("I understand"), this); + acknowledgmentLayout->addWidget(_acknowledgmentCheckbox, 1, 1); + + if (_isQuit) { + auto *bottomTextBox = new QLabel(this); + bottomTextBox->setObjectName("largeNormalTextLabel"); + bottomTextBox->setText(tr("Are you sure you want to leave the beta program?")); + bottomTextBox->setWordWrap(true); + layout->addWidget(bottomTextBox); + } + + layout->addStretch(); + + // Buttons + auto *buttonLayout = new QHBoxLayout(this); + layout->addItem(buttonLayout); + buttonLayout->setSpacing(subLayoutSpacing); + _saveButton = new QPushButton(this); + _saveButton->setObjectName("defaultbutton"); + _saveButton->setFlat(true); + _saveButton->setText(tr("Save")); + _saveButton->setEnabled(false); + buttonLayout->addWidget(_saveButton); + auto *cancelButton = new QPushButton(this); + cancelButton->setObjectName("nondefaultbutton"); + cancelButton->setFlat(true); + cancelButton->setText(tr("Cancel")); + buttonLayout->addWidget(cancelButton); + buttonLayout->addStretch(); + + connect(_saveButton, &QPushButton::clicked, this, &BetaProgramDialog::onSave); + connect(cancelButton, &QPushButton::clicked, this, &BetaProgramDialog::reject); + connect(this, &BetaProgramDialog::exit, this, &BetaProgramDialog::reject); + connect(_acknowledgmentCheckbox, &QCheckBox::toggled, this, &BetaProgramDialog::onAcknowledgment); +} + +void BetaProgramDialog::onAcknowledgment() { + _saveButton->setEnabled(_acknowledgmentCheckbox->isChecked()); +} + +DistributionChannel toDistributionChannel(const int index) { + switch (index) { + case indexNo: + return DistributionChannel::Prod; + case indexBeta: + return DistributionChannel::Beta; + case indexInternal: + return DistributionChannel::Internal; + default: + break; + } + return DistributionChannel::Unknown; +} + +void BetaProgramDialog::onSave() { + if (_isStaff) { + _newChannel = toDistributionChannel(_staffSelectionBox->currentIndex()); + } else { + if (_isQuit) { + _newChannel = DistributionChannel::Prod; + } else { + _newChannel = DistributionChannel::Beta; + } + } + + accept(); +} + +void BetaProgramDialog::onChannelChange(const int index) { + _acknowledgmentCheckbox->setChecked(false); + if (_initialIndex == index) { + _acknowledgmentFrame->setVisible(false); + return; + } + + _acknowledgmentFrame->setVisible(true); + if (index > _initialIndex) + setInstabilityMessage(); + else + setTooRecentMessage(); +} + +void BetaProgramDialog::setTooRecentMessage() const { + _acknowledgmentLabel->setText( + tr("Your current version of the application may be too recent, your choice will be effective when the next update is " + "available.")); +} + +void BetaProgramDialog::setInstabilityMessage() const { + _acknowledgmentLabel->setText(tr("Beta versions may leave unexpectedly or cause instabilities.")); +} + +} // namespace KDC diff --git a/src/gui/betaprogramdialog.h b/src/gui/betaprogramdialog.h new file mode 100644 index 000000000..9ebe25c9e --- /dev/null +++ b/src/gui/betaprogramdialog.h @@ -0,0 +1,62 @@ +/* + * Infomaniak kDrive - Desktop + * Copyright (C) 2023-2024 Infomaniak Network SA + * + * 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 . + */ + +#pragma once + +#include "customcombobox.h" +#include "customdialog.h" + +#include "utility/types.h" + + +class QLabel; +class QCheckBox; + +namespace KDC { + +class CustomComboBox; + +class BetaProgramDialog final : public CustomDialog { + Q_OBJECT + + public: + explicit BetaProgramDialog(bool isQuit, bool isStaff, QWidget *parent = nullptr); + + [[nodiscard]] DistributionChannel selectedDistributionChannel() const { return _newChannel; } + + private slots: + void onAcknowledgment(); + void onSave(); + void onChannelChange(int index); + + private: + void setTooRecentMessage() const; + void setInstabilityMessage() const; + + bool _isQuit{false}; + bool _isStaff{false}; + DistributionChannel _newChannel{DistributionChannel::Unknown}; + QCheckBox *_acknowledgmentCheckbox{nullptr}; + QFrame *_acknowledgmentFrame{nullptr}; + QLabel *_acknowledgmentLabel{nullptr}; + CustomComboBox *_staffSelectionBox{nullptr}; + QPushButton *_saveButton{nullptr}; + int _initialIndex{0}; +}; + +} // namespace KDC diff --git a/src/gui/clientgui.cpp b/src/gui/clientgui.cpp index a22a1fc58..3cbcb79ab 100644 --- a/src/gui/clientgui.cpp +++ b/src/gui/clientgui.cpp @@ -456,9 +456,9 @@ void ClientGui::setupSynthesisPopover() { _workaroundManualVisibility = true; #endif - qCInfo(lcClientGui) << "Tray menu workarounds:" << "noabouttoshow:" << _workaroundNoAboutToShowUpdate - << "fakedoubleclick:" << _workaroundFakeDoubleClick << "showhide:" << _workaroundShowAndHideTray - << "manualvisibility:" << _workaroundManualVisibility; + qCInfo(lcClientGui) << "Tray menu workarounds:" + << "noabouttoshow:" << _workaroundNoAboutToShowUpdate << "fakedoubleclick:" << _workaroundFakeDoubleClick + << "showhide:" << _workaroundShowAndHideTray << "manualvisibility:" << _workaroundManualVisibility; connect(&_delayedTrayUpdateTimer, &QTimer::timeout, this, &ClientGui::onUpdateSystray); _delayedTrayUpdateTimer.setInterval(2 * 1000); @@ -758,7 +758,7 @@ void ClientGui::onNewDriveWizard() { void ClientGui::onShowWindowsUpdateDialog(const VersionInfo &versionInfo) const { static std::mutex mutex; - std::unique_lock lock(mutex, std::try_to_lock); + const std::unique_lock lock(mutex, std::try_to_lock); if (!lock.owns_lock()) return; if (UpdateDialog dialog(versionInfo); dialog.exec() == QDialog::Accepted) { GuiRequests::startInstaller(); @@ -1016,6 +1016,7 @@ void ClientGui::onUserUpdated(const UserInfo &userInfo) { if (userInfo.connected()) { userInfoMapIt->second.setCredentialsAsked(false); } + userInfoMapIt->second.setIsStaff(userInfo.isStaff()); emit userListRefreshed(); } } diff --git a/src/gui/customdialog.cpp b/src/gui/customdialog.cpp index 15781f19e..2d72134d0 100644 --- a/src/gui/customdialog.cpp +++ b/src/gui/customdialog.cpp @@ -47,7 +47,7 @@ static const int resizeStripeWidth = 5; Q_LOGGING_CATEGORY(lcCustomDialog, "gui.customdialog", QtInfoMsg) -CustomDialog::CustomDialog(bool popup, QWidget *parent) : +CustomDialog::CustomDialog(const bool popup, QWidget *parent) : QDialog(parent), _backgroundColor(QColor()), _buttonIconColor(QColor()), _backgroundForcedColor(QColor()), _layout(nullptr) { setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint); setAttribute(Qt::WA_TranslucentBackground); diff --git a/src/gui/fixconflictingfilesdialog.cpp b/src/gui/fixconflictingfilesdialog.cpp index d64dd911a..1f0cbcfed 100644 --- a/src/gui/fixconflictingfilesdialog.cpp +++ b/src/gui/fixconflictingfilesdialog.cpp @@ -64,8 +64,7 @@ void FixConflictingFilesDialog::onLinkActivated(const QString &link) { } } else { if (!QDesktopServices::openUrl(QUrl(link))) { - CustomMessageBox msgBox(QMessageBox::Warning, tr("Unable to open link %1.").arg(link), - QMessageBox::Ok, this); + CustomMessageBox msgBox(QMessageBox::Warning, tr("Unable to open link %1.").arg(link), QMessageBox::Ok, this); msgBox.exec(); } } diff --git a/src/gui/guirequests.cpp b/src/gui/guirequests.cpp index 18a791a78..e0f06e990 100644 --- a/src/gui/guirequests.cpp +++ b/src/gui/guirequests.cpp @@ -1212,9 +1212,14 @@ ExitCode GuiRequests::changeDistributionChannel(const DistributionChannel channe return ExitCode::Ok; } -ExitCode GuiRequests::versionInfo(VersionInfo &versionInfo) { +ExitCode GuiRequests::versionInfo(VersionInfo &versionInfo, + const DistributionChannel channel /*= DistributionChannel::Unknown*/) { + QByteArray params; + QDataStream paramsStream(¶ms, QIODevice::WriteOnly); + paramsStream << channel; + QByteArray results; - if (!CommClient::instance()->execute(RequestNum::UPDATER_VERSION_INFO, {}, results)) { + if (!CommClient::instance()->execute(RequestNum::UPDATER_VERSION_INFO, params, results)) { return ExitCode::SystemError; } diff --git a/src/gui/guirequests.h b/src/gui/guirequests.h index ee2327462..2cdec1fdf 100644 --- a/src/gui/guirequests.h +++ b/src/gui/guirequests.h @@ -129,7 +129,7 @@ struct GuiRequests { static ExitCode crash(); static ExitCode changeDistributionChannel(DistributionChannel channel); - static ExitCode versionInfo(VersionInfo &versionInfo); + static ExitCode versionInfo(VersionInfo &versionInfo, DistributionChannel channel = DistributionChannel::Unknown); static ExitCode updateState(UpdateState &state); static ExitCode startInstaller(); static ExitCode skipUpdate(const std::string &version); diff --git a/src/gui/parameterscache.h b/src/gui/parameterscache.h index c018ebcc4..62bc36876 100644 --- a/src/gui/parameterscache.h +++ b/src/gui/parameterscache.h @@ -27,12 +27,12 @@ namespace KDC { class ParametersCache { public: static std::shared_ptr instance() noexcept; - inline static bool isExtendedLogEnabled() noexcept { return instance()->_parametersInfo.extendedLog(); }; + static bool isExtendedLogEnabled() noexcept { return instance()->_parametersInfo.extendedLog(); }; ParametersCache(ParametersCache const &) = delete; void operator=(ParametersCache const &) = delete; - inline ParametersInfo ¶metersInfo() { return _parametersInfo; } + ParametersInfo ¶metersInfo() { return _parametersInfo; } bool saveParametersInfo(bool displayMessageBoxOnError = true); private: diff --git a/src/gui/preferenceswidget.cpp b/src/gui/preferenceswidget.cpp index dd6c2867e..bd49e7f10 100644 --- a/src/gui/preferenceswidget.cpp +++ b/src/gui/preferenceswidget.cpp @@ -423,7 +423,7 @@ void PreferencesWidget::showErrorBanner(const bool unresolvedErrors) const { void PreferencesWidget::showEvent(QShowEvent *event) { Q_UNUSED(event) retranslateUi(); - _versionWidget->refresh(); + _versionWidget->refresh(isStaff()); } void PreferencesWidget::clearUndecidedLists() { @@ -441,6 +441,11 @@ void PreferencesWidget::clearUndecidedLists() { } } +bool PreferencesWidget::isStaff() const { + constexpr auto isStaffCallback = [](std::pair const &item) { return item.second.isStaff(); }; + return std::ranges::find_if(_gui->userInfoMap(), isStaffCallback) != _gui->userInfoMap().end(); +} + void PreferencesWidget::onFolderConfirmationSwitchClicked(bool checked) { ParametersCache::instance()->parametersInfo().setUseBigFolderSizeLimit(checked); if (!ParametersCache::instance()->saveParametersInfo()) { @@ -501,7 +506,7 @@ void PreferencesWidget::onLanguageChange() { CommonUtility::setupTranslations(QApplication::instance(), language); retranslateUi(); - _versionWidget->refresh(); + _versionWidget->refresh(isStaff()); } void PreferencesWidget::onMoveToTrashSwitchClicked(bool checked) { diff --git a/src/gui/preferenceswidget.h b/src/gui/preferenceswidget.h index fb228b87b..5769efe66 100644 --- a/src/gui/preferenceswidget.h +++ b/src/gui/preferenceswidget.h @@ -99,6 +99,8 @@ class PreferencesWidget : public LargeWidgetWithCustomToolTip { void clearUndecidedLists(); + [[nodiscard]] bool isStaff() const; + private slots: void onFolderConfirmationSwitchClicked(bool checked = false); void onFolderConfirmationAmountTextEdited(const QString &text); diff --git a/src/gui/synthesispopover.cpp b/src/gui/synthesispopover.cpp index 37c6598b2..55268a5d7 100644 --- a/src/gui/synthesispopover.cpp +++ b/src/gui/synthesispopover.cpp @@ -963,9 +963,7 @@ void SynthesisPopover::onItemCompleted(int syncDbId, const SyncFileItemInfo &ite } } -void SynthesisPopover::onOpenErrorsMenu(bool checked) { - Q_UNUSED(checked) - +void SynthesisPopover::onOpenErrorsMenu() { QList driveErrorList; getDriveErrorList(driveErrorList); @@ -1080,7 +1078,7 @@ void SynthesisPopover::onUpdateAvailabilityChange(const UpdateState updateState) default: _lockedAppUpdateButton->setText(tr("Unavailable")); sentry::Handler::captureMessage(sentry::Level::Fatal, "AppLocked", - "HTTP Error426 received but unable to fetch an update"); + "HTTP Error426 received but unable to fetch an update"); break; } } @@ -1110,16 +1108,12 @@ void SynthesisPopover::onRefreshErrorList(int /*driveDbId*/) { refreshErrorsButton(); } -void SynthesisPopover::onOpenFolder(bool checked) { - Q_UNUSED(checked) - +void SynthesisPopover::onOpenFolder() { int syncDbId = qvariant_cast(sender()->property(MenuWidget::actionTypeProperty.c_str())); openUrl(syncDbId); } -void SynthesisPopover::onOpenWebview(bool checked) { - Q_UNUSED(checked) - +void SynthesisPopover::onOpenWebview() { if (_gui->currentDriveDbId() != 0) { const auto driveInfoIt = _gui->driveInfoMap().find(_gui->currentDriveDbId()); if (driveInfoIt == _gui->driveInfoMap().end()) { @@ -1143,17 +1137,15 @@ void SynthesisPopover::onOpenWebview(bool checked) { } } -void SynthesisPopover::onOpenMiscellaneousMenu(bool checked) { - Q_UNUSED(checked) - - MenuWidget *menu = new MenuWidget(MenuWidget::Menu, this); +void SynthesisPopover::onOpenMiscellaneousMenu() { + auto *menu = new MenuWidget(MenuWidget::Menu, this); // Open Folder std::map syncInfoMap; _gui->loadSyncInfoMap(_gui->currentDriveDbId(), syncInfoMap); - if (syncInfoMap.size() >= 1) { - QWidgetAction *foldersMenuAction = new QWidgetAction(this); - MenuItemWidget *foldersMenuItemWidget = new MenuItemWidget(tr("Open in folder")); + if (!syncInfoMap.empty()) { + auto *foldersMenuAction = new QWidgetAction(this); + auto *foldersMenuItemWidget = new MenuItemWidget(tr("Open in folder")); foldersMenuItemWidget->setLeftIcon(":/client/resources/icons/actions/folder.svg"); foldersMenuAction->setDefaultWidget(foldersMenuItemWidget); @@ -1165,16 +1157,15 @@ void SynthesisPopover::onOpenMiscellaneousMenu(bool checked) { foldersMenuItemWidget->setHasSubmenu(true); // Open folders submenu - MenuWidget *submenu = new MenuWidget(MenuWidget::Submenu, menu); + auto *submenu = new MenuWidget(MenuWidget::Submenu, menu); - QActionGroup *openFolderActionGroup = new QActionGroup(this); + auto *openFolderActionGroup = new QActionGroup(this); openFolderActionGroup->setExclusive(true); - QWidgetAction *openFolderAction; - for (auto const &syncInfoMapElt: syncInfoMap) { - openFolderAction = new QWidgetAction(this); - openFolderAction->setProperty(MenuWidget::actionTypeProperty.c_str(), syncInfoMapElt.first); - MenuItemWidget *openFolderMenuItemWidget = new MenuItemWidget(syncInfoMapElt.second.name()); + for (auto const &[syncId, syncInfo]: syncInfoMap) { + auto *openFolderAction = new QWidgetAction(this); + openFolderAction->setProperty(MenuWidget::actionTypeProperty.c_str(), syncId); + auto *openFolderMenuItemWidget = new MenuItemWidget(syncInfo.name()); openFolderMenuItemWidget->setLeftIcon(":/client/resources/icons/actions/folder.svg"); openFolderAction->setDefaultWidget(openFolderMenuItemWidget); connect(openFolderAction, &QWidgetAction::triggered, this, &SynthesisPopover::onOpenFolder); @@ -1189,8 +1180,8 @@ void SynthesisPopover::onOpenMiscellaneousMenu(bool checked) { } // Open web version - QWidgetAction *driveOpenWebViewAction = new QWidgetAction(this); - MenuItemWidget *driveOpenWebViewMenuItemWidget = new MenuItemWidget(tr("Open %1 web version").arg(APPLICATION_SHORTNAME)); + auto *driveOpenWebViewAction = new QWidgetAction(this); + auto *driveOpenWebViewMenuItemWidget = new MenuItemWidget(tr("Open %1 web version").arg(APPLICATION_SHORTNAME)); driveOpenWebViewMenuItemWidget->setLeftIcon(":/client/resources/icons/actions/webview.svg"); driveOpenWebViewAction->setDefaultWidget(driveOpenWebViewMenuItemWidget); driveOpenWebViewAction->setVisible(_gui->currentDriveDbId() != 0); @@ -1198,9 +1189,9 @@ void SynthesisPopover::onOpenMiscellaneousMenu(bool checked) { menu->addAction(driveOpenWebViewAction); // Drive parameters - if (_gui->driveInfoMap().size()) { - QWidgetAction *driveParametersAction = new QWidgetAction(this); - MenuItemWidget *driveParametersMenuItemWidget = new MenuItemWidget(tr("Drive parameters")); + if (!_gui->driveInfoMap().empty()) { + auto *driveParametersAction = new QWidgetAction(this); + auto *driveParametersMenuItemWidget = new MenuItemWidget(tr("Drive parameters")); driveParametersMenuItemWidget->setLeftIcon(":/client/resources/icons/actions/drive.svg"); driveParametersAction->setDefaultWidget(driveParametersMenuItemWidget); connect(driveParametersAction, &QWidgetAction::triggered, this, &SynthesisPopover::onOpenDriveParameters); @@ -1208,10 +1199,10 @@ void SynthesisPopover::onOpenMiscellaneousMenu(bool checked) { } // Disable Notifications - QWidgetAction *notificationsMenuAction = new QWidgetAction(this); - bool notificationAlreadyDisabledForPeriod = + auto *notificationsMenuAction = new QWidgetAction(this); + const auto notificationAlreadyDisabledForPeriod = _notificationsDisabled != NotificationsDisabled::Never && _notificationsDisabled != NotificationsDisabled::Always; - MenuItemWidget *notificationsMenuItemWidget = + auto *notificationsMenuItemWidget = new MenuItemWidget(notificationAlreadyDisabledForPeriod ? tr("Notifications disabled until %1").arg(_notificationsDisabledUntilDateTime.toString()) : tr("Disable Notifications")); @@ -1221,9 +1212,9 @@ void SynthesisPopover::onOpenMiscellaneousMenu(bool checked) { menu->addAction(notificationsMenuAction); // Disable Notifications submenu - MenuWidget *submenu = new MenuWidget(MenuWidget::Submenu, menu); + auto *submenu = new MenuWidget(MenuWidget::Submenu, menu); - QActionGroup *notificationActionGroup = new QActionGroup(this); + auto *notificationActionGroup = new QActionGroup(this); notificationActionGroup->setExclusive(true); const std::map ¬ificationMap = @@ -1231,13 +1222,12 @@ void SynthesisPopover::onOpenMiscellaneousMenu(bool checked) { ? _notificationsDisabledMap : _notificationsDisabledForPeriodMap; - QWidgetAction *notificationAction; - for (auto const ¬ificationMapElt: notificationMap) { - notificationAction = new QWidgetAction(this); - notificationAction->setProperty(MenuWidget::actionTypeProperty.c_str(), toInt(notificationMapElt.first)); - QString text = QCoreApplication::translate("KDC::SynthesisPopover", notificationMapElt.second.toStdString().c_str()); - MenuItemWidget *notificationMenuItemWidget = new MenuItemWidget(text); - notificationMenuItemWidget->setChecked(notificationMapElt.first == _notificationsDisabled); + for (auto const &[notifDisabled, str]: notificationMap) { + auto *notificationAction = new QWidgetAction(this); + notificationAction->setProperty(MenuWidget::actionTypeProperty.c_str(), toInt(notifDisabled)); + QString text = QCoreApplication::translate("KDC::SynthesisPopover", str.toStdString().c_str()); + auto *notificationMenuItemWidget = new MenuItemWidget(text); + notificationMenuItemWidget->setChecked(notifDisabled == _notificationsDisabled); notificationAction->setDefaultWidget(notificationMenuItemWidget); connect(notificationAction, &QWidgetAction::triggered, this, &SynthesisPopover::onNotificationActionTriggered); notificationActionGroup->addAction(notificationAction); @@ -1247,24 +1237,32 @@ void SynthesisPopover::onOpenMiscellaneousMenu(bool checked) { notificationsMenuAction->setMenu(submenu); // Application preferences - QWidgetAction *preferencesAction = new QWidgetAction(this); - MenuItemWidget *preferencesMenuItemWidget = new MenuItemWidget(tr("Application preferences")); + auto *preferencesAction = new QWidgetAction(this); + auto *preferencesMenuItemWidget = new MenuItemWidget(tr("Application preferences")); preferencesMenuItemWidget->setLeftIcon(":/client/resources/icons/actions/parameters.svg"); preferencesAction->setDefaultWidget(preferencesMenuItemWidget); connect(preferencesAction, &QWidgetAction::triggered, this, &SynthesisPopover::onOpenPreferences); menu->addAction(preferencesAction); // Help - QWidgetAction *helpAction = new QWidgetAction(this); - MenuItemWidget *helpMenuItemWidget = new MenuItemWidget(tr("Need help")); + auto *helpAction = new QWidgetAction(this); + auto *helpMenuItemWidget = new MenuItemWidget(tr("Need help")); helpMenuItemWidget->setLeftIcon(":/client/resources/icons/actions/help.svg"); helpAction->setDefaultWidget(helpMenuItemWidget); connect(helpAction, &QWidgetAction::triggered, this, &SynthesisPopover::onDisplayHelp); menu->addAction(helpAction); + // Send feedbacks + auto *feedbacksAction = new QWidgetAction(this); + auto *feedbacksMenuItemWidget = new MenuItemWidget(tr("Send feedbacks")); + feedbacksMenuItemWidget->setLeftIcon(":/client/resources/icons/actions/messages-bubble-square-typing.svg"); + feedbacksAction->setDefaultWidget(feedbacksMenuItemWidget); + connect(feedbacksAction, &QWidgetAction::triggered, this, &SynthesisPopover::onSendFeedback); + menu->addAction(feedbacksAction); + // Quit - QWidgetAction *exitAction = new QWidgetAction(this); - MenuItemWidget *exitMenuItemWidget = new MenuItemWidget(tr("Quit kDrive")); + auto *exitAction = new QWidgetAction(this); + auto *exitMenuItemWidget = new MenuItemWidget(tr("Quit kDrive")); exitMenuItemWidget->setLeftIcon(":/client/resources/icons/actions/error-sync.svg"); exitAction->setDefaultWidget(exitMenuItemWidget); connect(exitAction, &QWidgetAction::triggered, this, &SynthesisPopover::onExit); @@ -1272,29 +1270,29 @@ void SynthesisPopover::onOpenMiscellaneousMenu(bool checked) { if (_debugCrash) { // Emulate a crash - QWidgetAction *crashAction = new QWidgetAction(this); - MenuItemWidget *crashMenuItemWidget = new MenuItemWidget("Emulate a crash"); + auto *crashAction = new QWidgetAction(this); + auto *crashMenuItemWidget = new MenuItemWidget("Emulate a crash"); crashAction->setDefaultWidget(crashMenuItemWidget); connect(crashAction, &QWidgetAction::triggered, this, &SynthesisPopover::onCrash); menu->addAction(crashAction); // Emulate a server crash - QWidgetAction *crashServerAction = new QWidgetAction(this); - MenuItemWidget *crashServerMenuItemWidget = new MenuItemWidget("Emulate a server crash"); + auto *crashServerAction = new QWidgetAction(this); + auto *crashServerMenuItemWidget = new MenuItemWidget("Emulate a server crash"); crashServerAction->setDefaultWidget(crashServerMenuItemWidget); connect(crashServerAction, &QWidgetAction::triggered, this, &SynthesisPopover::onCrashServer); menu->addAction(crashServerAction); // Emulate an ENFORCE crash - QWidgetAction *crashEnforceAction = new QWidgetAction(this); - MenuItemWidget *crashEnforceMenuItemWidget = new MenuItemWidget("Emulate an ENFORCE crash"); + auto *crashEnforceAction = new QWidgetAction(this); + auto *crashEnforceMenuItemWidget = new MenuItemWidget("Emulate an ENFORCE crash"); crashEnforceAction->setDefaultWidget(crashEnforceMenuItemWidget); connect(crashEnforceAction, &QWidgetAction::triggered, this, &SynthesisPopover::onCrashEnforce); menu->addAction(crashEnforceAction); // Emulate a qFatal crash - QWidgetAction *crashFatalAction = new QWidgetAction(this); - MenuItemWidget *crashFatalMenuItemWidget = new MenuItemWidget("Emulate a qFatal crash"); + auto *crashFatalAction = new QWidgetAction(this); + auto *crashFatalMenuItemWidget = new MenuItemWidget("Emulate a qFatal crash"); crashFatalAction->setDefaultWidget(crashFatalMenuItemWidget); connect(crashFatalAction, &QWidgetAction::triggered, this, &SynthesisPopover::onCrashFatal); menu->addAction(crashFatalAction); @@ -1303,52 +1301,41 @@ void SynthesisPopover::onOpenMiscellaneousMenu(bool checked) { menu->exec(QWidget::mapToGlobal(_menuButton->geometry().center())); } -void SynthesisPopover::onOpenPreferences(bool checked) { - Q_UNUSED(checked) - +void SynthesisPopover::onOpenPreferences() { emit showParametersDialog(); } -void SynthesisPopover::onDisplayHelp(bool checked) { - Q_UNUSED(checked) - +void SynthesisPopover::onDisplayHelp() { QDesktopServices::openUrl(QUrl(Theme::instance()->helpUrl())); } -void SynthesisPopover::onExit(bool checked) { - Q_UNUSED(checked) +void SynthesisPopover::onSendFeedback() { + const auto url = QUrl(Theme::instance()->feedbackUrl(ParametersCache::instance()->parametersInfo().language())); + QDesktopServices::openUrl(url); +} +void SynthesisPopover::onExit() { hide(); emit exit(); } -void SynthesisPopover::onCrash(bool checked) { - Q_UNUSED(checked) - +void SynthesisPopover::onCrash() { emit crash(); } -void SynthesisPopover::onCrashServer(bool checked) { - Q_UNUSED(checked) - +void SynthesisPopover::onCrashServer() { emit crashServer(); } -void SynthesisPopover::onCrashEnforce(bool checked) { - Q_UNUSED(checked) - +void SynthesisPopover::onCrashEnforce() { emit crashEnforce(); } -void SynthesisPopover::onCrashFatal(bool checked) { - Q_UNUSED(checked) - +void SynthesisPopover::onCrashFatal() { emit crashFatal(); } -void SynthesisPopover::onNotificationActionTriggered(bool checked) { - Q_UNUSED(checked) - +void SynthesisPopover::onNotificationActionTriggered() { bool notificationAlreadyDisabledForPeriod = _notificationsDisabled != NotificationsDisabled::Never && _notificationsDisabled != NotificationsDisabled::Always; @@ -1383,9 +1370,7 @@ void SynthesisPopover::onNotificationActionTriggered(bool checked) { emit disableNotifications(_notificationsDisabled, _notificationsDisabledUntilDateTime); } -void SynthesisPopover::onOpenDriveParameters(bool checked) { - Q_UNUSED(checked) - +void SynthesisPopover::onOpenDriveParameters() { emit showParametersDialog(_gui->currentDriveDbId()); } diff --git a/src/gui/synthesispopover.h b/src/gui/synthesispopover.h index e97a75567..c9ad1319e 100644 --- a/src/gui/synthesispopover.h +++ b/src/gui/synthesispopover.h @@ -148,20 +148,21 @@ class SynthesisPopover : public QDialog { void handleRemovedDrives(); private slots: - void onOpenErrorsMenu(bool checked = false); + void onOpenErrorsMenu(); void onDisplayErrors(int syncDbId); - void onOpenFolder(bool checked); - void onOpenWebview(bool checked); - void onOpenMiscellaneousMenu(bool checked); - void onOpenPreferences(bool checked = false); - void onNotificationActionTriggered(bool checked = false); - void onOpenDriveParameters(bool checked = false); - void onDisplayHelp(bool checked = false); - void onExit(bool checked = false); - void onCrash(bool checked = false); - void onCrashServer(bool checked = false); - void onCrashEnforce(bool checked = false); - void onCrashFatal(bool checked = false); + void onOpenFolder(); + void onOpenWebview(); + void onOpenMiscellaneousMenu(); + void onOpenPreferences(); + void onNotificationActionTriggered(); + void onOpenDriveParameters(); + void onDisplayHelp(); + void onSendFeedback(); + void onExit(); + void onCrash(); + void onCrashServer(); + void onCrashEnforce(); + void onCrashFatal(); void onDriveSelected(int driveDbId); void onAddDrive(); void onPauseSync(ActionTarget target, int syncDbId = 0); diff --git a/src/gui/taglabel.cpp b/src/gui/taglabel.cpp new file mode 100644 index 000000000..b5bbb1187 --- /dev/null +++ b/src/gui/taglabel.cpp @@ -0,0 +1,78 @@ +/* + * Infomaniak kDrive - Desktop + * Copyright (C) 2023-2024 Infomaniak Network SA + * + * 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 . + */ + +#include "taglabel.h" + +#include "guiutility.h" + +#include +#include +#include +#include + +namespace KDC { + +static constexpr int hMargin = 4; +#ifdef __APPLE__ +static constexpr int offset = 0; +#else +static constexpr int offset = 2; +#endif + +TagLabel::TagLabel(const QColor &color /*= Qt::transparent*/, QWidget *parent /*= nullptr*/) : + QLabel(parent), _backgroundColor(color) { + setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + setAlignment(Qt::AlignCenter); +} + + +QSize TagLabel::sizeHint() const { + const QFontMetrics fm(_customFont); + const auto textSize = fm.size(Qt::TextSingleLine, text()); + return {textSize.width() + 2 * hMargin + offset, textSize.height() + offset}; +} + +void TagLabel::paintEvent(QPaintEvent *event) { + Q_UNUSED(event) + + // Update shadow color + if (auto *effect = qobject_cast(graphicsEffect())) { + effect->setColor(GuiUtility::getShadowColor()); + } + + // Draw round rectangle + QPainterPath painterPath; + painterPath.addRoundedRect(rect(), rect().height() / 2, rect().height() / 2); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setPen(Qt::NoPen); + painter.setBrush(backgroundColor()); + painter.drawPath(painterPath); + + // Draw text + painter.setPen(QColor(255, 255, 255)); + QTextOption textOption; + textOption.setAlignment(Qt::AlignCenter); + painter.setFont(_customFont); + QRect tmp = rect(); + tmp.translate(0, offset); + painter.drawText(tmp, text(), textOption); +} + +} // namespace KDC diff --git a/src/gui/taglabel.h b/src/gui/taglabel.h new file mode 100644 index 000000000..3db8bfe48 --- /dev/null +++ b/src/gui/taglabel.h @@ -0,0 +1,45 @@ +/* + * Infomaniak kDrive - Desktop + * Copyright (C) 2023-2024 Infomaniak Network SA + * + * 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 . + */ + +#pragma once + +#include + +namespace KDC { + +class TagLabel final : public QLabel { + Q_OBJECT + + public: + explicit TagLabel(const QColor &color = Qt::transparent, QWidget *parent = nullptr); + + [[nodiscard]] QColor backgroundColor() const { return _backgroundColor; } + void setBackgroundColor(const QColor &value) { _backgroundColor = value; } + + [[nodiscard]] const QFont &setCustomFont() const { return _customFont; } + void customFont(const QFont &font) { _customFont = font; } + + private: + [[nodiscard]] QSize sizeHint() const override; + void paintEvent(QPaintEvent *event) override; + + QColor _backgroundColor{Qt::transparent}; + QFont _customFont{"Suisse Int'l", 12}; +}; + +} // namespace KDC diff --git a/src/gui/versionwidget.cpp b/src/gui/versionwidget.cpp index 8d8049fb8..5bf2f67ed 100644 --- a/src/gui/versionwidget.cpp +++ b/src/gui/versionwidget.cpp @@ -22,11 +22,12 @@ #include "enablestateholder.h" #include "guirequests.h" #include "guiutility.h" +#include "betaprogramdialog.h" #include "parameterscache.h" #include "preferencesblocwidget.h" -#include "utility/utility.h" -#include "utility/widgetsignalblocker.h" +#include "taglabel.h" #include "libcommon/utility/utility.h" +#include "utility/utility.h" #include #include @@ -43,6 +44,12 @@ static const QString versionLink = "versionLink"; static const QString releaseNoteLink = "releaseNoteLink"; static const QString downloadPageLink = "downloadPageLink"; +static constexpr int statusLayoutSpacing = 8; +static constexpr auto betaTagColor = QColor(214, 56, 100); +static constexpr auto internalTagColor = QColor(120, 116, 176); + +Q_LOGGING_CATEGORY(lcVersionWidget, "gui.versionwidget", QtInfoMsg) + VersionWidget::VersionWidget(QWidget *parent /*= nullptr*/) : QWidget(parent) { const auto mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); @@ -50,67 +57,94 @@ VersionWidget::VersionWidget(QWidget *parent /*= nullptr*/) : QWidget(parent) { _versionLabel = new QLabel(this); _versionLabel->setObjectName("blocLabel"); - layout()->addWidget(_versionLabel); + mainLayout->addWidget(_versionLabel); - const auto versionBloc = new PreferencesBlocWidget(); - layout()->addWidget(versionBloc); - QBoxLayout *versionBox = versionBloc->addLayout(QBoxLayout::Direction::LeftToRight); - const auto versionVBox = new QVBoxLayout(); - versionVBox->setContentsMargins(0, 0, 0, 0); - versionVBox->setSpacing(1); - versionBox->addLayout(versionVBox); - versionBox->setStretchFactor(versionVBox, 1); + const auto prefBloc = new PreferencesBlocWidget(); + mainLayout->addWidget(prefBloc); - _updateStatusLabel = new QLabel(this); - _updateStatusLabel->setObjectName("boldTextLabel"); - _updateStatusLabel->setWordWrap(true); - versionVBox->addWidget(_updateStatusLabel); - - // TODO : add it back later (version 3.6.8 or 4.0) - // const auto channelBox = new QHBoxLayout(this); - // _prodButton = new QRadioButton(tr("Prod"), this); - // channelBox->addWidget(_prodButton); - // channelBox->addStretch(); - // _betaButton = new QRadioButton(tr("Beta"), this); - // channelBox->addWidget(_betaButton); - // channelBox->addStretch(); - // _internalButton = new QRadioButton(tr("Internal"), this); - // channelBox->addWidget(_internalButton); - // channelBox->addStretch(); - // versionVBox->addLayout(channelBox); - - _showReleaseNotesLabel = new QLabel(this); - _showReleaseNotesLabel->setObjectName("boldTextLabel"); - _showReleaseNotesLabel->setWordWrap(true); - _showReleaseNotesLabel->setVisible(false); - versionVBox->addWidget(_showReleaseNotesLabel); - - static const QString versionNumberLinkText = - tr(R"(%3)").arg(CommonUtility::linkStyle, versionLink, KDRIVE_VERSION_STRING); - _versionNumberLabel = new QLabel(this); - _versionNumberLabel->setContextMenuPolicy(Qt::PreventContextMenu); - _versionNumberLabel->setText(versionNumberLinkText); - versionVBox->addWidget(_versionNumberLabel); - - const auto copyrightLabel = new QLabel(QString("Copyright %1").arg(APPLICATION_VENDOR)); - copyrightLabel->setObjectName("description"); - versionVBox->addWidget(copyrightLabel); - - _updateButton = new QPushButton(this); - _updateButton->setObjectName("defaultbutton"); - _updateButton->setFlat(true); - versionBox->addWidget(_updateButton); + initVersionInfoBloc(prefBloc); + prefBloc->addSeparator(); + initBetaBloc(prefBloc); refresh(); - // TODO : add it back later (version 3.6.8 or 4.0) - // connect(_prodButton, &QRadioButton::clicked, this, &VersionWidget::onChannelButtonClicked); - // connect(_betaButton, &QRadioButton::clicked, this, &VersionWidget::onChannelButtonClicked); - // connect(_internalButton, &QRadioButton::clicked, this, &VersionWidget::onChannelButtonClicked); connect(_updateStatusLabel, &QLabel::linkActivated, this, &VersionWidget::onLinkActivated); connect(_versionNumberLabel, &QLabel::linkActivated, this, &VersionWidget::onLinkActivated); connect(_showReleaseNotesLabel, &QLabel::linkActivated, this, &VersionWidget::onLinkActivated); connect(_updateButton, &QPushButton::clicked, this, &VersionWidget::onUpdateButtonClicked); + connect(_joinBetaButton, &QPushButton::clicked, this, &VersionWidget::onJoinBetaButtonClicked); +} + +void VersionWidget::refresh(const bool isStaff) { + _isStaff = isStaff; + refresh(); +} + +void VersionWidget::showAboutDialog() { + EnableStateHolder _(this); + AboutDialog dialog(this); + dialog.execAndMoveToCenter(GuiUtility::getTopLevelWidget(this)); +} + +void VersionWidget::showReleaseNotes() const { + QString os; +#if defined(__APPLE__) + os = "macos"; +#elif defined(_WIN32) + os = "win"; +#else + os = "linux"; +#endif + + VersionInfo versionInfo; + GuiRequests::versionInfo(versionInfo); + + const Language &appLanguage = ParametersCache::instance()->parametersInfo().language(); + QString languageCode = CommonUtility::languageCode(appLanguage); + if (languageCode.isEmpty()) languageCode = "en"; + QDesktopServices::openUrl( + QUrl(QString("%1-%2-%3-%4.html") + .arg(APPLICATION_STORAGE_URL, versionInfo.fullVersion().c_str(), os, languageCode.left(2)))); +} + +void VersionWidget::showDownloadPage() const { + QDesktopServices::openUrl(QUrl(APPLICATION_DOWNLOAD_URL)); +} + +void VersionWidget::onUpdateStateChanged(const UpdateState state) const { + refresh(state); +} + +void VersionWidget::onLinkActivated(const QString &link) { + if (link == versionLink) + showAboutDialog(); + else if (link == releaseNoteLink) + showReleaseNotes(); + else if (link == downloadPageLink) + showDownloadPage(); + else { + qCWarning(lcVersionWidget) << "Unknown link clicked: " << link; + Q_ASSERT(false); + } +} + +void VersionWidget::onUpdateButtonClicked() { +#if defined(__APPLE__) + GuiRequests::startInstaller(); +#else + VersionInfo versionInfo; + GuiRequests::versionInfo(versionInfo); + emit showUpdateDialog(versionInfo); +#endif +} + +void VersionWidget::onJoinBetaButtonClicked() { + if (auto dialog = BetaProgramDialog( + ParametersCache::instance()->parametersInfo().distributionChannel() != DistributionChannel::Prod, _isStaff, this); + dialog.exec() == QDialog::Accepted) { + saveDistributionChannel(dialog.selectedDistributionChannel()); + refresh(); + } } void VersionWidget::refresh(UpdateState state /*= UpdateState::Unknown*/) const { @@ -179,97 +213,104 @@ void VersionWidget::refresh(UpdateState state /*= UpdateState::Unknown*/) const _updateStatusLabel->setText(statusString); _showReleaseNotesLabel->setVisible(showReleaseNote); _updateButton->setVisible(showUpdateButton); -} -void VersionWidget::showAboutDialog() { - EnableStateHolder _(this); - AboutDialog dialog(this); - dialog.execAndMoveToCenter(GuiUtility::getTopLevelWidget(this)); + // Beta version info + if (_betaVersionLabel) { + _betaVersionLabel->setText(tr("Beta program")); + _betaVersionDescription->setText(tr("Get early access to new versions of the application")); + + if (const auto channel = ParametersCache::instance()->parametersInfo().distributionChannel(); + channel == DistributionChannel::Prod) { + _joinBetaButton->setText(tr("Join")); + _betaTag->setVisible(false); + } else { + _joinBetaButton->setText(_isStaff ? tr("Modify") : tr("Quit")); + _betaTag->setVisible(true); + _betaTag->setBackgroundColor(channel == DistributionChannel::Beta ? betaTagColor : internalTagColor); + _betaTag->setText(channel == DistributionChannel::Beta ? "BETA" : "INTERNAL"); + } + } } -void VersionWidget::showReleaseNotes() const { - QString os; -#if defined(__APPLE__) - os = "macos"; -#elif defined(_WIN32) - os = "win"; -#else - os = "linux"; -#endif +void VersionWidget::initVersionInfoBloc(PreferencesBlocWidget *prefBloc) { + auto *versionLayout = prefBloc->addLayout(QBoxLayout::Direction::LeftToRight); - VersionInfo versionInfo; - GuiRequests::versionInfo(versionInfo); + auto *verticalLayout = new QVBoxLayout(this); + verticalLayout->setSpacing(1); + verticalLayout->setContentsMargins(0, 0, 0, 0); + versionLayout->addLayout(verticalLayout); - const Language &appLanguage = ParametersCache::instance()->parametersInfo().language(); - QString languageCode = CommonUtility::languageCode(appLanguage); - if (languageCode.isEmpty()) languageCode = "en"; - QDesktopServices::openUrl( - QUrl(QString("%1-%2-%3-%4.html") - .arg(APPLICATION_STORAGE_URL, versionInfo.fullVersion().c_str(), os, languageCode.left(2)))); -} + auto *statusLayout = new QHBoxLayout(this); + statusLayout->setSpacing(statusLayoutSpacing); + _updateStatusLabel = new QLabel(this); + _updateStatusLabel->setObjectName("boldTextLabel"); + statusLayout->addWidget(_updateStatusLabel); -void VersionWidget::showDownloadPage() const { - QDesktopServices::openUrl(QUrl(APPLICATION_DOWNLOAD_URL)); -} + _betaTag = new TagLabel(betaTagColor, this); + _betaTag->setText("BETA"); + _betaTag->setVisible(false); + statusLayout->addWidget(_betaTag); + statusLayout->addStretch(); + verticalLayout->addLayout(statusLayout); -void VersionWidget::onUpdateStateChanged(const UpdateState state) const { - refresh(state); -} + _showReleaseNotesLabel = new QLabel(this); + _showReleaseNotesLabel->setObjectName("boldTextLabel"); + _showReleaseNotesLabel->setWordWrap(true); + _showReleaseNotesLabel->setVisible(false); + verticalLayout->addWidget(_showReleaseNotesLabel); -void VersionWidget::onChannelButtonClicked() const { - auto channel = DistributionChannel::Unknown; - if (sender() == _prodButton) - channel = DistributionChannel::Prod; - else if (sender() == _betaButton) - channel = DistributionChannel::Beta; - else if (sender() == _internalButton) - channel = DistributionChannel::Internal; - else - return; + static const QString versionNumberLinkText = + tr(R"(%3)").arg(CommonUtility::linkStyle, versionLink, KDRIVE_VERSION_STRING); + _versionNumberLabel = new QLabel(this); + _versionNumberLabel->setContextMenuPolicy(Qt::PreventContextMenu); + _versionNumberLabel->setText(versionNumberLinkText); + verticalLayout->addWidget(_versionNumberLabel); - GuiRequests::changeDistributionChannel(channel); - refresh(); -} + const auto copyrightLabel = new QLabel(QString("Copyright %1").arg(APPLICATION_VENDOR)); + copyrightLabel->setObjectName("description"); + verticalLayout->addWidget(copyrightLabel); -void VersionWidget::onLinkActivated(const QString &link) { - if (link == versionLink) - showAboutDialog(); - else if (link == releaseNoteLink) - showReleaseNotes(); - else if (link == downloadPageLink) - showDownloadPage(); + _updateButton = new QPushButton(this); + _updateButton->setObjectName("defaultbutton"); + _updateButton->setFlat(true); + versionLayout->addWidget(_updateButton); } -void VersionWidget::onUpdateButtonClicked() { -#if defined(__APPLE__) - GuiRequests::startInstaller(); -#else - VersionInfo versionInfo; - GuiRequests::versionInfo(versionInfo); - emit showUpdateDialog(versionInfo); +void VersionWidget::initBetaBloc(PreferencesBlocWidget *prefBloc) { +#if defined(__unix__) && !defined(__APPLE__) + return; // Beta program is not available on Linux for now #endif + + auto *betaLayout = prefBloc->addLayout(QBoxLayout::Direction::LeftToRight); + + auto *verticalLayout = new QVBoxLayout(this); + verticalLayout->setSpacing(1); + verticalLayout->setContentsMargins(0, 0, 0, 0); + betaLayout->addLayout(verticalLayout); + + _betaVersionLabel = new QLabel(this); + _betaVersionLabel->setObjectName("boldTextLabel"); + _betaVersionLabel->setWordWrap(true); + verticalLayout->addWidget(_betaVersionLabel); + + _betaVersionDescription = new QLabel(this); + _betaVersionDescription->setObjectName("description"); + _betaVersionDescription->setWordWrap(true); + _betaVersionDescription->setMinimumWidth(300); + verticalLayout->addWidget(_betaVersionDescription); + + betaLayout->addStretch(); + + _joinBetaButton = new QPushButton(this); + _joinBetaButton->setObjectName("transparentbutton"); + _joinBetaButton->setFlat(true); + betaLayout->addWidget(_joinBetaButton); } -void VersionWidget::refreshChannelButtons(const DistributionChannel channel) const { - switch (channel) { - case DistributionChannel::Prod: { - WidgetSignalBlocker _(_prodButton); - _prodButton->setChecked(true); - break; - } - case DistributionChannel::Beta: { - WidgetSignalBlocker _(_betaButton); - _betaButton->setChecked(true); - break; - } - case DistributionChannel::Internal: { - WidgetSignalBlocker _(_internalButton); - _internalButton->setChecked(true); - break; - } - default: - break; - } +void VersionWidget::saveDistributionChannel(const DistributionChannel channel) const { + GuiRequests::changeDistributionChannel(channel); + ParametersCache::instance()->parametersInfo().setDistributionChannel(channel); + ParametersCache::instance()->saveParametersInfo(); } } // namespace KDC diff --git a/src/gui/versionwidget.h b/src/gui/versionwidget.h index cfa18b204..26e3e3ba7 100644 --- a/src/gui/versionwidget.h +++ b/src/gui/versionwidget.h @@ -35,13 +35,16 @@ class QLabel; class QBoxLayout; namespace KDC { +class TagLabel; + +class PreferencesBlocWidget; class VersionWidget final : public QWidget { Q_OBJECT public: explicit VersionWidget(QWidget *parent = nullptr); - void refresh(UpdateState state = UpdateState::Unknown) const; + void refresh(bool isStaff); void showAboutDialog(); void showReleaseNotes() const; @@ -54,22 +57,29 @@ class VersionWidget final : public QWidget { void onUpdateStateChanged(UpdateState state) const; private slots: - void onChannelButtonClicked() const; void onLinkActivated(const QString &link); void onUpdateButtonClicked(); + void onJoinBetaButtonClicked(); private: - void refreshChannelButtons(DistributionChannel channel) const; + void refresh(UpdateState state = UpdateState::Unknown) const; + void initVersionInfoBloc(PreferencesBlocWidget *prefBloc); + void initBetaBloc(PreferencesBlocWidget *prefBloc); + void saveDistributionChannel(DistributionChannel channel) const; - QRadioButton *_prodButton{nullptr}; - QRadioButton *_betaButton{nullptr}; - QRadioButton *_internalButton{nullptr}; + bool _isStaff{false}; QLabel *_versionLabel{nullptr}; + QLabel *_updateStatusLabel{nullptr}; + TagLabel *_betaTag{nullptr}; QLabel *_showReleaseNotesLabel{nullptr}; QLabel *_versionNumberLabel{nullptr}; QPushButton *_updateButton{nullptr}; + + QLabel *_betaVersionLabel{nullptr}; + QLabel *_betaVersionDescription{nullptr}; + QPushButton *_joinBetaButton{nullptr}; }; } // namespace KDC diff --git a/src/libcommon/info/parametersinfo.cpp b/src/libcommon/info/parametersinfo.cpp index 916157a78..8b01ea79d 100644 --- a/src/libcommon/info/parametersinfo.cpp +++ b/src/libcommon/info/parametersinfo.cpp @@ -42,7 +42,7 @@ QDataStream &operator>>(QDataStream &in, ParametersInfo ¶metersInfo) { parametersInfo._extendedLog >> parametersInfo._purgeOldLogs >> parametersInfo._syncHiddenFiles >> parametersInfo._useBigFolderSizeLimit >> parametersInfo._bigFolderSizeLimit >> parametersInfo._darkTheme >> parametersInfo._showShortcuts >> parametersInfo._dialogGeometry >> parametersInfo._maxAllowedCpu >> - parametersInfo._proxyConfigInfo; + parametersInfo._proxyConfigInfo >> parametersInfo._distributionChannel; return in; } @@ -52,7 +52,7 @@ QDataStream &operator<<(QDataStream &out, const ParametersInfo ¶metersInfo) << parametersInfo._extendedLog << parametersInfo._purgeOldLogs << parametersInfo._syncHiddenFiles << parametersInfo._useBigFolderSizeLimit << parametersInfo._bigFolderSizeLimit << parametersInfo._darkTheme << parametersInfo._showShortcuts << parametersInfo._dialogGeometry << parametersInfo._maxAllowedCpu - << parametersInfo._proxyConfigInfo; + << parametersInfo._proxyConfigInfo << parametersInfo._distributionChannel; return out; } diff --git a/src/libcommon/info/parametersinfo.h b/src/libcommon/info/parametersinfo.h index f41ebf871..9ffadbf4b 100644 --- a/src/libcommon/info/parametersinfo.h +++ b/src/libcommon/info/parametersinfo.h @@ -75,6 +75,8 @@ class ParametersInfo { inline const QMap &dialogGeometry() const { return _dialogGeometry; } inline int maxAllowedCpu() const { return _maxAllowedCpu; } inline void setMaxAllowedCpu(int maxAllowedCpu) { _maxAllowedCpu = maxAllowedCpu; } + [[nodiscard]] DistributionChannel distributionChannel() const { return _distributionChannel; } + void setDistributionChannel(const DistributionChannel channel) { _distributionChannel = channel; } friend QDataStream &operator>>(QDataStream &in, ParametersInfo ¶metersInfo); friend QDataStream &operator<<(QDataStream &out, const ParametersInfo ¶metersInfo); @@ -97,6 +99,7 @@ class ParametersInfo { bool _showShortcuts; QMap _dialogGeometry; int _maxAllowedCpu; + DistributionChannel _distributionChannel{DistributionChannel::Prod}; }; } // namespace KDC diff --git a/src/libcommon/info/userinfo.cpp b/src/libcommon/info/userinfo.cpp index cbc7103ce..778d6e562 100644 --- a/src/libcommon/info/userinfo.cpp +++ b/src/libcommon/info/userinfo.cpp @@ -20,26 +20,29 @@ namespace KDC { -UserInfo::UserInfo(int dbId, int userId, const QString &name, const QString &email, const QImage &avatar, bool connected) : +UserInfo::UserInfo(const int dbId, const int userId, const QString &name, const QString &email, const QImage &avatar, + const bool connected) : _dbId(dbId), _userId(userId), _name(name), _email(email), _avatar(avatar), _connected(connected) {} UserInfo::UserInfo() {} QDataStream &operator>>(QDataStream &in, UserInfo &userInfo) { - in >> userInfo._dbId >> userInfo._userId >> userInfo._name >> userInfo._email >> userInfo._avatar >> userInfo._connected; + in >> userInfo._dbId >> userInfo._userId >> userInfo._name >> userInfo._email >> userInfo._avatar >> userInfo._connected >> + userInfo._isStaff; return in; } QDataStream &operator<<(QDataStream &out, const UserInfo &userInfo) { - out << userInfo._dbId << userInfo._userId << userInfo._name << userInfo._email << userInfo._avatar << userInfo._connected; + out << userInfo._dbId << userInfo._userId << userInfo._name << userInfo._email << userInfo._avatar << userInfo._connected + << userInfo._isStaff; return out; } QDataStream &operator<<(QDataStream &out, const QList &list) { - int count = static_cast(list.size()); + const auto count = static_cast(list.size()); out << count; for (int i = 0; i < count; i++) { - UserInfo userInfo = list[i]; + const UserInfo &userInfo = list[i]; out << userInfo; } return out; @@ -49,9 +52,9 @@ QDataStream &operator>>(QDataStream &in, QList &list) { int count = 0; in >> count; for (int i = 0; i < count; i++) { - UserInfo *userInfo = new UserInfo(); - in >> *userInfo; - list.push_back(*userInfo); + UserInfo userInfo; + in >> userInfo; + list.push_back(userInfo); } return in; } diff --git a/src/libcommon/info/userinfo.h b/src/libcommon/info/userinfo.h index 94c122405..b9c8f8a26 100644 --- a/src/libcommon/info/userinfo.h +++ b/src/libcommon/info/userinfo.h @@ -44,6 +44,8 @@ class UserInfo { inline bool connected() const { return _connected; } inline bool credentialsAsked() const { return _credentialsAsked; } inline void setCredentialsAsked(bool newCredentialsAsked) { _credentialsAsked = newCredentialsAsked; } + [[nodiscard]] bool isStaff() const { return _isStaff; } + void setIsStaff(const bool is_staff) { _isStaff = is_staff; } friend QDataStream &operator>>(QDataStream &in, UserInfo &userInfo); friend QDataStream &operator<<(QDataStream &out, const UserInfo &userInfo); @@ -52,13 +54,16 @@ class UserInfo { friend QDataStream &operator<<(QDataStream &out, const QList &list); private: - int _dbId = -1; - int _userId = -1; + int _dbId{-1}; + int _userId{-1}; QString _name; QString _email; QImage _avatar; - bool _connected = false; - bool _credentialsAsked = false; + bool _connected{false}; + + // Non DB attributes + bool _credentialsAsked{false}; + bool _isStaff{false}; }; } // namespace KDC diff --git a/src/libcommon/log/sentry/abstractcounterscopedptrace.h b/src/libcommon/log/sentry/abstractcounterscopedptrace.h index b5e81ed2e..f9d6791b5 100644 --- a/src/libcommon/log/sentry/abstractcounterscopedptrace.h +++ b/src/libcommon/log/sentry/abstractcounterscopedptrace.h @@ -43,9 +43,7 @@ class AbstractCounterScopedPTrace : public AbstractScopedPTrace { void stop([[maybe_unused]] PTraceStatus status = PTraceStatus::Ok) final { assert(false && "stop() should not be called with CounterScopedPTrace."); } - void restart() final { - assert(false && "restart() should not be called with CounterScopedPTrace."); - } + void restart() final { assert(false && "restart() should not be called with CounterScopedPTrace."); } unsigned int _nbOfCyclesPerTrace = 0; // The number of time start() should be called before stopping the trace. unsigned int _counter = 0; }; diff --git a/src/libcommon/log/sentry/abstractptrace.h b/src/libcommon/log/sentry/abstractptrace.h index f54c65379..d6ad1f47c 100644 --- a/src/libcommon/log/sentry/abstractptrace.h +++ b/src/libcommon/log/sentry/abstractptrace.h @@ -34,8 +34,8 @@ class AbstractPTrace { virtual void restart() { _restart(); } protected: - explicit AbstractPTrace(const PTraceDescriptor &info) : _pTraceInfo(info){}; - explicit AbstractPTrace(const PTraceDescriptor &info, int dbId) : _pTraceInfo(info), _syncDbId(dbId){}; + explicit AbstractPTrace(const PTraceDescriptor &info) : _pTraceInfo(info) {}; + explicit AbstractPTrace(const PTraceDescriptor &info, int dbId) : _pTraceInfo(info), _syncDbId(dbId) {}; // Start a new performance trace. inline AbstractPTrace &_start() { diff --git a/src/libcommon/log/sentry/handler.cpp b/src/libcommon/log/sentry/handler.cpp index df9f4eef6..cb42268a1 100644 --- a/src/libcommon/log/sentry/handler.cpp +++ b/src/libcommon/log/sentry/handler.cpp @@ -448,8 +448,7 @@ Handler::~Handler() { Handler::SentryEvent::SentryEvent(const std::string &title, const std::string &message, Level level, sentry::ConfidentialityLevel confidentialityLevel, const SentryUser &user) : - title(title), - message(message), level(level), confidentialityLevel(confidentialityLevel), userId(user.userId()) {} + title(title), message(message), level(level), confidentialityLevel(confidentialityLevel), userId(user.userId()) {} void Handler::stopPTrace(const pTraceId &id, PTraceStatus status) { if (id == 0) return; @@ -489,7 +488,7 @@ pTraceId Handler::makeUniquePTraceId() { bool reseted = false; do { _pTraceIdCounter++; - if (_pTraceIdCounter >= std::numeric_limits::max()-1) { + if (_pTraceIdCounter >= std::numeric_limits::max() - 1) { if (reseted) { assert(false && "No more unique pTraceId available"); return 0; diff --git a/src/libcommon/log/sentry/ptracedescriptor.h b/src/libcommon/log/sentry/ptracedescriptor.h index d81597461..5d4df148f 100644 --- a/src/libcommon/log/sentry/ptracedescriptor.h +++ b/src/libcommon/log/sentry/ptracedescriptor.h @@ -68,9 +68,8 @@ enum class PTraceName { struct PTraceDescriptor { PTraceDescriptor() = default; PTraceDescriptor(std::string pTraceTitle, std::string pTraceDescription, PTraceName pTraceName, - PTraceName parentPTraceName = PTraceName::None) : - _pTraceName{pTraceName}, - _parentPTraceName{parentPTraceName}, _pTraceTitle{std::move(pTraceTitle)}, + PTraceName parentPTraceName = PTraceName::None) : + _pTraceName{pTraceName}, _parentPTraceName{parentPTraceName}, _pTraceTitle{std::move(pTraceTitle)}, _pTraceDescription{std::move(pTraceDescription)} {} const PTraceName _pTraceName = PTraceName::None; diff --git a/src/libcommon/log/sentry/ptraces.h b/src/libcommon/log/sentry/ptraces.h index 0ab6ee189..b7a3f7d90 100644 --- a/src/libcommon/log/sentry/ptraces.h +++ b/src/libcommon/log/sentry/ptraces.h @@ -24,14 +24,11 @@ namespace KDC::sentry::pTraces { struct None : public AbstractPTrace { - None() : AbstractPTrace({}){}; + None() : AbstractPTrace({}) {}; explicit None(int syncdbId) : AbstractPTrace({}, syncdbId) {} - void start() final { /* Do nothing */ - } - void stop([[maybe_unused]] PTraceStatus status = PTraceStatus::Ok) final { /* Do nothing */ - } - void restart() final { /* Do nothing */ - } + void start() final { /* Do nothing */ } + void stop([[maybe_unused]] PTraceStatus status = PTraceStatus::Ok) final { /* Do nothing */ } + void restart() final { /* Do nothing */ } }; /* @@ -52,7 +49,7 @@ struct AppStart : public AbstractPTrace { struct Sync : public AbstractPTrace { [[nodiscard]] explicit Sync(int dbId) : - AbstractPTrace({"Synchronisation", "Synchronisation initialization", PTraceName::Sync}, dbId){}; + AbstractPTrace({"Synchronisation", "Synchronisation initialization", PTraceName::Sync}, dbId) {}; }; struct UpdateDetection1 : public AbstractPTrace { @@ -68,7 +65,7 @@ struct UpdateDetection2 : public AbstractPTrace { struct Reconciliation1 : public AbstractPTrace { [[nodiscard]] explicit Reconciliation1(int dbId) : AbstractPTrace({"Reconciliation1", "Platform inconsistency check", PTraceName::Reconciliation1, PTraceName::Sync}, - dbId){}; + dbId) {}; }; struct Reconciliation2 : public AbstractPTrace { @@ -126,7 +123,7 @@ struct RFSOChangeDetected : public AbstractScopedPTrace { struct RFSOGenerateInitialSnapshot : public AbstractScopedPTrace { explicit RFSOGenerateInitialSnapshot(int syncDbId) : AbstractScopedPTrace({"RFSO_GenerateInitialSnapshot", "Generate snapshot", PTraceName::RFSOGenerateInitialSnapshot}, - PTraceStatus::Aborted, syncDbId){}; + PTraceStatus::Aborted, syncDbId) {}; }; // This scoped performance trace expects to be manually stopped. diff --git a/src/libcommon/log/sentry/utility.h b/src/libcommon/log/sentry/utility.h index 62cb39816..210918a4a 100644 --- a/src/libcommon/log/sentry/utility.h +++ b/src/libcommon/log/sentry/utility.h @@ -50,4 +50,4 @@ static inline std::unique_ptr syncStepToPTrace(SyncStep step, in } return std::make_unique(syncDbId); } -} // namespace KDC +} // namespace KDC::sentry diff --git a/src/libcommon/theme/theme.cpp b/src/libcommon/theme/theme.cpp index 75f9bebe8..e89a1411d 100644 --- a/src/libcommon/theme/theme.cpp +++ b/src/libcommon/theme/theme.cpp @@ -140,6 +140,22 @@ QString Theme::helpUrl() const { #endif } +QString Theme::feedbackUrl(const Language language) const { + switch (language) { + case Language::French: + return FEEDBACK_FR_URL; + case Language::German: + return FEEDBACK_DE_URL; + case Language::Spanish: + return FEEDBACK_ES_URL; + case Language::Italian: + return FEEDBACK_IT_URL; + default: + break; + } + return FEEDBACK_EN_URL; +} + QString Theme::conflictHelpUrl() const { #ifdef APPLICATION_CONFLICT_HELP_URL return QString::fromLatin1(APPLICATION_CONFLICT_HELP_URL); diff --git a/src/libcommon/theme/theme.h b/src/libcommon/theme/theme.h index d1543b5b0..53ac6925d 100644 --- a/src/libcommon/theme/theme.h +++ b/src/libcommon/theme/theme.h @@ -46,6 +46,7 @@ class Theme : public QObject { virtual QString version() const; virtual QString helpUrl() const; + QString feedbackUrl(Language language) const; virtual QString conflictHelpUrl() const; diff --git a/src/libcommon/utility/types.h b/src/libcommon/utility/types.h index 48e8a73f8..7f16bf1d9 100644 --- a/src/libcommon/utility/types.h +++ b/src/libcommon/utility/types.h @@ -529,6 +529,8 @@ struct VersionInfo { return out; } }; +using AllVersionsInfo = std::unordered_map; + namespace sentry { enum class ConfidentialityLevel { Anonymous, // The sentry will not be able to identify the user (no ip, no email, no username, ...) diff --git a/src/libcommon/utility/utility_win.cpp b/src/libcommon/utility/utility_win.cpp index ce289a81b..7f030de65 100644 --- a/src/libcommon/utility/utility_win.cpp +++ b/src/libcommon/utility/utility_win.cpp @@ -48,7 +48,7 @@ static SyncPath getAppSupportDir_private() { return appDataPath; } sentry::Handler::captureMessage(sentry::Level::Warning, "Utility_win::getAppSupportDir_private", - "Fail to get AppSupportDir through SHGetKnownFolderPath, using fallback method"); + "Fail to get AppSupportDir through SHGetKnownFolderPath, using fallback method"); return std::filesystem::temp_directory_path().parent_path().parent_path().native(); } diff --git a/src/libcommonserver/commserver.cpp b/src/libcommonserver/commserver.cpp index c30a21dc7..a0919c6df 100644 --- a/src/libcommonserver/commserver.cpp +++ b/src/libcommonserver/commserver.cpp @@ -90,6 +90,11 @@ void CommServer::sendReply(int id, const QByteArray &result) { } } +bool CommServer::sendSignal(const SignalNum num, const QByteArray ¶ms) { + int id = 0; + return sendSignal(num, params, id); +} + bool CommServer::sendSignal(SignalNum num, const QByteArray ¶ms, int &id) { if (_tcpSocket && _tcpSocket->isOpen()) { _requestWorker->addSignal(num, params, id); diff --git a/src/libcommonserver/commserver.h b/src/libcommonserver/commserver.h index fff7b3e23..d24dc5704 100644 --- a/src/libcommonserver/commserver.h +++ b/src/libcommonserver/commserver.h @@ -45,6 +45,7 @@ class CommServer : public QObject { void operator=(CommServer const &) = delete; void sendReply(int id, const QByteArray &result); + bool sendSignal(SignalNum num, const QByteArray ¶ms); bool sendSignal(SignalNum num, const QByteArray ¶ms, int &id); inline quint16 commPort() const { return _tcpServer.serverPort(); } diff --git a/src/libcommonserver/db/db.cpp b/src/libcommonserver/db/db.cpp index 7bf5145f3..6fe5cbea3 100644 --- a/src/libcommonserver/db/db.cpp +++ b/src/libcommonserver/db/db.cpp @@ -339,36 +339,32 @@ bool Db::init(const std::string &version) { queryFree(SELECT_VERSION_REQUEST_ID); - if (_fromVersion != version) { - // Upgrade DB - LOG_INFO(_logger, - "Upgrade " << dbType().c_str() << " DB from " << _fromVersion.c_str() << " to " << version.c_str()); - if (!upgrade(_fromVersion, version)) { - LOG_WARN(_logger, "Error in Db::upgrade"); - return false; - } - - // Update version - if (!createAndPrepareRequest(UPDATE_VERSION_REQUEST_ID, UPDATE_VERSION_REQUEST)) return false; - if (!updateVersion(version, found)) { - LOG_WARN(_logger, "Error in Db::updateVersion"); - return false; - } - if (!found) { - LOG_WARN(_logger, "Version not found"); - return false; - } + // Upgrade DB + LOG_INFO(_logger, "Upgrade " << dbType() << " DB from " << _fromVersion << " to " << version); + if (!upgrade(_fromVersion, version)) { + LOG_WARN(_logger, "Error in Db::upgrade"); + return false; + } - queryFree(UPDATE_VERSION_REQUEST_ID); + // Update version + if (!createAndPrepareRequest(UPDATE_VERSION_REQUEST_ID, UPDATE_VERSION_REQUEST)) return false; + if (!updateVersion(version, found)) { + LOG_WARN(_logger, "Error in Db::updateVersion"); + return false; } + if (!found) { + LOG_WARN(_logger, "Version not found"); + return false; + } + + queryFree(UPDATE_VERSION_REQUEST_ID); } else { // Create version table LOG_DEBUG(_logger, "Create version table"); if (!createAndPrepareRequest(CREATE_VERSION_TABLE_ID, CREATE_VERSION_TABLE)) return false; int errId = -1; - std::string error; - if (!queryExec(CREATE_VERSION_TABLE_ID, errId, error)) { + if (std::string error; !queryExec(CREATE_VERSION_TABLE_ID, errId, error)) { queryFree(CREATE_VERSION_TABLE_ID); return sqlFail(CREATE_VERSION_TABLE_ID, error); } diff --git a/src/libcommonserver/utility/utility_win.cpp b/src/libcommonserver/utility/utility_win.cpp index 3fba6fb6b..148a5ef64 100644 --- a/src/libcommonserver/utility/utility_win.cpp +++ b/src/libcommonserver/utility/utility_win.cpp @@ -110,8 +110,7 @@ static bool moveItemToTrash_private(const SyncPath &itemPath) { std::wstring errorStr = errorStream.str(); LOGW_WARN(Log::instance()->getLogger(), errorStr.c_str()); - sentry::Handler::captureMessage(sentry::Level::Error, "Utility::moveItemToTrash", - "SHCreateItemFromParsingName failed"); + sentry::Handler::captureMessage(sentry::Level::Error, "Utility::moveItemToTrash", "SHCreateItemFromParsingName failed"); fileOperation->Release(); CoUninitialize(); return false; diff --git a/src/libparms/db/parmsdb.cpp b/src/libparms/db/parmsdb.cpp index 3fe4360f4..4e3f68b02 100644 --- a/src/libparms/db/parmsdb.cpp +++ b/src/libparms/db/parmsdb.cpp @@ -60,7 +60,9 @@ "extendedLog INTEGER," \ "maxAllowedCpu INTEGER," \ "uploadSessionParallelJobs INTEGER," \ - "jobPoolCapacityFactor INTEGER);" + "jobPoolCapacityFactor INTEGER," \ + "distributionChannel INTEGER" \ + ");" #define INSERT_PARAMETERS_REQUEST_ID "insert_parameters" #define INSERT_PARAMETERS_REQUEST \ @@ -68,9 +70,9 @@ "syncHiddenFiles, proxyType, proxyHostName, proxyPort, proxyNeedsAuth, proxyUser, proxyToken, useBigFolderSizeLimit, " \ "bigFolderSizeLimit, darkTheme, showShortcuts, updateFileAvailable, updateTargetVersion, updateTargetVersionString, " \ "autoUpdateAttempted, seenVersion, dialogGeometry, extendedLog, maxAllowedCpu, uploadSessionParallelJobs, " \ - "jobPoolCapacityFactor) " \ + "jobPoolCapacityFactor, distributionChannel) " \ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, " \ - "?25, ?26, ?27, ?28, ?29);" + "?25, ?26, ?27, ?28, ?29, ?30);" #define UPDATE_PARAMETERS_REQUEST_ID "update_parameters" #define UPDATE_PARAMETERS_REQUEST \ @@ -81,7 +83,7 @@ "bigFolderSizeLimit=?17, darkTheme=?18, showShortcuts=?19, updateFileAvailable=?20, updateTargetVersion=?21, " \ "updateTargetVersionString=?22, " \ "autoUpdateAttempted=?23, seenVersion=?24, dialogGeometry=?25, extendedLog=?26, maxAllowedCpu=?27, " \ - "uploadSessionParallelJobs=?28, jobPoolCapacityFactor=?29;" + "uploadSessionParallelJobs=?28, jobPoolCapacityFactor=?29, distributionChannel=?30;" #define SELECT_PARAMETERS_REQUEST_ID "select_parameters" #define SELECT_PARAMETERS_REQUEST \ @@ -89,15 +91,14 @@ "syncHiddenFiles, proxyType, proxyHostName, proxyPort, proxyNeedsAuth, proxyUser, proxyToken, useBigFolderSizeLimit, " \ "bigFolderSizeLimit, darkTheme, showShortcuts, updateFileAvailable, updateTargetVersion, updateTargetVersionString, " \ "autoUpdateAttempted, seenVersion, dialogGeometry, extendedLog, maxAllowedCpu, uploadSessionParallelJobs, " \ - "jobPoolCapacityFactor " \ + "jobPoolCapacityFactor, distributionChannel " \ "FROM parameters;" #define UPDATE_PARAMETERS_JOB_REQUEST_ID "update_parameters_job" #define UPDATE_PARAMETERS_JOB_REQUEST "UPDATE parameters SET uploadSessionParallelJobs=?1, jobPoolCapacityFactor=?2;" -// TODO : will be added later -// #define ALTER_PARAMETERS_ADD_DISTRIBUTION_CHANNEL_REQUEST_ID "alter_parameters_add_distribution" -// #define ALTER_PARAMETERS_ADD_DISTRIBUTION_CHANNEL_REQUEST "ALTER TABLE parameters ADD COLUMN distributionChannel INTEGER;" +#define ALTER_PARAMETERS_ADD_DISTRIBUTION_CHANNEL_REQUEST_ID "alter_parameters_add_distribution" +#define ALTER_PARAMETERS_ADD_DISTRIBUTION_CHANNEL_REQUEST "ALTER TABLE parameters ADD COLUMN distributionChannel INTEGER;" // // user @@ -601,7 +602,7 @@ bool ParmsDb::insertDefaultParameters() { ASSERT(queryBindValue(INSERT_PARAMETERS_REQUEST_ID, 27, parameters.maxAllowedCpu())); ASSERT(queryBindValue(INSERT_PARAMETERS_REQUEST_ID, 28, parameters.uploadSessionParallelJobs())); ASSERT(queryBindValue(INSERT_PARAMETERS_REQUEST_ID, 29, parameters.jobPoolCapacityFactor())); - // ASSERT(queryBindValue(INSERT_PARAMETERS_REQUEST_ID, 30, static_cast(parameters.distributionChannel()))); + ASSERT(queryBindValue(INSERT_PARAMETERS_REQUEST_ID, 30, static_cast(parameters.distributionChannel()))); if (!queryExec(INSERT_PARAMETERS_REQUEST_ID, errId, error)) { LOG_WARN(_logger, "Error running query: " << INSERT_PARAMETERS_REQUEST_ID); @@ -1006,6 +1007,11 @@ bool ParmsDb::upgrade(const std::string & /*fromVersion*/, const std::string & / queryFree(UPDATE_PARAMETERS_JOB_REQUEST_ID); } + columnName = "distributionChannel"; + if (!addIntegerColumnIfMissing(tableName, columnName)) { + return false; + } + bool exist = false; if (!tableExists("app_state", exist)) return false; if (!exist) { @@ -1094,7 +1100,7 @@ bool ParmsDb::updateParameters(const Parameters ¶meters, bool &found) { ASSERT(queryBindValue(UPDATE_PARAMETERS_REQUEST_ID, 27, parameters.maxAllowedCpu())); ASSERT(queryBindValue(UPDATE_PARAMETERS_REQUEST_ID, 28, parameters.uploadSessionParallelJobs())); ASSERT(queryBindValue(UPDATE_PARAMETERS_REQUEST_ID, 29, parameters.jobPoolCapacityFactor())); - // ASSERT(queryBindValue(UPDATE_PARAMETERS_REQUEST_ID, 30, static_cast(parameters.distributionChannel()))); + ASSERT(queryBindValue(UPDATE_PARAMETERS_REQUEST_ID, 30, static_cast(parameters.distributionChannel()))); if (!queryExec(UPDATE_PARAMETERS_REQUEST_ID, errId, error)) { LOG_WARN(_logger, "Error running query: " << UPDATE_PARAMETERS_REQUEST_ID); @@ -1213,9 +1219,8 @@ bool ParmsDb::selectParameters(Parameters ¶meters, bool &found) { ASSERT(queryIntValue(SELECT_PARAMETERS_REQUEST_ID, 28, intResult)); parameters.setJobPoolCapacityFactor(intResult); - // TODO : not supported for now - // ASSERT(queryIntValue(SELECT_PARAMETERS_REQUEST_ID, 29, intResult)); - // parameters.setDistributionChannel(static_cast(intResult)); + ASSERT(queryIntValue(SELECT_PARAMETERS_REQUEST_ID, 29, intResult)); + parameters.setDistributionChannel(static_cast(intResult)); ASSERT(queryResetAndClearBindings(SELECT_PARAMETERS_REQUEST_ID)); diff --git a/src/libparms/db/user.h b/src/libparms/db/user.h index 12e462e1c..608016986 100644 --- a/src/libparms/db/user.h +++ b/src/libparms/db/user.h @@ -51,6 +51,8 @@ class PARMS_EXPORT User { inline void setAvatar(std::shared_ptr> avatar) { _avatar = avatar; } inline void setToMigrate(bool toMigrate) { _toMigrate = toMigrate; } inline int toMigrate() const { return _toMigrate; } + [[nodiscard]] bool isStaff() const { return _isStaff; } + void setIsStaff(const bool isStaff) { _isStaff = isStaff; } private: log4cplus::Logger _logger; @@ -62,6 +64,9 @@ class PARMS_EXPORT User { std::string _avatarUrl; std::shared_ptr> _avatar; bool _toMigrate; + + // Non DB attributes + bool _isStaff{false}; }; } // namespace KDC diff --git a/src/libsyncengine/jobs/jobmanager.cpp b/src/libsyncengine/jobs/jobmanager.cpp index 51513b47c..671469330 100644 --- a/src/libsyncengine/jobs/jobmanager.cpp +++ b/src/libsyncengine/jobs/jobmanager.cpp @@ -120,7 +120,7 @@ void JobManager::decreasePoolCapacity() { LOG_DEBUG(Log::instance()->getLogger(), "Job Manager capacity set to " << _maxNbThread); } else { sentry::Handler::captureMessage(sentry::Level::Warning, "JobManager::defaultCallback", - "JobManager capacity cannot be decreased"); + "JobManager capacity cannot be decreased"); } } diff --git a/src/libsyncengine/jobs/jobmanager.h b/src/libsyncengine/jobs/jobmanager.h index b7dc0d6f9..225f4fda8 100644 --- a/src/libsyncengine/jobs/jobmanager.h +++ b/src/libsyncengine/jobs/jobmanager.h @@ -40,7 +40,7 @@ namespace KDC { class JobPriorityCmp { public: bool operator()(const std::pair, Poco::Thread::Priority> &j1, - const std::pair, Poco::Thread::Priority> &j2) { + const std::pair, Poco::Thread::Priority> &j2) const { if (j1.second == j2.second) { // Same thread priority, use the job ID to define priority return j1.first->jobId() > j2.first->jobId(); diff --git a/src/libsyncengine/jobs/local/localdeletejob.cpp b/src/libsyncengine/jobs/local/localdeletejob.cpp index 5f14039d8..72b631a3a 100644 --- a/src/libsyncengine/jobs/local/localdeletejob.cpp +++ b/src/libsyncengine/jobs/local/localdeletejob.cpp @@ -32,7 +32,7 @@ namespace KDC { -LocalDeleteJob::Path::Path(const SyncPath &path) : _path(path){}; +LocalDeleteJob::Path::Path(const SyncPath &path) : _path(path) {}; bool LocalDeleteJob::Path::endsWith(SyncPath &&ending) const { if (!_path.empty() && ending.empty()) return false; @@ -58,9 +58,8 @@ bool LocalDeleteJob::matchRelativePaths(const SyncPath &targetPath, const SyncPa LocalDeleteJob::LocalDeleteJob(const SyncPalInfo &syncPalInfo, const SyncPath &relativePath, bool isDehydratedPlaceholder, NodeId remoteId, bool forceToTrash /* = false */) : - _absolutePath(syncPalInfo.localPath / relativePath), - _syncInfo(syncPalInfo), _relativePath(relativePath), _isDehydratedPlaceholder(isDehydratedPlaceholder), - _remoteNodeId(remoteId), _forceToTrash(forceToTrash) {} + _absolutePath(syncPalInfo.localPath / relativePath), _syncInfo(syncPalInfo), _relativePath(relativePath), + _isDehydratedPlaceholder(isDehydratedPlaceholder), _remoteNodeId(remoteId), _forceToTrash(forceToTrash) {} LocalDeleteJob::LocalDeleteJob(const SyncPath &absolutePath) : _absolutePath(absolutePath) { setBypassCheck(true); diff --git a/src/libsyncengine/jobs/network/API_v2/downloadjob.cpp b/src/libsyncengine/jobs/network/API_v2/downloadjob.cpp index 16dba22cd..e6b3fc052 100644 --- a/src/libsyncengine/jobs/network/API_v2/downloadjob.cpp +++ b/src/libsyncengine/jobs/network/API_v2/downloadjob.cpp @@ -43,9 +43,8 @@ namespace KDC { DownloadJob::DownloadJob(int driveDbId, const NodeId &remoteFileId, const SyncPath &localpath, int64_t expectedSize, SyncTime creationTime, SyncTime modtime, bool isCreate) : - AbstractTokenNetworkJob(ApiType::Drive, 0, 0, driveDbId, 0, false), - _remoteFileId(remoteFileId), _localpath(localpath), _expectedSize(expectedSize), _creationTime(creationTime), - _modtimeIn(modtime), _isCreate(isCreate) { + AbstractTokenNetworkJob(ApiType::Drive, 0, 0, driveDbId, 0, false), _remoteFileId(remoteFileId), _localpath(localpath), + _expectedSize(expectedSize), _creationTime(creationTime), _modtimeIn(modtime), _isCreate(isCreate) { _httpMethod = Poco::Net::HTTPRequest::HTTP_GET; _customTimeout = 60; _trials = TRIALS; @@ -118,8 +117,8 @@ bool DownloadJob::canRun() { } if (_isCreate && exists) { - LOGW_DEBUG(_logger, - L"Item with " << Utility::formatSyncPath(_localpath) << L" already exists. Aborting current sync and restarting."); + LOGW_DEBUG(_logger, L"Item with " << Utility::formatSyncPath(_localpath) + << L" already exists. Aborting current sync and restarting."); _exitCode = ExitCode::NeedRestart; _exitCause = ExitCause::UnexpectedFileSystemEvent; return false; @@ -399,8 +398,7 @@ bool DownloadJob::handleResponse(std::istream &is) { std::make_optional(_modtimeIn), isLink, exists)) { LOGW_WARN(_logger, L"Error in Utility::setFileDates: " << Utility::formatSyncPath(_localpath)); // Do nothing (remote file will be updated during the next sync) - sentry::Handler::captureMessage(sentry::Level::Warning, "DownloadJob::handleResponse", - "Unable to set file dates"); + sentry::Handler::captureMessage(sentry::Level::Warning, "DownloadJob::handleResponse", "Unable to set file dates"); } else if (!exists) { LOGW_INFO(_logger, L"Item does not exist anymore. Restarting sync: " << Utility::formatSyncPath(_localpath)); _exitCode = ExitCode::DataError; @@ -450,9 +448,9 @@ bool DownloadJob::createLink(const std::string &mimeType, const std::string &dat return false; } - LOGW_DEBUG(_logger, - L"Create symlink with target " << Utility::formatSyncPath(targetPath) << L", " << Utility::formatSyncPath(_localpath)); - + LOGW_DEBUG(_logger, L"Create symlink with target " << Utility::formatSyncPath(targetPath) << L", " + << Utility::formatSyncPath(_localpath)); + bool isFolder = mimeType == mimeTypeSymlinkFolder; IoError ioError = IoError::Success; if (!IoHelper::createSymlink(targetPath, _localpath, isFolder, ioError)) { diff --git a/src/libsyncengine/jobs/network/API_v2/getinfouserjob.cpp b/src/libsyncengine/jobs/network/API_v2/getinfouserjob.cpp index 9b106635f..955696e2f 100644 --- a/src/libsyncengine/jobs/network/API_v2/getinfouserjob.cpp +++ b/src/libsyncengine/jobs/network/API_v2/getinfouserjob.cpp @@ -18,12 +18,45 @@ #include "getinfouserjob.h" +#include "utility/jsonparserutility.h" + namespace KDC { -GetInfoUserJob::GetInfoUserJob(int userDbId) : AbstractTokenNetworkJob(ApiType::Profile, userDbId, 0, 0, 0) { +static const std::string displayNameKey = "display_name"; +static const std::string emailKey = "email"; +static const std::string avatarKey = "avatar"; +static const std::string isStaffKey = "is_staff"; + +GetInfoUserJob::GetInfoUserJob(const int userDbId) : AbstractTokenNetworkJob(ApiType::Profile, userDbId, 0, 0, 0) { _httpMethod = Poco::Net::HTTPRequest::HTTP_GET; } +bool GetInfoUserJob::handleJsonResponse(std::istream& is) { + if (!AbstractTokenNetworkJob::handleJsonResponse(is)) return false; + + Poco::JSON::Object::Ptr dataObj = jsonRes()->getObject(dataKey); + if (!dataObj || dataObj->size() == 0) return false; + + + if (!JsonParserUtility::extractValue(dataObj, displayNameKey, _name)) { + _exitCode = ExitCode::BackError; + return false; + } + + if (!JsonParserUtility::extractValue(dataObj, emailKey, _email)) { + _exitCode = ExitCode::BackError; + return false; + } + + if (!JsonParserUtility::extractValue(dataObj, avatarKey, _avatarUrl)) { + _exitCode = ExitCode::BackError; + return false; + } + + JsonParserUtility::extractValue(dataObj, isStaffKey, _isStaff, false); + return true; +} + std::string GetInfoUserJob::getSpecificUrl() { std::string str = AbstractTokenNetworkJob::getSpecificUrl(); return str; diff --git a/src/libsyncengine/jobs/network/API_v2/getinfouserjob.h b/src/libsyncengine/jobs/network/API_v2/getinfouserjob.h index d720c9a12..b2ea5c209 100644 --- a/src/libsyncengine/jobs/network/API_v2/getinfouserjob.h +++ b/src/libsyncengine/jobs/network/API_v2/getinfouserjob.h @@ -22,14 +22,27 @@ namespace KDC { -class GetInfoUserJob : public AbstractTokenNetworkJob { +class GetInfoUserJob final : public AbstractTokenNetworkJob { public: - GetInfoUserJob(int userDbId); + explicit GetInfoUserJob(int userDbId); + + [[nodiscard]] const std::string& name() const { return _name; } + [[nodiscard]] const std::string& email() const { return _email; } + [[nodiscard]] const std::string& avatarUrl() const { return _avatarUrl; } + [[nodiscard]] bool isStaff() const { return _isStaff; } + + protected: + bool handleJsonResponse(std::istream& is) override; private: - virtual std::string getSpecificUrl() override; - virtual void setQueryParameters(Poco::URI &, bool &canceled) override { canceled = false; } - inline virtual ExitInfo setData() override { return ExitCode::Ok; } + std::string getSpecificUrl() override; + void setQueryParameters(Poco::URI&, bool& canceled) override { canceled = false; } + ExitInfo setData() override { return ExitCode::Ok; } + + std::string _name; + std::string _email; + std::string _avatarUrl; + bool _isStaff{false}; }; } // namespace KDC diff --git a/src/libsyncengine/jobs/network/getappversionjob.cpp b/src/libsyncengine/jobs/network/getappversionjob.cpp index b1d411fbe..974a2eae5 100644 --- a/src/libsyncengine/jobs/network/getappversionjob.cpp +++ b/src/libsyncengine/jobs/network/getappversionjob.cpp @@ -20,8 +20,6 @@ #include "utility/jsonparserutility.h" #include "utility/utility.h" -#include - namespace KDC { static const std::string prodVersionKey = "prod_version"; @@ -47,7 +45,7 @@ GetAppVersionJob::GetAppVersionJob(const Platform platform, const std::string &a _httpMethod = Poco::Net::HTTPRequest::HTTP_GET; } -std::string toStr(const Platform platform) { +std::string GetAppVersionJob::toStr(const Platform platform) { switch (platform) { case Platform::MacOS: return platformMacOsKey; @@ -70,6 +68,21 @@ DistributionChannel toDistributionChannel(const std::string &str) { return DistributionChannel::Unknown; } +std::string GetAppVersionJob::toStr(const DistributionChannel channel) { + switch (channel) { + case DistributionChannel::Prod: + return versionTypeProdKey; + case DistributionChannel::Next: + return versionTypeNextKey; + case DistributionChannel::Beta: + return versionTypeBetaKey; + case DistributionChannel::Internal: + return versionTypeInternalKey; + default: + return "unknown"; + } +} + std::string GetAppVersionJob::getSpecificUrl() { std::stringstream ss; ss << "/app-information/kstore-update/" << toStr(_platform) << "/com.infomaniak.drive/" << _appId; @@ -123,15 +136,15 @@ bool GetAppVersionJob::handleResponse(std::istream &is) { if (!JsonParserUtility::extractValue(obj, typeKey, versionType)) return false; const DistributionChannel channel = toDistributionChannel(versionType); - _versionInfo[channel].channel = channel; + _versionsInfo[channel].channel = channel; - if (!JsonParserUtility::extractValue(obj, tagKey, _versionInfo[channel].tag)) return false; - if (!JsonParserUtility::extractValue(obj, buildVersionKey, _versionInfo[channel].buildVersion)) return false; - if (!JsonParserUtility::extractValue(obj, buildMinOsVersionKey, _versionInfo[channel].buildMinOsVersion, false)) + if (!JsonParserUtility::extractValue(obj, tagKey, _versionsInfo[channel].tag)) return false; + if (!JsonParserUtility::extractValue(obj, buildVersionKey, _versionsInfo[channel].buildVersion)) return false; + if (!JsonParserUtility::extractValue(obj, buildMinOsVersionKey, _versionsInfo[channel].buildMinOsVersion, false)) return false; - if (!JsonParserUtility::extractValue(obj, downloadUrlKey, _versionInfo[channel].downloadUrl)) return false; + if (!JsonParserUtility::extractValue(obj, downloadUrlKey, _versionsInfo[channel].downloadUrl)) return false; - if (!_versionInfo[channel].isValid()) { + if (!_versionsInfo[channel].isValid()) { LOG_WARN(_logger, "Missing mandatory value."); return false; } diff --git a/src/libsyncengine/jobs/network/getappversionjob.h b/src/libsyncengine/jobs/network/getappversionjob.h index 5b86e0f84..ab0e12cc6 100644 --- a/src/libsyncengine/jobs/network/getappversionjob.h +++ b/src/libsyncengine/jobs/network/getappversionjob.h @@ -30,15 +30,19 @@ class GetAppVersionJob : public AbstractNetworkJob { GetAppVersionJob(Platform platform, const std::string &appID, const std::vector &userIdList); ~GetAppVersionJob() override = default; - const VersionInfo &getVersionInfo(const DistributionChannel channel) { - return _versionInfo.contains(channel) ? _versionInfo[channel] : _defaultVersionInfo; - } - const VersionInfo &getProdVersionInfo() { - return _versionInfo.contains(_prodVersionChannel) ? _versionInfo[_prodVersionChannel] : _defaultVersionInfo; - } + /** + * @brief Return the adequat version info between Production or Production-Next. + * @return `DistributionChannel` enum value. + */ + [[nodiscard]] DistributionChannel prodVersionChannel() const { return _prodVersionChannel; } + [[nodiscard]] const VersionInfo &versionInfo(const DistributionChannel channel) { return _versionsInfo[channel]; } + [[nodiscard]] const AllVersionsInfo &versionsInfo() const { return _versionsInfo; } std::string getUrl() override { return INFOMANIAK_API_URL + getSpecificUrl(); } + static std::string toStr(Platform platform); + static std::string toStr(DistributionChannel channel); + protected: bool handleResponse(std::istream &is) override; @@ -54,10 +58,8 @@ class GetAppVersionJob : public AbstractNetworkJob { const Platform _platform{Platform::Unknown}; const std::string _appId; const std::vector _userIdList; - - const VersionInfo _defaultVersionInfo; DistributionChannel _prodVersionChannel{DistributionChannel::Unknown}; - std::unordered_map _versionInfo; + AllVersionsInfo _versionsInfo; }; } // namespace KDC diff --git a/src/libsyncengine/jobs/network/networkjobsparams.h b/src/libsyncengine/jobs/network/networkjobsparams.h index 5a63275f0..c5a56004c 100644 --- a/src/libsyncengine/jobs/network/networkjobsparams.h +++ b/src/libsyncengine/jobs/network/networkjobsparams.h @@ -83,9 +83,6 @@ static const std::string filesKey = "files"; static const std::string idKey = "id"; static const std::string parentIdKey = "parent_id"; static const std::string nameKey = "name"; -static const std::string emailKey = "email"; -static const std::string avatarKey = "avatar"; -static const std::string displayNameKey = "display_name"; static const std::string typeKey = "type"; static const std::string sizeKey = "size"; static const std::string visibilityKey = "visibility"; diff --git a/src/libsyncengine/requests/parameterscache.cpp b/src/libsyncengine/requests/parameterscache.cpp index 7b9294a71..b6f634afb 100644 --- a/src/libsyncengine/requests/parameterscache.cpp +++ b/src/libsyncengine/requests/parameterscache.cpp @@ -112,7 +112,7 @@ void ParametersCache::decreaseUploadSessionParallelThreads() { "Upload session max parallel threads parameters set to " << newUploadSessionParallelJobs); } else { sentry::Handler::captureMessage(sentry::Level::Warning, "AppServer::addError", - "Upload session max parallel threads parameters cannot be decreased"); + "Upload session max parallel threads parameters cannot be decreased"); } } diff --git a/src/libsyncengine/requests/serverrequests.cpp b/src/libsyncengine/requests/serverrequests.cpp index dc41c1852..315ef08de 100644 --- a/src/libsyncengine/requests/serverrequests.cpp +++ b/src/libsyncengine/requests/serverrequests.cpp @@ -787,33 +787,32 @@ ExitCode ServerRequests::createUser(const User &user, UserInfo &userInfo) { } // Load User info - User userUpdated(user); - bool updated; - ExitCode exitCode = loadUserInfo(userUpdated, updated); - if (exitCode != ExitCode::Ok) { + User updatedUser(user); + bool updated = false; + if (ExitCode exitCode = loadUserInfo(updatedUser, updated); exitCode != ExitCode::Ok) { LOG_WARN(Log::instance()->getLogger(), "Error in loadUserInfo"); return exitCode; } if (updated) { - bool found; - if (!ParmsDb::instance()->updateUser(userUpdated, found)) { + bool found = false; + if (!ParmsDb::instance()->updateUser(updatedUser, found)) { LOG_WARN(Log::instance()->getLogger(), "Error in ParmsDb::updateUser"); return ExitCode::DbError; } if (!found) { - LOG_WARN(Log::instance()->getLogger(), "User not found for userDbId=" << userUpdated.dbId()); + LOG_WARN(Log::instance()->getLogger(), "User not found for userDbId=" << updatedUser.dbId()); return ExitCode::DataError; } } - userToUserInfo(userUpdated, userInfo); + userToUserInfo(updatedUser, userInfo); return ExitCode::Ok; } ExitCode ServerRequests::updateUser(const User &user, UserInfo &userInfo) { - bool found; + bool found = false; if (!ParmsDb::instance()->updateUser(user, found)) { LOG_WARN(Log::instance()->getLogger(), "Error in ParmsDb::updateUser"); return ExitCode::DbError; @@ -825,13 +824,11 @@ ExitCode ServerRequests::updateUser(const User &user, UserInfo &userInfo) { // Load User info User userUpdated(user); - bool updated; - ExitCode exitCode = loadUserInfo(userUpdated, updated); - if (exitCode != ExitCode::Ok) { + bool updated = false; + if (const ExitCode exitCode = loadUserInfo(userUpdated, updated); exitCode != ExitCode::Ok) { LOG_WARN(Log::instance()->getLogger(), "Error in loadUserInfo"); return exitCode; } - userToUserInfo(userUpdated, userInfo); return ExitCode::Ok; @@ -1680,7 +1677,7 @@ ExitCode ServerRequests::loadUserInfo(User &user, bool &updated) { try { job = std::make_shared(user.dbId()); } catch (const std::exception &e) { - std::string what = e.what(); + const std::string what = e.what(); LOG_WARN(Log::instance()->getLogger(), "Error in GetInfoUserJob::GetInfoUserJob for userDbId=" << user.dbId() << " error=" << what.c_str()); if (what == invalidToken) { @@ -1697,8 +1694,8 @@ ExitCode ServerRequests::loadUserInfo(User &user, bool &updated) { return exitCode; } - Poco::Net::HTTPResponse::HTTPStatus httpStatus = job->getStatusCode(); - if (httpStatus == Poco::Net::HTTPResponse::HTTPStatus::HTTP_FORBIDDEN || + if (const Poco::Net::HTTPResponse::HTTPStatus httpStatus = job->getStatusCode(); + httpStatus == Poco::Net::HTTPResponse::HTTPStatus::HTTP_FORBIDDEN || httpStatus == Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND) { LOG_WARN(Log::instance()->getLogger(), "Unable to get user info for userId=" << user.userId()); return ExitCode::DataError; @@ -1707,47 +1704,34 @@ ExitCode ServerRequests::loadUserInfo(User &user, bool &updated) { return ExitCode::NetworkError; } - Poco::JSON::Object::Ptr dataObj = job->jsonRes()->getObject(dataKey); - if (dataObj && dataObj->size() != 0) { - std::string name; - if (!JsonParserUtility::extractValue(dataObj, displayNameKey, name)) { - return ExitCode::BackError; - } - if (user.name() != name) { - user.setName(name); - updated = true; - } + if (user.name() != job->name()) { + user.setName(job->name()); + updated = true; + } - std::string email; - if (!JsonParserUtility::extractValue(dataObj, emailKey, email)) { - return ExitCode::BackError; - } - if (user.email() != email) { - user.setEmail(email); - updated = true; - } + if (user.email() != job->email()) { + user.setEmail(job->email()); + updated = true; + } - std::string avatarUrl; - if (!JsonParserUtility::extractValue(dataObj, avatarKey, avatarUrl)) { - return ExitCode::BackError; - } - if (user.avatarUrl() != avatarUrl) { - if (avatarUrl.empty()) { - user.setAvatar(nullptr); - user.setAvatarUrl(std::string()); - } else if (user.avatarUrl() != avatarUrl) { - // get avatarData - user.setAvatarUrl(avatarUrl); - exitCode = loadUserAvatar(user); - if (exitCode != ExitCode::Ok) { - return exitCode; - } - } - updated = true; + if (user.avatarUrl() != job->avatarUrl()) { + if (job->avatarUrl().empty()) { + user.setAvatar(nullptr); + user.setAvatarUrl(std::string()); + } else if (user.avatarUrl() != job->avatarUrl()) { + // get avatarData + user.setAvatarUrl(job->avatarUrl()); + exitCode = loadUserAvatar(user); } + updated = true; } - return ExitCode::Ok; + if (user.isStaff() != job->isStaff()) { + user.setIsStaff(job->isStaff()); + updated = true; + } + + return exitCode; } ExitCode ServerRequests::loadUserAvatar(User &user) { @@ -1941,6 +1925,7 @@ void ServerRequests::userToUserInfo(const User &user, UserInfo &userInfo) { } userInfo.setConnected(!user.keychainKey().empty()); userInfo.setCredentialsAsked(false); + userInfo.setIsStaff(user.isStaff()); } void ServerRequests::accountToAccountInfo(const Account &account, AccountInfo &accountInfo) { @@ -2051,6 +2036,7 @@ void ServerRequests::parametersToParametersInfo(const Parameters ¶meters, Pa } } parametersInfo.setMaxAllowedCpu(parameters.maxAllowedCpu()); + parametersInfo.setDistributionChannel(parameters.distributionChannel()); } void ServerRequests::parametersInfoToParameters(const ParametersInfo ¶metersInfo, Parameters ¶meters) { @@ -2086,6 +2072,7 @@ void ServerRequests::parametersInfoToParameters(const ParametersInfo ¶meters std::shared_ptr>(new std::vector(dialogGeometryArr.begin(), dialogGeometryArr.end()))); } parameters.setMaxAllowedCpu(parametersInfo.maxAllowedCpu()); + parameters.setDistributionChannel(parametersInfo.distributionChannel()); } void ServerRequests::proxyConfigToProxyConfigInfo(const ProxyConfig &proxyConfig, ProxyConfigInfo &proxyConfigInfo) { diff --git a/src/libsyncengine/syncpal/syncpal.cpp b/src/libsyncengine/syncpal/syncpal.cpp index a0928f1d8..212a79a8f 100644 --- a/src/libsyncengine/syncpal/syncpal.cpp +++ b/src/libsyncengine/syncpal/syncpal.cpp @@ -516,7 +516,6 @@ void SyncPal::loadProgress(int64_t ¤tFile, int64_t &totalFiles, int64_t &c void SyncPal::createSharedObjects() { LOG_SYNCPAL_DEBUG(_logger, "Create shared objects"); - _interruptSync = std::make_shared(false); _localSnapshot = std::make_shared(ReplicaSide::Local, _syncDb->rootNode()); _remoteSnapshot = std::make_shared(ReplicaSide::Remote, _syncDb->rootNode()); _localSnapshotCopy = std::make_shared(ReplicaSide::Local, _syncDb->rootNode()); @@ -534,7 +533,6 @@ void SyncPal::createSharedObjects() { void SyncPal::freeSharedObjects() { LOG_SYNCPAL_DEBUG(_logger, "Free shared objects"); - _interruptSync.reset(); _localSnapshot.reset(); _remoteSnapshot.reset(); _localSnapshotCopy.reset(); @@ -548,7 +546,6 @@ void SyncPal::freeSharedObjects() { _progressInfo.reset(); // Check that there is no memory leak - ASSERT(_interruptSync.use_count() == 0); ASSERT(_localSnapshot.use_count() == 0); ASSERT(_remoteSnapshot.use_count() == 0); ASSERT(_localSnapshotCopy.use_count() == 0); @@ -773,6 +770,11 @@ bool SyncPal::isSnapshotValid(ReplicaSide side) { return side == ReplicaSide::Local ? _localSnapshot->isValid() : _remoteSnapshot->isValid(); } +void SyncPal::resetSnapshotInvalidationCounters() { + _localFSObserverWorker->resetInvalidateCounter(); + _remoteFSObserverWorker->resetInvalidateCounter(); +} + ExitCode SyncPal::addDlDirectJob(const SyncPath &relativePath, const SyncPath &localPath) { std::optional localNodeId = std::nullopt; bool found = false; diff --git a/src/libsyncengine/syncpal/syncpal.h b/src/libsyncengine/syncpal/syncpal.h index ad285600d..47412c405 100644 --- a/src/libsyncengine/syncpal/syncpal.h +++ b/src/libsyncengine/syncpal/syncpal.h @@ -236,6 +236,7 @@ class SYNCENGINE_EXPORT SyncPal : public std::enable_shared_from_this { [[nodiscard]] bool getSyncFileItem(const SyncPath &path, SyncFileItem &item); bool isSnapshotValid(ReplicaSide side); + void resetSnapshotInvalidationCounters(); ExitCode addDlDirectJob(const SyncPath &relativePath, const SyncPath &localPath); ExitCode cancelDlDirectJobs(const std::list &fileList); @@ -316,7 +317,6 @@ class SYNCENGINE_EXPORT SyncPal : public std::enable_shared_from_this { std::shared_ptr _syncDb{nullptr}; // Shared objects - std::shared_ptr _interruptSync{nullptr}; std::shared_ptr _localSnapshot{nullptr}; // Real time local snapshot std::shared_ptr _remoteSnapshot{nullptr}; // Real time remote snapshot std::shared_ptr _localSnapshotCopy{ @@ -357,7 +357,6 @@ class SYNCENGINE_EXPORT SyncPal : public std::enable_shared_from_this { bool createOrOpenDb(const SyncPath &syncDbPath, const std::string &version, const std::string &targetNodeId = std::string()); void setSyncHasFullyCompletedInParms(bool syncHasFullyCompleted); - inline bool interruptSync() const { return *_interruptSync; } ExitCode setListingCursor(const std::string &value, int64_t timestamp); ExitCode listingCursor(std::string &value, int64_t ×tamp); ExitCode updateSyncNode(SyncNodeType syncNodeType); diff --git a/src/libsyncengine/syncpal/syncpalworker.cpp b/src/libsyncengine/syncpal/syncpalworker.cpp index d52299145..a1de7a259 100644 --- a/src/libsyncengine/syncpal/syncpalworker.cpp +++ b/src/libsyncengine/syncpal/syncpalworker.cpp @@ -202,13 +202,6 @@ void SyncPalWorker::execute() { setExitCause(ExitCause::WorkerExited); } break; - } else if (interruptCondition()) { - LOG_SYNCPAL_INFO(_logger, "***** Step " << stepName(_step).c_str() << " interruption"); - - // Stop the step workers and restart a sync - stopAndWaitForExitOfWorkers(stepWorkers); - initStepFirst(stepWorkers, inputSharedObject, false); - continue; } } else { // Start workers @@ -360,6 +353,7 @@ void SyncPalWorker::initStep(SyncStep step, std::shared_ptr (&worke inputSharedObject[0] = nullptr; inputSharedObject[1] = nullptr; _syncPal->stopEstimateUpdates(); + _syncPal->resetSnapshotInvalidationCounters(); if (!_syncPal->restart()) { _syncPal->setSyncHasFullyCompletedInParms(true); } @@ -383,33 +377,9 @@ void SyncPalWorker::initStepFirst(std::shared_ptr (&workers)[2], _syncPal->resetSharedObjects(); } - *_syncPal->_interruptSync = false; initStep(SyncStep::Idle, workers, inputSharedObject); } -bool SyncPalWorker::interruptCondition() const { - switch (_step) { - case SyncStep::Idle: - return false; - break; - case SyncStep::UpdateDetection1: - case SyncStep::UpdateDetection2: - case SyncStep::Reconciliation1: - case SyncStep::Reconciliation2: - case SyncStep::Reconciliation3: - case SyncStep::Reconciliation4: - case SyncStep::Propagation1: - case SyncStep::Propagation2: - case SyncStep::Done: - return _syncPal->interruptSync(); - break; - default: - LOG_SYNCPAL_WARN(_logger, "Invalid status"); - return false; - break; - } -} - SyncStep SyncPalWorker::nextStep() const { switch (_step) { case SyncStep::Idle: @@ -419,10 +389,9 @@ SyncStep SyncPalWorker::nextStep() const { _syncPal->restart())) ? SyncStep::UpdateDetection1 : SyncStep::Idle; - break; case SyncStep::UpdateDetection1: { - auto logNbOps = [=](const ReplicaSide side) { - auto opsSet = _syncPal->operationSet(side); + auto logNbOps = [this](const ReplicaSide side) { + const auto opsSet = _syncPal->operationSet(side); LOG_SYNCPAL_DEBUG(_logger, opsSet->nbOps() << " " << side << " operations detected (# CREATE: " << opsSet->nbOpsByType(OperationType::Create) @@ -444,38 +413,29 @@ SyncStep SyncPalWorker::nextStep() const { _syncPal->operationSet(ReplicaSide::Remote)->updated()) ? SyncStep::UpdateDetection2 : SyncStep::Done; - break; } case SyncStep::UpdateDetection2: return (_syncPal->updateTree(ReplicaSide::Local)->updated() || _syncPal->updateTree(ReplicaSide::Remote)->updated()) ? SyncStep::Reconciliation1 : SyncStep::Done; - break; case SyncStep::Reconciliation1: return SyncStep::Reconciliation2; - break; case SyncStep::Reconciliation2: - LOG_SYNCPAL_DEBUG(_logger, _syncPal->_conflictQueue->size() << " conflicts found"); + LOG_SYNCPAL_DEBUG(_logger, _syncPal->_conflictQueue->size() << " conflicts found") return _syncPal->_conflictQueue->empty() ? SyncStep::Reconciliation4 : SyncStep::Reconciliation3; - break; case SyncStep::Reconciliation3: case SyncStep::Reconciliation4: - LOG_SYNCPAL_DEBUG(_logger, _syncPal->_syncOps->size() << " operations generated"); + LOG_SYNCPAL_DEBUG(_logger, _syncPal->_syncOps->size() << " operations generated") return _syncPal->_conflictQueue->empty() ? SyncStep::Propagation1 : SyncStep::Propagation2; - break; case SyncStep::Propagation1: return SyncStep::Propagation2; - break; case SyncStep::Propagation2: return SyncStep::Done; - break; case SyncStep::Done: return SyncStep::Idle; - break; default: - LOG_SYNCPAL_WARN(_logger, "Invalid status"); + LOG_SYNCPAL_WARN(_logger, "Invalid status") return SyncStep::Idle; - break; } } diff --git a/src/libsyncengine/syncpal/syncpalworker.h b/src/libsyncengine/syncpal/syncpalworker.h index 71e4a7339..b53feafe3 100644 --- a/src/libsyncengine/syncpal/syncpalworker.h +++ b/src/libsyncengine/syncpal/syncpalworker.h @@ -42,7 +42,6 @@ class SyncPalWorker : public ISyncWorker { std::shared_ptr (&inputSharedObject)[2]); void initStepFirst(std::shared_ptr (&workers)[2], std::shared_ptr (&inputSharedObject)[2], bool reset); - bool interruptCondition() const; SyncStep nextStep() const; void stopAndWaitForExitOfWorker(std::shared_ptr worker); void stopWorkers(std::shared_ptr workers[2]); diff --git a/src/libsyncengine/syncpal/tmpblacklistmanager.cpp b/src/libsyncengine/syncpal/tmpblacklistmanager.cpp index a8e7f8ed0..f1604add8 100644 --- a/src/libsyncengine/syncpal/tmpblacklistmanager.cpp +++ b/src/libsyncengine/syncpal/tmpblacklistmanager.cpp @@ -62,7 +62,7 @@ void TmpBlacklistManager::increaseErrorCount(const NodeId &nodeId, NodeType type insertInBlacklist(nodeId, side); sentry::Handler::captureMessage(sentry::Level::Warning, "TmpBlacklistManager::increaseErrorCount", - "Blacklisting item temporarily to avoid infinite loop"); + "Blacklisting item temporarily to avoid infinite loop"); Error err(_syncPal->syncDbId(), "", nodeId, type, relativePath, ConflictType::None, InconsistencyType::None, CancelType::TmpBlacklisted); _syncPal->addError(err); diff --git a/src/libsyncengine/update_detection/file_system_observer/computefsoperationworker.cpp b/src/libsyncengine/update_detection/file_system_observer/computefsoperationworker.cpp index e609e8fb8..ffde7602b 100644 --- a/src/libsyncengine/update_detection/file_system_observer/computefsoperationworker.cpp +++ b/src/libsyncengine/update_detection/file_system_observer/computefsoperationworker.cpp @@ -30,13 +30,11 @@ namespace KDC { ComputeFSOperationWorker::ComputeFSOperationWorker(std::shared_ptr syncPal, const std::string &name, const std::string &shortName) : - ISyncWorker(syncPal, name, shortName), - _syncDb(syncPal->syncDb()) {} + ISyncWorker(syncPal, name, shortName), _syncDb(syncPal->syncDb()) {} ComputeFSOperationWorker::ComputeFSOperationWorker(const std::shared_ptr testSyncDb, const std::string &name, const std::string &shortName) : - ISyncWorker(nullptr, name, shortName, true), - _syncDb(testSyncDb) {} + ISyncWorker(nullptr, name, shortName, true), _syncDb(testSyncDb) {} void ComputeFSOperationWorker::execute() { ExitCode exitCode(ExitCode::Unknown); diff --git a/src/libsyncengine/update_detection/file_system_observer/filesystemobserverworker.cpp b/src/libsyncengine/update_detection/file_system_observer/filesystemobserverworker.cpp index 915b2a0f0..174bb8429 100644 --- a/src/libsyncengine/update_detection/file_system_observer/filesystemobserverworker.cpp +++ b/src/libsyncengine/update_detection/file_system_observer/filesystemobserverworker.cpp @@ -22,23 +22,29 @@ namespace KDC { -FileSystemObserverWorker::FileSystemObserverWorker(std::shared_ptr syncPal, const std::string &name, - const std::string &shortName, ReplicaSide side) : - ISyncWorker(syncPal, name, shortName), - _syncDb(syncPal->_syncDb), _snapshot(syncPal->snapshot(side)) {} +constexpr int maxRetryBeforeInvalidation = 3; -FileSystemObserverWorker::~FileSystemObserverWorker() {} +FileSystemObserverWorker::FileSystemObserverWorker(std::shared_ptr syncPal, const std::string &name, + const std::string &shortName, const ReplicaSide side) : + ISyncWorker(syncPal, name, shortName), _syncDb(syncPal->_syncDb), _snapshot(syncPal->snapshot(side)) {} void FileSystemObserverWorker::invalidateSnapshot() { - if (_snapshot->isValid()) { - _snapshot->init(); - // *_interruptSync = true; // TODO : check if it is possible to avoid restarting the full sync is those cases - LOG_SYNCPAL_DEBUG(_logger, (_snapshot->side() == ReplicaSide::Local ? "Local" : "Remote") << " snapshot invalidated"); + if (!_snapshot->isValid()) return; + + _invalidateCounter++; + if (_invalidateCounter < maxRetryBeforeInvalidation) { + LOG_SYNCPAL_DEBUG(_logger, _snapshot->side() + << " snapshot is not invalidated. Invalidation count: " << _invalidateCounter); + return; } + + _snapshot->init(); + _invalidateCounter = 0; + LOG_SYNCPAL_DEBUG(_logger, _snapshot->side() << " snapshot invalidated"); } void FileSystemObserverWorker::forceUpdate() { - const std::lock_guard lock(_mutex); + const std::scoped_lock lock(_mutex); _updating = true; } diff --git a/src/libsyncengine/update_detection/file_system_observer/filesystemobserverworker.h b/src/libsyncengine/update_detection/file_system_observer/filesystemobserverworker.h index 5c9b4c2a3..7bfca5259 100644 --- a/src/libsyncengine/update_detection/file_system_observer/filesystemobserverworker.h +++ b/src/libsyncengine/update_detection/file_system_observer/filesystemobserverworker.h @@ -32,13 +32,13 @@ class FileSystemObserverWorker : public ISyncWorker { public: FileSystemObserverWorker(std::shared_ptr syncPal, const std::string &name, const std::string &shortName, ReplicaSide side); - ~FileSystemObserverWorker() override; void invalidateSnapshot(); + void resetInvalidateCounter() { _invalidateCounter = 0; } virtual void forceUpdate(); - virtual inline bool updating() const { return _updating; } + [[nodiscard]] virtual bool updating() const { return _updating; } - std::shared_ptr snapshot() const { return _snapshot; } + [[nodiscard]] std::shared_ptr snapshot() const { return _snapshot; }; protected: std::shared_ptr _syncDb; @@ -52,10 +52,12 @@ class FileSystemObserverWorker : public ISyncWorker { virtual ExitCode generateInitialSnapshot() = 0; virtual ExitCode processEvents() { return ExitCode::Ok; } - virtual bool isFolderWatcherReliable() const { return true; } + [[nodiscard]] virtual bool isFolderWatcherReliable() const { return true; } private: - virtual ReplicaSide getSnapshotType() const = 0; + [[nodiscard]] virtual ReplicaSide getSnapshotType() const = 0; + + int _invalidateCounter{false}; // A counter used to invalidate the snapshot only after a few attempt. friend class TestLocalFileSystemObserverWorker; friend class TestRemoteFileSystemObserverWorker; diff --git a/src/libsyncengine/update_detection/file_system_observer/folderwatcher.h b/src/libsyncengine/update_detection/file_system_observer/folderwatcher.h index 7cd59ecb4..af69a36ce 100644 --- a/src/libsyncengine/update_detection/file_system_observer/folderwatcher.h +++ b/src/libsyncengine/update_detection/file_system_observer/folderwatcher.h @@ -34,14 +34,15 @@ class FolderWatcher { FolderWatcher(LocalFileSystemObserverWorker *parent, const SyncPath &rootFolder); virtual ~FolderWatcher() = default; - const log4cplus::Logger &logger() const { return _logger; } + [[nodiscard]] const log4cplus::Logger &logger() const { return _logger; } void start(); void stop(); + [[nodiscard]] bool isReady() const { return _ready; } // The FolderWatcher can only become unreliable on Linux - inline bool isReliable() const { return _isReliable; } - ExitInfo exitInfo() const { return _exitInfo; } + [[nodiscard]] bool isReliable() const { return _isReliable; } + [[nodiscard]] ExitInfo exitInfo() const { return _exitInfo; } protected: // Implement this method in your subclass with the code you want your thread to run @@ -53,6 +54,7 @@ class FolderWatcher { SyncPath _folder; bool _isReliable = true; bool _stop = false; + bool _ready{false}; private: static void executeFunc(void *thisWorker); diff --git a/src/libsyncengine/update_detection/file_system_observer/folderwatcher_linux.cpp b/src/libsyncengine/update_detection/file_system_observer/folderwatcher_linux.cpp index 8ae2d121b..6f477d9e1 100644 --- a/src/libsyncengine/update_detection/file_system_observer/folderwatcher_linux.cpp +++ b/src/libsyncengine/update_detection/file_system_observer/folderwatcher_linux.cpp @@ -57,6 +57,7 @@ void FolderWatcher_linux::startWatching() { } while (!_stop) { + _ready = true; unsigned int avail; ioctl(_fileDescriptor, FIONREAD, &avail); // Since read() is blocking until something has changed, we use ioctl to check if there is changes diff --git a/src/libsyncengine/update_detection/file_system_observer/folderwatcher_linux.h b/src/libsyncengine/update_detection/file_system_observer/folderwatcher_linux.h index 0f5d72093..33e52094f 100644 --- a/src/libsyncengine/update_detection/file_system_observer/folderwatcher_linux.h +++ b/src/libsyncengine/update_detection/file_system_observer/folderwatcher_linux.h @@ -35,9 +35,6 @@ class FolderWatcher_linux : public FolderWatcher { void startWatching() override; void stopWatching() override; - /// On linux the watcher is ready when the ctor finished. - bool _ready = true; - private: int _fileDescriptor = -1; diff --git a/src/libsyncengine/update_detection/file_system_observer/folderwatcher_mac.cpp b/src/libsyncengine/update_detection/file_system_observer/folderwatcher_mac.cpp index b878b2f05..8e032a0e3 100644 --- a/src/libsyncengine/update_detection/file_system_observer/folderwatcher_mac.cpp +++ b/src/libsyncengine/update_detection/file_system_observer/folderwatcher_mac.cpp @@ -90,6 +90,7 @@ static void callback([[maybe_unused]] ConstFSEventStreamRef streamRef, void *cli void FolderWatcher_mac::startWatching() { LOGW_DEBUG(_logger, L"Start watching folder: " << Utility::formatSyncPath(_folder)); LOG_DEBUG(_logger, "File system format: " << Utility::fileSystemName(_folder).c_str()); + _ready = true; CFStringRef path = CFStringCreateWithCString(nullptr, _folder.c_str(), kCFStringEncodingUTF8); CFArrayRef pathsToWatch = CFArrayCreate(nullptr, (const void **) &path, 1, nullptr); diff --git a/src/libsyncengine/update_detection/file_system_observer/folderwatcher_win.cpp b/src/libsyncengine/update_detection/file_system_observer/folderwatcher_win.cpp index a4a93caeb..7197bf3d4 100644 --- a/src/libsyncengine/update_detection/file_system_observer/folderwatcher_win.cpp +++ b/src/libsyncengine/update_detection/file_system_observer/folderwatcher_win.cpp @@ -30,10 +30,6 @@ namespace KDC { FolderWatcher_win::FolderWatcher_win(LocalFileSystemObserverWorker *parent, const SyncPath &path) : FolderWatcher(parent, path) {} -bool FolderWatcher_win::ready() const { - return _ready; -} - void FolderWatcher_win::changesLost() { // Current snapshot needs to be invalidated _parent->invalidateSnapshot(); diff --git a/src/libsyncengine/update_detection/file_system_observer/folderwatcher_win.h b/src/libsyncengine/update_detection/file_system_observer/folderwatcher_win.h index cc0537b8c..394aef0cc 100644 --- a/src/libsyncengine/update_detection/file_system_observer/folderwatcher_win.h +++ b/src/libsyncengine/update_detection/file_system_observer/folderwatcher_win.h @@ -33,8 +33,6 @@ class FolderWatcher_win : public FolderWatcher { public: FolderWatcher_win(LocalFileSystemObserverWorker *parent, const SyncPath &path); - bool ready() const; - void changesLost(); void changeDetected(const SyncPath &path, OperationType opType); @@ -43,9 +41,6 @@ class FolderWatcher_win : public FolderWatcher { void stopWatching() override; private: - /// Set to true once the WatcherThread is capturing events. - bool _ready = false; // TODO : mutex??? - HANDLE _directoryHandle = nullptr; HANDLE _resultEventHandle = nullptr; HANDLE _stopEventHandle = nullptr; diff --git a/src/libsyncengine/update_detection/file_system_observer/localfilesystemobserverworker.cpp b/src/libsyncengine/update_detection/file_system_observer/localfilesystemobserverworker.cpp index 81ef4d9f3..17f5b84c6 100644 --- a/src/libsyncengine/update_detection/file_system_observer/localfilesystemobserverworker.cpp +++ b/src/libsyncengine/update_detection/file_system_observer/localfilesystemobserverworker.cpp @@ -37,8 +37,7 @@ static const int waitForUpdateDelay = 1000; // 1sec LocalFileSystemObserverWorker::LocalFileSystemObserverWorker(std::shared_ptr syncPal, const std::string &name, const std::string &shortName) : - FileSystemObserverWorker(syncPal, name, shortName, ReplicaSide::Local), - _rootFolder(syncPal->localPath()) {} + FileSystemObserverWorker(syncPal, name, shortName, ReplicaSide::Local), _rootFolder(syncPal->localPath()) {} LocalFileSystemObserverWorker::~LocalFileSystemObserverWorker() { LOG_SYNCPAL_DEBUG(_logger, "~LocalFileSystemObserverWorker"); diff --git a/src/libsyncengine/update_detection/file_system_observer/remotefilesystemobserverworker.cpp b/src/libsyncengine/update_detection/file_system_observer/remotefilesystemobserverworker.cpp index 69bd1ce1e..4e0611647 100644 --- a/src/libsyncengine/update_detection/file_system_observer/remotefilesystemobserverworker.cpp +++ b/src/libsyncengine/update_detection/file_system_observer/remotefilesystemobserverworker.cpp @@ -47,8 +47,7 @@ namespace KDC { RemoteFileSystemObserverWorker::RemoteFileSystemObserverWorker(std::shared_ptr syncPal, const std::string &name, const std::string &shortName) : - FileSystemObserverWorker(syncPal, name, shortName, ReplicaSide::Remote), - _driveDbId(syncPal->driveDbId()) {} + FileSystemObserverWorker(syncPal, name, shortName, ReplicaSide::Remote), _driveDbId(syncPal->driveDbId()) {} RemoteFileSystemObserverWorker::~RemoteFileSystemObserverWorker() { LOG_SYNCPAL_DEBUG(_logger, "~RemoteFileSystemObserverWorker"); diff --git a/src/libsyncengine/update_detection/file_system_observer/snapshot/snapshotitem.cpp b/src/libsyncengine/update_detection/file_system_observer/snapshot/snapshotitem.cpp index f4356c88f..c0443e747 100644 --- a/src/libsyncengine/update_detection/file_system_observer/snapshot/snapshotitem.cpp +++ b/src/libsyncengine/update_detection/file_system_observer/snapshot/snapshotitem.cpp @@ -27,8 +27,7 @@ SnapshotItem::SnapshotItem(const NodeId &id) : _id(id) {} SnapshotItem::SnapshotItem(const NodeId &id, const NodeId &parentId, const SyncName &name, SyncTime createdAt, SyncTime lastModified, NodeType type, int64_t size, bool isLink /*= false*/, bool canWrite /*= true*/, bool canShare /*= true*/) : - _id(id), - _parentId(parentId), _name(name), _createdAt(createdAt), _lastModified(lastModified), _type(type), _size(size), + _id(id), _parentId(parentId), _name(name), _createdAt(createdAt), _lastModified(lastModified), _type(type), _size(size), _isLink(isLink), _canWrite(canWrite), _canShare(canShare) { setName(name); // Needed for the computation of _normalizedName } diff --git a/src/libsyncengine/update_detection/update_detector/node.h b/src/libsyncengine/update_detection/update_detector/node.h index cd9bf3b28..d5933ac34 100644 --- a/src/libsyncengine/update_detection/update_detector/node.h +++ b/src/libsyncengine/update_detection/update_detector/node.h @@ -126,9 +126,7 @@ class Node { friend class UpdateTree; // The node id should not be changed without also changing the map in the UpdateTree and the parent/child relationship in // other nodes - inline void setId(const std::optional &nodeId) { - _id = nodeId; - } + inline void setId(const std::optional &nodeId) { _id = nodeId; } std::optional _idb = std::nullopt; ReplicaSide _side = ReplicaSide::Unknown; diff --git a/src/libsyncengine/update_detection/update_detector/updatetreeworker.cpp b/src/libsyncengine/update_detection/update_detector/updatetreeworker.cpp index 70d1a8be5..aa5e097f8 100644 --- a/src/libsyncengine/update_detection/update_detector/updatetreeworker.cpp +++ b/src/libsyncengine/update_detection/update_detector/updatetreeworker.cpp @@ -30,14 +30,13 @@ namespace KDC { UpdateTreeWorker::UpdateTreeWorker(std::shared_ptr syncPal, const std::string &name, const std::string &shortName, ReplicaSide side) : - ISyncWorker(syncPal, name, shortName), - _syncDb(syncPal->_syncDb), _operationSet(syncPal->operationSet(side)), _updateTree(syncPal->updateTree(side)), _side(side) {} + ISyncWorker(syncPal, name, shortName), _syncDb(syncPal->_syncDb), _operationSet(syncPal->operationSet(side)), + _updateTree(syncPal->updateTree(side)), _side(side) {} UpdateTreeWorker::UpdateTreeWorker(std::shared_ptr syncDb, std::shared_ptr operationSet, std::shared_ptr updateTree, const std::string &name, const std::string &shortName, ReplicaSide side) : - ISyncWorker(nullptr, name, shortName), - _syncDb(syncDb), _operationSet(operationSet), _updateTree(updateTree), _side(side) {} + ISyncWorker(nullptr, name, shortName), _syncDb(syncDb), _operationSet(operationSet), _updateTree(updateTree), _side(side) {} UpdateTreeWorker::~UpdateTreeWorker() { _operationSet.reset(); diff --git a/src/server/appserver.cpp b/src/server/appserver.cpp index 4e5a83cca..7791f6bf2 100644 --- a/src/server/appserver.cpp +++ b/src/server/appserver.cpp @@ -1980,7 +1980,10 @@ void AppServer::onRequestReceived(int id, RequestNum num, const QByteArray ¶ break; } case RequestNum::UPDATER_VERSION_INFO: { - VersionInfo versionInfo = _updateManager->versionInfo(); + auto channel = DistributionChannel::Unknown; + QDataStream paramsStream(params); + paramsStream >> channel; + VersionInfo versionInfo = _updateManager->versionInfo(channel); resultStream << versionInfo; break; } @@ -2196,20 +2199,17 @@ void AppServer::onScheduleAppRestart() { } void AppServer::onShowWindowsUpdateDialog() { - int id = 0; - QByteArray params; QDataStream paramsStream(¶ms, QIODevice::WriteOnly); paramsStream << _updateManager->versionInfo(); - CommServer::instance()->sendSignal(SignalNum::UPDATER_SHOW_DIALOG, params, id); + CommServer::instance()->sendSignal(SignalNum::UPDATER_SHOW_DIALOG, params); } void AppServer::onUpdateStateChanged(const UpdateState state) { - int id = 0; QByteArray params; QDataStream paramsStream(¶ms, QIODevice::WriteOnly); paramsStream << state; - CommServer::instance()->sendSignal(SignalNum::UPDATER_STATE_CHANGED, params, id); + CommServer::instance()->sendSignal(SignalNum::UPDATER_STATE_CHANGED, params); } void AppServer::onRestartClientReceived() { diff --git a/src/server/socketapi.cpp b/src/server/socketapi.cpp index dbf1bfbae..22bc2469e 100644 --- a/src/server/socketapi.cpp +++ b/src/server/socketapi.cpp @@ -1323,7 +1323,7 @@ void SocketApi::processFileList(const QStringList &inFileList, std::listsetCallback(callback); } -ExitCode AbstractUpdater::checkUpdateAvailable(const DistributionChannel channel, UniqueId* id /*= nullptr*/) { +ExitCode AbstractUpdater::checkUpdateAvailable(const DistributionChannel currentChannel, UniqueId* id /*= nullptr*/) { + _currentChannel = currentChannel; setState(UpdateState::Checking); - return _updateChecker->checkUpdateAvailability(channel, id); + return _updateChecker->checkUpdateAvailability(id); } void AbstractUpdater::setStateChangeCallback(const std::function& stateChangeCallback) { @@ -43,16 +44,21 @@ void AbstractUpdater::setStateChangeCallback(const std::functionversionInfo().isValid()) { + if (!_updateChecker->isVersionReceived()) { setState(UpdateState::CheckError); LOG_WARN(Log::instance()->getLogger(), "Error while retrieving latest app version"); return; } - const bool available = - CommonUtility::isVersionLower(CommonUtility::currentVersion(), _updateChecker->versionInfo().fullVersion()); - setState(available ? UpdateState::Available : UpdateState::UpToDate); - if (available) { + const VersionInfo& versionInfo = _updateChecker->versionInfo(_currentChannel); + if (!versionInfo.isValid()) { + LOG_INFO(Log::instance()->getLogger(), "No valid update info retrieved for distribution channel: " << _currentChannel); + setState(UpdateState::UpToDate); + } + + const bool newVersionAvailable = CommonUtility::isVersionLower(CommonUtility::currentVersion(), versionInfo.fullVersion()); + setState(newVersionAvailable ? UpdateState::Available : UpdateState::UpToDate); + if (newVersionAvailable) { LOG_INFO(Log::instance()->getLogger(), "New app version available"); } else { LOG_INFO(Log::instance()->getLogger(), "App version is up to date"); diff --git a/src/server/updater/abstractupdater.h b/src/server/updater/abstractupdater.h index 41cf8c82b..7a00b5627 100644 --- a/src/server/updater/abstractupdater.h +++ b/src/server/updater/abstractupdater.h @@ -30,21 +30,18 @@ class AbstractUpdater { AbstractUpdater(); virtual ~AbstractUpdater() = default; - [[nodiscard]] const VersionInfo &versionInfo() const { return _updateChecker->versionInfo(); } + [[nodiscard]] const VersionInfo &versionInfo(const DistributionChannel channel) const { + return _updateChecker->versionInfo(channel); + } [[nodiscard]] const UpdateState &state() const { return _state; } - /** - * @brief Checks if an update is available with the currently set distribution channel. - * @return ExitCode::Ok if no errors. - */ - ExitCode checkUpdateAvailable() { return checkUpdateAvailable(_updateChecker->versionInfo().channel); } /** * @brief Updates distribution channel and checks if an update is available. - * @param channel New distribution channel selected by the user. + * @param currentChannel The currently selected distribution channel. * @param id Optional. ID of the created asynchronous job. Useful in tests. * @return ExitCode::Ok if no errors. */ - ExitCode checkUpdateAvailable(DistributionChannel channel, UniqueId *id = nullptr); + ExitCode checkUpdateAvailable(DistributionChannel currentChannel, UniqueId *id = nullptr); /** * @brief Start the installation. @@ -68,14 +65,17 @@ class AbstractUpdater { static void unskipVersion(); [[nodiscard]] static bool isVersionSkipped(const std::string &version); + void setCurrentChannel(const DistributionChannel currentChannel) { _currentChannel = currentChannel; } + protected: void setState(UpdateState newState); + DistributionChannel _currentChannel{DistributionChannel::Unknown}; + private: void onAppVersionReceived(); std::unique_ptr _updateChecker; - UpdateState _state{UpdateState::UpToDate}; // Current state of the update process. std::function _stateChangeCallback = nullptr; }; diff --git a/src/server/updater/sparkleupdater.mm b/src/server/updater/sparkleupdater.mm index d52f83042..7c7fe55d4 100644 --- a/src/server/updater/sparkleupdater.mm +++ b/src/server/updater/sparkleupdater.mm @@ -187,8 +187,8 @@ - (BOOL)supportsGentleScheduledUpdateReminders { } void SparkleUpdater::onUpdateFound() { - if (isVersionSkipped(versionInfo().fullVersion())) { - LOG_INFO(KDC::Log::instance()->getLogger(), "Version " << versionInfo().fullVersion().c_str() << " is skipped."); + if (isVersionSkipped(versionInfo(_currentChannel).fullVersion())) { + LOG_INFO(KDC::Log::instance()->getLogger(), "Version " << versionInfo(_currentChannel).fullVersion().c_str() << " is skipped."); return; } @@ -213,7 +213,7 @@ - (BOOL)supportsGentleScheduledUpdateReminders { } void SparkleUpdater::startInstaller() { - reset(versionInfo().downloadUrl); + reset(versionInfo(_currentChannel).downloadUrl); if (!d->updater || !d->spuStandardUserDriver) { LOG_WARN(KDC::Log::instance()->getLogger(), "Initialization error!"); @@ -284,7 +284,7 @@ - (BOOL)supportsGentleScheduledUpdateReminders { } void SparkleUpdater::skipVersionCallback() { - skipVersion(versionInfo().fullVersion()); + skipVersion(versionInfo(_currentChannel).fullVersion()); } } // namespace KDC diff --git a/src/server/updater/updatechecker.cpp b/src/server/updater/updatechecker.cpp index 4020ef1d9..0121b75b2 100644 --- a/src/server/updater/updatechecker.cpp +++ b/src/server/updater/updatechecker.cpp @@ -24,12 +24,11 @@ #include "jobs/network/getappversionjob.h" #include "libcommon/utility/utility.h" #include "log/log.h" +#include "utility/utility.h" namespace KDC { -ExitCode UpdateChecker::checkUpdateAvailability(const DistributionChannel channel, UniqueId *id /*= nullptr*/) { - _channel = channel; - +ExitCode UpdateChecker::checkUpdateAvailability(UniqueId *id /*= nullptr*/) { std::shared_ptr job; if (const auto exitCode = generateGetAppVersionJob(job); exitCode != ExitCode::Ok) return exitCode; if (id) *id = job->jobId(); @@ -46,8 +45,43 @@ void UpdateChecker::setCallback(const std::function &callback) { _callback = callback; } +class VersionInfoCmp { + public: + bool operator()(const VersionInfo &v1, const VersionInfo &v2) const { + if (v1.fullVersion() == v2.fullVersion()) { + // Same build version, use the channel to define priority + return v1.channel < v2.channel; + } + return CommonUtility::isVersionLower(v2.fullVersion(), v1.fullVersion()); + } +}; + +const VersionInfo &UpdateChecker::versionInfo(const DistributionChannel choosedChannel) { + const VersionInfo &prodVersion = prodVersionInfo(); + + // If the user wants only `Production` versions, just return the current `Production` version. + if (choosedChannel == DistributionChannel::Prod) return prodVersion; + + // Otherwise, we need to check if there is not a newer version in other channels. + const VersionInfo &betaVersion = + _versionsInfo.contains(DistributionChannel::Beta) ? _versionsInfo[DistributionChannel::Beta] : _defaultVersionInfo; + const VersionInfo &internalVersion = _versionsInfo.contains(DistributionChannel::Internal) + ? _versionsInfo[DistributionChannel::Internal] + : _defaultVersionInfo; + std::set, VersionInfoCmp> sortedVersionList; + sortedVersionList.insert(prodVersion); + sortedVersionList.insert(betaVersion); + sortedVersionList.insert(internalVersion); + for (const auto &versionInfo: sortedVersionList) { + if (versionInfo.get().channel <= choosedChannel) return versionInfo; + } + + return _defaultVersionInfo; +} + void UpdateChecker::versionInfoReceived(UniqueId jobId) { - _versionInfo.clear(); + _isVersionReceived = false; + _versionsInfo.clear(); LOG_INFO(Log::instance()->getLogger(), "App version info received"); auto job = JobManager::instance()->getJob(jobId); @@ -65,13 +99,14 @@ void UpdateChecker::versionInfoReceived(UniqueId jobId) { ss << errorCode.c_str() << " - " << errorDescr; sentry::Handler::captureMessage(sentry::Level::Warning, "AbstractUpdater::checkUpdateAvailable", ss.str()); LOG_ERROR(Log::instance()->getLogger(), ss.str().c_str()); + } else if (getAppVersionJobPtr->exitCode() != ExitCode::Ok) { + LOG_ERROR(Log::instance()->getLogger(), "Error in UpdateChecker::versionInfoReceived : exit code: " + << getAppVersionJobPtr->exitCode() + << ", exit cause: " << getAppVersionJobPtr->exitCause()); } else { - _versionInfo = getAppVersionJobPtr->getProdVersionInfo(); - if (!_versionInfo.isValid()) { - std::string error = "Invalid version info!"; - sentry::Handler::captureMessage(sentry::Level::Warning, "AbstractUpdater::checkUpdateAvailable", error); - LOG_ERROR(Log::instance()->getLogger(), error.c_str()); - } + _versionsInfo = getAppVersionJobPtr->versionsInfo(); + _prodVersionChannel = getAppVersionJobPtr->prodVersionChannel(); + _isVersionReceived = true; } _callback(); diff --git a/src/server/updater/updatechecker.h b/src/server/updater/updatechecker.h index 6f70410a8..07520dda5 100644 --- a/src/server/updater/updatechecker.h +++ b/src/server/updater/updatechecker.h @@ -30,21 +30,32 @@ class UpdateChecker { virtual ~UpdateChecker() = default; /** - * @brief Asynchronously check for new version informations. - * @param channel Distribution channel (i.e. Production, Beta or Internal). + * @brief Asynchronously check for new version information. * @param id Optional. ID of the created asynchronous job. Useful in tests. - * @return ExitCode::Ok if the job has been succesfully created. + * @return ExitCode::Ok if the job has been successfully created. */ - ExitCode checkUpdateAvailability(DistributionChannel channel, UniqueId *id = nullptr); + ExitCode checkUpdateAvailability(UniqueId *id = nullptr); void setCallback(const std::function &callback); - [[nodiscard]] const VersionInfo &versionInfo() const { return _versionInfo; } + /** + * @brief Return the version information. Implements some logic to always return the highest available versions according + * to the selected distribution channel. That means if the `Beta` version is newer than the `Internal` version, the the + * `Beta` version wins over the `Internal` one and must be proposed even if the user has selected the `Internal` channel. + * The rule is the `Production` version wins over all others, the `Beta` verison wins over the `Internal` version. + * @param choosedChannel The selected distribution channel. + * @return A reference to the found `VersionInfo` object. If not found, return a reference to default constructed, invalid + * `VersionInfo`object. + */ + const VersionInfo &versionInfo(DistributionChannel choosedChannel); + + [[nodiscard]] const std::unordered_map &versionsInfo() const { return _versionsInfo; } + [[nodiscard]] bool isVersionReceived() const { return _isVersionReceived; } private: /** * @brief Callback used to extract the version info. - * @param jobId ID of + * @param jobId ID of the terminated job. */ void versionInfoReceived(UniqueId jobId); @@ -52,13 +63,26 @@ class UpdateChecker { * @brief Create a shared pointer to the `GetAppVersionJob`. Override this method in test class to test different * scenarios. * @param job The `GetAppVersionJob` we want to use in `checkUpdateAvailable()`. - * @return ExitCode::Ok if the job has been succesfully created. + * @return ExitCode::Ok if the job has been successfully created. */ virtual ExitCode generateGetAppVersionJob(std::shared_ptr &job); + /** + * @brief Return the adequate version info, according to whether the current app has been selected in the progressive + * update distribution process. + * @return const reference on a VersionInfo + */ + const VersionInfo &prodVersionInfo() { + return _versionsInfo.contains(_prodVersionChannel) ? _versionsInfo[_prodVersionChannel] : _defaultVersionInfo; + } + std::function _callback = nullptr; - DistributionChannel _channel = DistributionChannel::Unknown; - VersionInfo _versionInfo; // A struct keeping all the informations about the currently available version. + DistributionChannel _prodVersionChannel{DistributionChannel::Unknown}; + const VersionInfo _defaultVersionInfo; + AllVersionsInfo _versionsInfo; + bool _isVersionReceived{false}; + + friend class TestUpdateChecker; }; } // namespace KDC diff --git a/src/server/updater/updatemanager.cpp b/src/server/updater/updatemanager.cpp index 51fa81536..db2f4ecf4 100644 --- a/src/server/updater/updatemanager.cpp +++ b/src/server/updater/updatemanager.cpp @@ -26,16 +26,15 @@ #include "linuxupdater.h" #endif - -#include "db/parmsdb.h" #include "libcommon/utility/utility.h" #include "log/log.h" #include "requests/parameterscache.h" -#include "utility/utility.h" namespace KDC { UpdateManager::UpdateManager(QObject *parent) : QObject(parent) { + _currentChannel = ParametersCache::instance()->parameters().distributionChannel(); + createUpdater(); connect(&_updateCheckTimer, &QTimer::timeout, this, &UpdateManager::slotTimerFired); @@ -49,7 +48,14 @@ UpdateManager::UpdateManager(QObject *parent) : QObject(parent) { connect(this, &UpdateManager::updateStateChanged, this, &UpdateManager::slotUpdateStateChanged, Qt::QueuedConnection); // At startup, do a check in any case and setup distribution channel. - QTimer::singleShot(3000, this, [this]() { setDistributionChannel(readDistributionChannelFromDb()); }); + QTimer::singleShot(3000, this, [this]() { setDistributionChannel(_currentChannel); }); +} + +void UpdateManager::setDistributionChannel(const DistributionChannel channel) { + _currentChannel = channel; + _updater->checkUpdateAvailable(channel); + ParametersCache::instance()->parameters().setDistributionChannel(channel); + ParametersCache::instance()->save(); } void UpdateManager::startInstaller() const { @@ -62,7 +68,7 @@ void UpdateManager::startInstaller() const { } void UpdateManager::slotTimerFired() const { - _updater->checkUpdateAvailable(); + _updater->checkUpdateAvailable(_currentChannel); } void UpdateManager::slotUpdateStateChanged(const UpdateState newState) { @@ -75,8 +81,9 @@ void UpdateManager::slotUpdateStateChanged(const UpdateState newState) { break; } case UpdateState::ManualUpdateAvailable: { - emit updateAnnouncement(tr("New update available."), - tr("Version %1 is available for download.").arg(_updater->versionInfo().tag.c_str())); + emit updateAnnouncement( + tr("New update available."), + tr("Version %1 is available for download.").arg(_updater->versionInfo(_currentChannel).tag.c_str())); break; } case UpdateState::Available: { @@ -85,7 +92,7 @@ void UpdateManager::slotUpdateStateChanged(const UpdateState newState) { break; } case UpdateState::Ready: { - if (AbstractUpdater::isVersionSkipped(_updater->versionInfo().fullVersion())) break; + if (AbstractUpdater::isVersionSkipped(_updater->versionInfo(_currentChannel).fullVersion())) break; // The new version is ready to be installed #if defined(_WIN32) emit showUpdateDialog(); @@ -118,8 +125,4 @@ void UpdateManager::onUpdateStateChanged(const UpdateState newState) { emit updateStateChanged(newState); } -DistributionChannel UpdateManager::readDistributionChannelFromDb() const { - return ParametersCache::instance()->parameters().distributionChannel(); -} - } // namespace KDC diff --git a/src/server/updater/updatemanager.h b/src/server/updater/updatemanager.h index ca3ef0a11..230d656ed 100644 --- a/src/server/updater/updatemanager.h +++ b/src/server/updater/updatemanager.h @@ -40,10 +40,10 @@ class UpdateManager final : public QObject { public: explicit UpdateManager(QObject *parent); - void setDistributionChannel(const DistributionChannel channel) const { - _updater->checkUpdateAvailable(channel); - } // TODO : write to DB - [[nodiscard]] const VersionInfo &versionInfo() const { return _updater->versionInfo(); } + void setDistributionChannel(DistributionChannel channel); + [[nodiscard]] const VersionInfo &versionInfo(const DistributionChannel channel = DistributionChannel::Unknown) const { + return _updater->versionInfo(channel == DistributionChannel::Unknown ? _currentChannel : channel); + } [[nodiscard]] const UpdateState &state() const { return _updater->state(); } void startInstaller() const; @@ -67,10 +67,8 @@ class UpdateManager final : public QObject { void onUpdateStateChanged(UpdateState newState); - [[nodiscard]] DistributionChannel readDistributionChannelFromDb() const; - std::unique_ptr _updater; - + DistributionChannel _currentChannel{DistributionChannel::Unknown}; QTimer _updateCheckTimer; /** Timer for the regular update check. */ }; diff --git a/src/server/updater/windowsupdater.cpp b/src/server/updater/windowsupdater.cpp index 1f945e410..eb0e06c41 100644 --- a/src/server/updater/windowsupdater.cpp +++ b/src/server/updater/windowsupdater.cpp @@ -61,7 +61,7 @@ void WindowsUpdater::downloadUpdate() noexcept { return; } - auto job = std::make_shared(filepath, versionInfo().downloadUrl); + auto job = std::make_shared(filepath, versionInfo(_currentChannel).downloadUrl); const std::function callback = std::bind_front(&WindowsUpdater::downloadFinished, this); JobManager::instance()->queueAsyncJob(job, Poco::Thread::PRIO_NORMAL, callback); setState(UpdateState::Downloading); @@ -110,12 +110,13 @@ void WindowsUpdater::downloadFinished(const UniqueId jobId) { } bool WindowsUpdater::getInstallerPath(SyncPath &path) const { - const auto pos = versionInfo().downloadUrl.find_last_of('/'); - const auto installerName = versionInfo().downloadUrl.substr(pos + 1); + const auto url = versionInfo(_currentChannel).downloadUrl; + const auto pos = url.find_last_of('/'); + const auto installerName = url.substr(pos + 1); SyncPath tmpDirPath; if (IoError ioError = IoError::Unknown; !IoHelper::tempDirectoryPath(tmpDirPath, ioError)) { sentry::Handler::captureMessage(sentry::Level::Warning, "WindowsUpdater::getInstallerPath", - "Impossible to retrieve installer destination directory."); + "Impossible to retrieve installer destination directory."); return false; } path = tmpDirPath / installerName; diff --git a/test/libcommon/log/sentry/testsentryhandler.h b/test/libcommon/log/sentry/testsentryhandler.h index 266a5f2b8..679b489fb 100644 --- a/test/libcommon/log/sentry/testsentryhandler.h +++ b/test/libcommon/log/sentry/testsentryhandler.h @@ -29,9 +29,10 @@ class MockTestSentryHandler : public sentry::Handler { MockTestSentryHandler(); int sentryUploadedEventCount() const { return _sentryUploadedEventCount; } void captureMessage(sentry::Level level, const std::string &title, const std::string &message, - const SentryUser &user = SentryUser()) { + const SentryUser &user = SentryUser()) { _captureMessage(level, title, message, user); } + private: void sendEventToSentry(const sentry::Level level, const std::string &title, const std::string &message) const final; mutable int _sentryUploadedEventCount = 0; diff --git a/test/libsyncengine/jobs/local/testlocaljobs.cpp b/test/libsyncengine/jobs/local/testlocaljobs.cpp index c75aff90c..633af5d6e 100644 --- a/test/libsyncengine/jobs/local/testlocaljobs.cpp +++ b/test/libsyncengine/jobs/local/testlocaljobs.cpp @@ -33,7 +33,7 @@ namespace KDC { class LocalDeleteJobMockingTrash : public LocalDeleteJob { public: - explicit LocalDeleteJobMockingTrash(const SyncPath &absolutePath) : LocalDeleteJob(absolutePath){}; + explicit LocalDeleteJobMockingTrash(const SyncPath &absolutePath) : LocalDeleteJob(absolutePath) {}; void setMoveToTrashFailed(bool failed) { _moveToTrashFailed = failed; }; protected: diff --git a/test/libsyncengine/jobs/network/testnetworkjobs.cpp b/test/libsyncengine/jobs/network/testnetworkjobs.cpp index cec872cdf..9c6f0c92a 100644 --- a/test/libsyncengine/jobs/network/testnetworkjobs.cpp +++ b/test/libsyncengine/jobs/network/testnetworkjobs.cpp @@ -350,8 +350,7 @@ void TestNetworkJobs::testGetAvatar() { CPPUNIT_ASSERT(exitCode == ExitCode::Ok); CPPUNIT_ASSERT(job.jsonRes()); - Poco::JSON::Object::Ptr data = job.jsonRes()->getObject(dataKey); - std::string avatarUrl = data->get(avatarKey); + const std::string avatarUrl = job.avatarUrl(); GetAvatarJob avatarJob(avatarUrl); exitCode = avatarJob.runSynchronously(); @@ -627,8 +626,9 @@ void TestNetworkJobs::testGetInfoUser() { const ExitCode exitCode = job.runSynchronously(); CPPUNIT_ASSERT(exitCode == ExitCode::Ok); - Poco::JSON::Object::Ptr data = job.jsonRes()->getObject(dataKey); - // CPPUNIT_ASSERT(data->get(emailKey).toString() == _email); + CPPUNIT_ASSERT_EQUAL(std::string("John Doe"), job.name()); + CPPUNIT_ASSERT_EQUAL(std::string("john.doe@nogafam.ch"), job.email()); + CPPUNIT_ASSERT_EQUAL(false, job.isStaff()); } void TestNetworkJobs::testGetInfoDrive() { @@ -959,11 +959,11 @@ void TestNetworkJobs::testGetAppVersionInfo() { GetAppVersionJob job(CommonUtility::platform(), appUid); job.runSynchronously(); CPPUNIT_ASSERT(!job.hasHttpError()); - CPPUNIT_ASSERT(job.getVersionInfo(DistributionChannel::Internal).isValid()); - CPPUNIT_ASSERT(job.getVersionInfo(DistributionChannel::Beta).isValid()); - CPPUNIT_ASSERT(job.getVersionInfo(DistributionChannel::Next).isValid()); - CPPUNIT_ASSERT(job.getVersionInfo(DistributionChannel::Prod).isValid()); - CPPUNIT_ASSERT(job.getProdVersionInfo().isValid()); + CPPUNIT_ASSERT(job.versionInfo(DistributionChannel::Internal).isValid()); + CPPUNIT_ASSERT(job.versionInfo(DistributionChannel::Beta).isValid()); + CPPUNIT_ASSERT(job.versionInfo(DistributionChannel::Next).isValid()); + CPPUNIT_ASSERT(job.versionInfo(DistributionChannel::Prod).isValid()); + CPPUNIT_ASSERT(job.versionInfo(job.prodVersionChannel()).isValid()); } // With 1 user ID { @@ -974,11 +974,11 @@ void TestNetworkJobs::testGetAppVersionInfo() { GetAppVersionJob job(CommonUtility::platform(), appUid, {user.userId()}); job.runSynchronously(); CPPUNIT_ASSERT(!job.hasHttpError()); - CPPUNIT_ASSERT(job.getVersionInfo(DistributionChannel::Internal).isValid()); - CPPUNIT_ASSERT(job.getVersionInfo(DistributionChannel::Beta).isValid()); - CPPUNIT_ASSERT(job.getVersionInfo(DistributionChannel::Next).isValid()); - CPPUNIT_ASSERT(job.getVersionInfo(DistributionChannel::Prod).isValid()); - CPPUNIT_ASSERT(job.getProdVersionInfo().isValid()); + CPPUNIT_ASSERT(job.versionInfo(DistributionChannel::Internal).isValid()); + CPPUNIT_ASSERT(job.versionInfo(DistributionChannel::Beta).isValid()); + CPPUNIT_ASSERT(job.versionInfo(DistributionChannel::Next).isValid()); + CPPUNIT_ASSERT(job.versionInfo(DistributionChannel::Prod).isValid()); + CPPUNIT_ASSERT(job.versionInfo(job.prodVersionChannel()).isValid()); } // // With several user IDs // TODO : commented out because we need valid user IDs but we have only one available in tests for now diff --git a/test/libsyncengine/update_detection/file_system_observer/testlocalfilesystemobserverworker.cpp b/test/libsyncengine/update_detection/file_system_observer/testlocalfilesystemobserverworker.cpp index 2130def45..c55ac792c 100644 --- a/test/libsyncengine/update_detection/file_system_observer/testlocalfilesystemobserverworker.cpp +++ b/test/libsyncengine/update_detection/file_system_observer/testlocalfilesystemobserverworker.cpp @@ -42,7 +42,7 @@ using namespace CppUnit; namespace KDC { -const uint64_t nbFileInTestDir = 5; // Test directory contains 5 files +constexpr uint64_t nbFileInTestDir = 5; // Test directory contains 5 files void TestLocalFileSystemObserverWorker::setUp() { _logger = Log::instance()->getLogger(); @@ -121,8 +121,8 @@ void TestLocalFileSystemObserverWorker::testLFSOWithInitialSnapshot() { uint64_t fileCounter = 0; for (const auto &id: ids) { - const auto name = _syncPal->snapshot(ReplicaSide::Local)->name(id); - if (name == Str(".DS_Store") || name == Str(".ds_store")) { + if (const auto name = _syncPal->snapshot(ReplicaSide::Local)->name(id); + name == Str(".DS_Store") || name == Str(".ds_store")) { continue; // Ignore ".DS_Store" } @@ -176,7 +176,7 @@ void TestLocalFileSystemObserverWorker::testLFSOWithFiles() { SyncPath sourcePath = testAbsolutePath; SyncPath destinationPath = _subDirPath / filename; - IoError ioError = IoError::Unknown; + auto ioError = IoError::Unknown; IoHelper::moveItem(sourcePath, destinationPath, ioError); Utility::msleep(1000); // Wait 1sec @@ -192,7 +192,7 @@ void TestLocalFileSystemObserverWorker::testLFSOWithFiles() { SyncPath source = testAbsolutePath; SyncPath destinationPath = _subDirPath / Str("test_file_renamed.txt"); - IoError ioError = IoError::Unknown; + auto ioError = IoError::Unknown; IoHelper::renameItem(source, destinationPath, ioError); Utility::msleep(1000); // Wait 1sec @@ -204,7 +204,7 @@ void TestLocalFileSystemObserverWorker::testLFSOWithFiles() { { /// Delete file LOGW_DEBUG(_logger, L"***** test delete file *****"); - IoError ioError = IoError::Unknown; + auto ioError = IoError::Unknown; IoHelper::deleteItem(testAbsolutePath, ioError); Utility::msleep(1000); // Wait 1sec @@ -232,8 +232,12 @@ void TestLocalFileSystemObserverWorker::testLFSOWithDuplicateFileNames() { _syncPal->_localFSObserverWorker = slowObserver; _syncPal->_localFSObserverWorker->start(); + auto localFSO = std::dynamic_pointer_cast(_syncPal->_localFSObserverWorker); + CPPUNIT_ASSERT(localFSO); + int count = 0; - while (!_syncPal->snapshot(ReplicaSide::Local)->isValid()) { // Wait for the snapshot generation + while (!_syncPal->snapshot(ReplicaSide::Local)->isValid() || + !localFSO->_folderWatcher->isReady()) { // Wait for the snapshot generation Utility::msleep(100); CPPUNIT_ASSERT(count++ < 20); // Do not wait more than 2s } @@ -295,7 +299,7 @@ void TestLocalFileSystemObserverWorker::testLFSOWithDirs() { LOGW_DEBUG(_logger, L"***** test move dir *****"); SyncPath sourcePath = testAbsolutePath; SyncPath destinationPath = _subDirPath / dirname; - IoError ioError = IoError::Unknown; + auto ioError = IoError::Unknown; IoHelper::moveItem(sourcePath, destinationPath, ioError); Utility::msleep(1000); // Wait 1sec @@ -312,7 +316,7 @@ void TestLocalFileSystemObserverWorker::testLFSOWithDirs() { LOGW_DEBUG(_logger, L"***** test rename dir *****"); SyncPath sourcePath = testAbsolutePath; SyncPath destinationPath = _subDirPath / (dirname + Str("_renamed")); - IoError ioError = IoError::Unknown; + auto ioError = IoError::Unknown; IoHelper::renameItem(sourcePath, destinationPath, ioError); Utility::msleep(1000); // Wait 1sec @@ -325,7 +329,7 @@ void TestLocalFileSystemObserverWorker::testLFSOWithDirs() { // Generate test item outside sync folder SyncName dirName = Str("dir_copy"); SyncPath sourcePath = _tempDir.path() / dirName; - IoError ioError = IoError::Unknown; + auto ioError = IoError::Unknown; IoHelper::copyFileOrDirectory(_subDirPath, sourcePath, ioError); /// Move dir from outside sync dir @@ -351,18 +355,14 @@ void TestLocalFileSystemObserverWorker::testLFSOWithDirs() { void TestLocalFileSystemObserverWorker::testLFSODeleteDir() { - NodeId itemId; - { - /// Delete dir and all its content - LOGW_DEBUG(_logger, L"***** test delete dir *****"); - IoError ioError = IoError::Unknown; - IoHelper::deleteItem(_subDirPath, ioError); + /// Delete dir and all its content + LOGW_DEBUG(_logger, L"***** test delete dir *****"); + auto ioError = IoError::Unknown; + IoHelper::deleteItem(_subDirPath, ioError); - Utility::msleep(1000); // Wait 1sec + Utility::msleep(1000); // Wait 1sec - CPPUNIT_ASSERT(!_syncPal->snapshot(ReplicaSide::Local)->exists(itemId)); - CPPUNIT_ASSERT(!_syncPal->snapshot(ReplicaSide::Local)->exists(_testFiles[0].first)); - } + CPPUNIT_ASSERT(!_syncPal->snapshot(ReplicaSide::Local)->exists(_testFiles[0].first)); LOGW_DEBUG(_logger, L"***** Tests for copy and deletion of directories succesfully finished! *****"); } @@ -379,7 +379,7 @@ void TestLocalFileSystemObserverWorker::testLFSOWithSpecialCases1() { IoHelper::getFileStat(sourcePath, &fileStat, exists); NodeId initItemId = std::to_string(fileStat.inode); - IoError ioError = IoError::Unknown; + auto ioError = IoError::Unknown; IoHelper::deleteItem(sourcePath, ioError); //// create KDC::testhelpers::generateOrEditTestFile(sourcePath); @@ -410,7 +410,7 @@ void TestLocalFileSystemObserverWorker::testLFSOWithSpecialCases2() { IoHelper::getFileStat(sourcePath, &fileStat, exists); NodeId initItemId = std::to_string(fileStat.inode); - IoError ioError = IoError::Unknown; + auto ioError = IoError::Unknown; IoHelper::moveItem(sourcePath, destinationPath, ioError); //// create KDC::testhelpers::generateOrEditTestFile(sourcePath); @@ -429,7 +429,7 @@ void TestLocalFileSystemObserverWorker::testLFSOWithSpecialCases2() { } void TestLocalFileSystemObserverWorker::testLFSOFastMoveDeleteMove() { // MS Office test - LOGW_DEBUG(_logger, L"***** Test fast move/delete *****"); + LOGW_DEBUG(_logger, L"***** Test fast move/delete *****") _syncPal->_localFSObserverWorker->stop(); _syncPal->_localFSObserverWorker->waitForExit(); _syncPal->_localFSObserverWorker.reset(); @@ -439,15 +439,19 @@ void TestLocalFileSystemObserverWorker::testLFSOFastMoveDeleteMove() { // MS Off _syncPal->_localFSObserverWorker = slowObserver; _syncPal->_localFSObserverWorker->start(); + auto localFSO = std::dynamic_pointer_cast(_syncPal->_localFSObserverWorker); + CPPUNIT_ASSERT(localFSO); + int count = 0; - while (!_syncPal->snapshot(ReplicaSide::Local)->isValid()) { // Wait for the snapshot generation + while (!_syncPal->snapshot(ReplicaSide::Local)->isValid() || + !localFSO->_folderWatcher->isReady()) { // Wait for the snapshot generation and folder watcher start Utility::msleep(100); CPPUNIT_ASSERT(count++ < 20); // Do not wait more than 2s } CPPUNIT_ASSERT(_syncPal->snapshot(ReplicaSide::Local)->exists(_testFiles[0].first)); - IoError ioError = IoError::Unknown; - SyncPath destinationPath = _testFiles[0].second.parent_path() / (_testFiles[0].second.filename().string() + "2"); + auto ioError = IoError::Unknown; + const SyncPath destinationPath = _testFiles[0].second.parent_path() / (_testFiles[0].second.filename().string() + "2"); CPPUNIT_ASSERT(IoHelper::renameItem(_testFiles[0].second, destinationPath, ioError)); // test0.txt -> test0.txt2 CPPUNIT_ASSERT_EQUAL(IoError::Success, ioError); CPPUNIT_ASSERT(IoHelper::deleteItem(destinationPath, ioError)); // Delete test0.txt2 (before the previous rename is processed) @@ -456,6 +460,8 @@ void TestLocalFileSystemObserverWorker::testLFSOFastMoveDeleteMove() { // MS Off ioError)); // test1.txt -> test0.txt (before the previous rename and delete is processed) CPPUNIT_ASSERT_EQUAL(IoError::Success, ioError); + LOG_DEBUG(_logger, "Operations finished") + slowObserver->waitForUpdate(); FileStat fileStat; @@ -484,21 +490,26 @@ void TestLocalFileSystemObserverWorker::testLFSOFastMoveDeleteMoveWithEncodingCh FileStat fileStat; bool exists = false; + auto localFSO = std::dynamic_pointer_cast(_syncPal->_localFSObserverWorker); + CPPUNIT_ASSERT(localFSO); + + while (!_syncPal->snapshot(ReplicaSide::Local)->isValid() || + !localFSO->_folderWatcher->isReady()) { // Wait for the snapshot generation + Utility::msleep(100); + CPPUNIT_ASSERT(count++ < 20); // Do not wait more than 2s + } + // Create an NFC encoded file. SyncPath tmpDirPath = _testFiles[0].second.parent_path(); SyncPath nfcFilePath = tmpDirPath / makeNfcSyncName(); generateOrEditTestFile(nfcFilePath); - NodeId nfcFileId; IoHelper::getFileStat(nfcFilePath, &fileStat, exists); - nfcFileId = std::to_string(fileStat.inode); + NodeId nfcFileId = std::to_string(fileStat.inode); // Prepare the path of the NFD encoded file. SyncPath nfdFilePath = tmpDirPath / makeNfdSyncName(); - while (!_syncPal->snapshot(ReplicaSide::Local)->isValid()) { // Wait for the snapshot generation - Utility::msleep(100); - CPPUNIT_ASSERT(count++ < 20); // Do not wait more than 2s - } + slowObserver->waitForUpdate(); CPPUNIT_ASSERT(_syncPal->snapshot(ReplicaSide::Local)->exists(nfcFileId)); @@ -522,9 +533,28 @@ void TestLocalFileSystemObserverWorker::testLFSOFastMoveDeleteMoveWithEncodingCh CPPUNIT_ASSERT(_syncPal->snapshot(ReplicaSide::Local)->exists(nfdFileId)); } -void MockLocalFileSystemObserverWorker::waitForUpdate(long long timeoutMs) const { +void TestLocalFileSystemObserverWorker::testInvalidateCounter() { + _syncPal->_localFSObserverWorker->invalidateSnapshot(); + CPPUNIT_ASSERT_EQUAL(true, _syncPal->snapshot(ReplicaSide::Local)->isValid()); // Snapshot is not invalidated yet. + _syncPal->_localFSObserverWorker->invalidateSnapshot(); + CPPUNIT_ASSERT_EQUAL(true, _syncPal->snapshot(ReplicaSide::Local)->isValid()); // Snapshot is not invalidated yet. + _syncPal->_localFSObserverWorker->invalidateSnapshot(); + CPPUNIT_ASSERT_EQUAL(false, _syncPal->snapshot(ReplicaSide::Local)->isValid()); // Snapshot has been invalidated. + + Utility::msleep(1000); // Wait for the snapshot to be rebuilt + + CPPUNIT_ASSERT_EQUAL(true, _syncPal->snapshot(ReplicaSide::Local)->isValid()); // Snapshot is now valid again. + _syncPal->_localFSObserverWorker->invalidateSnapshot(); + CPPUNIT_ASSERT_EQUAL(true, _syncPal->snapshot(ReplicaSide::Local)->isValid()); // Snapshot is not invalidated yet. + _syncPal->_localFSObserverWorker->invalidateSnapshot(); + CPPUNIT_ASSERT_EQUAL(true, _syncPal->snapshot(ReplicaSide::Local)->isValid()); // Snapshot is not invalidated yet. + _syncPal->_localFSObserverWorker->invalidateSnapshot(); + CPPUNIT_ASSERT_EQUAL(false, _syncPal->snapshot(ReplicaSide::Local)->isValid()); // Snapshot has been invalidated. +} + +void MockLocalFileSystemObserverWorker::waitForUpdate(const long long timeoutMs) const { using namespace std::chrono; - auto start = system_clock::now(); + const auto start = system_clock::now(); while (!_updating && duration_cast(system_clock::now() - start).count() < timeoutMs) { Utility::msleep(10); } diff --git a/test/libsyncengine/update_detection/file_system_observer/testlocalfilesystemobserverworker.h b/test/libsyncengine/update_detection/file_system_observer/testlocalfilesystemobserverworker.h index 75a59e75a..e831834fa 100644 --- a/test/libsyncengine/update_detection/file_system_observer/testlocalfilesystemobserverworker.h +++ b/test/libsyncengine/update_detection/file_system_observer/testlocalfilesystemobserverworker.h @@ -73,6 +73,7 @@ class TestLocalFileSystemObserverWorker : public CppUnit::TestFixture { CPPUNIT_TEST(testLFSOFastMoveDeleteMoveWithEncodingChange); CPPUNIT_TEST(testLFSOWithSpecialCases1); CPPUNIT_TEST(testLFSOWithSpecialCases2); + CPPUNIT_TEST(testInvalidateCounter); CPPUNIT_TEST_SUITE_END(); public: @@ -97,6 +98,7 @@ class TestLocalFileSystemObserverWorker : public CppUnit::TestFixture { void testLFSOFastMoveDeleteMoveWithEncodingChange(); void testLFSOWithSpecialCases1(); void testLFSOWithSpecialCases2(); + void testInvalidateCounter(); static bool vfsStatus(int, const SyncPath &, bool &, bool &, bool &, int &) { return true; }; static bool vfsPinState(int, const SyncPath &, PinState &) { return true; }; diff --git a/test/server/updater/testupdatechecker.cpp b/test/server/updater/testupdatechecker.cpp index a8de9e0ae..f4c838144 100644 --- a/test/server/updater/testupdatechecker.cpp +++ b/test/server/updater/testupdatechecker.cpp @@ -21,15 +21,67 @@ #include "jobs/jobmanager.h" #include "jobs/network/getappversionjob.h" #include "libcommon/utility/utility.h" +#include "requests/parameterscache.h" #include "server/updater/updatechecker.h" #include "utility/utility.h" namespace KDC { -static const std::string bigVersionJsonUpdateStr = - R"({"result":"success","data":{"application_id":27,"prod_version":"production","version":{"tag":"99.99.99","tag_updated_at":"2124-06-04 15:06:37","version_changelog":"test","type":"production","build_version":"21240604","build_min_os_version":"21240604","download_link":"test","data":["[]"]},"application":{"id":27,"name":"com.infomaniak.drive","platform":"mac-os","store":"kStore","api_id":"com.infomaniak.drive","min_version":"99.99.99","next_version_rate":0,"published_versions":[{"tag":"99.99.99","tag_updated_at":"2124-06-04 15:06:37","version_changelog":"test","type":"production","build_version":"21240604","build_min_os_version":"21240604","download_link":"test","data":["[]"]},{"tag":"99.99.99","tag_updated_at":"2124-06-04 15:06:12","version_changelog":"test","type":"beta","build_version":"21240604","build_min_os_version":"21240604","download_link":"test","data":["[]"]},{"tag":"99.99.99","tag_updated_at":"2124-06-04 15:05:44","version_changelog":"test","type":"internal","build_version":"21240604","build_min_os_version":"21240604","download_link":"test","data":["[]"]},{"tag":"99.99.99","tag_updated_at":"2124-06-04 15:03:29","version_changelog":"test","type":"production-next","build_version":"21240604","build_min_os_version":"21240604","download_link":"test","data":["[]"]}]}}})"; -static const std::string smallVersionJsonUpdateStr = - R"({"result":"success","data":{"application_id":27,"prod_version":"production","version":{"tag":"1.1.1","tag_updated_at":"2020-06-04 15:06:37","version_changelog":"test","type":"production","build_version":"20200604","build_min_os_version":"20200604","download_link":"test","data":["[]"]},"application":{"id":27,"name":"com.infomaniak.drive","platform":"mac-os","store":"kStore","api_id":"com.infomaniak.drive","min_version":"1.1.1","next_version_rate":0,"published_versions":[{"tag":"1.1.1","tag_updated_at":"2020-06-04 15:06:37","version_changelog":"test","type":"production","build_version":"20200604","build_min_os_version":"20200604","download_link":"test","data":["[]"]},{"tag":"1.1.1","tag_updated_at":"2020-06-04 15:06:12","version_changelog":"test","type":"beta","build_version":"20200604","build_min_os_version":"20200604","download_link":"test","data":["[]"]},{"tag":"1.1.1","tag_updated_at":"2020-06-04 15:05:44","version_changelog":"test","type":"internal","build_version":"20200604","build_min_os_version":"20200604","download_link":"test","data":["[]"]},{"tag":"1.1.1","tag_updated_at":"2020-06-04 15:03:29","version_changelog":"test","type":"production-next","build_version":"20200604","build_min_os_version":"20200604","download_link":"test","data":["[]"]}]}}})"; +static const std::string highTagValue = "99.99.99"; +static constexpr uint64_t highBuildVersionValue = 21240604; +static const std::string mediumTagValue = "55.55.55"; +static constexpr uint64_t mediumBuildVersionValue = 20240604; +static const std::string lowTagValue = "1.1.1"; +static constexpr uint64_t lowBuildVersionValue = 20200604; + +std::string generateJsonReply(const std::string &tag, uint64_t buildVersion) { + Poco::JSON::Object versionObj; + versionObj.set("tag", tag); + versionObj.set("tag_updated_at", "2020-06-04 15:06:37"); + versionObj.set("version_changelog", "test"); + versionObj.set("type", "production"); + versionObj.set("build_version", buildVersion); + versionObj.set("build_min_os_version", "XXXX"); + versionObj.set("download_link", "test"); + + Poco::JSON::Array publishedVersionsArray; + for (const auto channel: + {DistributionChannel::Prod, DistributionChannel::Next, DistributionChannel::Beta, DistributionChannel::Internal}) { + Poco::JSON::Object tmpObj; + tmpObj.set("tag", tag); + tmpObj.set("tag_updated_at", "2020-06-04 15:06:37"); + tmpObj.set("version_changelog", "test"); + tmpObj.set("type", GetAppVersionJob::toStr(channel)); + tmpObj.set("build_version", buildVersion); + tmpObj.set("build_min_os_version", "XXXX"); + tmpObj.set("download_link", "test"); + publishedVersionsArray.add(tmpObj); + } + + Poco::JSON::Object applicationObj; + applicationObj.set("id", "27"); + applicationObj.set("name", "com.infomaniak.drive"); + applicationObj.set("platform", "mac-os"); + applicationObj.set("store", "kStore"); + applicationObj.set("api_id", "com.infomaniak.drive"); + applicationObj.set("min_version", "3.6.2"); + applicationObj.set("next_version_rate", "0"); + applicationObj.set("published_versions", publishedVersionsArray); + + Poco::JSON::Object dataObj; + dataObj.set("application_id", "27"); + dataObj.set("prod_version", "production"); + dataObj.set("version", versionObj); + dataObj.set("application", applicationObj); + + Poco::JSON::Object mainObj; + mainObj.set("result", "success"); + mainObj.set("data", dataObj); + + std::ostringstream out; + mainObj.stringify(out); + return out.str(); +} class GetAppVersionJobTest final : public GetAppVersionJob { public: @@ -37,9 +89,12 @@ class GetAppVersionJobTest final : public GetAppVersionJob { GetAppVersionJob(platform, appID), _updateShouldBeAvailable(updateShouldBeAvailable) {} void runJob() noexcept override { - const std::istringstream iss(_updateShouldBeAvailable ? bigVersionJsonUpdateStr : smallVersionJsonUpdateStr); + const auto str = _updateShouldBeAvailable ? generateJsonReply(highTagValue, highBuildVersionValue) + : generateJsonReply(lowTagValue, lowBuildVersionValue); + const std::istringstream iss(str); std::istream is(iss.rdbuf()); GetAppVersionJob::handleResponse(is); + _exitCode = ExitCode::Ok; } private: @@ -60,15 +115,19 @@ class UpdateCheckerTest final : public UpdateChecker { bool _updateShouldBeAvailable{false}; }; +void TestUpdateChecker::setUp() { + ParametersCache::instance(true); +} + void TestUpdateChecker::testCheckUpdateAvailable() { // Version is higher than current version { UpdateCheckerTest testObj; UniqueId jobId = 0; testObj.setUpdateShoudBeAvailable(true); - testObj.checkUpdateAvailability(DistributionChannel::Internal, &jobId); + testObj.checkUpdateAvailability(&jobId); while (!JobManager::instance()->isJobFinished(jobId)) Utility::msleep(10); - CPPUNIT_ASSERT(testObj.versionInfo().isValid()); + CPPUNIT_ASSERT(testObj.versionInfo(DistributionChannel::Beta).isValid()); } // Version is lower than current version @@ -76,10 +135,102 @@ void TestUpdateChecker::testCheckUpdateAvailable() { UpdateCheckerTest testObj; UniqueId jobId = 0; testObj.setUpdateShoudBeAvailable(false); - testObj.checkUpdateAvailability(DistributionChannel::Internal, &jobId); + testObj.checkUpdateAvailability(&jobId); while (!JobManager::instance()->isJobFinished(jobId)) Utility::msleep(10); - CPPUNIT_ASSERT(testObj.versionInfo().isValid()); + CPPUNIT_ASSERT(testObj.versionInfo(DistributionChannel::Beta).isValid()); } } +enum VersionValue { High, Medium, Low }; + +const std::string &tag(const VersionValue versionNumber) { + switch (versionNumber) { + case High: + return highTagValue; + case Medium: + return mediumTagValue; + case Low: + return lowTagValue; + } + return lowTagValue; +} + +uint64_t buildVersion(const VersionValue versionNumber) { + switch (versionNumber) { + case High: + return highBuildVersionValue; + case Medium: + return mediumBuildVersionValue; + case Low: + return lowBuildVersionValue; + } + return lowBuildVersionValue; +} + +VersionInfo getVersionInfo(const DistributionChannel channel, const VersionValue versionNumber) { + VersionInfo versionInfo; + versionInfo.channel = channel; + versionInfo.tag = tag(versionNumber); + versionInfo.buildVersion = buildVersion(versionNumber); + versionInfo.downloadUrl = "test"; + return versionInfo; +} + +void TestUpdateChecker::testVersionInfo() { + UpdateChecker testObj; + testObj._prodVersionChannel = DistributionChannel::Prod; + + auto testFunc = [&testObj](const VersionValue expectedValue, const DistributionChannel expectedChannel, + const DistributionChannel selectedChannel, const std::vector &versionsNumber, + const CPPUNIT_NS::SourceLine &sourceline) { + testObj._versionsInfo.clear(); + testObj._versionsInfo.try_emplace(DistributionChannel::Prod, + getVersionInfo(DistributionChannel::Prod, versionsNumber[0])); + testObj._versionsInfo.try_emplace(DistributionChannel::Beta, + getVersionInfo(DistributionChannel::Beta, versionsNumber[1])); + testObj._versionsInfo.try_emplace(DistributionChannel::Internal, + getVersionInfo(DistributionChannel::Internal, versionsNumber[2])); + const auto &versionInfo = testObj.versionInfo(selectedChannel); + CPPUNIT_NS::assertEquals(expectedChannel, versionInfo.channel, sourceline, ""); + CPPUNIT_NS::assertEquals(tag(expectedValue), versionInfo.tag, sourceline, ""); + CPPUNIT_NS::assertEquals(buildVersion(expectedValue), versionInfo.buildVersion, sourceline, ""); + }; + + // selected version: Prod + /// versions values: Prod > Beta > Internal + testFunc(High, DistributionChannel::Prod, DistributionChannel::Prod, {High, Medium, Low}, CPPUNIT_SOURCELINE()); + /// versions values: Internal > Beta > Prod + testFunc(Low, DistributionChannel::Prod, DistributionChannel::Prod, {Low, Medium, High}, CPPUNIT_SOURCELINE()); + /// versions values: Prod == Beta == Internal + testFunc(Medium, DistributionChannel::Prod, DistributionChannel::Prod, {Medium, Medium, Medium}, CPPUNIT_SOURCELINE()); + + // selected version: Beta + /// versions values: Prod > Beta > Internal + testFunc(High, DistributionChannel::Prod, DistributionChannel::Beta, {High, Medium, Low}, CPPUNIT_SOURCELINE()); + /// versions values: Internal > Beta > Prod + testFunc(Medium, DistributionChannel::Beta, DistributionChannel::Beta, {Low, Medium, High}, CPPUNIT_SOURCELINE()); + /// versions values: Prod == Beta == Internal + testFunc(Medium, DistributionChannel::Prod, DistributionChannel::Beta, {Medium, Medium, Medium}, CPPUNIT_SOURCELINE()); + + // selected version: Internal + /// versions values: Prod > Beta > Internal + testFunc(High, DistributionChannel::Prod, DistributionChannel::Internal, {High, Medium, Low}, CPPUNIT_SOURCELINE()); + /// versions values: Internal > Beta > Prod + testFunc(High, DistributionChannel::Internal, DistributionChannel::Internal, {Low, Medium, High}, CPPUNIT_SOURCELINE()); + /// versions values: Beta > Prod > Internal + testFunc(High, DistributionChannel::Beta, DistributionChannel::Internal, {Medium, High, Low}, CPPUNIT_SOURCELINE()); + /// versions values: Prod > Internal > Beta + testFunc(High, DistributionChannel::Prod, DistributionChannel::Internal, {High, Low, Medium}, CPPUNIT_SOURCELINE()); + /// versions values: Beta > Internal > Prod + testFunc(High, DistributionChannel::Beta, DistributionChannel::Internal, {Low, High, Medium}, CPPUNIT_SOURCELINE()); + /// versions values: Prod == Beta == Internal + testFunc(Medium, DistributionChannel::Prod, DistributionChannel::Internal, {Medium, Medium, Medium}, CPPUNIT_SOURCELINE()); + /// versions values: Beta == Prod > Internal + testFunc(High, DistributionChannel::Prod, DistributionChannel::Internal, {High, High, Low}, CPPUNIT_SOURCELINE()); + /// versions values: Beta == Internal > Prod + testFunc(Medium, DistributionChannel::Beta, DistributionChannel::Internal, {Low, Medium, Medium}, CPPUNIT_SOURCELINE()); + /// versions values: Prod == Internal > Beta + testFunc(Medium, DistributionChannel::Prod, DistributionChannel::Internal, {Medium, Low, Medium}, CPPUNIT_SOURCELINE()); +} + } // namespace KDC diff --git a/test/server/updater/testupdatechecker.h b/test/server/updater/testupdatechecker.h index 63662b07b..dac0898db 100644 --- a/test/server/updater/testupdatechecker.h +++ b/test/server/updater/testupdatechecker.h @@ -26,10 +26,15 @@ class TestUpdateChecker final : public CppUnit::TestFixture { public: CPPUNIT_TEST_SUITE(TestUpdateChecker); CPPUNIT_TEST(testCheckUpdateAvailable); + CPPUNIT_TEST(testVersionInfo); CPPUNIT_TEST_SUITE_END(); + public: + void setUp() override; + protected: void testCheckUpdateAvailable(); + void testVersionInfo(); }; } // namespace KDC diff --git a/translations/client_de.ts b/translations/client_de.ts index 13091c170..99c88ec31 100644 --- a/translations/client_de.ts +++ b/translations/client_de.ts @@ -178,15 +178,6 @@ END ABSCHLIESSEN - - - This folder is not compatible with Lite Sync.<br> -Please select another folder. If you continue Lite Sync will be disabled.<br> -<a style="%1" href="%2">Learn more</a> - Dieser Ordner ist nicht mit Lite Sync kompatibel.<br> -Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync deaktiviert.<br> -<a style="%1" href="%2">Mehr erfahren</a> - Select folder @@ -202,6 +193,15 @@ Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync You will find all your files in this folder when the configuration is complete. You can drop new files there to sync them to your kDrive. Alle Ihre Dateien befinden sich in diesem Ordner, sobald die Einrichtung abgeschlossen ist. Sie können dort neue Dateien ablegen und mit Ihrem kDrive synchronisieren. + + + This folder is not compatible with Lite Sync.<br> +Please select another folder. If you continue Lite Sync will be disabled.<br> +<a style="%1" href="%2">Learn more</a> + Dieser Ordner ist nicht mit Lite Sync kompatibel.<br> +Bitte wählen Sie einen anderen Ordner. Wenn Sie fortfahren, wird Lite Sync deaktiviert.<br> +<a style="%1" href="%2">Weitere Informationen</a> + Unable to open link %1. @@ -280,7 +280,7 @@ Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync Der kDrive Client läuft bereits! - + The user %1 is not connected. Please log in again. Der Benutzer %1 ist nicht angemeldet. Bitte melden Sie sich erneut an. @@ -288,12 +288,12 @@ Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync KDC::AppServer - + kDrive application is already running! Die kDrive-Anwendung läuft bereits! - + %1 and %n other file(s) have been removed. %1 und %n andere Datei(en) wurde(n) gelöscht. @@ -301,13 +301,13 @@ Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync - + %1 has been removed. %1 names a file. %1 wurde gelöscht. - + %1 and %n other file(s) have been added. %1 und %n andere Datei(en) wurde(n) hinzugefügt. @@ -315,13 +315,13 @@ Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync - + %1 has been added. %1 names a file. %1 wurde hinzugefügt. - + %1 and %n other file(s) have been updated. %1 und %n andere Datei(en) wurde(n) aktualisiert. @@ -329,13 +329,13 @@ Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync - + %1 has been updated. %1 names a file. %1 wurde aktualisiert. - + %1 has been moved to %2 and %n other file(s) have been moved. %1 wurde zu %2 verschoben und %n andere Datei(en) wurde(n) verschoben. @@ -343,17 +343,17 @@ Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync - + %1 has been moved to %2. %1 wurde zu %2 verschoben. - + Sync Activity Synchronisierungsaktivität - + A new folder larger than %1 MB has been added in the drive %2, you must validate its synchronization: %3. Ein neuer Ordner mit einer Grösse von mehr als %1 MB wurde zum Laufwerk %2 hinzugefügt, Sie müssen die Synchronisierung überprüfen: %3. @@ -368,6 +368,74 @@ Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync Aktuell befinden sich keine Unterordner auf dem Server. + + KDC::BetaProgramDialog + + + Quit the beta program + Beenden Sie das Beta-Programm + + + + Join the beta program + Nehmen Sie am Beta-Programm teil + + + + Get early access to new versions of the application before they are released to the general public, and take part in improving the application by sending us your comments. + Erhalten Sie frühzeitigen Zugang zu neuen Versionen der Anwendung, bevor sie für die Allgemeinheit freigegeben werden, und beteiligen Sie sich an der Verbesserung der Anwendung, indem Sie uns Ihre Kommentare schicken. + + + + Benefit from application beta updates + Profitieren Sie von Beta-Updates für Anwendungen + + + + No + Nein + + + + Public beta version + Öffentliche Beta-Version + + + + Internal beta version + Interne Betaversion + + + + I understand + Ich verstehe + + + + Are you sure you want to leave the beta program? + Sind Sie sicher, dass Sie das Beta-Programm verlassen wollen? + + + + Save + Speichern + + + + Cancel + Abbrechen + + + + Your current version of the application may be too recent, your choice will be effective when the next update is available. + Ihre aktuelle Version der Anwendung ist möglicherweise zu aktuell. Ihre Auswahl wird ab dem nächsten verfügbaren Update wirksam. + + + + Beta versions may leave unexpectedly or cause instabilities. + Beta-Versionen können unerwartet verlassen werden oder Instabilitäten verursachen. + + KDC::BigFoldersDialog @@ -411,17 +479,17 @@ Wählen Sie diejenigen aus, die Sie synchronisieren möchten: Es wurden keine Synchronisierungsordner konfiguriert. - + Synthesis Synthese - + Preferences Einstellungen - + Quit Beenden @@ -466,22 +534,22 @@ Wählen Sie diejenigen aus, die Sie synchronisieren möchten: %1 (Synchronisierung wurde angehalten) - + Do you really want to remove the synchronizations of the account <i>%1</i> ?<br><b>Note:</b> This will <b>not</b> delete any files. Möchten Sie die Synchronisierungen des Kontos <i>%1</i> wirklich entfernen?<br><b>Hinweis:</b> Dadurch werden <b>keine</b> Dateien gelöscht. - + REMOVE ALL SYNCHRONIZATIONS ALLE SYNC. ENTFERNEN - + CANCEL ABBRECHEN - + Failed to start synchronizations! Synchronisierungen konnten nicht gestartet werden! @@ -1221,57 +1289,68 @@ Wählen Sie diejenigen aus, die Sie synchronisieren möchten: KDC::FixConflictingFilesDialog - + + Unable to open link %1. Link %1 kann nicht geöffnet werden. - + Solve conflict(s) Konflikt(e) lösen - - <b>What do you want to do with the %1 conflicted item(s) that is(are) not synced in kDrive?</b> - <b>Was möchten Sie mit %1 in Konflikt stehenden Elementen tun, die nicht in kDrive synchronisiert sind?</b> + + <b>What do you want to do with the %1 conflicted item(s)?</b> + <b>Was möchten Sie mit %1 in Konflikt stehenden Elementen tun?</b> - - Synchronize the local version of my item(s) in kDrive. - Synchronisieren Sie die lokale Version meiner Artikel in kDrive. + + Save my changes and replace other users' versions. + Meine Änderungen speichern und die Versionen anderer Benutzer ersetzen. - - Move the local version of my item(s) to the computer's trash. - Verschieben Sie die lokale Version meiner Artikel in den Papierkorb des Computers. + + Undo my changes and keep other users' versions. + Meine Änderungen rückgängig machen und die Versionen anderer Benutzer behalten. - - Permanently delete the local version of my item(s). - Löschen Sie die lokale Version meiner Artikel dauerhaft. + + Your changes may be permanently deleted. They cannot be restored from the kDrive web application. + Ihre Änderungen können unwiederbringlich gelöscht werden. Sie können über die kDrive-Webanwendung nicht wiederhergestellt werden. - + + <a style=%1 href="%2">Learn more</a> + <a style=%1 href="%2">Mehr erfahren</a> + + + + Your changes will be permanently deleted. They cannot be restored from the kDrive web application. + Ihre Änderungen werden dauerhaft gelöscht. Sie können über die kDrive-Webanwendung nicht wiederhergestellt werden. + + + Show item(s) Artikel anzeigen - + VALIDATE BESTÄTIGEN - + CANCEL ABBRECHEN - - When an item has been modified on both the computer and the kDrive or when an item has been created on the computer with a name that already exists on the kDrive, kDrive renames your local item and downloads kDrive's version on your computer so as not to lose any data.<br> - Wenn ein Element sowohl auf dem Computer als auch auf kDrive geändert wurde oder wenn auf dem Computer ein Element mit einem Namen erstellt wurde, der bereits auf kDrive vorhanden ist, benennt kDrive Ihr lokales Element um und lädt die kDrive-Version auf Ihren Computer herunter, um es nicht zu verlieren beliebige Daten.<br> + + Modifications have been made to these files by several users in several places (online on kDrive, a computer or a mobile). Folders containing these files may also have been deleted.<br> + An diesen Dateien wurden von mehreren Benutzern an mehreren Orten (online auf kDrive, auf einem Computer oder einem Mobiltelefon) Änderungen vorgenommen. Ordner, die diese Dateien enthalten, wurden möglicherweise auch gelöscht.<br> - + The local version of your item <b>is not synced</b> with kDrive. <a style="color: #489EF3" href="%1">Learn more</a> Die lokale Version Ihres Artikels ist <b>nicht mit kDrive synchronisiert</b>. <a style="color: #489EF3" href="%1">Weitere Informationen</a> @@ -1351,12 +1430,12 @@ Wählen Sie diejenigen aus, die Sie synchronisieren möchten: KDC::LargeFolderConfirmation - + Ask for confirmation before synchronizing folders greater than Um Bestätigung bitten, bevor Ordner synchronisiert werden, die grösser sind als - + MB MB @@ -1470,8 +1549,8 @@ Wählen Sie diejenigen aus, die Sie synchronisieren möchten: Please select another folder. If you continue Lite Sync will be disabled.<br> <a style="%1" href="%2">Learn more</a> Dieser Ordner ist nicht mit Lite Sync kompatibel.<br> -Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync deaktiviert.<br> -<a style="%1" href="%2">Mehr erfahren</a> +Bitte wählen Sie einen anderen Ordner. Wenn Sie fortfahren, wird Lite Sync deaktiviert.<br> +<a style="%1" href="%2">Weitere Informationen</a> @@ -1487,12 +1566,12 @@ Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync KDC::Logger - + Error Fehler - + <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> <nobr>File '%1'<br/>kann nicht zum Schreiben geöffnet werden.<br/><br/>Die Log-Datei kann <b>nicht</b> gespeichert werden!</nobr> @@ -1513,24 +1592,24 @@ Bitte wählen Sie einen anderen Ordner aus. Wenn Sie fortfahren, wird Lite Sync KDC::ParametersDialog - + Unable to open folder path %1. Ordnerpfad %1 kann nicht geöffnet werden. - + Transmission done!<br>Please refer to identifier <b>%1</b> in bug reports. Übertragung abgeschlossen!<br>Weitere Einzelheiten enthält der Identifier <b>%1</b> in den Fehlerberichten. - + Transmission failed! Please, use the following link to send the logs to the support: <a style="%1" href="%2">%2</a> Übertragung fehlgeschlagen! Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu senden: <a style="%1" href="%2">%2</a> - + No kDrive configured! Kein kDrive eingerichtet! @@ -1781,66 +1860,61 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send The operation performed on this item failed.<br>The item has been temporarily blacklisted. Der für diesen Artikel durchgeführte Vorgang ist fehlgeschlagen.<br>Der Artikel wurde vorübergehend auf die schwarze Liste gesetzt. - - - Move to trash failed. - Verschieben in den Papierkorb fehlgeschlagen. - The file is too large to be uploaded. It has been temporarily blacklisted. Die Datei ist zu groß, um hochgeladen zu werden. Sie wurde vorübergehend auf die schwarze Liste gesetzt. + + + Impossible to download the file. + Es ist nicht möglich, die Datei herunterzuladen. + + + + You have exceeded your quota. Increase your space quota to re-enable file upload. + Sie haben Ihr Kontingent überschritten. Erhöhen Sie Ihr Speicherplatzkontingent, um den Datei-Upload wieder zu aktivieren. + + + + The synchronization folder is inaccessible (error %1).<br>Please check that you have read and write access to this folder. + Auf den Synchronisationsordner kann nicht zugegriffen werden (Fehler %1).<br>Bitte überprüfen Sie, ob Sie Lese- und Schreibrechte für diesen Ordner haben. + An existing item has an identical name with the same case options (same upper and lower case letters).<br>It has been temporarily blacklisted. - Ein vorhandenes Element hat einen identischen Namen mit denselben Groß- und Kleinschreibungsoptionen (gleiche Groß- und Kleinbuchstaben).<br>Es wurde vorübergehend auf die schwarze Liste gesetzt. + Ein vorhandenes Element hat einen identischen Namen mit derselben Groß- und Kleinschreibung (dieselbe Groß- und Kleinschreibung).<br>Es wurde vorübergehend auf die schwarze Liste gesetzt. The item name contains an unsupported character.<br>It has been temporarily blacklisted. - Der Elementname enthält ein nicht unterstütztes Zeichen.<br>Es wurde vorübergehend auf die schwarze Liste gesetzt. + Der Artikelname enthält ein nicht unterstütztes Zeichen.<br>Er wurde vorübergehend auf die schwarze Liste gesetzt. This item name is reserved by your operating system.<br>It has been temporarily blacklisted. - Dieser Elementname ist von Ihrem Betriebssystem reserviert.<br>Es wurde vorübergehend auf die schwarze Liste gesetzt. + Dieser Elementname ist von Ihrem Betriebssystem reserviert.<br>Er wurde vorübergehend auf die schwarze Liste gesetzt. The item name is too long.<br>It has been temporarily blacklisted. - Der Elementname ist zu lang.<br>Es wurde vorübergehend auf die schwarze Liste gesetzt. + Der Artikelname ist zu lang.<br>Er wurde vorübergehend auf die schwarze Liste gesetzt. The item path is too long.<br>It has been ignored. - Der Elementpfad ist zu lang.<br>Es wurde ignoriert. + Der Elementpfad ist zu lang.<br>Er wurde ignoriert. The item name contains a recent UNICODE character not yet supported by your filesystem.<br>It has been excluded from synchronization. - Der Elementname enthält ein aktuelles UNICODE-Zeichen, das von Ihrem Dateisystem noch nicht unterstützt wird.<br>Es wurde von der Synchronisierung ausgeschlossen. + Der Elementname enthält ein neues UNICODE-Zeichen, das von Ihrem Dateisystem noch nicht unterstützt wird.<br>Es wurde von der Synchronisierung ausgeschlossen. The item name coincides with the name of another item in the same directory.<br>It has been temporarily blacklisted. Consider removing duplicate items. Der Elementname stimmt mit dem Namen eines anderen Elements im selben Verzeichnis überein.<br>Es wurde vorübergehend auf die schwarze Liste gesetzt. Erwägen Sie, doppelte Elemente zu entfernen. - - - Impossible to download the file. - Es ist nicht möglich, die Datei herunterzuladen. - - - - You have exceeded your quota. Increase your space quota to re-enable file upload. - Sie haben Ihr Kontingent überschritten. Erhöhen Sie Ihr Speicherplatzkontingent, um den Datei-Upload wieder zu aktivieren. - - - - The synchronization folder is inaccessible (error %1).<br>Please check that you have read and write access to this folder. - Auf den Synchronisationsordner kann nicht zugegriffen werden (Fehler %1).<br>Bitte überprüfen Sie, ob Sie Lese- und Schreibrechte für diesen Ordner haben. - This item already exists on remote kDrive. It is not synced because it has been blacklisted. @@ -1863,7 +1937,7 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send - + Synchronization error. Synchronisierungsfehler. @@ -1873,12 +1947,12 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send Zugriff auf Element nicht möglich.<br>Bitte korrigieren Sie die Lese- und Schreibberechtigungen. - + System error. Systemfehler. - + A technical error has occurred.<br>Please empty the history and if the error persists, contact our support team. Es ist ein technischer Fehler aufgetreten.<br>Bitte leeren Sie den Verlauf. Wenn der Fehler weiterhin besteht, wenden Sie sich an unser Support-Team. @@ -1907,122 +1981,137 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send KDC::PreferencesWidget - + General Allgemein - + Activate dark theme Dunkles Thema aktivieren - + Activate monochrome icons Einfarbige Schaltflächen aktivieren - + Launch kDrive at startup kDrive beim Start öffnen - - Move deleted files to trash - Verschiebe gelöschte Dateien in den Papierkorb - - - + Advanced Erweitert - + Debugging information Debugging-Informationen - + <a style="%1" href="%2">Open debugging folder</a> <a style="%1" href="%2">Debugging-Ordner öffnen</a> - + Files to exclude Auszuschliessende Dateien - + Proxy server Proxy-Server - + Unable to open folder %1. Ordner %1 kann nicht geöffnet werden. - + Unable to open link %1. Link %1 kann nicht geöffnet werden. - + Invalid link %1. Ungültiger Link %1. - + Show synchronized folders in File Explorer navigation pane Synchronisierte Ordner im Navigationsbereich des Datei-Explorers anzeigen - + You must restart your opened File Explorers for this change to take effect. Sie müssen Ihre geöffneten Datei-Explorer neu starten, damit diese Änderung wirksam wird. - + Lite Sync Lite Sync - + Language Sprache - + Some process failed to run. Ein Prozess ist fehlgeschlagen. - + + Move deleted files to my computer's trash + Gelöschte Dateien in den Papierkorb meines Computers verschieben + + + + Some files or folders may not be moved to the computer's trash. + Einige Dateien oder Ordner werden möglicherweise nicht in den Papierkorb des Computers verschoben. + + + + You can always retrieve already synced files from the kDrive web application trash. + Sie können bereits synchronisierte Dateien jederzeit aus dem Papierkorb der kDrive-Webanwendung abrufen. + + + + <a style="%1" href="%2">Learn more</a> + <a style=%1 href="%2">Mehr erfahren</a> + + + English Englisch - + French Französisch - + German Deutsch - + Spanish Spanisch - + Italian Italienisch - + Default Standard @@ -2208,35 +2297,35 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send KDC::SocketApi - - - + + + Copy private share link Link zur privaten Freigabe kopieren - - + + Resharing this file is not allowed Die erneute Freigabe dieser Datei ist nicht erlaubt - - + + Resharing this folder is not allowed Die erneute Freigabe dieses Ordners ist nicht erlaubt - - - - + + + + Copy public share link Öffentlichen Freigabelink kopieren - - + + Open in browser Im Browser öffnen @@ -2360,43 +2449,48 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send Für 1 weitere Woche - + + Send feedbacks + Senden Sie Feedback + + + Update kDrive App Aktualisieren Sie die kDrive-App - + This kDrive app version is not supported anymore. To access the latest features and enhancements, please update. Diese kDrive-App-Version wird nicht mehr unterstützt. Um auf die neuesten Funktionen und Verbesserungen zuzugreifen, aktualisieren Sie bitte. - - + + Update Aktualisieren - + Please download the latest version on the website. Bitte laden Sie die neueste Version auf der Website herunter. - + Update download in progress Update-Download läuft - + Looking for update... Auf der Suche nach Updates... - + Manual update Manuelles Update - + Unavailable Nicht verfügbar @@ -2406,27 +2500,27 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send kDrive beenden - + Show errors and informations Fehler und Informationen anzeigen - + Show informations Informationen anzeigen - + You can synchronize files <a style="%1" href="%2">from your computer</a> or on <a style="%1" href="%3">kdrive.infomaniak.com</a>. Sie können Dateien <a style="%1" href="%2">von Ihrem Computer</a> oder von <a style="%1" href="%3">kdrive.infomaniak.com</a> synchronisieren. - + Open in folder Im Ordner öffnen - + More actions Weitere Aktionen @@ -2446,53 +2540,53 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send Aktivität - - + + Not implemented! Nicht implementiert! - + No synchronized folder for this Drive! Kein synchronisierter Ordner für diesen kDrive! - + No kDrive configured! Kein kDrive eingerichtet! - + Unable to access web site %1. Zugang zu Website %1 nicht möglich. - + Open %1 web version Webversion %1 öffnen - + Notifications disabled until %1 Benachrichtigungen deaktiviert bis %1 - + Disable Notifications Benachrichtigungen deaktivieren - + Need help Hilfe benötigt - + Unable to open link %1. Link %1 kann nicht geöffnet werden. - + Invalid link %1. Ungültiger Link %1. @@ -2502,12 +2596,12 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send Ordner-URL %1 kann nicht geöffnet werden. - + Drive parameters Drive-Einstellungen - + Application preferences Anwendungseinstellungen @@ -2543,12 +2637,12 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send KDC::UpdateManager - + New update available. Neues Update verfügbar. - + Version %1 is available for download. Version %1 ist zum Download verfügbar. @@ -2564,65 +2658,90 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send KDC::VersionWidget - + <a style="%1" href="%2">%3</a> <a style="%1" href="%2">%3</a> - + <a style="%1" href="%2">Show release note</a> <a style="%1" href="%2">Versionshinweis anzeigen</a> - + Version Version - + UPDATE AKTUALISIEREN - + %1 is up to date! %1 ist auf dem neuesten Stand! - + Checking update on server... Überprüfe Update auf dem Server... - + An update is available: %1.<br>Please download it from <a style="%2" href="%3">here</a>. Ein Update ist verfügbar: %1.<br>Bitte laden Sie es <a style="%2" href="%3">hier</a> herunter. - + An update is available: %1 Ein Update ist verfügbar: %1 - + Downloading %1. Please wait... Herunterladen von %1. Bitte warten… - + Could not check for new updates. Es konnte nicht nach neuen Updates gesucht werden. - + An error occurred during update. Während der Aktualisierung ist ein Fehler aufgetreten. - + Could not download update. Das Update konnte nicht heruntergeladen werden. + + + Beta program + Beta-Programm + + + + Get early access to new versions of the application + Frühzeitiger Zugang zu neuen Versionen der Anwendung + + + + Join + Beitreten + + + + Modify + Ändern Sie + + + + Quit + Beenden + QObject @@ -2647,37 +2766,37 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send Kann keinen gültigen Pfad finden - + No valid folder selected! Kein gültiger Ordner ausgewählt! - + The selected path does not exist! Der ausgewählte Pfad existiert nicht! - + The selected path is not a folder! Der ausgewählte Pfad ist kein Ordner! - + You have no permission to write to the selected folder! Sie haben keine Schreibberechtigung für den ausgewählten Ordner! - + The local folder %1 contains a folder already synced. Please pick another one! Der lokale Ordner %1 enthält einen bereits synchronisierten Ordner. Bitte wählen Sie einen anderen! - + The local folder %1 is contained in a folder already synced. Please pick another one! Der lokale Ordner %1 befindet sich in einem bereits synchronisierten Ordner. Bitte wählen Sie einen anderen! - + The local folder %1 is already synced on the same drive. Please pick another one! Der lokale Ordner %1 ist bereits auf demselben Drive synchronisiert. Bitte wählen Sie einen anderen! @@ -2797,23 +2916,23 @@ Bitte verwenden Sie den folgenden Link, um die Protokolle an den Support zu send utility - + Cancel free up local space Lokalen Speicherplatz freigeben abbrechen - - + + Cancel make available locally Lokal verfügbar machen abbrechen - + Free up local space Lokalen Speicherplatz freigeben - + Always make available locally Immer lokal verfügbar machen diff --git a/translations/client_es.ts b/translations/client_es.ts index cd7f9561a..be158a699 100644 --- a/translations/client_es.ts +++ b/translations/client_es.ts @@ -178,15 +178,6 @@ END FIN - - - This folder is not compatible with Lite Sync.<br> -Please select another folder. If you continue Lite Sync will be disabled.<br> -<a style="%1" href="%2">Learn more</a> - Este carpeta no es compatible con Lite Sync.<br> -Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.<br> -<a style="%1" href="%2">Aprende más</a> - Select folder @@ -202,6 +193,15 @@ Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.& You will find all your files in this folder when the configuration is complete. You can drop new files there to sync them to your kDrive. Encontrarás todos tus archivos en esta carpeta cuando se complete la configuración. Puedes arrastrar nuevos archivos para sincronizarlos en tu kDrive. + + + This folder is not compatible with Lite Sync.<br> +Please select another folder. If you continue Lite Sync will be disabled.<br> +<a style="%1" href="%2">Learn more</a> + Esta carpeta no es compatible con Lite Sync.<br> +Seleccione otra carpeta. Si continúa, Lite Sync se deshabilitará.<br> +<a style="%1" href="%2">Más información</a> + Unable to open link %1. @@ -280,7 +280,7 @@ Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.& ¡El cliente de kDrive ya se está ejecutando! - + The user %1 is not connected. Please log in again. El usuario %1 no está conectado. Inicia sesión de nuevo. @@ -288,12 +288,12 @@ Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.& KDC::AppServer - + kDrive application is already running! ¡La aplicación kDrive ya se está ejecutando! - + %1 and %n other file(s) have been removed. %1 y otros %n archivo(s) han sido borrados. @@ -301,13 +301,13 @@ Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.& - + %1 has been removed. %1 names a file. %1 ha sido eliminado. - + %1 and %n other file(s) have been added. %1 y % otros archivo(s) han sido añadidos. @@ -315,13 +315,13 @@ Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.& - + %1 has been added. %1 names a file. %1 ha sido añadido. - + %1 and %n other file(s) have been updated. %1 y otros %n archivo(s) han sido actualizados. @@ -329,13 +329,13 @@ Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.& - + %1 has been updated. %1 names a file. %1 ha sido actualizado. - + %1 has been moved to %2 and %n other file(s) have been moved. %1 ha sido movido a %2 y otros %n archivo(s) han sido movidos. @@ -343,17 +343,17 @@ Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.& - + %1 has been moved to %2. %1 ha sido movido a %2. - + Sync Activity Actividad de sincronización - + A new folder larger than %1 MB has been added in the drive %2, you must validate its synchronization: %3. Se ha añadido una nueva carpeta mayor a %1 MB en el drive %2, debes validar su sincronización: %3. @@ -368,6 +368,74 @@ Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.& No hay subcarpetas actualmente en el servidor. + + KDC::BetaProgramDialog + + + Quit the beta program + Abandonar el programa beta + + + + Join the beta program + Únase al programa beta + + + + Get early access to new versions of the application before they are released to the general public, and take part in improving the application by sending us your comments. + Obtenga acceso anticipado a las nuevas versiones de la aplicación antes de que se publiquen para el público en general, y participe en la mejora de la aplicación enviándonos sus comentarios. + + + + Benefit from application beta updates + Benefíciese de las actualizaciones beta de las aplicaciones + + + + No + No + + + + Public beta version + Versión beta pública + + + + Internal beta version + Versión beta interna + + + + I understand + Comprendo + + + + Are you sure you want to leave the beta program? + ¿Estás seguro de que quieres abandonar el programa beta? + + + + Save + Guardar + + + + Cancel + Cancelar + + + + Your current version of the application may be too recent, your choice will be effective when the next update is available. + Es posible que su versión actual de la aplicación sea demasiado reciente, su elección será efectiva a partir de la próxima actualización disponible. + + + + Beta versions may leave unexpectedly or cause instabilities. + Las versiones beta pueden salir inesperadamente o causar inestabilidades. + + KDC::BigFoldersDialog @@ -411,17 +479,17 @@ Selecciona los que quieras sincronizar: No hay carpetas de sincronización configuradas. - + Synthesis Síntesis - + Preferences Preferencias - + Quit Salir @@ -466,22 +534,22 @@ Selecciona los que quieras sincronizar: %1 (sincronización en pausa) - + Do you really want to remove the synchronizations of the account <i>%1</i> ?<br><b>Note:</b> This will <b>not</b> delete any files. ¿Realmente desea eliminar las sincronizaciones de la cuenta <i>%1</i>?<br><b>Nota:</b> Esto <b>no</b> eliminará ningún archivo. - + REMOVE ALL SYNCHRONIZATIONS ELIMINAR TODAS LAS SINC. - + CANCEL CANCELAR - + Failed to start synchronizations! ¡Error al iniciar las sincronizaciones! @@ -1219,57 +1287,68 @@ Selecciona los que quieras sincronizar: KDC::FixConflictingFilesDialog - + + Unable to open link %1. No se puede abrir el enlace %1. - + Solve conflict(s) Resolver conflictos - - <b>What do you want to do with the %1 conflicted item(s) that is(are) not synced in kDrive?</b> - <b>¿Qué desea hacer con %1 elementos en conflicto que no están sincronizados en kDrive?</b> + + <b>What do you want to do with the %1 conflicted item(s)?</b> + <b>¿Qué desea hacer con los %1 elementos en conflicto?</b> - - Synchronize the local version of my item(s) in kDrive. - Sincronizar la versión local de mis artículos en kDrive. + + Save my changes and replace other users' versions. + Guardar mis cambios y reemplazar las versiones de otros usuarios. - - Move the local version of my item(s) to the computer's trash. - Mueve la versión local de mis artículos a la papelera de la computadora. + + Undo my changes and keep other users' versions. + Deshacer mis cambios y conservar las versiones de otros usuarios. - - Permanently delete the local version of my item(s). - Eliminar permanentemente la versión local de mis artículos. + + Your changes may be permanently deleted. They cannot be restored from the kDrive web application. + Es posible que los cambios se eliminen de forma permanente. No se pueden restaurar desde la aplicación web de kDrive. + + + + <a style=%1 href="%2">Learn more</a> + <a style=%1 href="%2">Más información</a> - + + Your changes will be permanently deleted. They cannot be restored from the kDrive web application. + Los cambios se eliminarán de forma permanente. No se podrán restaurar desde la aplicación web de kDrive. + + + Show item(s) Mostrar artículo(s) - + VALIDATE VALIDAR - + CANCEL CANCELAR - - When an item has been modified on both the computer and the kDrive or when an item has been created on the computer with a name that already exists on the kDrive, kDrive renames your local item and downloads kDrive's version on your computer so as not to lose any data.<br> - Cuando un elemento se modifica tanto en la computadora como en kDrive o cuando se crea un elemento en la computadora con un nombre que ya existe en kDrive, kDrive cambia el nombre de su elemento local y descarga la versión de kDrive en su computadora para no perderlo. cualquier dato.<br> + + Modifications have been made to these files by several users in several places (online on kDrive, a computer or a mobile). Folders containing these files may also have been deleted.<br> + Varios usuarios han realizado modificaciones en estos archivos en distintos lugares (en línea, en kDrive, en un ordenador o en un dispositivo móvil). Es posible que también se hayan eliminado las carpetas que contienen estos archivos.<br> - + The local version of your item <b>is not synced</b> with kDrive. <a style="color: #489EF3" href="%1">Learn more</a> La versión local de su elemento <b>no está sincronizada</b> con kDrive. <a style="color: #489EF3" href="%1">Más información</a> @@ -1349,12 +1428,12 @@ Selecciona los que quieras sincronizar: KDC::LargeFolderConfirmation - + Ask for confirmation before synchronizing folders greater than Solicitar confirmación antes de sincronizar carpetas mayores de - + MB MB @@ -1467,9 +1546,9 @@ Selecciona los que quieras sincronizar: This folder is not compatible with Lite Sync.<br> Please select another folder. If you continue Lite Sync will be disabled.<br> <a style="%1" href="%2">Learn more</a> - Este carpeta no es compatible con Lite Sync.<br> -Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.<br> -<a style="%1" href="%2">Aprende más</a> + Esta carpeta no es compatible con Lite Sync.<br> +Seleccione otra carpeta. Si continúa, Lite Sync se deshabilitará.<br> +<a style="%1" href="%2">Más información</a> @@ -1485,12 +1564,12 @@ Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.& KDC::Logger - + Error Error - + <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> <nobr>El archivo '%1'<br/>no se puede abrir para escritura.<br/><br/>¡El archivo de registro <b>no</b> se puede guardar!</nobr> @@ -1511,24 +1590,24 @@ Por favor seleccione otro carpeta. Si continúa, Lite Sync será deshabilitado.& KDC::ParametersDialog - + Unable to open folder path %1. No es posible abrir la ruta de la carpeta %1. - + Transmission done!<br>Please refer to identifier <b>%1</b> in bug reports. ¡Transmisión realizada!<br>Consulta el identificador <b>%1</b> en informes de fallos. - + Transmission failed! Please, use the following link to send the logs to the support: <a style="%1" href="%2">%2</a> ¡La transmisión falló! Por favor, utilice el siguiente enlace para enviar los registros al soporte: <a style="%1" href="%2">%2</a> - + No kDrive configured! ¡No hay ningún kDrive configurado! @@ -1779,35 +1858,45 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < The operation performed on this item failed.<br>The item has been temporarily blacklisted. La operación realizada en este artículo ha fallado.<br>El artículo ha sido incluido temporalmente en la lista negra. - - - Move to trash failed. - - The file is too large to be uploaded. It has been temporarily blacklisted. El archivo es demasiado grande para ser cargado. Ha sido temporalmente bloqueado. + + + Impossible to download the file. + Imposible descargar el archivo. + + + + You have exceeded your quota. Increase your space quota to re-enable file upload. + Has excedido tu cuota. Aumente su cuota de espacio para volver a habilitar la carga de archivos. + + + + The synchronization folder is inaccessible (error %1).<br>Please check that you have read and write access to this folder. + La carpeta de sincronización es inaccesible (error %1).<br>Por favor, compruebe que tiene acceso de lectura y escritura a esta carpeta. + An existing item has an identical name with the same case options (same upper and lower case letters).<br>It has been temporarily blacklisted. - Un elemento existente tiene un nombre idéntico con las mismas opciones de mayúsculas y minúsculas.<br>Se ha incluido temporalmente en la lista negra. + Un elemento existente tiene un nombre idéntico con las mismas opciones de mayúsculas y minúsculas (mismas letras mayúsculas y minúsculas).<br>Ha sido incluido temporalmente en la lista negra. The item name contains an unsupported character.<br>It has been temporarily blacklisted. - El nombre del elemento contiene un carácter no compatible.<br>Se ha incluido temporalmente en la lista negra. + El nombre del artículo contiene un carácter no compatible.<br>Se ha incluido temporalmente en la lista negra. This item name is reserved by your operating system.<br>It has been temporarily blacklisted. - El nombre de este elemento está reservado por su sistema operativo.<br>Se ha incluido temporalmente en la lista negra. + Este nombre de artículo está reservado por su sistema operativo.<br>Se ha incluido temporalmente en la lista negra. The item name is too long.<br>It has been temporarily blacklisted. - El nombre del elemento es demasiado largo.<br>Se ha incluido temporalmente en la lista negra. + El nombre del artículo es demasiado largo.<br>Ha sido incluido temporalmente en la lista negra. @@ -1822,22 +1911,7 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < The item name coincides with the name of another item in the same directory.<br>It has been temporarily blacklisted. Consider removing duplicate items. - El nombre del elemento coincide con el nombre de otro elemento en el mismo directorio.<br>Se ha incluido temporalmente en la lista negra. Considere eliminar los elementos duplicados. - - - - Impossible to download the file. - Imposible descargar el archivo. - - - - You have exceeded your quota. Increase your space quota to re-enable file upload. - Has excedido tu cuota. Aumente su cuota de espacio para volver a habilitar la carga de archivos. - - - - The synchronization folder is inaccessible (error %1).<br>Please check that you have read and write access to this folder. - La carpeta de sincronización es inaccesible (error %1).<br>Por favor, compruebe que tiene acceso de lectura y escritura a esta carpeta. + El nombre del elemento coincide con el nombre de otro elemento del mismo directorio.<br>Se ha incluido temporalmente en la lista negra. Considere eliminar los elementos duplicados. @@ -1861,7 +1935,7 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < - + Synchronization error. Error de sincronización. @@ -1871,12 +1945,12 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < No se puede acceder al elemento.<br>Por favor, corrija los permisos de lectura y escritura. - + System error. Error del sistema. - + A technical error has occurred.<br>Please empty the history and if the error persists, contact our support team. Se ha producido un error técnico.<br>Vacíe el historial y, si el error persiste, póngase en contacto con nuestro equipo de soporte. @@ -1905,122 +1979,137 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < KDC::PreferencesWidget - + General General - + Activate dark theme Activar tema oscuro - + Activate monochrome icons Activar iconos monocromos - + Launch kDrive at startup Ejecutar kDrive al arrancar - - Move deleted files to trash - Mueve los archivos eliminados a la papelera - - - + Advanced Avanzado - + Debugging information Información de depuración - + <a style="%1" href="%2">Open debugging folder</a> <a style="%1" href="%2">Abrir carpeta de depuración</a> - + Files to exclude Archivos a excluir - + Proxy server Servidor proxy - + Unable to open folder %1. No se puede abrir la carpeta %1. - + Unable to open link %1. No se puede abrir el enlace %1. - + Invalid link %1. Enlace %1 no válido. - + Show synchronized folders in File Explorer navigation pane Mostrar carpetas sincronizadas en el panel de navegación del Explorador de archivos - + You must restart your opened File Explorers for this change to take effect. Debes reiniciar los Exploradores de archivos abiertos para que este cambio surta efecto. - + Lite Sync Lite Sync - + Language Idioma - + Some process failed to run. Algún proceso no se ha ejecutado. - + + Move deleted files to my computer's trash + Mueva los archivos eliminados a la papelera de mi computadora + + + + Some files or folders may not be moved to the computer's trash. + Es posible que algunos archivos o carpetas no se muevan a la papelera de la computadora. + + + + You can always retrieve already synced files from the kDrive web application trash. + Siempre puedes recuperar archivos ya sincronizados desde la papelera de la aplicación web kDrive. + + + + <a style="%1" href="%2">Learn more</a> + <a style=%1 href="%2">Más información</a> + + + English Inglés - + French Francés - + German Alemán - + Spanish Español - + Italian Italiano - + Default Predeterminado @@ -2206,35 +2295,35 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < KDC::SocketApi - - - + + + Copy private share link Copiar enlace para compartir privado - - + + Resharing this file is not allowed No está permitido volver a compartir este archivo - - + + Resharing this folder is not allowed No está permitido volver a compartir esta carpeta - - - - + + + + Copy public share link Copiar enlace para compartir públicamente - - + + Open in browser Abrir en navegador @@ -2358,43 +2447,48 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < Por 1 semana más - + + Send feedbacks + Enviar comentarios + + + Update kDrive App Actualizar la aplicación kDrive - + This kDrive app version is not supported anymore. To access the latest features and enhancements, please update. Esta versión de la aplicación kDrive ya no es compatible. Para acceder a las últimas funciones y mejoras, actualice. - - + + Update Actualizar - + Please download the latest version on the website. Descargue la última versión del sitio web. - + Update download in progress Descarga de actualización en curso - + Looking for update... Buscando actualización... - + Manual update Actualización manual - + Unavailable Indisponible @@ -2404,27 +2498,27 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < Salir de kDrive - + Show errors and informations Mostrar errores e informaciones - + Show informations Mostrar información - + You can synchronize files <a style="%1" href="%2">from your computer</a> or on <a style="%1" href="%3">kdrive.infomaniak.com</a>. Puedes sincronizar archivos <a style="%1" href="%2">desde tu ordenador</a> o en <a style="%1" href="%3">kdrive.infomaniak.com</a>. - + Open in folder Abrir en carpeta - + More actions Más acciones @@ -2444,53 +2538,53 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < Actividad - - + + Not implemented! ¡No implementado! - + No synchronized folder for this Drive! ¡No hay ninguna carpeta sincronizada para este Drive! - + No kDrive configured! ¡No hay ningún kDrive configurado! - + Unable to access web site %1. No es posible acceder al sitio web %1. - + Open %1 web version Abrir la versión web de %1 - + Notifications disabled until %1 Notificaciones deshabilitadas hasta %1 - + Disable Notifications Deshabilitar Notificaciones - + Need help Se necesita ayuda - + Unable to open link %1. No se puede abrir el enlace %1. - + Invalid link %1. Enlace %1 no válido. @@ -2500,12 +2594,12 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < No es posible abrir la url de carpeta %1. - + Drive parameters Parámetros de drive - + Application preferences Preferencias de aplicación @@ -2541,12 +2635,12 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < KDC::UpdateManager - + New update available. Nueva actualización disponible. - + Version %1 is available for download. La versión %1 está disponible para su descarga. @@ -2562,65 +2656,90 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < KDC::VersionWidget - + <a style="%1" href="%2">%3</a> <a style="%1" href="%2">%3</a> - + <a style="%1" href="%2">Show release note</a> <a style="%1" href="%2">Mostrar nota de lanzamiento</a> - + Version Versión - + UPDATE ACTUALIZAR - + %1 is up to date! ¡%1 está actualizado! - + Checking update on server... Comprobando actualización en servidor... - + An update is available: %1.<br>Please download it from <a style="%2" href="%3">here</a>. Hay una actualización disponible: %1. Descárgala <a style="%2" href="%3">aquí</a>. - + An update is available: %1 Hay disponible una actualización: %1 - + Downloading %1. Please wait... Descargando %1. Espera... - + Could not check for new updates. No se puede comprobar si hay actualizaciones. - + An error occurred during update. Se ha producido un error durante la actualización. - + Could not download update. No se ha podido descargar la actualización. + + + Beta program + Programa Beta + + + + Get early access to new versions of the application + Obtenga acceso anticipado a las nuevas versiones de la aplicación + + + + Join + Contacto + + + + Modify + Modifique + + + + Quit + Salir + QObject @@ -2645,37 +2764,37 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < No se puede encontrar una ruta válida - + No valid folder selected! ¡No se ha seleccionado una carpeta válida! - + The selected path does not exist! ¡La ruta seleccionada no existe! - + The selected path is not a folder! ¡La ruta seleccionada no es una carpeta! - + You have no permission to write to the selected folder! ¡No tienes permiso para escribir en la carpeta seleccionada! - + The local folder %1 contains a folder already synced. Please pick another one! La carpeta local %1 contiene una carpeta ya sincronizada.¡Elige otra! - + The local folder %1 is contained in a folder already synced. Please pick another one! La carpeta local %1 está contenida en una carpeta ya sincronizada.¡Elige otra! - + The local folder %1 is already synced on the same drive. Please pick another one! La carpeta local %1 ya está sincronizada en el mismo drive.¡Elige otra! @@ -2795,23 +2914,23 @@ Por favor, utilice el siguiente enlace para enviar los registros al soporte: < utility - + Cancel free up local space Cancelar liberar espacio local - - + + Cancel make available locally Cancelar disponible localmente - + Free up local space Liberar espacio local - + Always make available locally Siempre disponible en local diff --git a/translations/client_fr.ts b/translations/client_fr.ts index c2ea1ad6d..542144286 100644 --- a/translations/client_fr.ts +++ b/translations/client_fr.ts @@ -178,15 +178,6 @@ END TERMINER - - - This folder is not compatible with Lite Sync.<br> -Please select another folder. If you continue Lite Sync will be disabled.<br> -<a style="%1" href="%2">Learn more</a> - Ce dossier n'est pas compatible avec la Lite Sync.<br> -Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera désactivée.<br> -<a style="%1" href="%2">En savoir plus</a> - Select folder @@ -202,6 +193,15 @@ Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera d You will find all your files in this folder when the configuration is complete. You can drop new files there to sync them to your kDrive. Vous retrouverez tous vos fichiers dans ce dossier une fois la configuration terminée. Vous pouvez y glisser de nouveaux fichiers pour les synchroniser avec votre kDrive. + + + This folder is not compatible with Lite Sync.<br> +Please select another folder. If you continue Lite Sync will be disabled.<br> +<a style="%1" href="%2">Learn more</a> + Ce dossier n'est pas compatible avec Lite Sync.<br> +Veuillez sélectionner un autre dossier. Si vous continuez, Lite Sync sera désactivé.<br> +<a style="%1" href="%2">En savoir plus</a> + Unable to open link %1. @@ -280,7 +280,7 @@ Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera d Le client kDrive est déjà en cours d'exécution ! - + The user %1 is not connected. Please log in again. L'utilisateur %1 n'est pas connecté. Veuillez vous reconnecter. @@ -288,12 +288,12 @@ Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera d KDC::AppServer - + kDrive application is already running! L'application kDrive est déjà en cours d'exécution! - + %1 and %n other file(s) have been removed. %1 a été supprimé. @@ -301,13 +301,13 @@ Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera d - + %1 has been removed. %1 names a file. %1 a été supprimé. - + %1 and %n other file(s) have been added. %1 et %n autre(s) fichier(s) ont été ajouté(s). @@ -315,13 +315,13 @@ Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera d - + %1 has been added. %1 names a file. %1 a été ajouté. - + %1 and %n other file(s) have been updated. %1 a été mis à jour. @@ -329,13 +329,13 @@ Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera d - + %1 has been updated. %1 names a file. %1 a été mis à jour. - + %1 has been moved to %2 and %n other file(s) have been moved. %1 a été déplacé vers %2. @@ -343,17 +343,17 @@ Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera d - + %1 has been moved to %2. %1 a été déplacé vers %2. - + Sync Activity Activité de synchronisation - + A new folder larger than %1 MB has been added in the drive %2, you must validate its synchronization: %3. Un nouveau dossier supérieur à %1 Mo a été ajouté dans le lecteur %2, vous devez valider sa synchronisation: %3. @@ -367,6 +367,74 @@ Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera d Aucun sous-dossier sur le serveur. + + KDC::BetaProgramDialog + + + Quit the beta program + Quitter le programme bêta + + + + Join the beta program + Rejoindre le programme bêta + + + + Get early access to new versions of the application before they are released to the general public, and take part in improving the application by sending us your comments. + Bénéficiez d'un accès anticipé aux nouvelles versions de l'application avant qu'elles ne soient diffusées au grand public et participez à l'amélioration de l'application en nous faisant part de vos commentaires. + + + + Benefit from application beta updates + Bénéficier des mises à jour de la version bêta des applications + + + + No + Non + + + + Public beta version + Version bêta publique + + + + Internal beta version + Version bêta interne + + + + I understand + Je comprends + + + + Are you sure you want to leave the beta program? + Êtes-vous sûr de vouloir quitter le programme bêta ? + + + + Save + Enregistrer + + + + Cancel + Annuler + + + + Your current version of the application may be too recent, your choice will be effective when the next update is available. + Votre version actuelle de l'application est peut-être trop récente, votre choix sera effectif lorsque la prochaine mise à jour sera disponible. + + + + Beta versions may leave unexpectedly or cause instabilities. + Les versions bêta peuvent quitter de manière inattendue ou provoquer des instabilités. + + KDC::BigFoldersDialog @@ -415,18 +483,18 @@ Sélectionnez ceux que vous souhaitez synchroniser: Aucun dossier à synchroniser n'est configuré. - + Synthesis Synthèse - + Preferences Préférences Préférences - + Quit Quitter @@ -471,22 +539,22 @@ Sélectionnez ceux que vous souhaitez synchroniser: %1 (Synchronisation en pause) - + Do you really want to remove the synchronizations of the account <i>%1</i> ?<br><b>Note:</b> This will <b>not</b> delete any files. Voulez-vous vraiment supprimer les synchronisations du compte <i>%1</i> ?<br><b>Remarque :</b> Cela <b>ne</b> supprimera aucun fichier. - + REMOVE ALL SYNCHRONIZATIONS SUPPRIMER TOUTES LES SYNC - + CANCEL ANNULER - + Failed to start synchronizations! Échec du démarrage des synchronisations ! @@ -1219,57 +1287,68 @@ Sélectionnez ceux que vous souhaitez synchroniser: KDC::FixConflictingFilesDialog - + + Unable to open link %1. Impossible d’ouvrir le lien %1. - + Solve conflict(s) Résoudre le(s) conflit(s) - - <b>What do you want to do with the %1 conflicted item(s) that is(are) not synced in kDrive?</b> - <b>Que souhaitez-vous faire avec les %1 éléments en conflit qui ne sont pas synchronisés dans kDrive ?</b> + + <b>What do you want to do with the %1 conflicted item(s)?</b> + <b>Que voulez-vous faire avec les %1 éléments en conflit ?</b> - - Synchronize the local version of my item(s) in kDrive. - Synchroniser la version locale de mes éléments dans kDrive. + + Save my changes and replace other users' versions. + Enregistrer mes modifications et remplacer les versions des autres utilisateurs. - - Move the local version of my item(s) to the computer's trash. - Déplacer la version locale de mes éléments vers la corbeille de l'ordinateur. + + Undo my changes and keep other users' versions. + Annuler mes modifications et conserver les versions des autres utilisateurs. - - Permanently delete the local version of my item(s). - Supprimer définitivement la version locale de mes éléments. + + Your changes may be permanently deleted. They cannot be restored from the kDrive web application. + Vos modifications peuvent être supprimées définitivement. Elles ne peuvent pas être restaurées à partir de l'application Web kDrive. - + + <a style=%1 href="%2">Learn more</a> + <a style=%1 href="%2">En savoir plus</a> + + + + Your changes will be permanently deleted. They cannot be restored from the kDrive web application. + Vos modifications seront définitivement supprimées. Elles ne pourront pas être restaurées à partir de l'application Web kDrive. + + + Show item(s) Voir l'(les) élément(s) - + VALIDATE VALIDER - + CANCEL ANNULER - - When an item has been modified on both the computer and the kDrive or when an item has been created on the computer with a name that already exists on the kDrive, kDrive renames your local item and downloads kDrive's version on your computer so as not to lose any data.<br> - Lorsqu'un élément a été modifié à la fois sur l'ordinateur et sur kDrive ou lorsqu'un élément a été créé sur l'ordinateur avec un nom qui existe déjà sur kDrive, kDrive renomme votre élément local pour ne pas le perdre et télécharge la version de kDrive sur votre ordinateur.<br> + + Modifications have been made to these files by several users in several places (online on kDrive, a computer or a mobile). Folders containing these files may also have been deleted.<br> + Des modifications ont été apportées à ces fichiers par plusieurs utilisateurs à plusieurs endroits (en ligne sur kDrive, un ordinateur ou un mobile). Les dossiers contenant ces fichiers peuvent également avoir été supprimés.<br> - + The local version of your item <b>is not synced</b> with kDrive. <a style="color: #489EF3" href="%1">Learn more</a> La version locale de votre élément <b>n'est pas synchronisée</b> avec kDrive. <a style="color: #489EF3" href="%1">En savoir plus</a> @@ -1349,12 +1428,12 @@ Sélectionnez ceux que vous souhaitez synchroniser: KDC::LargeFolderConfirmation - + Ask for confirmation before synchronizing folders greater than Demander confirmation avant de synchroniser les dossiers supérieurs à - + MB Mo @@ -1467,8 +1546,8 @@ Sélectionnez ceux que vous souhaitez synchroniser: This folder is not compatible with Lite Sync.<br> Please select another folder. If you continue Lite Sync will be disabled.<br> <a style="%1" href="%2">Learn more</a> - Ce dossier n'est pas compatible avec la Lite Sync.<br> -Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera désactivée.<br> + Ce dossier n'est pas compatible avec Lite Sync.<br> +Veuillez sélectionner un autre dossier. Si vous continuez, Lite Sync sera désactivé.<br> <a style="%1" href="%2">En savoir plus</a> @@ -1485,12 +1564,12 @@ Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera d KDC::Logger - + Error Erreur - + <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> <nobr>Le fichier '%1'<br/>ne peut être ouvert en écriture.<br/><br/>Le fichier de journalisation <b>ne peut pas</b> être enregistré !</nobr> @@ -1511,22 +1590,22 @@ Veuillez sélectionner un autre dossier. Si vous continuez, la Lite Sync sera d KDC::ParametersDialog - + Unable to open folder path %1. Impossible d’ouvrir le dossier de débogage %1. - + Transmission done!<br>Please refer to identifier <b>%1</b> in bug reports. Transmission effectuée !<br>Veuillez vous référer à l'identifiant <b>%1</b> dans vos rapports de bugs. - + No kDrive configured! Aucun kDrive configuré ! - + Transmission failed! Please, use the following link to send the logs to the support: <a style="%1" href="%2">%2</a> Échec de la transmission ! @@ -1779,16 +1858,26 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= The operation performed on this item failed.<br>The item has been temporarily blacklisted. L'opération effectuée sur cet élément a échoué.<br>L'article a été temporairement mis sur liste noire. - - - Move to trash failed. - Le déplacement vers la corbeille a échoué. - The file is too large to be uploaded. It has been temporarily blacklisted. Le fichier est trop volumineux pour être téléchargé. Il a été temporairement mis sur liste noire. + + + Impossible to download the file. + Impossible de télécharger le fichier. + + + + You have exceeded your quota. Increase your space quota to re-enable file upload. + Vous avez dépassé votre quota. Augmentez votre quota d'espace pour réactiver le téléchargement de fichiers. + + + + The synchronization folder is inaccessible (error %1).<br>Please check that you have read and write access to this folder. + Le dossier de synchronisation est inaccessible (erreur %1).<br>Veuillez vérifier que vous avez accès en lecture et en écriture à ce dossier. + An existing item has an identical name with the same case options (same upper and lower case letters).<br>It has been temporarily blacklisted. @@ -1812,32 +1901,17 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= The item path is too long.<br>It has been ignored. - Le chemin d'accès à l'élément est trop long.<br>Il a été ignoré. + Le chemin de l'élément est trop long.<br>Il a été ignoré. The item name contains a recent UNICODE character not yet supported by your filesystem.<br>It has been excluded from synchronization. - Le nom de l'élément contient un caractère UNICODE récent qui n'est pas encore pris en charge par votre système de fichiers. <br>Il a été exclu de la synchronisation. + Le nom de l'élément contient un caractère UNICODE récent qui n'est pas encore pris en charge par votre système de fichiers.<br>Il a été exclu de la synchronisation. The item name coincides with the name of another item in the same directory.<br>It has been temporarily blacklisted. Consider removing duplicate items. - Le nom de l'élément coïncide avec le nom d'un autre élément dans le même répertoire.<br>Il a été temporairement mis sur liste noire. Envisagez de supprimer les éléments en double. - - - - Impossible to download the file. - Impossible de télécharger le fichier. - - - - You have exceeded your quota. Increase your space quota to re-enable file upload. - Vous avez dépassé votre quota. Augmentez votre quota d'espace pour réactiver le téléchargement de fichiers. - - - - The synchronization folder is inaccessible (error %1).<br>Please check that you have read and write access to this folder. - Le dossier de synchronisation est inaccessible (erreur %1).<br>Veuillez vérifier que vous avez accès en lecture et en écriture à ce dossier. + Le nom de l'élément coïncide avec le nom d'un autre élément dans le même répertoire.<br>Il a été temporairement mis sur liste noire. Pensez à supprimer les éléments en double. @@ -1861,7 +1935,7 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= - + Synchronization error. Erreur de synchronisation. @@ -1871,12 +1945,12 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= Impossible d'accéder à l'élément.<br>Veuillez corriger les autorisations de lecture et d'écriture. - + System error. Erreur système. - + A technical error has occurred.<br>Please empty the history and if the error persists, contact our support team. Une erreur technique s'est produite.<br>Veuillez vider l'historique et si l'erreur persiste, contactez notre équipe d'assistance. @@ -1905,122 +1979,137 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= KDC::PreferencesWidget - + General Paramètres - + Activate dark theme Activer le thème foncé - + Activate monochrome icons Activer les icônes monochromes - + Launch kDrive at startup Lancer kDrive au démarrage - - Move deleted files to trash - Déplacez les fichiers supprimés dans la corbeille - - - + Advanced Avancé - + Debugging information Informations de débogage - + <a style="%1" href="%2">Open debugging folder</a> <a style="%1" href="%2">Ouvrir le dossier de débogage</a> - + Files to exclude Fichiers à exclure - + Proxy server Serveur proxy - + Unable to open folder %1. Impossible d'ouvrir le dossier %1. - + Unable to open link %1. Impossible d’ouvrir le lien %1. - + Invalid link %1. Lien %1 invalide. - + Show synchronized folders in File Explorer navigation pane Afficher les dossiers synchronisés dans le volet de navigation de l'Explorateur de Fichier - + You must restart your opened File Explorers for this change to take effect. Vous devez redémarrer vos explorateurs de fichiers ouverts pour que cette modification prenne effet. - + Lite Sync Lite Sync - + Language Langue - + Some process failed to run. Certains processus n'ont pas pu s'exécuter. - + + Move deleted files to my computer's trash + Déplacer les fichiers supprimés vers la corbeille de mon ordinateur + + + + Some files or folders may not be moved to the computer's trash. + Certains fichiers ou dossiers peuvent ne pas être déplacés vers la corbeille de l'ordinateur. + + + + You can always retrieve already synced files from the kDrive web application trash. + Vous pouvez toujours récupérer les fichiers déjà synchronisés depuis la corbeille de l'application Web kDrive. + + + + <a style="%1" href="%2">Learn more</a> + <a style=%1 href="%2">En savoir plus</a> + + + English Anglais - + French Français - + German Allemand - + Spanish Espagnol - + Italian Italien - + Default Défaut @@ -2206,35 +2295,35 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= KDC::SocketApi - - - + + + Copy private share link Copier le lien de partage privé - - + + Resharing this file is not allowed Le partage de ce fichier n'est pas autorisé - - + + Resharing this folder is not allowed Le partage de ce dossier n'est pas autorisé - - - - + + + + Copy public share link Copier le lien de partage public - - + + Open in browser Ouvrir dans le navigateur web @@ -2358,43 +2447,48 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= Pour 1 semaine de plus - + + Send feedbacks + Envoyer des commentaires + + + Update kDrive App Mettre à jour l'application kDrive - + This kDrive app version is not supported anymore. To access the latest features and enhancements, please update. Cette version de l'application kDrive n'est plus prise en charge. Pour accéder aux dernières fonctionnalités et améliorations, veuillez mettre à jour. - - + + Update Mise à jour - + Please download the latest version on the website. Veuillez télécharger la dernière version sur le site Web. - + Update download in progress Téléchargement de la mise à jour en cours - + Looking for update... Recherche d'une mise à jour... - + Manual update Mise à jour manuelle - + Unavailable Indisponible @@ -2404,27 +2498,27 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= Quitter kDrive - + Show errors and informations Afficher les erreurs et les informations - + Show informations Afficher les informations - + You can synchronize files <a style="%1" href="%2">from your computer</a> or on <a style="%1" href="%3">kdrive.infomaniak.com</a>. Vous pouvez synchroniser les fichiers <a style="%1" href="%2">depuis votre ordinateur</a> ou sur <a style="%1" href="%3">kdrive.infomaniak.com</ un>. - + Open in folder Ouvrir dans le dossier - + More actions Autres actions @@ -2444,53 +2538,53 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= Activité - - + + Not implemented! Non implémenté ! - + No synchronized folder for this Drive! Aucun fichier synchronisé pour ce kDrive ! - + No kDrive configured! Aucun kDrive configuré ! - + Unable to access web site %1. Impossible d’accéder au site %1. - + Open %1 web version Ouvrir la version Web de %1 - + Notifications disabled until %1 Notifications désactivées jusqu’à %1 - + Disable Notifications Désactiver les notifications - + Need help Besoin d’aide - + Unable to open link %1. Impossible d’ouvrir le lien %1. - + Invalid link %1. Lien %1 invalide. @@ -2500,12 +2594,12 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= Impossible d’ouvrir l’URL du dossier %1. - + Drive parameters Paramètres du kDrive - + Application preferences Préférences de l’application @@ -2541,12 +2635,12 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= KDC::UpdateManager - + New update available. Nouvelle mise à jour disponible. - + Version %1 is available for download. La version %1 est disponible au téléchargement. @@ -2562,65 +2656,90 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= KDC::VersionWidget - + <a style="%1" href="%2">%3</a> <a style="%1" href="%2">%3</a> - + <a style="%1" href="%2">Show release note</a> <a style="%1" href="%2">Afficher la note de version</a> - + Version Version - + UPDATE METTRE A JOUR - + %1 is up to date! %1 est à jour ! - + Checking update on server... Vérification de la mise à jour sur le serveur... - + An update is available: %1.<br>Please download it from <a style="%2" href="%3">here</a>. Une mise à jour est disponible : %1.<br>Veuillez la télécharger depuis <a style="%2" href="%3">ici</a>. - + An update is available: %1 Une mise à jour est disponible: %1 - + Downloading %1. Please wait... Téléchargement %1 en cours. Veuillez patienter… - + Could not check for new updates. Impossible de vérifier la présence de nouvelles mises à jour. - + An error occurred during update. Une erreur s'est produite lors de la mise à jour. - + Could not download update. Impossible de télécharger la mise à jour. + + + Beta program + Programme bêta + + + + Get early access to new versions of the application + Bénéficier d'un accès anticipé aux nouvelles versions de l'application + + + + Join + Rejoindre + + + + Modify + Modifier + + + + Quit + Quitter + QObject @@ -2645,37 +2764,37 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= Impossible de trouver un chemin valide - + No valid folder selected! Aucun dossier valable sélectionné! - + The selected path does not exist! Le chemin sélectionné n’existe pas! - + The selected path is not a folder! Le chemin sélectionné n'est pas un dossier! - + You have no permission to write to the selected folder! Vous n'avez pas la permission d'écrire dans le dossier sélectionné! - + The local folder %1 contains a folder already synced. Please pick another one! Le dossier local %1 contient un dossier déjà synchronisé. Veuillez en choisir un autre ! - + The local folder %1 is contained in a folder already synced. Please pick another one! Le dossier local %1 est contenu dans un dossier déjà synchronisé. Veuillez en choisir un autre ! - + The local folder %1 is already synced on the same drive. Please pick another one! Le dossier local %1 est déjà synchronisé sur le même lecteur. Veuillez en choisir un autre ! @@ -2860,18 +2979,18 @@ Veuillez utiliser le lien suivant pour envoyer les logs au support: <a style= Démarrage de la synchronisation - + Free up local space Libérer de l’espace local - + Cancel free up local space Annuler libérer l'espace local - - + + Cancel make available locally Annuler rendre disponible localement @@ -2910,7 +3029,7 @@ You can add one from the kDrive settings. Ajoutez-en un depuis la configuration du kDrive. - + Always make available locally Rendre toujours disponible localement diff --git a/translations/client_it.ts b/translations/client_it.ts index 6827e328f..5e71a487a 100644 --- a/translations/client_it.ts +++ b/translations/client_it.ts @@ -178,15 +178,6 @@ END FINE - - - This folder is not compatible with Lite Sync.<br> -Please select another folder. If you continue Lite Sync will be disabled.<br> -<a style="%1" href="%2">Learn more</a> - Questa cartella non è compatibile con Lite Sync.<br> -Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.<br> -<a style="%1" href="%2">Scopri di più</a> - Select folder @@ -202,6 +193,15 @@ Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.< You will find all your files in this folder when the configuration is complete. You can drop new files there to sync them to your kDrive. Al termine della configurazione tutti i tuoi file saranno in questa cartella. Puoi trascinare nuovi file in questa cartella per sincronizzarli con il tuo kDrive. + + + This folder is not compatible with Lite Sync.<br> +Please select another folder. If you continue Lite Sync will be disabled.<br> +<a style="%1" href="%2">Learn more</a> + Questa cartella non è compatibile con Lite Sync.<br> +Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.<br> +<a style="%1" href="%2">Scopri di più</a> + Unable to open link %1. @@ -280,7 +280,7 @@ Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.< Il client kDrive è già in esecuzione! - + The user %1 is not connected. Please log in again. L'utente %1 non è connesso. Effettuare nuovamente il login. @@ -288,12 +288,12 @@ Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.< KDC::AppServer - + kDrive application is already running! L'applicazione kDrive è già in esecuzione! - + %1 and %n other file(s) have been removed. %1 e %n altri file sono stati rimossi. @@ -301,13 +301,13 @@ Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.< - + %1 has been removed. %1 names a file. %1 è stato rimosso. - + %1 and %n other file(s) have been added. %1 e %n altri file sono stati aggiunti. @@ -315,13 +315,13 @@ Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.< - + %1 has been added. %1 names a file. %1 è stato aggiunto. - + %1 and %n other file(s) have been updated. %1 e %n altri file sono stati aggiornati. @@ -329,13 +329,13 @@ Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.< - + %1 has been updated. %1 names a file. %1 è stato aggiornato. - + %1 has been moved to %2 and %n other file(s) have been moved. %1 è stato spostato in %2 e %n altri file sono stati spostati. @@ -343,17 +343,17 @@ Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.< - + %1 has been moved to %2. %1 è stato spostato in %2. - + Sync Activity Sincronizza attività - + A new folder larger than %1 MB has been added in the drive %2, you must validate its synchronization: %3. È stata aggiunta una nuova cartella più grande di %1 MB nell'unità %2, devi convalidarne la sincronizzazione: %3. @@ -368,6 +368,74 @@ Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.< Attualmente non ci sono sottocartelle sul server. + + KDC::BetaProgramDialog + + + Quit the beta program + Abbandonare il programma beta + + + + Join the beta program + Partecipare al programma beta + + + + Get early access to new versions of the application before they are released to the general public, and take part in improving the application by sending us your comments. + Ottenete l'accesso anticipato alle nuove versioni dell'applicazione prima che vengano rilasciate al pubblico e partecipate al miglioramento dell'applicazione inviandoci i vostri commenti. + + + + Benefit from application beta updates + Beneficiare degli aggiornamenti beta delle applicazioni + + + + No + No + + + + Public beta version + Versione beta pubblica + + + + Internal beta version + Versione beta interna + + + + I understand + Capisco + + + + Are you sure you want to leave the beta program? + Siete sicuri di voler abbandonare il programma beta? + + + + Save + Salva + + + + Cancel + Annulla + + + + Your current version of the application may be too recent, your choice will be effective when the next update is available. + La tua attuale versione dell'applicazione potrebbe essere troppo recente, la tua scelta sarà effettiva dal prossimo aggiornamento disponibile. + + + + Beta versions may leave unexpectedly or cause instabilities. + Le versioni beta possono uscire inaspettatamente o causare instabilità. + + KDC::BigFoldersDialog @@ -411,17 +479,17 @@ Seleziona quelli che desideri sincronizzare: Non è stata configurata alcuna cartella per la sincronizzazione. - + Synthesis Sintesi - + Preferences Preferenze - + Quit Esci @@ -466,22 +534,22 @@ Seleziona quelli che desideri sincronizzare: %1 (Sincronizzazione sospesa) - + Do you really want to remove the synchronizations of the account <i>%1</i> ?<br><b>Note:</b> This will <b>not</b> delete any files. Vuoi davvero rimuovere le sincronizzazioni dell'account <i>%1</i> ?<br><b>Nota:</b> questo <b>non</b> eliminerà alcun file. - + REMOVE ALL SYNCHRONIZATIONS RIMUOVERE TUTTE LE SINC - + CANCEL ANNULLA - + Failed to start synchronizations! Impossibile avviare la sincronizzazione! @@ -1219,57 +1287,68 @@ Seleziona quelli che desideri sincronizzare: KDC::FixConflictingFilesDialog - + + Unable to open link %1. Impossibile aprire il link %1. - + Solve conflict(s) Risolvere i conflitti - - <b>What do you want to do with the %1 conflicted item(s) that is(are) not synced in kDrive?</b> - <b>Cosa vuoi fare con gli %1 elementi in conflitto che non sono sincronizzati in kDrive?</b> + + <b>What do you want to do with the %1 conflicted item(s)?</b> + <b>Cosa vuoi fare con gli elementi in conflitto %1?</b> - - Synchronize the local version of my item(s) in kDrive. - Sincronizza la versione locale dei miei articoli in kDrive. + + Save my changes and replace other users' versions. + Salva le mie modifiche e sostituisci le versioni degli altri utenti. - - Move the local version of my item(s) to the computer's trash. - Sposta la versione locale dei miei articoli nel cestino del computer. + + Undo my changes and keep other users' versions. + Annulla le mie modifiche e conserva le versioni degli altri utenti. - - Permanently delete the local version of my item(s). - Elimina definitivamente la versione locale dei miei articoli. + + Your changes may be permanently deleted. They cannot be restored from the kDrive web application. + Le tue modifiche potrebbero essere eliminate definitivamente. Non possono essere ripristinate dall'applicazione web kDrive. + + + + <a style=%1 href="%2">Learn more</a> + <a style=%1 href="%2">Scopri di più</a> + + + + Your changes will be permanently deleted. They cannot be restored from the kDrive web application. + Le tue modifiche saranno eliminate definitivamente. Non potranno essere ripristinate dall'applicazione web kDrive. - + Show item(s) Mostra articolo(i) - + VALIDATE CONVALIDA - + CANCEL ANNULLA - - When an item has been modified on both the computer and the kDrive or when an item has been created on the computer with a name that already exists on the kDrive, kDrive renames your local item and downloads kDrive's version on your computer so as not to lose any data.<br> - Quando un elemento è stato modificato sia sul computer che sul kDrive o quando un elemento è stato creato sul computer con un nome già esistente sul kDrive, kDrive rinomina l'elemento locale e scarica la versione di kDrive sul tuo computer per non perdere tutti i dati.<br> + + Modifications have been made to these files by several users in several places (online on kDrive, a computer or a mobile). Folders containing these files may also have been deleted.<br> + Sono state apportate modifiche a questi file da diversi utenti in diversi posti (online su kDrive, un computer o un dispositivo mobile). Le cartelle contenenti questi file potrebbero anche essere state eliminate.<br> - + The local version of your item <b>is not synced</b> with kDrive. <a style="color: #489EF3" href="%1">Learn more</a> La versione locale del tuo elemento <b>non è sincronizzata</b> con kDrive. <a style="color: #489EF3" href="%1">Ulteriori informazioni</a> @@ -1349,12 +1428,12 @@ Seleziona quelli che desideri sincronizzare: KDC::LargeFolderConfirmation - + Ask for confirmation before synchronizing folders greater than Chiedi conferma prima di sincronizzare cartelle più grandi di - + MB MB @@ -1467,7 +1546,7 @@ Seleziona quelli che desideri sincronizzare: This folder is not compatible with Lite Sync.<br> Please select another folder. If you continue Lite Sync will be disabled.<br> <a style="%1" href="%2">Learn more</a> - Questa cartella non è compatibile con Lite Sync.<br> + Questa cartella non è compatibile con Lite Sync.<br> Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.<br> <a style="%1" href="%2">Scopri di più</a> @@ -1485,12 +1564,12 @@ Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.< KDC::Logger - + Error Errore - + <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> <nobr>Il file '%1'<br/>non può essere aperto in scrittura.<br/><br/>Il risultato del log <b>non</b> può essere salvato!</nobr> @@ -1511,24 +1590,24 @@ Seleziona un'altra cartella. Se continui, Lite Sync verrà disabilitato.< KDC::ParametersDialog - + Unable to open folder path %1. Impossibile aprire il percorso della cartella %1. - + Transmission done!<br>Please refer to identifier <b>%1</b> in bug reports. Invio completato!<br>Fai riferimento all'identificatore <b>%1</b> nelle segnalazioni di bug. - + Transmission failed! Please, use the following link to send the logs to the support: <a style="%1" href="%2">%2</a> Trasmissione fallita! Per favore, utilizza il seguente link per inviare i log al supporto: <a style="%1" href="%2">%2</a> - + No kDrive configured! Nessun kDrive configurato! @@ -1779,16 +1858,26 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style The operation performed on this item failed.<br>The item has been temporarily blacklisted. L'operazione eseguita su questo articolo non è riuscita.<br>L'articolo è stato temporaneamente inserito nella lista nera. - - - Move to trash failed. - Spostamento nel cestino non riuscito. - The file is too large to be uploaded. It has been temporarily blacklisted. Il file è troppo grande per essere caricato. È stato temporaneamente inserito nella lista nera. + + + Impossible to download the file. + Impossibile scaricare il file. + + + + You have exceeded your quota. Increase your space quota to re-enable file upload. + Hai superato la tua quota. Aumenta la tua quota di spazio per riattivare il caricamento dei file. + + + + The synchronization folder is inaccessible (error %1).<br>Please check that you have read and write access to this folder. + La cartella di sincronizzazione è inaccessibile (errore %1).<br>Verificare di avere accesso in lettura e scrittura a questa cartella. + An existing item has an identical name with the same case options (same upper and lower case letters).<br>It has been temporarily blacklisted. @@ -1802,7 +1891,7 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style This item name is reserved by your operating system.<br>It has been temporarily blacklisted. - Questo nome dell'elemento è riservato dal tuo sistema operativo.<br>È stato temporaneamente inserito nella blacklist. + Il nome di questo elemento è riservato dal tuo sistema operativo.<br>È stato temporaneamente inserito nella blacklist. @@ -1817,28 +1906,13 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style The item name contains a recent UNICODE character not yet supported by your filesystem.<br>It has been excluded from synchronization. - Il nome dell'elemento contiene un carattere UNICODE recente non ancora supportato dal tuo file system.<br>È stato escluso dalla sincronizzazione. + Il nome dell'elemento contiene un carattere UNICODE recente non ancora supportato dal file system.<br>È stato escluso dalla sincronizzazione. The item name coincides with the name of another item in the same directory.<br>It has been temporarily blacklisted. Consider removing duplicate items. Il nome dell'elemento coincide con il nome di un altro elemento nella stessa directory.<br>È stato temporaneamente inserito nella blacklist. Prendi in considerazione la rimozione degli elementi duplicati. - - - Impossible to download the file. - Impossibile scaricare il file. - - - - You have exceeded your quota. Increase your space quota to re-enable file upload. - Hai superato la tua quota. Aumenta la tua quota di spazio per riattivare il caricamento dei file. - - - - The synchronization folder is inaccessible (error %1).<br>Please check that you have read and write access to this folder. - La cartella di sincronizzazione è inaccessibile (errore %1).<br>Verificare di avere accesso in lettura e scrittura a questa cartella. - This item already exists on remote kDrive. It is not synced because it has been blacklisted. @@ -1861,7 +1935,7 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style - + Synchronization error. Errore di sincronizzazione. @@ -1871,12 +1945,12 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style Impossibile accedere all'elemento.<br>Si prega di correggere i permessi di lettura e scrittura. - + System error. Errore di sistema. - + A technical error has occurred.<br>Please empty the history and if the error persists, contact our support team. Si è verificato un errore tecnico.<br>Svuota la cronologia e, se l'errore persiste, contatta il nostro team di assistenza. @@ -1905,122 +1979,137 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style KDC::PreferencesWidget - + General Generale - + Activate dark theme Attiva tema scuro - + Activate monochrome icons Attiva icone monocolore - + Launch kDrive at startup Lancia kDrive all'avvio del sistema - - Move deleted files to trash - Sposta i file eliminati nel cestino - - - + Advanced Avanzate - + Debugging information Informazioni di debug - + <a style="%1" href="%2">Open debugging folder</a> <a style="%1" href="%2">Apri cartella di debug</a> - + Files to exclude File da escludere - + Proxy server Server proxy - + Unable to open folder %1. Impossibile aprire la cartella %1. - + Unable to open link %1. Impossibile aprire il link %1. - + Invalid link %1. Link %1 non valido. - + Show synchronized folders in File Explorer navigation pane Mostra le cartelle sincronizzate nel riquadro di navigazione di Esplora file - + You must restart your opened File Explorers for this change to take effect. È necessario riavviare gli strumenti di esplorazione di file aperti affinché questa modifica abbia effetto. - + Lite Sync Lite Sync - + Language Lingua - + Some process failed to run. Non è possibile eseguire alcuni processi. - + + Move deleted files to my computer's trash + Sposta i file eliminati nel cestino del mio computer + + + + Some files or folders may not be moved to the computer's trash. + Alcuni file o cartelle potrebbero non essere spostati nel cestino del computer. + + + + You can always retrieve already synced files from the kDrive web application trash. + Puoi sempre recuperare i file già sincronizzati dal cestino dell'applicazione web kDrive. + + + + <a style="%1" href="%2">Learn more</a> + <a style=%1 href="%2">Scopri di più</a> + + + English Inglese - + French Francese - + German Tedesco - + Spanish Spagnolo - + Italian Italiano - + Default Predefinito @@ -2206,35 +2295,35 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style KDC::SocketApi - - - + + + Copy private share link Copia il collegamento di condivisione privata - - + + Resharing this file is not allowed Ricondivisione di questo file non consentita - - + + Resharing this folder is not allowed Ricondivisione di questa cartella non consentita - - - - + + + + Copy public share link Copia il collegamento di condivisione pubblica - - + + Open in browser Apri nel browser @@ -2358,43 +2447,48 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style Per 1 altra settimana - + + Send feedbacks + Invia feedback + + + Update kDrive App Aggiorna l'app kDrive - + This kDrive app version is not supported anymore. To access the latest features and enhancements, please update. Questa versione dell'app kDrive non è più supportata. Per accedere alle funzionalità e ai miglioramenti più recenti, aggiornare. - - + + Update Aggiornamento - + Please download the latest version on the website. Si prega di scaricare l'ultima versione dal sito web. - + Update download in progress Download dell'aggiornamento in corso - + Looking for update... In cerca di aggiornamento... - + Manual update Aggiornamento manuale - + Unavailable Non disponibile @@ -2404,27 +2498,27 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style Esci da kDrive - + Show errors and informations Mostra errori e informazioni - + Show informations Mostra informazioni - + You can synchronize files <a style="%1" href="%2">from your computer</a> or on <a style="%1" href="%3">kdrive.infomaniak.com</a>. Puoi sincronizzare i file <a style="%1" href="%2">dal tuo computer</a> o su <a style="%1" href="%3">kdrive.infomaniak.com</a>. - + Open in folder Apri nella cartella - + More actions Più azioni @@ -2444,53 +2538,53 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style Attività - - + + Not implemented! Non implementato! - + No synchronized folder for this Drive! Nessuna cartella sincronizzata per questo kDrive! - + No kDrive configured! Nessun kDrive configurato! - + Unable to access web site %1. Impossibile accedere al sito web %1. - + Open %1 web version Apri la versione web %1 - + Notifications disabled until %1 Notifiche disabilitate fino al %1 - + Disable Notifications Disabilita notifiche - + Need help Hai bisogno di aiuto - + Unable to open link %1. Impossibile aprire il link %1. - + Invalid link %1. Link %1 non valido. @@ -2500,12 +2594,12 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style Impossibile aprire l'URL della cartella %1. - + Drive parameters Parametri unità - + Application preferences Preferenze dell'applicazione @@ -2541,12 +2635,12 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style KDC::UpdateManager - + New update available. È disponibile un nuovo aggiornamento. - + Version %1 is available for download. La versione %1 è disponibile per il download. @@ -2562,65 +2656,90 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style KDC::VersionWidget - + <a style="%1" href="%2">%3</a> <a style="%1" href="%2">%3</a> - + <a style="%1" href="%2">Show release note</a> <a style="%1" href="%2">Mostra nota di rilascio</a> - + Version Versione - + UPDATE AGGIORNAMENTO - + %1 is up to date! %1 è aggiornato! - + Checking update on server... Controllo dell'aggiornamento sul server... - + An update is available: %1.<br>Please download it from <a style="%2" href="%3">here</a>. È disponibile un aggiornamento: %1.<br>Scaricalo da <a style="%2" href="%3">qui</a>. - + An update is available: %1 È disponibile un aggiornamento: %1 - + Downloading %1. Please wait... Download in corso %1. Attendere... - + Could not check for new updates. Impossibile verificare la presenza di nuovi aggiornamenti. - + An error occurred during update. Si è verificato un errore durante l'aggiornamento. - + Could not download update. Impossibile scaricare l'aggiornamento. + + + Beta program + Beta program + + + + Get early access to new versions of the application + Ottenere l'accesso anticipato alle nuove versioni dell'applicazione + + + + Join + Contatto + + + + Modify + Modificare + + + + Quit + Esci + QObject @@ -2645,37 +2764,37 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style Impossibile trovare un percorso valido - + No valid folder selected! Nessuna cartella valida selezionata! - + The selected path does not exist! Il percorso selezionato non esiste! - + The selected path is not a folder! Il percorso selezionato non è una cartella! - + You have no permission to write to the selected folder! Non disponi dell'autorizzazione di scrittura per la cartella selezionata! - + The local folder %1 contains a folder already synced. Please pick another one! La cartella locale %1 contiene una cartella già sincronizzata. Scegline un'altra! - + The local folder %1 is contained in a folder already synced. Please pick another one! La cartella locale %1 è contenuta in una cartella già sincronizzata. Scegline un'altra! - + The local folder %1 is already synced on the same drive. Please pick another one! La cartella locale %1 è già sincronizzata sulla stessa unità. Scegline un'altra! @@ -2796,23 +2915,23 @@ Per favore, utilizza il seguente link per inviare i log al supporto: <a style utility - + Free up local space Libera spazio locale - + Cancel free up local space Annulla liberazione di spazio locale - - + + Cancel make available locally Annulla rendere disponibile localmente - + Always make available locally Rendi sempre disponibile localmente diff --git a/translations/translate.py b/translations/translate.py index a7a868a16..26f6011cf 100755 --- a/translations/translate.py +++ b/translations/translate.py @@ -54,9 +54,9 @@ translator = deepl.Translator(deepl_key) target_lang = [ - 'FR', 'DE', 'ES', + 'FR', 'IT' ]