mirror of
https://github.com/chylex/Nextcloud-Desktop.git
synced 2026-04-07 01:34:16 +02:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b45f5fd1a9 | ||
|
|
a4317ca50d | ||
|
|
6c6eeab479 | ||
|
|
7fdbc72991 | ||
|
|
2187c6f4fc | ||
|
|
f3b825f333 | ||
|
|
676d4e1308 | ||
|
|
e03309a05e | ||
|
|
de15f20006 | ||
|
|
7ed1062314 | ||
|
|
80138b1cbf | ||
|
|
93547ced6d | ||
|
|
0aefa58644 | ||
|
|
992bcf56f5 | ||
|
|
35de69f7bc | ||
|
|
1c44909254 | ||
|
|
c3ccb72f2b | ||
|
|
890bd09ce9 | ||
|
|
b5309c47fc | ||
|
|
d24a09e47f | ||
|
|
bdae4a9be7 | ||
|
|
d9ce9814da | ||
|
|
f6d019af21 | ||
|
|
423ee61817 | ||
|
|
4871776a4b | ||
|
|
f4e83891e1 | ||
|
|
d589b524e0 | ||
|
|
de0158e2bb | ||
|
|
62856f9001 | ||
|
|
1313bc573f | ||
|
|
c2838fb8a8 | ||
|
|
ba71bf13f4 | ||
|
|
7e36c7ba59 | ||
|
|
e9641a3b94 | ||
|
|
7ff1911492 | ||
|
|
57f8b866a5 | ||
|
|
883df2f4fa | ||
|
|
5880c4954e | ||
|
|
b02bd066a9 | ||
|
|
6abec7cea9 | ||
|
|
cc4e6b236a | ||
|
|
821946ad94 | ||
|
|
94c3e19ede | ||
|
|
feba6910ce | ||
|
|
4f37249750 | ||
|
|
fda8c406f6 | ||
|
|
a804c4650a | ||
|
|
e1f4963973 | ||
|
|
6ae761c43c | ||
|
|
504bb34d26 | ||
|
|
1136cee383 | ||
|
|
591d4c812b | ||
|
|
a105e3f758 | ||
|
|
a88687bfe3 | ||
|
|
879ed544e1 | ||
|
|
13aaffc46b | ||
|
|
62fc12fe40 | ||
|
|
f4543e0c79 | ||
|
|
d35f466773 | ||
|
|
e3cb3b28ff | ||
|
|
36049afbc4 | ||
|
|
59c165aa1d | ||
|
|
071b4abeeb | ||
|
|
93e04fc72b | ||
|
|
a9915c4b46 | ||
|
|
a265ff52e7 | ||
|
|
85b4965d7f | ||
|
|
963beec760 | ||
|
|
2f812063ac | ||
|
|
a485120a34 | ||
|
|
6337116de7 | ||
|
|
a56eb2e95e | ||
|
|
ac3246f9f2 | ||
|
|
aa9849c112 | ||
|
|
a89e49ef84 |
@@ -198,7 +198,7 @@ X-GNOME-Autostart-Delay=3
|
||||
|
||||
|
||||
# Translations
|
||||
Icon[cs_CZ]=@NAZEV_IKONY_APLIKACE@
|
||||
Icon[cs_CZ]=@APPLICATION_ICON_NAME@
|
||||
Name[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
|
||||
Comment[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
|
||||
GenericName[cs_CZ]=Synchronizace složek
|
||||
|
||||
@@ -198,7 +198,7 @@ X-GNOME-Autostart-Delay=3
|
||||
|
||||
|
||||
# Translations
|
||||
Icon[de]=@APPLICATION_ICON_NAME@
|
||||
Name[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
|
||||
Comment[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
|
||||
GenericName[de]=Synchronisationsordner
|
||||
Icon[de_DE]=@APPLICATION_ICON_NAME@
|
||||
Name[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
|
||||
Comment[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
|
||||
GenericName[de_DE]=Synchronisierungsordner
|
||||
|
||||
204
.tx/nextcloud.client-desktop/es_AR_translation
Normal file
204
.tx/nextcloud.client-desktop/es_AR_translation
Normal file
@@ -0,0 +1,204 @@
|
||||
[Desktop Entry]
|
||||
Categories=Utility;X-SuSE-SyncUtility;
|
||||
Type=Application
|
||||
Exec=@APPLICATION_EXECUTABLE@
|
||||
Name=@APPLICATION_NAME@ desktop sync client
|
||||
Comment=@APPLICATION_NAME@ desktop synchronization client
|
||||
GenericName=Folder Sync
|
||||
Icon=@APPLICATION_ICON_NAME@
|
||||
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||
X-GNOME-Autostart-Delay=3
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
Icon[es_AR]=@APPLICATION_ICON_NAME@
|
||||
Name[es_AR]=@APPLICATION_NAME@ cliente de sincronización de escritorio
|
||||
Comment[es_AR]=@APPLICATION_NAME@ cliente de sincronización de escritorio
|
||||
GenericName[es_AR]=Sincronización de carpetas
|
||||
201
.tx/nextcloud.client-desktop/fa_translation
Normal file
201
.tx/nextcloud.client-desktop/fa_translation
Normal file
@@ -0,0 +1,201 @@
|
||||
[Desktop Entry]
|
||||
Categories=Utility;X-SuSE-SyncUtility;
|
||||
Type=Application
|
||||
Exec=@APPLICATION_EXECUTABLE@
|
||||
Name=@APPLICATION_NAME@ desktop sync client
|
||||
Comment=@APPLICATION_NAME@ desktop synchronization client
|
||||
GenericName=Folder Sync
|
||||
Icon=@APPLICATION_ICON_NAME@
|
||||
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||
X-GNOME-Autostart-Delay=3
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
Comment[fa]=@ APPLICATION_NAME @ مشتری هماهنگ سازی دسکتاپ
|
||||
@@ -199,4 +199,6 @@ X-GNOME-Autostart-Delay=3
|
||||
|
||||
# Translations
|
||||
Icon[sv]=@APPLICATION_ICON_NAME@
|
||||
Name[sv]=@APPLICATION_NAME@ desktopssynkklient
|
||||
Comment[sv]=@APPLICATION_NAME@ desktopssynkroniseringsklient
|
||||
GenericName[sv]=Mappsynkronisering
|
||||
|
||||
@@ -198,6 +198,7 @@ X-GNOME-Autostart-Delay=3
|
||||
|
||||
|
||||
# Translations
|
||||
Icon[zh_TW]=@APPLICATION_ICON_NAME@
|
||||
Name[zh_TW]=@APPLICATION_NAME@ 桌面同步客戶端
|
||||
Comment[zh_TW]=@APPLICATION_NAME@ 桌面同步客戶端
|
||||
GenericName[zh_TW]=資料夾同步
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
set( MIRALL_VERSION_MAJOR 2 )
|
||||
set( MIRALL_VERSION_MINOR 6 )
|
||||
set( MIRALL_VERSION_PATCH 2 )
|
||||
set( MIRALL_VERSION_YEAR 2019 )
|
||||
set( MIRALL_VERSION_PATCH 4 )
|
||||
set( MIRALL_VERSION_YEAR 2020 )
|
||||
set( MIRALL_SOVERSION 0 )
|
||||
|
||||
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
||||
|
||||
@@ -18,11 +18,11 @@ if [ $SUFFIX != "master" ]; then
|
||||
SUFFIX="PR-$SUFFIX"
|
||||
fi
|
||||
|
||||
#QtKeyChain 0.9.1
|
||||
#QtKeyChain master
|
||||
cd /build
|
||||
git clone https://github.com/frankosterfeld/qtkeychain.git
|
||||
cd qtkeychain
|
||||
git checkout v0.9.1
|
||||
git checkout master
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -D CMAKE_INSTALL_PREFIX=/usr ../
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
usr/bin
|
||||
usr/share/applications
|
||||
usr/share/icons
|
||||
debian/101-sync-inotify.conf etc/sysctl.d
|
||||
@@ -1,4 +1,6 @@
|
||||
usr/bin
|
||||
usr/share/applications
|
||||
usr/share/cloud-providers/
|
||||
usr/share/dbus-1/services/
|
||||
usr/share/icons
|
||||
debian/101-sync-inotify.conf etc/sysctl.d
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>@MIRALL_VERSION_STRING@</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>(C) 2014-2019 @APPLICATION_VENDOR@</string>
|
||||
<string>(C) 2014-2020 @APPLICATION_VENDOR@</string>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
<key>SUShowReleaseNotes</key>
|
||||
|
||||
@@ -87,7 +87,7 @@ To prevent automatic updates and disallow manual overrides:
|
||||
|
||||
1. Edit this Registry key:
|
||||
|
||||
``HKEY_LOCAL_MACHINE\Software\Policies\Nextcloud\Nextcloud``
|
||||
``HKEY_LOCAL_MACHINE\Software\Policies\Nextcloud GmbH\Nextcloud``
|
||||
|
||||
2. Add the key ``skipUpdateCheck`` (of type DWORD).
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ download page.
|
||||
System Requirements
|
||||
----------------------------------
|
||||
|
||||
- Windows 7+
|
||||
- Windows 8.1+
|
||||
- macOS 10.7+ (**64-bit only**)
|
||||
- CentOS 6 & 7 (64-bit only)
|
||||
- Debian 8.0 & 9.0
|
||||
@@ -36,8 +36,7 @@ System Requirements
|
||||
- openSUSE Leap 42.2 & 42.3
|
||||
|
||||
.. note::
|
||||
For Linux distributions, we support, if technically feasible, the latest 2 versions per platform and the previous `LTS`_.
|
||||
>>>>>>> b2da03441... update supported linux platforms
|
||||
For Linux distributions, we support, if technically feasible, the latest 2 versions per platform and the previous LTS.
|
||||
|
||||
Installation Wizard
|
||||
-------------------
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[D-BUS Service]
|
||||
Name=@LIBCLOUDPROVIDERS_DBUS_BUS_NAME@
|
||||
Exec=@APPLICATION_EXECUTABLE@
|
||||
Exec=@APPLICATION_EXECUTABLE@ --background
|
||||
|
||||
|
||||
@@ -182,20 +182,38 @@ IFACEMETHODIMP OCContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
|
||||
{
|
||||
std::wstring command;
|
||||
|
||||
CMINVOKECOMMANDINFOEX *piciEx = nullptr;
|
||||
if (pici->cbSize == sizeof(CMINVOKECOMMANDINFOEX))
|
||||
piciEx = (CMINVOKECOMMANDINFOEX*)pici;
|
||||
|
||||
// For the Unicode case, if the high-order word is not zero, the
|
||||
// command's verb string is in lpcmi->lpVerbW.
|
||||
if (HIWORD(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW))
|
||||
{
|
||||
command = ((CMINVOKECOMMANDINFOEX *)pici)->lpVerbW;
|
||||
} else {
|
||||
if (piciEx
|
||||
&& (piciEx->fMask & CMIC_MASK_UNICODE)
|
||||
&& HIWORD(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW)) {
|
||||
|
||||
command = piciEx->lpVerbW;
|
||||
|
||||
// Verify that we handle the verb
|
||||
bool handled = false;
|
||||
for (auto &item : m_info.menuItems) {
|
||||
if (item.command == command) {
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!handled)
|
||||
return E_FAIL;
|
||||
} else if (IS_INTRESOURCE(pici->lpVerb)) {
|
||||
// If the command cannot be identified through the verb string, then
|
||||
// check the identifier offset.
|
||||
|
||||
auto offset = LOWORD(pici->lpVerb);
|
||||
if (offset >= m_info.menuItems.size())
|
||||
return E_FAIL;
|
||||
|
||||
command = m_info.menuItems[offset].command;
|
||||
} else {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
OCClientInterface::SendRequest(command.data(), m_selectedFiles);
|
||||
|
||||
@@ -90,7 +90,7 @@ set(client_SRCS
|
||||
syncrunfilelog.cpp
|
||||
systray.cpp
|
||||
thumbnailjob.cpp
|
||||
quotainfo.cpp
|
||||
userinfo.cpp
|
||||
accountstate.cpp
|
||||
addcertificatedialog.cpp
|
||||
authenticationdialog.cpp
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "configfile.h"
|
||||
#include "account.h"
|
||||
#include "accountstate.h"
|
||||
#include "quotainfo.h"
|
||||
#include "userinfo.h"
|
||||
#include "accountmanager.h"
|
||||
#include "owncloudsetupwizard.h"
|
||||
#include "creds/abstractcredentials.h"
|
||||
@@ -112,7 +112,7 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
|
||||
, _ui(new Ui::AccountSettings)
|
||||
, _wasDisabledBefore(false)
|
||||
, _accountState(accountState)
|
||||
, _quotaInfo(accountState)
|
||||
, _userInfo(accountState, false, true)
|
||||
, _menuShown(false)
|
||||
{
|
||||
_ui->setupUi(this);
|
||||
@@ -192,7 +192,7 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
|
||||
connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged);
|
||||
slotAccountStateChanged();
|
||||
|
||||
connect(&_quotaInfo, &QuotaInfo::quotaUpdated,
|
||||
connect(&_userInfo, &UserInfo::quotaUpdated,
|
||||
this, &AccountSettings::slotUpdateQuota);
|
||||
|
||||
// Connect E2E stuff
|
||||
@@ -1238,7 +1238,7 @@ void AccountSettings::slotDeleteAccount()
|
||||
bool AccountSettings::event(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::Hide || e->type() == QEvent::Show) {
|
||||
_quotaInfo.setActive(isVisible());
|
||||
_userInfo.setActive(isVisible());
|
||||
}
|
||||
if (e->type() == QEvent::Show) {
|
||||
// Expand the folder automatically only if there's only one, see #4283
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "folder.h"
|
||||
#include "quotainfo.h"
|
||||
#include "userinfo.h"
|
||||
#include "progressdispatcher.h"
|
||||
#include "owncloudgui.h"
|
||||
#include "folderstatusmodel.h"
|
||||
@@ -142,7 +142,7 @@ private:
|
||||
QUrl _OCUrl;
|
||||
bool _wasDisabledBefore;
|
||||
AccountState *_accountState;
|
||||
QuotaInfo _quotaInfo;
|
||||
UserInfo _userInfo;
|
||||
QAction *_toggleSignInOutAction;
|
||||
QAction *_addAccountAction;
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@
|
||||
<string/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Storage space: ...</string>
|
||||
<string>Storage space: …</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
|
||||
@@ -229,7 +229,7 @@ void AccountState::checkConnectivity()
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionValidator *conValidator = new ConnectionValidator(account());
|
||||
ConnectionValidator *conValidator = new ConnectionValidator(AccountStatePtr(this));
|
||||
_connectionValidator = conValidator;
|
||||
connect(conValidator, &ConnectionValidator::connectionResult,
|
||||
this, &AccountState::slotConnectionValidatorResult);
|
||||
|
||||
@@ -139,7 +139,9 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
||||
case ActivityItemDelegate::AccountRole:
|
||||
return a._accName;
|
||||
case ActivityItemDelegate::PointInTimeRole:
|
||||
return QString("%1 (%2)").arg(a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate), Utility::timeAgoInWords(a._dateTime.toLocalTime()));
|
||||
return Utility::timeAgoInWords(a._dateTime.toLocalTime());
|
||||
case Qt::ToolTipRole:
|
||||
return a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate);
|
||||
case ActivityItemDelegate::AccountConnectedRole:
|
||||
return (ast && ast->isConnected());
|
||||
default:
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonBrowseCertificate">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
<string>Browse …</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -57,7 +57,7 @@
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelPWDCertificate">
|
||||
<property name="text">
|
||||
<string>Certificate password :</string>
|
||||
<string>Certificate password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -53,7 +53,7 @@ bool ClientProxy::isUsingSystemDefault()
|
||||
return cfg.proxyType() == QNetworkProxy::DefaultProxy;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
QString printQNetworkProxy(const QNetworkProxy &proxy)
|
||||
|
||||
@@ -52,8 +52,8 @@ void CloudProviderManager::registerSignals()
|
||||
CloudProviderManager::CloudProviderManager(QObject *parent) : QObject(parent)
|
||||
{
|
||||
_map = new QMap<QString, CloudProviderWrapper*>();
|
||||
QString busName = QString(LIBCLOUDPROVIDERS_DBUS_BUS_NAME);
|
||||
g_bus_own_name (G_BUS_TYPE_SESSION, busName.toAscii().data(), G_BUS_NAME_OWNER_FLAGS_NONE, nullptr, on_name_acquired, nullptr, this, nullptr);
|
||||
_folder_index = 0;
|
||||
g_bus_own_name (G_BUS_TYPE_SESSION, LIBCLOUDPROVIDERS_DBUS_BUS_NAME, G_BUS_NAME_OWNER_FLAGS_NONE, nullptr, on_name_acquired, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
void CloudProviderManager::slotFolderListChanged(const Folder::Map &folderMap)
|
||||
@@ -72,7 +72,7 @@ void CloudProviderManager::slotFolderListChanged(const Folder::Map &folderMap)
|
||||
while (j.hasNext()) {
|
||||
j.next();
|
||||
if (!_map->contains(j.key())) {
|
||||
auto *cpo = new CloudProviderWrapper(this, j.value(), _providerExporter);
|
||||
auto *cpo = new CloudProviderWrapper(this, j.value(), _folder_index++, _providerExporter);
|
||||
_map->insert(j.key(), cpo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ public slots:
|
||||
|
||||
private:
|
||||
QMap<QString, CloudProviderWrapper*> *_map;
|
||||
unsigned int _folder_index;
|
||||
};
|
||||
|
||||
#endif // CLOUDPROVIDERMANAGER_H
|
||||
|
||||
@@ -33,13 +33,13 @@ using namespace OCC;
|
||||
|
||||
GSimpleActionGroup *actionGroup = nullptr;
|
||||
|
||||
CloudProviderWrapper::CloudProviderWrapper(QObject *parent, Folder *folder, CloudProvidersProviderExporter* cloudprovider) : QObject(parent)
|
||||
CloudProviderWrapper::CloudProviderWrapper(QObject *parent, Folder *folder, int folderId, CloudProvidersProviderExporter* cloudprovider) : QObject(parent)
|
||||
, _folder(folder)
|
||||
{
|
||||
GMenuModel *model;
|
||||
GActionGroup *action_group;
|
||||
_recentlyChanged = new QList<QPair<QString, QString>>();
|
||||
QString accountName = QString("Account%1Folder%2").arg(folder->alias(), folder->accountState()->account()->id());
|
||||
QString accountName = QString("Folder/%1").arg(folderId);
|
||||
|
||||
_cloudProvider = CLOUD_PROVIDERS_PROVIDER_EXPORTER(cloudprovider);
|
||||
_cloudProviderAccount = cloud_providers_account_exporter_new(_cloudProvider, accountName.toUtf8().data());
|
||||
|
||||
@@ -38,7 +38,7 @@ class CloudProviderWrapper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CloudProviderWrapper(QObject *parent = nullptr, Folder *folder = nullptr, CloudProvidersProviderExporter* cloudprovider = nullptr);
|
||||
explicit CloudProviderWrapper(QObject *parent = nullptr, Folder *folder = nullptr, int folderId = 0, CloudProvidersProviderExporter* cloudprovider = nullptr);
|
||||
~CloudProviderWrapper();
|
||||
CloudProvidersAccountExporter* accountExporter();
|
||||
Folder* folder();
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include "connectionvalidator.h"
|
||||
#include "account.h"
|
||||
#include "accountstate.h"
|
||||
#include "userinfo.h"
|
||||
#include "networkjobs.h"
|
||||
#include "clientproxy.h"
|
||||
#include <creds/abstractcredentials.h>
|
||||
@@ -34,9 +36,10 @@ Q_LOGGING_CATEGORY(lcConnectionValidator, "nextcloud.sync.connectionvalidator",
|
||||
// This makes sure we get tried often enough without "ConnectionValidator already running"
|
||||
static qint64 timeoutToUseMsec = qMax(1000, ConnectionValidator::DefaultCallingIntervalMsec - 5 * 1000);
|
||||
|
||||
ConnectionValidator::ConnectionValidator(AccountPtr account, QObject *parent)
|
||||
ConnectionValidator::ConnectionValidator(AccountStatePtr accountState, QObject *parent)
|
||||
: QObject(parent)
|
||||
, _account(account)
|
||||
, _accountState(accountState)
|
||||
, _account(accountState->account())
|
||||
, _isCheckingServerAndAuth(false)
|
||||
{
|
||||
}
|
||||
@@ -44,7 +47,7 @@ ConnectionValidator::ConnectionValidator(AccountPtr account, QObject *parent)
|
||||
void ConnectionValidator::checkServerAndAuth()
|
||||
{
|
||||
if (!_account) {
|
||||
_errors << tr("No ownCloud account configured");
|
||||
_errors << tr("No Nextcloud account configured");
|
||||
reportResult(NotConfigured);
|
||||
return;
|
||||
}
|
||||
@@ -265,10 +268,9 @@ void ConnectionValidator::ocsConfigReceived(const QJsonDocument &json, AccountPt
|
||||
|
||||
void ConnectionValidator::fetchUser()
|
||||
{
|
||||
JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/user"), this);
|
||||
job->setTimeout(timeoutToUseMsec);
|
||||
QObject::connect(job, &JsonApiJob::jsonReceived, this, &ConnectionValidator::slotUserFetched);
|
||||
job->start();
|
||||
UserInfo *userInfo = new UserInfo(_accountState.data(), true, true, this);
|
||||
QObject::connect(userInfo, &UserInfo::fetchedLastInfo, this, &ConnectionValidator::slotUserFetched);
|
||||
userInfo->setActive(true);
|
||||
}
|
||||
|
||||
bool ConnectionValidator::setAndCheckServerVersion(const QString &version)
|
||||
@@ -300,34 +302,22 @@ bool ConnectionValidator::setAndCheckServerVersion(const QString &version)
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectionValidator::slotUserFetched(const QJsonDocument &json)
|
||||
void ConnectionValidator::slotUserFetched(UserInfo *userInfo)
|
||||
{
|
||||
QString user = json.object().value("ocs").toObject().value("data").toObject().value("id").toString();
|
||||
if (!user.isEmpty()) {
|
||||
_account->setDavUser(user);
|
||||
}
|
||||
QString displayName = json.object().value("ocs").toObject().value("data").toObject().value("display-name").toString();
|
||||
if (!displayName.isEmpty()) {
|
||||
_account->setDavDisplayName(displayName);
|
||||
if(userInfo) {
|
||||
userInfo->setActive(false);
|
||||
userInfo->deleteLater();
|
||||
}
|
||||
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
AvatarJob *job = new AvatarJob(_account, _account->davUser(), 128, this);
|
||||
job->setTimeout(20 * 1000);
|
||||
QObject::connect(job, &AvatarJob::avatarPixmap, this, &ConnectionValidator::slotAvatarImage);
|
||||
job->start();
|
||||
connect(_account->e2e(), &ClientSideEncryption::initializationFinished, this, &ConnectionValidator::reportConnected);
|
||||
_account->e2e()->initialize();
|
||||
#else
|
||||
reportResult(Connected);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
void ConnectionValidator::slotAvatarImage(const QImage &img)
|
||||
{
|
||||
_account->setAvatar(img);
|
||||
connect(_account->e2e(), &ClientSideEncryption::initializationFinished, this, &ConnectionValidator::reportConnected);
|
||||
_account->e2e()->initialize();
|
||||
}
|
||||
|
||||
void ConnectionValidator::reportConnected() {
|
||||
reportResult(Connected);
|
||||
}
|
||||
|
||||
@@ -67,23 +67,20 @@ namespace OCC {
|
||||
+---------------------------------+
|
||||
|
|
||||
fetchUser
|
||||
PropfindJob
|
||||
|
|
||||
+-> slotUserFetched
|
||||
AvatarJob
|
||||
|
|
||||
+-> slotAvatarImage -->
|
||||
Utilizes the UserInfo class to fetch the user and avatar image
|
||||
+-----------------------------------+
|
||||
|
|
||||
+-> Client Side Encryption Checks --+ --reportResult()
|
||||
\endcode
|
||||
*/
|
||||
|
||||
class UserInfo;
|
||||
|
||||
class ConnectionValidator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ConnectionValidator(AccountPtr account, QObject *parent = nullptr);
|
||||
explicit ConnectionValidator(AccountStatePtr accountState, QObject *parent = nullptr);
|
||||
|
||||
enum Status {
|
||||
Undefined,
|
||||
@@ -125,13 +122,12 @@ protected slots:
|
||||
void slotAuthSuccess();
|
||||
|
||||
void slotCapabilitiesRecieved(const QJsonDocument &);
|
||||
void slotUserFetched(const QJsonDocument &);
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
void slotAvatarImage(const QImage &img);
|
||||
#endif
|
||||
void slotUserFetched(UserInfo *userInfo);
|
||||
|
||||
private:
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
void reportConnected();
|
||||
#endif
|
||||
void reportResult(Status status);
|
||||
void checkServerCapabilities();
|
||||
void fetchUser();
|
||||
@@ -144,6 +140,7 @@ private:
|
||||
bool setAndCheckServerVersion(const QString &version);
|
||||
|
||||
QStringList _errors;
|
||||
AccountStatePtr _accountState;
|
||||
AccountPtr _account;
|
||||
bool _isCheckingServerAndAuth;
|
||||
};
|
||||
|
||||
@@ -183,7 +183,11 @@ void WebFlowCredentials::askFromUser() {
|
||||
void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host) {
|
||||
Q_UNUSED(host)
|
||||
|
||||
if (_user != user) {
|
||||
// Compare the re-entered username case-insensitive and save the new value (avoid breaking the account)
|
||||
// See issue: https://github.com/nextcloud/desktop/issues/1741
|
||||
if (QString::compare(_user, user, Qt::CaseInsensitive) == 0) {
|
||||
_user = user;
|
||||
} else {
|
||||
qCInfo(lcWebFlowCredentials()) << "Authed with the wrong user!";
|
||||
|
||||
QString msg = tr("Please login with the user: %1")
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<item>
|
||||
<widget class="QPushButton" name="localFolderChooseBtn">
|
||||
<property name="text">
|
||||
<string>&Choose...</string>
|
||||
<string>&Choose …</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="addFolderButton">
|
||||
<property name="text">
|
||||
<string>Create Folder</string>
|
||||
<string>Create folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -100,6 +100,9 @@ GeneralSettings::GeneralSettings(QWidget *parent)
|
||||
if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows10)
|
||||
#endif
|
||||
_ui->showInExplorerNavigationPaneCheckBox->setVisible(false);
|
||||
#else
|
||||
// Hide on non-Windows
|
||||
_ui->showInExplorerNavigationPaneCheckBox->setVisible(false);
|
||||
#endif
|
||||
|
||||
/* Set the left contents margin of the layout to zero to make the checkboxes
|
||||
|
||||
@@ -24,7 +24,7 @@ LegalNotice::LegalNotice(QDialog *parent)
|
||||
{
|
||||
_ui->setupUi(this);
|
||||
|
||||
QString notice = tr("<p>Copyright 2017-2019 Nextcloud GmbH<br />"
|
||||
QString notice = tr("<p>Copyright 2017-2020 Nextcloud GmbH<br />"
|
||||
"Copyright 2012-2018 ownCloud GmbH</p>");
|
||||
|
||||
notice += tr("<p>Licensed under the GNU General Public License (GPL) Version 2.0 or any later version.</p>");
|
||||
|
||||
@@ -32,6 +32,11 @@ NavigationPaneHelper::NavigationPaneHelper(FolderMan *folderMan)
|
||||
|
||||
_updateCloudStorageRegistryTimer.setSingleShot(true);
|
||||
connect(&_updateCloudStorageRegistryTimer, &QTimer::timeout, this, &NavigationPaneHelper::updateCloudStorageRegistry);
|
||||
|
||||
// Ensure that the folder integration stays persistent in Explorer,
|
||||
// the uninstaller removes the folder upon updating the client.
|
||||
_showInExplorerNavigationPane = !_showInExplorerNavigationPane;
|
||||
setShowInExplorerNavigationPane(!_showInExplorerNavigationPane);
|
||||
}
|
||||
|
||||
void NavigationPaneHelper::setShowInExplorerNavigationPane(bool show)
|
||||
@@ -74,73 +79,78 @@ void NavigationPaneHelper::updateCloudStorageRegistry()
|
||||
});
|
||||
#endif
|
||||
|
||||
// Then re-save every folder that has a valid navigationPaneClsid to the registry.
|
||||
// We currently don't distinguish between new and existing CLSIDs, if it's there we just
|
||||
// save over it. We at least need to update the tile in case we are suddently using multiple accounts.
|
||||
foreach (Folder *folder, _folderMan->map()) {
|
||||
if (!folder->navigationPaneClsid().isNull()) {
|
||||
// If it already exists, unmark it for removal, this is a valid sync root.
|
||||
entriesToRemove.removeOne(folder->navigationPaneClsid());
|
||||
// Only save folder entries if the option is enabled.
|
||||
if (_showInExplorerNavigationPane) {
|
||||
// Then re-save every folder that has a valid navigationPaneClsid to the registry.
|
||||
// We currently don't distinguish between new and existing CLSIDs, if it's there we just
|
||||
// save over it. We at least need to update the tile in case we are suddently using multiple accounts.
|
||||
foreach (Folder *folder, _folderMan->map()) {
|
||||
if (!folder->navigationPaneClsid().isNull()) {
|
||||
// If it already exists, unmark it for removal, this is a valid sync root.
|
||||
entriesToRemove.removeOne(folder->navigationPaneClsid());
|
||||
|
||||
QString clsidStr = folder->navigationPaneClsid().toString();
|
||||
QString clsidPath = QString() % "Software\\Classes\\CLSID\\" % clsidStr;
|
||||
QString clsidPathWow64 = QString() % "Software\\Classes\\Wow6432Node\\CLSID\\" % clsidStr;
|
||||
QString namespacePath = QString() % "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\" % clsidStr;
|
||||
QString clsidStr = folder->navigationPaneClsid().toString();
|
||||
QString clsidPath = QString() % "Software\\Classes\\CLSID\\" % clsidStr;
|
||||
QString clsidPathWow64 = QString() % "Software\\Classes\\Wow6432Node\\CLSID\\" % clsidStr;
|
||||
QString namespacePath = QString() % "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\" % clsidStr;
|
||||
|
||||
QString title = folder->shortGuiRemotePathOrAppName();
|
||||
// Write the account name in the sidebar only when using more than one account.
|
||||
if (AccountManager::instance()->accounts().size() > 1)
|
||||
title = title % " - " % folder->accountState()->account()->displayName();
|
||||
QString iconPath = QDir::toNativeSeparators(qApp->applicationFilePath());
|
||||
QString targetFolderPath = QDir::toNativeSeparators(folder->cleanPath());
|
||||
QString title = folder->shortGuiRemotePathOrAppName();
|
||||
// Write the account name in the sidebar only when using more than one account.
|
||||
if (AccountManager::instance()->accounts().size() > 1)
|
||||
title = title % " - " % folder->accountState()->account()->displayName();
|
||||
QString iconPath = QDir::toNativeSeparators(qApp->applicationFilePath());
|
||||
QString targetFolderPath = QDir::toNativeSeparators(folder->cleanPath());
|
||||
|
||||
qCInfo(lcNavPane) << "Explorer Cloud storage provider: saving path" << targetFolderPath << "to CLSID" << clsidStr;
|
||||
qCInfo(lcNavPane) << "Explorer Cloud storage provider: saving path" << targetFolderPath << "to CLSID" << clsidStr;
|
||||
#ifdef Q_OS_WIN
|
||||
// Steps taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/dn889934%28v=vs.85%29.aspx
|
||||
// Step 1: Add your CLSID and name your extension
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QString(), REG_SZ, title);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QString(), REG_SZ, title);
|
||||
// Step 2: Set the image for your icon
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath);
|
||||
// Step 3: Add your extension to the Navigation Pane and make it visible
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1);
|
||||
// Step 4: Set the location for your extension in the Navigation Pane
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41);
|
||||
// Step 5: Provide the dll that hosts your extension.
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll"));
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll"));
|
||||
// Step 6: Define the instance object
|
||||
// Indicate that your namespace extension should function like other file folder structures in File Explorer.
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}"));
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}"));
|
||||
// Step 7: Provide the file system attributes of the target folder
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11);
|
||||
// Step 8: Set the path for the sync root
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath);
|
||||
// Step 9: Set appropriate shell flags
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28);
|
||||
// Step 10: Set the appropriate flags to control your shell behavior
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D);
|
||||
// Step 11: Register your extension in the namespace root
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QString(), REG_SZ, title);
|
||||
// Step 12: Hide your extension from the Desktop
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel"), clsidStr, REG_DWORD, 0x1);
|
||||
// Steps taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/dn889934%28v=vs.85%29.aspx
|
||||
// Step 1: Add your CLSID and name your extension
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QString(), REG_SZ, title);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QString(), REG_SZ, title);
|
||||
// Step 2: Set the image for your icon
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath);
|
||||
// Step 3: Add your extension to the Navigation Pane and make it visible
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1);
|
||||
// Step 4: Set the location for your extension in the Navigation Pane
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41);
|
||||
// Step 5: Provide the dll that hosts your extension.
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll"));
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll"));
|
||||
// Step 6: Define the instance object
|
||||
// Indicate that your namespace extension should function like other file folder structures in File Explorer.
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}"));
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}"));
|
||||
// Step 7: Provide the file system attributes of the target folder
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11);
|
||||
// Step 8: Set the path for the sync root
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath);
|
||||
// Step 9: Set appropriate shell flags
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28);
|
||||
// Step 10: Set the appropriate flags to control your shell behavior
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D);
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D);
|
||||
// Step 11: Register your extension in the namespace root
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QString(), REG_SZ, title);
|
||||
// Step 12: Hide your extension from the Desktop
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel"), clsidStr, REG_DWORD, 0x1);
|
||||
|
||||
// For us, to later be able to iterate and find our own namespace entries and associated CLSID.
|
||||
// Use the macro instead of the theme to make sure it matches with the uninstaller.
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QStringLiteral("ApplicationName"), REG_SZ, QLatin1String(APPLICATION_NAME));
|
||||
// For us, to later be able to iterate and find our own namespace entries and associated CLSID.
|
||||
// Use the macro instead of the theme to make sure it matches with the uninstaller.
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QStringLiteral("ApplicationName"), REG_SZ, QLatin1String(APPLICATION_NAME));
|
||||
#else
|
||||
// This code path should only occur on Windows (the config will be false, and the checkbox invisible on other platforms).
|
||||
// Add runtime checks rather than #ifdefing out the whole code to help catch breakages when developing on other platforms.
|
||||
Q_ASSERT(false);
|
||||
// This code path should only occur on Windows (the config will be false, and the checkbox invisible on other platforms).
|
||||
// Add runtime checks rather than #ifdefing out the whole code to help catch breakages when developing on other platforms.
|
||||
|
||||
// Don't crash, by any means!
|
||||
// Q_ASSERT(false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>:</string>
|
||||
<string notr="true">:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -404,7 +404,7 @@ void OwncloudSetupWizard::slotAuthError()
|
||||
|
||||
// Something else went wrong, maybe the response was 200 but with invalid data.
|
||||
} else {
|
||||
errorMsg = tr("There was an invalid response to an authenticated webdav request");
|
||||
errorMsg = tr("There was an invalid response to an authenticated WebDAV request");
|
||||
}
|
||||
|
||||
// bring wizard to top
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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.
|
||||
*/
|
||||
|
||||
#include "quotainfo.h"
|
||||
#include "account.h"
|
||||
#include "accountstate.h"
|
||||
#include "networkjobs.h"
|
||||
#include "folderman.h"
|
||||
#include "creds/abstractcredentials.h"
|
||||
#include <theme.h>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
namespace {
|
||||
static const int defaultIntervalT = 30 * 1000;
|
||||
static const int failIntervalT = 5 * 1000;
|
||||
}
|
||||
|
||||
QuotaInfo::QuotaInfo(AccountState *accountState, QObject *parent)
|
||||
: QObject(parent)
|
||||
, _accountState(accountState)
|
||||
, _lastQuotaTotalBytes(0)
|
||||
, _lastQuotaUsedBytes(0)
|
||||
, _active(false)
|
||||
{
|
||||
connect(accountState, &AccountState::stateChanged,
|
||||
this, &QuotaInfo::slotAccountStateChanged);
|
||||
connect(&_jobRestartTimer, &QTimer::timeout, this, &QuotaInfo::slotCheckQuota);
|
||||
_jobRestartTimer.setSingleShot(true);
|
||||
}
|
||||
|
||||
void QuotaInfo::setActive(bool active)
|
||||
{
|
||||
_active = active;
|
||||
slotAccountStateChanged();
|
||||
}
|
||||
|
||||
|
||||
void QuotaInfo::slotAccountStateChanged()
|
||||
{
|
||||
if (canGetQuota()) {
|
||||
auto elapsed = _lastQuotaRecieved.msecsTo(QDateTime::currentDateTime());
|
||||
if (_lastQuotaRecieved.isNull() || elapsed >= defaultIntervalT) {
|
||||
slotCheckQuota();
|
||||
} else {
|
||||
_jobRestartTimer.start(defaultIntervalT - elapsed);
|
||||
}
|
||||
} else {
|
||||
_jobRestartTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void QuotaInfo::slotRequestFailed()
|
||||
{
|
||||
_lastQuotaTotalBytes = 0;
|
||||
_lastQuotaUsedBytes = 0;
|
||||
_jobRestartTimer.start(failIntervalT);
|
||||
}
|
||||
|
||||
bool QuotaInfo::canGetQuota() const
|
||||
{
|
||||
if (!_accountState || !_active) {
|
||||
return false;
|
||||
}
|
||||
AccountPtr account = _accountState->account();
|
||||
return _accountState->isConnected()
|
||||
&& account->credentials()
|
||||
&& account->credentials()->ready();
|
||||
}
|
||||
|
||||
QString QuotaInfo::quotaBaseFolder() const
|
||||
{
|
||||
return Theme::instance()->quotaBaseFolder();
|
||||
}
|
||||
|
||||
void QuotaInfo::slotCheckQuota()
|
||||
{
|
||||
if (!canGetQuota()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_job) {
|
||||
// The previous job was not finished? Then we cancel it!
|
||||
_job->deleteLater();
|
||||
}
|
||||
|
||||
AccountPtr account = _accountState->account();
|
||||
_job = new PropfindJob(account, quotaBaseFolder(), this);
|
||||
_job->setProperties(QList<QByteArray>() << "quota-available-bytes"
|
||||
<< "quota-used-bytes");
|
||||
connect(_job.data(), &PropfindJob::result, this, &QuotaInfo::slotUpdateLastQuota);
|
||||
connect(_job.data(), &AbstractNetworkJob::networkError, this, &QuotaInfo::slotRequestFailed);
|
||||
_job->start();
|
||||
}
|
||||
|
||||
void QuotaInfo::slotUpdateLastQuota(const QVariantMap &result)
|
||||
{
|
||||
// The server can return fractional bytes (#1374)
|
||||
// <d:quota-available-bytes>1374532061.2</d:quota-available-bytes>
|
||||
qint64 avail = result["quota-available-bytes"].toDouble();
|
||||
_lastQuotaUsedBytes = result["quota-used-bytes"].toDouble();
|
||||
// negative value of the available quota have special meaning (#3940)
|
||||
_lastQuotaTotalBytes = avail >= 0 ? _lastQuotaUsedBytes + avail : avail;
|
||||
emit quotaUpdated(_lastQuotaTotalBytes, _lastQuotaUsedBytes);
|
||||
_jobRestartTimer.start(defaultIntervalT);
|
||||
_lastQuotaRecieved = QDateTime::currentDateTime();
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QFileIconProvider>
|
||||
#include <QInputDialog>
|
||||
#include <QPointer>
|
||||
#include <QPushButton>
|
||||
#include <QFrame>
|
||||
@@ -137,6 +138,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
|
||||
_manager = new ShareManager(accountState->account(), this);
|
||||
connect(_manager, &ShareManager::sharesFetched, this, &ShareDialog::slotSharesFetched);
|
||||
connect(_manager, &ShareManager::linkShareCreated, this, &ShareDialog::slotAddLinkShareWidget);
|
||||
connect(_manager, &ShareManager::linkShareRequiresPassword, this, &ShareDialog::slotLinkShareRequiresPassword);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,6 +305,24 @@ void ShareDialog::slotCreateLinkShare()
|
||||
_manager->createLinkShare(_sharePath, QString(), QString());
|
||||
}
|
||||
|
||||
void ShareDialog::slotLinkShareRequiresPassword()
|
||||
{
|
||||
bool ok;
|
||||
QString password = QInputDialog::getText(this,
|
||||
tr("Password for share required"),
|
||||
tr("Please enter a password for your link share:"),
|
||||
QLineEdit::Normal,
|
||||
QString(),
|
||||
&ok);
|
||||
|
||||
if (!ok) {
|
||||
// The dialog was canceled so no need to do anything
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to create the link share again with the newly entered password
|
||||
_manager->createLinkShare(_sharePath, QString(), password);
|
||||
}
|
||||
|
||||
void ShareDialog::slotDeleteShare()
|
||||
{
|
||||
|
||||
@@ -64,6 +64,7 @@ private slots:
|
||||
void slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
|
||||
void slotDeleteShare();
|
||||
void slotCreateLinkShare();
|
||||
void slotLinkShareRequiresPassword();
|
||||
void slotAdjustScrollWidgetSize();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace Ui {
|
||||
}
|
||||
|
||||
class AbstractCredentials;
|
||||
class QuotaInfo;
|
||||
class SyncResult;
|
||||
class LinkShare;
|
||||
class Share;
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace Ui {
|
||||
}
|
||||
|
||||
class AbstractCredentials;
|
||||
class QuotaInfo;
|
||||
class SyncResult;
|
||||
class Share;
|
||||
class Sharee;
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<item>
|
||||
<widget class="QLineEdit" name="shareeLineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Share with users or groups ...</string>
|
||||
<string>Share with users or groups …</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>User name</string>
|
||||
<string>Username</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
|
||||
@@ -49,8 +49,10 @@
|
||||
#include <QLocalSocket>
|
||||
#include <QStringBuilder>
|
||||
#include <QMessageBox>
|
||||
#include <QInputDialog>
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QDesktopServices>
|
||||
|
||||
#include <QStandardPaths>
|
||||
|
||||
@@ -290,6 +292,12 @@ void SocketApi::slotReadSocket()
|
||||
int indexOfMethod = staticMetaObject.indexOfMethod(functionWithArguments);
|
||||
|
||||
QString argument = line.remove(0, command.length() + 1);
|
||||
if (indexOfMethod == -1) {
|
||||
// Fallback: Try upper-case command
|
||||
functionWithArguments = "command_" + command.toUpper() + "(QString,SocketListener*)";
|
||||
indexOfMethod = staticMetaObject.indexOfMethod(functionWithArguments);
|
||||
}
|
||||
|
||||
if (indexOfMethod != -1) {
|
||||
staticMetaObject.method(indexOfMethod).invoke(this, Q_ARG(QString, argument), Q_ARG(SocketListener *, listener));
|
||||
} else {
|
||||
@@ -477,6 +485,8 @@ public:
|
||||
this, &GetOrCreatePublicLinkShare::linkShareCreated);
|
||||
connect(&_shareManager, &ShareManager::serverError,
|
||||
this, &GetOrCreatePublicLinkShare::serverError);
|
||||
connect(&_shareManager, &ShareManager::linkShareRequiresPassword,
|
||||
this, &GetOrCreatePublicLinkShare::passwordRequired);
|
||||
}
|
||||
|
||||
void run()
|
||||
@@ -512,6 +522,24 @@ private slots:
|
||||
success(share->getLink().toString());
|
||||
}
|
||||
|
||||
void passwordRequired() {
|
||||
bool ok;
|
||||
QString password = QInputDialog::getText(nullptr,
|
||||
tr("Password for share required"),
|
||||
tr("Please enter a password for your link share:"),
|
||||
QLineEdit::Normal,
|
||||
QString(),
|
||||
&ok);
|
||||
|
||||
if (!ok) {
|
||||
// The dialog was canceled so no need to do anything
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to create the link share again with the newly entered password
|
||||
_shareManager.createLinkShare(_localFile, QString(), password);
|
||||
}
|
||||
|
||||
void serverError(int code, const QString &message)
|
||||
{
|
||||
qCWarning(lcPublicLink) << "Share fetch/create error" << code << message;
|
||||
@@ -565,6 +593,24 @@ void SocketApi::command_COPY_PUBLIC_LINK(const QString &localFile, SocketListene
|
||||
job->run();
|
||||
}
|
||||
|
||||
// Windows Shell / Explorer pinning fallbacks, see issue: https://github.com/nextcloud/desktop/issues/1599
|
||||
#ifdef Q_OS_WIN
|
||||
void SocketApi::command_COPYASPATH(const QString &localFile, SocketListener *)
|
||||
{
|
||||
QApplication::clipboard()->setText(localFile);
|
||||
}
|
||||
|
||||
void SocketApi::command_OPENNEWWINDOW(const QString &localFile, SocketListener *)
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(localFile));
|
||||
}
|
||||
|
||||
void SocketApi::command_OPEN(const QString &localFile, SocketListener *socketListener)
|
||||
{
|
||||
command_OPENNEWWINDOW(localFile, socketListener);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Fetches the private link url asynchronously and then calls the target slot
|
||||
void SocketApi::fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun)
|
||||
{
|
||||
|
||||
@@ -105,6 +105,13 @@ private:
|
||||
Q_INVOKABLE void command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *listener);
|
||||
Q_INVOKABLE void command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *listener);
|
||||
|
||||
// Windows Shell / Explorer pinning fallbacks, see issue: https://github.com/nextcloud/desktop/issues/1599
|
||||
#ifdef Q_OS_WIN
|
||||
Q_INVOKABLE void command_COPYASPATH(const QString &localFile, SocketListener *listener);
|
||||
Q_INVOKABLE void command_OPENNEWWINDOW(const QString &localFile, SocketListener *listener);
|
||||
Q_INVOKABLE void command_OPEN(const QString &localFile, SocketListener *listener);
|
||||
#endif
|
||||
|
||||
// Fetch the private link and call targetFun
|
||||
void fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "version.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "configfile.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
@@ -75,6 +76,11 @@ QUrlQuery Updater::getQueryParams()
|
||||
// to beta channel
|
||||
}
|
||||
|
||||
// updateSegment (see configfile.h)
|
||||
ConfigFile cfg;
|
||||
auto updateSegment = cfg.updateSegment();
|
||||
query.addQueryItem(QLatin1String("updatesegment"), QString::number(updateSegment));
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
156
src/gui/userinfo.cpp
Normal file
156
src/gui/userinfo.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
|
||||
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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.
|
||||
*/
|
||||
|
||||
#include "userinfo.h"
|
||||
#include "account.h"
|
||||
#include "accountstate.h"
|
||||
#include "networkjobs.h"
|
||||
#include "folderman.h"
|
||||
#include "creds/abstractcredentials.h"
|
||||
#include <theme.h>
|
||||
|
||||
#include <QTimer>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
namespace {
|
||||
static const int defaultIntervalT = 30 * 1000;
|
||||
static const int failIntervalT = 5 * 1000;
|
||||
}
|
||||
|
||||
UserInfo::UserInfo(AccountState *accountState, bool allowDisconnectedAccountState, bool fetchAvatarImage, QObject *parent)
|
||||
: QObject(parent)
|
||||
, _accountState(accountState)
|
||||
, _allowDisconnectedAccountState(allowDisconnectedAccountState)
|
||||
, _fetchAvatarImage(fetchAvatarImage)
|
||||
, _lastQuotaTotalBytes(0)
|
||||
, _lastQuotaUsedBytes(0)
|
||||
, _active(false)
|
||||
{
|
||||
connect(accountState, &AccountState::stateChanged,
|
||||
this, &UserInfo::slotAccountStateChanged);
|
||||
connect(&_jobRestartTimer, &QTimer::timeout, this, &UserInfo::slotFetchInfo);
|
||||
_jobRestartTimer.setSingleShot(true);
|
||||
}
|
||||
|
||||
void UserInfo::setActive(bool active)
|
||||
{
|
||||
_active = active;
|
||||
slotAccountStateChanged();
|
||||
}
|
||||
|
||||
|
||||
void UserInfo::slotAccountStateChanged()
|
||||
{
|
||||
if (canGetInfo()) {
|
||||
auto elapsed = _lastInfoReceived.msecsTo(QDateTime::currentDateTime());
|
||||
if (_lastInfoReceived.isNull() || elapsed >= defaultIntervalT) {
|
||||
slotFetchInfo();
|
||||
} else {
|
||||
_jobRestartTimer.start(defaultIntervalT - elapsed);
|
||||
}
|
||||
} else {
|
||||
_jobRestartTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void UserInfo::slotRequestFailed()
|
||||
{
|
||||
_lastQuotaTotalBytes = 0;
|
||||
_lastQuotaUsedBytes = 0;
|
||||
_jobRestartTimer.start(failIntervalT);
|
||||
}
|
||||
|
||||
bool UserInfo::canGetInfo() const
|
||||
{
|
||||
if (!_accountState || !_active) {
|
||||
return false;
|
||||
}
|
||||
AccountPtr account = _accountState->account();
|
||||
return (_accountState->isConnected() || _allowDisconnectedAccountState)
|
||||
&& account->credentials()
|
||||
&& account->credentials()->ready();
|
||||
}
|
||||
|
||||
void UserInfo::slotFetchInfo()
|
||||
{
|
||||
if (!canGetInfo()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_job) {
|
||||
// The previous job was not finished? Then we cancel it!
|
||||
_job->deleteLater();
|
||||
}
|
||||
|
||||
AccountPtr account = _accountState->account();
|
||||
_job = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/user"), this);
|
||||
_job->setTimeout(20 * 1000);
|
||||
connect(_job.data(), &JsonApiJob::jsonReceived, this, &UserInfo::slotUpdateLastInfo);
|
||||
connect(_job.data(), &AbstractNetworkJob::networkError, this, &UserInfo::slotRequestFailed);
|
||||
_job->start();
|
||||
}
|
||||
|
||||
void UserInfo::slotUpdateLastInfo(const QJsonDocument &json)
|
||||
{
|
||||
auto objData = json.object().value("ocs").toObject().value("data").toObject();
|
||||
|
||||
AccountPtr account = _accountState->account();
|
||||
|
||||
// User Info
|
||||
QString user = objData.value("id").toString();
|
||||
if (!user.isEmpty()) {
|
||||
account->setDavUser(user);
|
||||
}
|
||||
QString displayName = objData.value("display-name").toString();
|
||||
if (!displayName.isEmpty()) {
|
||||
account->setDavDisplayName(displayName);
|
||||
}
|
||||
|
||||
// Quota
|
||||
auto objQuota = objData.value("quota").toObject();
|
||||
qint64 used = objQuota.value("used").toDouble();
|
||||
qint64 total = objQuota.value("total").toDouble();
|
||||
|
||||
if(_lastInfoReceived.isNull() || _lastQuotaUsedBytes != used || _lastQuotaTotalBytes != total) {
|
||||
_lastQuotaUsedBytes = used;
|
||||
_lastQuotaTotalBytes = total;
|
||||
emit quotaUpdated(_lastQuotaTotalBytes, _lastQuotaUsedBytes);
|
||||
}
|
||||
|
||||
_jobRestartTimer.start(defaultIntervalT);
|
||||
_lastInfoReceived = QDateTime::currentDateTime();
|
||||
|
||||
// Avatar Image
|
||||
if(_fetchAvatarImage) {
|
||||
AvatarJob *job = new AvatarJob(account, account->davUser(), 128, this);
|
||||
job->setTimeout(20 * 1000);
|
||||
QObject::connect(job, &AvatarJob::avatarPixmap, this, &UserInfo::slotAvatarImage);
|
||||
job->start();
|
||||
}
|
||||
else
|
||||
emit fetchedLastInfo(this);
|
||||
}
|
||||
|
||||
void UserInfo::slotAvatarImage(const QImage &img)
|
||||
{
|
||||
_accountState->account()->setAvatar(img);
|
||||
|
||||
emit fetchedLastInfo(this);
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
|
||||
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -12,8 +13,8 @@
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#ifndef QUOTAINFO_H
|
||||
#define QUOTAINFO_H
|
||||
#ifndef USERINFO_H
|
||||
#define USERINFO_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
@@ -23,31 +24,51 @@
|
||||
|
||||
namespace OCC {
|
||||
class AccountState;
|
||||
class PropfindJob;
|
||||
class JsonApiJob;
|
||||
|
||||
/**
|
||||
* @brief handles getting the quota to display in the UI
|
||||
* @brief handles getting the user info and quota to display in the UI
|
||||
*
|
||||
* It is typically owned by the AccountSetting page.
|
||||
*
|
||||
* The quota is requested if these 3 conditions are met:
|
||||
* The user info and quota is requested if these 3 conditions are met:
|
||||
* - This object is active via setActive() (typically if the settings page is visible.)
|
||||
* - The account is connected.
|
||||
* - Every 30 seconds (defaultIntervalT) or 5 seconds in case of failure (failIntervalT)
|
||||
*
|
||||
* We only request the quota when the UI is visible otherwise this might slow down the server with
|
||||
* We only request the info when the UI is visible otherwise this might slow down the server with
|
||||
* too many requests. But we still need to do it every 30 seconds otherwise user complains that the
|
||||
* quota is not updated fast enough when changed on the server.
|
||||
*
|
||||
* If the quota job is not finished within 30 seconds, it is cancelled and another one is started
|
||||
* If the fetch job is not finished within 30 seconds, it is cancelled and another one is started
|
||||
*
|
||||
* Constructor notes:
|
||||
* - allowDisconnectedAccountState: set to true if you want to ignore AccountState's isConnected() state,
|
||||
* this is used by ConnectionValidator (prior having a valid AccountState).
|
||||
* - fetchAvatarImage: set to false if you don't want to fetch the avatar image
|
||||
*
|
||||
* @ingroup gui
|
||||
*/
|
||||
class QuotaInfo : public QObject
|
||||
*
|
||||
* Here follows the state machine
|
||||
|
||||
\code{.unparsed}
|
||||
*---> slotFetchInfo
|
||||
JsonApiJob (ocs/v1.php/cloud/user)
|
||||
|
|
||||
+-> slotUpdateLastInfo
|
||||
AvatarJob (if _fetchAvatarImage is true)
|
||||
|
|
||||
+-> slotAvatarImage -->
|
||||
+-----------------------------------+
|
||||
|
|
||||
+-> Client Side Encryption Checks --+ --reportResult()
|
||||
\endcode
|
||||
*/
|
||||
class UserInfo : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit QuotaInfo(OCC::AccountState *accountState, QObject *parent = nullptr);
|
||||
explicit UserInfo(OCC::AccountState *accountState, bool allowDisconnectedAccountState, bool fetchAvatarImage, QObject *parent = nullptr);
|
||||
|
||||
qint64 lastQuotaTotalBytes() const { return _lastQuotaTotalBytes; }
|
||||
qint64 lastQuotaUsedBytes() const { return _lastQuotaUsedBytes; }
|
||||
@@ -60,32 +81,34 @@ public:
|
||||
void setActive(bool active);
|
||||
|
||||
public Q_SLOTS:
|
||||
void slotCheckQuota();
|
||||
void slotFetchInfo();
|
||||
|
||||
private Q_SLOTS:
|
||||
void slotUpdateLastQuota(const QVariantMap &);
|
||||
void slotUpdateLastInfo(const QJsonDocument &json);
|
||||
void slotAccountStateChanged();
|
||||
void slotRequestFailed();
|
||||
void slotAvatarImage(const QImage &img);
|
||||
|
||||
Q_SIGNALS:
|
||||
void quotaUpdated(qint64 total, qint64 used);
|
||||
void fetchedLastInfo(UserInfo *userInfo);
|
||||
|
||||
private:
|
||||
bool canGetQuota() const;
|
||||
|
||||
/// Returns the folder that quota shall be retrieved for
|
||||
QString quotaBaseFolder() const;
|
||||
bool canGetInfo() const;
|
||||
|
||||
QPointer<AccountState> _accountState;
|
||||
bool _allowDisconnectedAccountState;
|
||||
bool _fetchAvatarImage;
|
||||
|
||||
qint64 _lastQuotaTotalBytes;
|
||||
qint64 _lastQuotaUsedBytes;
|
||||
QTimer _jobRestartTimer;
|
||||
QDateTime _lastQuotaRecieved; // the time at which the quota was received last
|
||||
QDateTime _lastInfoReceived; // the time at which the user info and quota was received last
|
||||
bool _active; // if we should check at regular interval (when the UI is visible)
|
||||
QPointer<PropfindJob> _job; // the currently running job
|
||||
QPointer<JsonApiJob> _job; // the currently running job
|
||||
};
|
||||
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
#endif //QUOTAINFO_H
|
||||
#endif //USERINFO_H
|
||||
@@ -178,11 +178,11 @@ void OwncloudSetupPage::slotUrlChanged(const QString &url)
|
||||
|
||||
if (!url.startsWith(QLatin1String("https://"))) {
|
||||
_ui.urlLabel->setPixmap(QPixmap(Theme::hidpiFileName(":/client/resources/lock-http.png")));
|
||||
_ui.urlLabel->setToolTip(tr("This url is NOT secure as it is not encrypted.\n"
|
||||
_ui.urlLabel->setToolTip(tr("This URL is NOT secure as it is not encrypted.\n"
|
||||
"It is not advisable to use it."));
|
||||
} else {
|
||||
_ui.urlLabel->setPixmap(QPixmap(Theme::hidpiFileName(":/client/resources/lock-https.png")));
|
||||
_ui.urlLabel->setToolTip(tr("This url is secure. You can use it."));
|
||||
_ui.urlLabel->setToolTip(tr("This URL is secure. You can use it."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,8 +95,15 @@ QNetworkReply *AccessManager::createRequest(QNetworkAccessManager::Operation op,
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 4)
|
||||
// only enable HTTP2 with Qt 5.9.4 because old Qt have too many bugs (e.g. QTBUG-64359 is fixed in >= Qt 5.9.4)
|
||||
|
||||
/* Disable http2 for now due to Qt bug but allow enabling it via env var, see: https://github.com/owncloud/client/pull/7620
|
||||
* and: https://github.com/nextcloud/desktop/pull/1806
|
||||
* Issue: https://github.com/nextcloud/desktop/issues/1503
|
||||
*/
|
||||
if (newRequest.url().scheme() == "https") { // Not for "http": QTBUG-61397
|
||||
newRequest.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
|
||||
static const bool http2EnabledEnv = qEnvironmentVariableIntValue("OWNCLOUD_HTTP2_ENABLED") == 1;
|
||||
|
||||
newRequest.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, http2EnabledEnv);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ namespace OCC {
|
||||
class AbstractCredentials;
|
||||
class Account;
|
||||
typedef QSharedPointer<Account> AccountPtr;
|
||||
class QuotaInfo;
|
||||
class AccessManager;
|
||||
class SimpleNetworkJob;
|
||||
|
||||
@@ -306,7 +305,6 @@ private:
|
||||
Capabilities _capabilities;
|
||||
QString _serverVersion;
|
||||
QScopedPointer<AbstractSslErrorHandler> _sslErrorHandler;
|
||||
QuotaInfo *_quotaInfo;
|
||||
QSharedPointer<QNetworkAccessManager> _am;
|
||||
QScopedPointer<AbstractCredentials> _credentials;
|
||||
bool _http2Supported = false;
|
||||
|
||||
@@ -64,6 +64,7 @@ static const char optionalServerNotificationsC[] = "optionalServerNotifications"
|
||||
static const char showInExplorerNavigationPaneC[] = "showInExplorerNavigationPane";
|
||||
static const char skipUpdateCheckC[] = "skipUpdateCheck";
|
||||
static const char updateCheckIntervalC[] = "updateCheckInterval";
|
||||
static const char updateSegmentC[] = "updateSegment";
|
||||
static const char geometryC[] = "geometry";
|
||||
static const char timeoutC[] = "timeout";
|
||||
static const char chunkSizeC[] = "chunkSize";
|
||||
@@ -576,6 +577,21 @@ void ConfigFile::setSkipUpdateCheck(bool skip, const QString &connection)
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
int ConfigFile::updateSegment() const
|
||||
{
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
int segment = settings.value(QLatin1String(updateSegmentC), -1).toInt();
|
||||
|
||||
// Invalid? (Unset at the very first launch)
|
||||
if(segment < 0 || segment > 99) {
|
||||
// Save valid segment value, normally has to be done only once.
|
||||
segment = qrand() % 99;
|
||||
settings.setValue(QLatin1String(updateSegmentC), segment);
|
||||
}
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
int ConfigFile::maxLogLines() const
|
||||
{
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
|
||||
@@ -149,6 +149,11 @@ public:
|
||||
bool skipUpdateCheck(const QString &connection = QString()) const;
|
||||
void setSkipUpdateCheck(bool, const QString &);
|
||||
|
||||
/** Query-parameter 'updatesegment' for the update check, value between 0 and 99.
|
||||
Used to throttle down desktop release rollout in order to keep the update servers alive at peak times.
|
||||
See: https://github.com/nextcloud/client_updater_server/pull/36 */
|
||||
int updateSegment() const;
|
||||
|
||||
void saveGeometryHeader(QHeaderView *header);
|
||||
void restoreGeometryHeader(QHeaderView *header);
|
||||
|
||||
|
||||
@@ -384,7 +384,14 @@ void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(QString file, con
|
||||
propertyMapToFileStat(map, file_stat.get());
|
||||
if (file_stat->type == ItemTypeDirectory)
|
||||
file_stat->size = 0;
|
||||
if (file_stat->type == ItemTypeSkip
|
||||
if (file_stat->remotePerm.hasPermission(RemotePermissions::IsShared) && file_stat->etag.isEmpty()) {
|
||||
/* Handle broken shared file error gracefully instead of stopping sync in the desktop client.
|
||||
DO not set _error */
|
||||
qCWarning(lcDiscovery)
|
||||
<< "Missing path to a share :" << file << file_stat->path << file_stat->type << file_stat->size
|
||||
<< file_stat->modtime << file_stat->remotePerm.toString()
|
||||
<< file_stat->etag << file_stat->file_id;
|
||||
} else if (file_stat->type == ItemTypeSkip
|
||||
|| file_stat->size == -1
|
||||
|| file_stat->remotePerm.isNull()
|
||||
|| file_stat->etag.isEmpty()
|
||||
|
||||
@@ -134,10 +134,6 @@ void GETFileJob::start()
|
||||
_bandwidthManager->registerDownloadJob(this);
|
||||
}
|
||||
|
||||
if (reply()->error() != QNetworkReply::NoError) {
|
||||
qCWarning(lcGetJob) << " Network error: " << errorString();
|
||||
}
|
||||
|
||||
connect(this, &AbstractNetworkJob::networkActivity, account().data(), &Account::propagatorNetworkActivity);
|
||||
|
||||
AbstractNetworkJob::start();
|
||||
@@ -168,7 +164,6 @@ void GETFileJob::slotMetaDataChanged()
|
||||
// If the status code isn't 2xx, don't write the reply body to the file.
|
||||
// For any error: handle it when the job is finished, not here.
|
||||
if (httpStatus / 100 != 2) {
|
||||
_device->close();
|
||||
return;
|
||||
}
|
||||
if (reply()->error() != QNetworkReply::NoError) {
|
||||
@@ -295,15 +290,13 @@ void GETFileJob::slotReadyRead()
|
||||
return;
|
||||
}
|
||||
|
||||
if (_device->isOpen() && _saveBodyToFile) {
|
||||
qint64 w = _device->write(buffer.constData(), r);
|
||||
if (w != r) {
|
||||
_errorString = _device->errorString();
|
||||
_errorStatus = SyncFileItem::NormalError;
|
||||
qCWarning(lcGetJob) << "Error while writing to file" << w << r << _errorString;
|
||||
reply()->abort();
|
||||
return;
|
||||
}
|
||||
qint64 w = _device->write(buffer.constData(), r);
|
||||
if (w != r) {
|
||||
_errorString = _device->errorString();
|
||||
_errorStatus = SyncFileItem::NormalError;
|
||||
qCWarning(lcGetJob) << "Error while writing to file" << w << r << _errorString;
|
||||
reply()->abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ list(APPEND FolderMan_SRC ../src/gui/syncrunfilelog.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/lockwatcher.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/guiutility.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/navigationpanehelper.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/userinfo.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/connectionvalidator.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/clientproxy.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/accountstate.cpp )
|
||||
@@ -75,6 +76,7 @@ nextcloud_add_test(FolderMan "${FolderMan_SRC}")
|
||||
SET(RemoteWipe_SRC ../src/gui/remotewipe.cpp)
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/clientproxy.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/guiutility.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/userinfo.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/connectionvalidator.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/accountstate.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/socketapi.cpp )
|
||||
|
||||
@@ -716,22 +716,26 @@ class FakeErrorReply : public QNetworkReply
|
||||
public:
|
||||
FakeErrorReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request,
|
||||
QObject *parent, int httpErrorCode)
|
||||
: QNetworkReply{parent}, _httpErrorCode(httpErrorCode) {
|
||||
: QNetworkReply{parent}, _httpErrorCode(httpErrorCode){
|
||||
setRequest(request);
|
||||
setUrl(request.url());
|
||||
setOperation(op);
|
||||
open(QIODevice::ReadOnly);
|
||||
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpErrorCode);
|
||||
setError(InternalServerError, "Internal Server Fake Error");
|
||||
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
Q_INVOKABLE void respond() {
|
||||
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, _httpErrorCode);
|
||||
setError(InternalServerError, "Internal Server Fake Error");
|
||||
emit metaDataChanged();
|
||||
emit readyRead();
|
||||
// finishing can come strictly after readyRead was called
|
||||
QTimer::singleShot(5, this, &FakeErrorReply::slotSetFinished);
|
||||
}
|
||||
|
||||
// make public to give tests easy interface
|
||||
using QNetworkReply::setError;
|
||||
using QNetworkReply::setAttribute;
|
||||
|
||||
public slots:
|
||||
void slotSetFinished() {
|
||||
|
||||
248
test/testdownload.cpp
Normal file
248
test/testdownload.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* This software is in the public domain, furnished "as is", without technical
|
||||
* support, and with no warranty, express or implied, as to its usefulness for
|
||||
* any purpose.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QtTest>
|
||||
#include "syncenginetestutils.h"
|
||||
#include <syncengine.h>
|
||||
#include <owncloudpropagator.h>
|
||||
|
||||
using namespace OCC;
|
||||
|
||||
static constexpr qint64 stopAfter = 3'123'668;
|
||||
|
||||
/* A FakeGetReply that sends max 'fakeSize' bytes, but whose ContentLength has the corect size */
|
||||
class BrokenFakeGetReply : public FakeGetReply
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using FakeGetReply::FakeGetReply;
|
||||
int fakeSize = stopAfter;
|
||||
|
||||
qint64 bytesAvailable() const override
|
||||
{
|
||||
if (aborted)
|
||||
return 0;
|
||||
return std::min(size, fakeSize) + QIODevice::bytesAvailable();
|
||||
}
|
||||
|
||||
qint64 readData(char *data, qint64 maxlen) override
|
||||
{
|
||||
qint64 len = std::min(qint64{ fakeSize }, maxlen);
|
||||
std::fill_n(data, len, payload);
|
||||
size -= len;
|
||||
fakeSize -= len;
|
||||
return len;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
SyncFileItemPtr getItem(const QSignalSpy &spy, const QString &path)
|
||||
{
|
||||
for (const QList<QVariant> &args : spy) {
|
||||
auto item = args[0].value<SyncFileItemPtr>();
|
||||
if (item->destination() == path)
|
||||
return item;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
class TestDownload : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
|
||||
void testResume()
|
||||
{
|
||||
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
|
||||
fakeFolder.syncEngine().setIgnoreHiddenFiles(true);
|
||||
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
||||
auto size = 30 * 1000 * 1000;
|
||||
fakeFolder.remoteModifier().insert("A/a0", size);
|
||||
|
||||
// First, download only the first 3 MB of the file
|
||||
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
|
||||
if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/a0")) {
|
||||
return new BrokenFakeGetReply(fakeFolder.remoteModifier(), op, request, this);
|
||||
}
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
QVERIFY(!fakeFolder.syncOnce()); // The sync must fail because not all the file was downloaded
|
||||
QCOMPARE(getItem(completeSpy, "A/a0")->_status, SyncFileItem::SoftError);
|
||||
QCOMPARE(getItem(completeSpy, "A/a0")->_errorString, QString("The file could not be downloaded completely."));
|
||||
QVERIFY(fakeFolder.syncEngine().isAnotherSyncNeeded());
|
||||
|
||||
// Now, we need to restart, this time, it should resume.
|
||||
QByteArray ranges;
|
||||
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
|
||||
if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/a0")) {
|
||||
ranges = request.rawHeader("Range");
|
||||
}
|
||||
return nullptr;
|
||||
});
|
||||
QVERIFY(fakeFolder.syncOnce()); // now this succeeds
|
||||
QCOMPARE(ranges, QByteArray("bytes=" + QByteArray::number(stopAfter) + "-"));
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
}
|
||||
|
||||
void testErrorMessage () {
|
||||
// This test's main goal is to test that the error string from the server is shown in the UI
|
||||
|
||||
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
|
||||
fakeFolder.syncEngine().setIgnoreHiddenFiles(true);
|
||||
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
||||
auto size = 3'500'000;
|
||||
fakeFolder.remoteModifier().insert("A/broken", size);
|
||||
|
||||
QByteArray serverMessage = "The file was not downloaded because the tests wants so!";
|
||||
|
||||
// First, download only the first 3 MB of the file
|
||||
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
|
||||
if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/broken")) {
|
||||
return new FakeErrorReply(op, request, this, 400,
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||
"<d:error xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\">\n"
|
||||
"<s:exception>Sabre\\DAV\\Exception\\Forbidden</s:exception>\n"
|
||||
"<s:message>"+serverMessage+"</s:message>\n"
|
||||
"</d:error>");
|
||||
}
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
bool timedOut = false;
|
||||
QTimer::singleShot(10000, &fakeFolder.syncEngine(), [&]() { timedOut = true; fakeFolder.syncEngine().abort(); });
|
||||
QVERIFY(!fakeFolder.syncOnce()); // Fail because A/broken
|
||||
QVERIFY(!timedOut);
|
||||
QCOMPARE(getItem(completeSpy, "A/broken")->_status, SyncFileItem::NormalError);
|
||||
QVERIFY(getItem(completeSpy, "A/broken")->_errorString.contains(serverMessage));
|
||||
}
|
||||
|
||||
void serverMaintenence() {
|
||||
// Server in maintenance must abort the sync.
|
||||
|
||||
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
|
||||
fakeFolder.remoteModifier().insert("A/broken");
|
||||
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
|
||||
if (op == QNetworkAccessManager::GetOperation) {
|
||||
return new FakeErrorReply(op, request, this, 503,
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||
"<d:error xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\">\n"
|
||||
"<s:exception>Sabre\\DAV\\Exception\\ServiceUnavailable</s:exception>\n"
|
||||
"<s:message>System in maintenance mode.</s:message>\n"
|
||||
"</d:error>");
|
||||
}
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted);
|
||||
QVERIFY(!fakeFolder.syncOnce()); // Fail because A/broken
|
||||
// FatalError means the sync was aborted, which is what we want
|
||||
QCOMPARE(getItem(completeSpy, "A/broken")->_status, SyncFileItem::FatalError);
|
||||
QVERIFY(getItem(completeSpy, "A/broken")->_errorString.contains("System in maintenance mode"));
|
||||
}
|
||||
|
||||
void testMoveFailsInAConflict() {
|
||||
#ifdef Q_OS_WIN
|
||||
QSKIP("Not run on windows because permission on directory does not do what is expected");
|
||||
#endif
|
||||
// Test for https://github.com/owncloud/client/issues/7015
|
||||
// We want to test the case in which the renaming of the original to the conflict file succeeds,
|
||||
// but renaming the temporary file fails.
|
||||
// This tests uses the fact that a "touchedFile" notification will be sent at the right moment.
|
||||
// Note that there will be first a notification on the file and the conflict file before.
|
||||
|
||||
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
|
||||
fakeFolder.syncEngine().setIgnoreHiddenFiles(true);
|
||||
fakeFolder.remoteModifier().setContents("A/a1", 'A');
|
||||
fakeFolder.localModifier().setContents("A/a1", 'B');
|
||||
|
||||
bool propConnected = false;
|
||||
QString conflictFile;
|
||||
auto transProgress = connect(&fakeFolder.syncEngine(), &SyncEngine::transmissionProgress,
|
||||
[&](const ProgressInfo &pi) {
|
||||
auto propagator = fakeFolder.syncEngine().getPropagator();
|
||||
if (pi.status() != ProgressInfo::Propagation || propConnected || !propagator)
|
||||
return;
|
||||
propConnected = true;
|
||||
connect(propagator.data(), &OwncloudPropagator::touchedFile, [&](const QString &s) {
|
||||
if (s.contains("conflicted copy")) {
|
||||
QCOMPARE(conflictFile, QString());
|
||||
conflictFile = s;
|
||||
return;
|
||||
}
|
||||
if (!conflictFile.isEmpty()) {
|
||||
// Check that the temporary file is still there
|
||||
QCOMPARE(QDir(fakeFolder.localPath() + "A/").entryList({"*.~*"}, QDir::Files | QDir::Hidden).count(), 1);
|
||||
// Set the permission to read only on the folder, so the rename of the temporary file will fail
|
||||
QFile(fakeFolder.localPath() + "A/").setPermissions(QFile::Permissions(0x5555));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
QVERIFY(!fakeFolder.syncOnce()); // The sync must fail because the rename failed
|
||||
QVERIFY(!conflictFile.isEmpty());
|
||||
|
||||
// restore permissions
|
||||
QFile(fakeFolder.localPath() + "A/").setPermissions(QFile::Permissions(0x7777));
|
||||
|
||||
QObject::disconnect(transProgress);
|
||||
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &, QIODevice *) -> QNetworkReply * {
|
||||
if (op == QNetworkAccessManager::GetOperation)
|
||||
QTest::qFail("There shouldn't be any download", __FILE__, __LINE__);
|
||||
return nullptr;
|
||||
});
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
|
||||
// The a1 file is still tere and have the right content
|
||||
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
|
||||
QCOMPARE(fakeFolder.currentRemoteState().find("A/a1")->contentChar, 'A');
|
||||
|
||||
QVERIFY(QFile::remove(conflictFile)); // So the comparison succeeds;
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
}
|
||||
|
||||
void testHttp2Resend() {
|
||||
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
|
||||
fakeFolder.remoteModifier().insert("A/resendme", 300);
|
||||
|
||||
QByteArray serverMessage = "Needs to be resend on a new connection!";
|
||||
int resendActual = 0;
|
||||
int resendExpected = 2;
|
||||
|
||||
// First, download only the first 3 MB of the file
|
||||
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
|
||||
if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/resendme") && resendActual < resendExpected) {
|
||||
auto errorReply = new FakeErrorReply(op, request, this, 400, "ignore this body");
|
||||
errorReply->setError(QNetworkReply::ContentReSendError, serverMessage);
|
||||
errorReply->setAttribute(QNetworkRequest::HTTP2WasUsedAttribute, true);
|
||||
errorReply->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, QVariant());
|
||||
resendActual += 1;
|
||||
return errorReply;
|
||||
}
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
QVERIFY(fakeFolder.syncOnce());
|
||||
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
||||
QCOMPARE(resendActual, 2);
|
||||
|
||||
fakeFolder.remoteModifier().appendByte("A/resendme");
|
||||
resendActual = 0;
|
||||
resendExpected = 10;
|
||||
|
||||
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
|
||||
QVERIFY(!fakeFolder.syncOnce());
|
||||
QCOMPARE(resendActual, 4); // the 4th fails because it only resends 3 times
|
||||
QCOMPARE(getItem(completeSpy, "A/resendme")->_status, SyncFileItem::NormalError);
|
||||
QVERIFY(getItem(completeSpy, "A/resendme")->_errorString.contains(serverMessage));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TestDownload)
|
||||
#include "testdownload.moc"
|
||||
4227
translations/client_af.ts
Normal file
4227
translations/client_af.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user