Comparar commits

..

1 Commits

Autor SHA1 Mensagem Data
Piotr M 79011916d7 prototype of bundled requests in the client 2016-10-25 22:38:27 +02:00
96 arquivos alterados com 13084 adições e 15136 exclusões
+1 -1
Ver Arquivo
@@ -27,7 +27,7 @@ https://github.com/owncloud/client.
## Building the source code
[Building the Client](http://doc.owncloud.org/desktop/2.2/building.html)
[Building the Client](http://doc.owncloud.org/desktop/2.0/building.html)
in the ownCloud Desktop Client manual.
## Maintainers and Contributors
+2 -2
Ver Arquivo
@@ -9,7 +9,6 @@ StrCpy $PageReinstall_NEW_Field_3 "No instal·lar"
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Ja instal·lat"
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_SUBTITLE "Trieu la manera com voleu instal·lar ${APPLICATION_NAME}."
StrCpy $PageReinstall_OLD_Field_1 "Una versió més recent de ${APPLICATION_NAME} ja està instal.lada!! No es recomana instal.lar una versió més antiga. Si realment voleu instal.lar una versió més antiga, és millor primer desinstal.lar la versió actual. Seleccioni l'operació que desitjeu realitzar i feu clic a Següent per a continuar."
StrCpy $PageReinstall_SAME_Field_1 "${APPLICATION_NAME} ${VERSION} ja està instal·lat.$\n$\nSeleccioneu la operació que voleu fer i feu clic a Següent per continuar."
StrCpy $PageReinstall_SAME_Field_2 "Afegir/Reinstal.lar components"
StrCpy $PageReinstall_SAME_Field_3 "Desinstal.lar ${APPLICATION_NAME}"
StrCpy $UNINSTALLER_APPDATA_TITLE "Desinstal.lar ${APPLICATION_NAME}"
@@ -38,6 +37,7 @@ StrCpy $UAC_ERROR_ELEVATE "No es pot elevar, error:"
StrCpy $UAC_INSTALLER_REQUIRE_ADMIN "Aquest instal·lador requereix accés d'administrador, intenteu-ho de nou"
StrCpy $INIT_INSTALLER_RUNNING "L'instal·lador ja s'està executant."
StrCpy $UAC_UNINSTALLER_REQUIRE_ADMIN "Aquest desinstal·lador requereix accés d'administrador, intenteu-ho de nou."
StrCpy $UAC_ERROR_LOGON_SERVICE "El servei de inici de sessió no s'està executant, s'està abortant!"
StrCpy $INIT_UNINSTALLER_RUNNING "El desinstal·lador ja s'està executant."
StrCpy $SectionGroup_Shortcuts "Dreceres"
StrCpy $PageReinstall_SAME_Field_1 "${APPLICATION_NAME} ${VERSION} is already installed.$\r$\nSelect the operation you want to perform and click Next to continue."
StrCpy $UAC_ERROR_LOGON_SERVICE "Logon service is not running, aborting!"
+1 -1
Ver Arquivo
@@ -32,7 +32,7 @@ find_library(CMOCKA_LIBRARY
NAMES
cmocka
PATHS
${CMOCKA_ROOT_DIR}/lib
${CMOCKA_ROOT_DIR}/include
)
if (CMOCKA_LIBRARY)
+1 -15
Ver Arquivo
@@ -65,21 +65,7 @@ static c_rbnode_t *_csync_check_ignored(c_rbtree_t *tree, const char *path, int
}
}
/**
* The main function in the reconcile pass.
*
* It's called for each entry in the local and remote rbtrees by
* csync_reconcile()
*
* Before the reconcile phase the trees already know about changes
* relative to the sync journal. This function's job is to spot conflicts
* between local and remote changes and adjust the nodes accordingly.
*
* See doc/dev/sync-algorithm.md for an overview.
*
*
* Older detail comment:
*
/*
* We merge replicas at the file level. The merged replica contains the
* superset of files that are on the local machine and server copies of
* the replica. In the case where the same file is in both the local
+1 -13
Ver Arquivo
@@ -158,19 +158,7 @@ static bool _csync_mtime_equal(time_t a, time_t b)
return false;
}
/**
* The main function of the discovery/update pass.
*
* It's called (indirectly) by csync_update(), once for each entity in the
* local filesystem and once for each entity in the server data.
*
* It has two main jobs:
* - figure out whether anything happened compared to the sync journal
* and set (primarily) the instruction flag accordingly
* - build the ctx->local.tree / ctx->remote.tree
*
* See doc/dev/sync-algorithm.md for an overview.
*/
static int _csync_detect_update(CSYNC *ctx, const char *file,
const csync_vio_file_stat_t *fs, const int type) {
uint64_t h = 0;
-31
Ver Arquivo
@@ -232,37 +232,6 @@ assertLocalAndRemoteDir( '', 0);
system("sqlite3 " . localDir().'.csync_journal.db .dump');
#######################################################################
printInfo( "multiple restores of a file create different conflict files" );
system("sleep 1"); #make sure changes have different mtime
system("echo 'modified_1' > ". localDir() . "readonlyDirectory_PERM_M_/canotBeModified_PERM_DVN_.data");
#do the sync
csync();
assertCsyncJournalOk(localDir());
system("sleep 1"); #make sure changes have different mtime
system("echo 'modified_2' > ". localDir() . "readonlyDirectory_PERM_M_/canotBeModified_PERM_DVN_.data");
#do the sync
csync();
assertCsyncJournalOk(localDir());
# there should be two conflict files
# TODO check that the conflict file has the right content
my @conflicts = glob(localDir().'readonlyDirectory_PERM_M_/canotBeModified_PERM_DVN__conflict-*.data' );
assert( scalar @conflicts == 2 );
# remove the conflicts for the next assertLocalAndRemoteDir
system("rm " . localDir().'readonlyDirectory_PERM_M_/canotBeModified_PERM_DVN__conflict-*.data' );
### Both side should still be the same
assertLocalAndRemoteDir( '', 0);
cleanup();
+2 -16
Ver Arquivo
@@ -66,18 +66,11 @@ recipes.
To set up your build environment for development using HomeBrew_:
1. Install Xcode
2. Install Xcode command line tools::
xcode-select --install
3. Install homebrew::
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
4. Add the ownCloud repository using the following command::
1. Add the ownCloud repository using the following command::
brew tap owncloud/owncloud
5. Install any missing dependencies::
2. Install any missing dependencies::
brew install $(brew deps owncloud-client)
@@ -87,9 +80,6 @@ To set up your build environment for development using HomeBrew_:
Where ``x.z`` is the current version of Qt 5 that brew has installed
on your machine.
4. Install qtkeychain from here: git clone https://github.com/frankosterfeld/qtkeychain.git
make sure you make the same install prefix as later while building the client e.g. -
``DCMAKE_INSTALL_PREFIX=/Path/to/client-install``
5. For compilation of the client, follow the :ref:`generic-build-instructions`.
@@ -244,10 +234,6 @@ To build the most up-to-date version of the client:
.. note:: On Mac OS X, you need to specify ``-DCMAKE_INSTALL_PREFIX=target``,
where ``target`` is a private location, i.e. in parallel to your build
dir by specifying ``../install``.
..note:: qtkeychain must be compiled with the same prefix e.g CMAKE_INSTALL_PREFIX=/Users/path/to/client/install/
.. note:: Example:: cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 -DCMAKE_INSTALL_PREFIX=/Users/path/to/client/install/ -D_OPENSSL_LIBDIR=/usr/local/opt/openssl/lib/ -D_OPENSSL_INCLUDEDIR=/usr/local/opt/openssl/include/ -DOPENSSL_INCLUDE_DIR=/usr/local/opt/openssl/include/ -DNO_SHIBBOLETH=1
4. Call ``make``.
-84
Ver Arquivo
@@ -1,84 +0,0 @@
Sync Algorithm
==============
Overview
--------
This is a technical description of the synchronization (sync) algorithm used by the ownCloud client.
The sync algorithm is the thing that looks at the local and remote file system trees and the sync journal and decides which steps need to be taken to bring the two trees into synchronization. It's different from the propagator, whose job it is to actually execute these steps.
Definitions
-----------
- local tree: The files and directories on the local file system that shall be kept in sync with the remote tree.
- remote tree: The files and directories on the ownCloud server that shall be kept in sync with the local tree.
- sync journal (journal): A snapshot of file and directory metadata that the sync algorithm uses as a baseline to detect local or remote changes. Typically stored in a database.
- file and directory metadata:
- mtimes
- sizes
- inodes (journal and local only): Representation of filesystem object. Useful for rename detection.
- etags (journal and remote only): The server assigns a new etag when a file or directory changes.
- checksums (journal and remote only): Checksum algorithm applied to a file's contents.
- permissions (journal and remote only)
Phases
------
### Discovery (aka Update)
The discovery phase collects file and directory metadata from the local and remote trees, detecting differences between each tree and the journal.
Afterwards, we have two trees that tell us what happened relative to the journal. But there may still be conflicts if something happened to an entity both locally and on the remote.
- Input: file system, server data, journal
- Output: two c_rbtree_t*, representing the local and remote trees
- Note on remote discovery: Since a change to a file on the server causes the etags of all parent folders to change, folders with an unchanged etag can be read from the journal directly and don't need to be walked into.
- Details
- csync_update() uses csync_ftw() on the local and remote trees, one after the other.
- csync_ftw() iterates through the entities in a tree and calls csync_walker() for each.
- csync_walker() calls _csync_detect_update() on each.
- _csync_detect_update() compares the item to its journal entry (if any) to detect new, changed or renamed files. This is the main function of this pass.
### Reconcile
The reconcile phase compares and adjusts the local and remote trees (in both directions), detecting conflicts.
Afterwards, there are still two trees, but conflicts are marked in them.
- Input: c_rbtree_t* for the local and remote trees, journal (for some rename-related queries)
- Output: changes c_rbtree_t* in-place
- Details
- csync_reconcile() runs csync_reconcile_updates() for the local and remote trees, one after the other.
- csync_reconcile_updates() uses c_rbtree_walk() to iterate through the entries, calling _csync_merge_algorithm_visitor() for each.
- _csync_merge_algorithm_visitor() checks whether the other tree also has an entry for that node and merges the actions, detecting conflicts. This is the main function of this pass.
### Post-Reconcile
The post-reconcile phase merges the two trees into one set of SyncFileItems.
Afterwards, there is a list of items that can tell the propagator what needs to be done.
- Input: c_rbtree_t* for the local and remote trees
- Output: QMap<QString, SyncFileItemPtr>
- Note that some "propagations", specifically cheap metadata-only updates, are already done at this stage.
- Details
- csync_walk_local_tree() and csync_walk_remote_tree() are called.
- They use _csync_walk_tree() to run SyncEngine::treewalkFile() on each entry.
- treewalkFile() creates and fills SyncFileItems for each entry, ensuring that each file only has a single instance. This is the main function of this pass.
See Also
--------
An overview of the propagation steps is still missing. The sync protocol is documented at https://github.com/cernbox/smashbox/blob/master/protocol/protocol.md.
+4 -83
Ver Arquivo
@@ -415,84 +415,6 @@ 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
Comment[oc]=@APPLICATION_NAME@ sincronizacion del client
GenericName[oc]=Dorsièr de Sincronizacion
@@ -583,10 +505,10 @@ Comment[cs_CZ]=@APPLICATION_NAME@ počítačový synchronizační klient
GenericName[cs_CZ]=Synchronizace adresáře
Name[cs_CZ]=@APPLICATION_NAME@ počítačový synchronizační klient
Icon[cs_CZ]=@APPLICATION_EXECUTABLE@
Comment[ru]=Настольный клиент синхронизации @APPLICATION_NAME@
GenericName[ru]=Синхронизация каталогов
Name[ru]=Настольный клиент синхронизации @APPLICATION_NAME@
Icon[ru]=@APPLICATION_EXECUTABLE@
Comment[ru]=Настольный клиент синхронизации @НАЗВАНИЕ_ПРИЛОЖЕНИЯ@
GenericName[ru]=Синхронизация папки
Name[ru]=Настольный клиент синхронизации @НАЗВАНИЕ_ПРИЛОЖЕНИЯ@
Icon[ru]=@ВЫПОЛНЯЕМОЕ_ПРИЛОЖЕНИЕ@
Comment[sl]=@APPLICATION_NAME@ Program za usklajevanje datotek z namizjem
GenericName[sl]=Usklajevanje map
Name[sl]=@APPLICATION_NAME@ Program za usklajevanje datotek z namizjem
@@ -628,7 +550,6 @@ Comment[th_TH]=@APPLICATION_NAME@ ไคลเอนต์ประสานข
GenericName[th_TH]=ประสานข้อมูลโฟลเดอร์
Name[th_TH]= @APPLICATION_NAME@ ไคลเอนต์ประสานข้อมูลเดสก์ท็อป
Icon[th_TH]=@APPLICATION_EXECUTABLE@
GenericName[es_MX]=Sincronización de Carpetas
Comment[nb_NO]=@APPLICATION_NAME@ skrivebordssynkroniseringsklient
GenericName[nb_NO]=Mappesynkronisering
Name[nb_NO]=@APPLICATION_NAME@ skrivebordssynkroniseringsklient
+37 -66
Ver Arquivo
@@ -18,7 +18,6 @@
import os
import urllib
import socket
import tempfile
from gi.repository import GObject, Nautilus
@@ -44,7 +43,7 @@ def get_runtime_dir():
try:
return os.environ['XDG_RUNTIME_DIR']
except KeyError:
fallback = os.path.join(tempfile.gettempdir(), 'runtime-' + os.environ['USER'])
fallback = '/tmp/runtime-' + os.environ['USER']
return fallback
@@ -87,17 +86,22 @@ class SocketConnect(GObject.GObject):
def _connectToSocketServer(self):
try:
self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock_file = os.path.join(get_runtime_dir(), appname, "socket")
try:
print("Socket File: " + sock_file)
self._sock.connect(sock_file) # fails if sock_file doesn't exist
self.connected = True
print("Setting connected to %r." % self.connected )
self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify)
print("Socket watch id: " + str(self._watch_id))
return False # Don't run again
except Exception as e:
print("Could not connect to unix socket. " + str(e))
postfix = "/" + appname + "/socket" # Should use os.path.join instead
sock_file = get_runtime_dir() + postfix
print ("Socket: " + sock_file + " <=> " + postfix)
if sock_file != postfix:
try:
print("Socket File: " + sock_file)
self._sock.connect(sock_file)
self.connected = True
print("Setting connected to %r." % self.connected )
self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify)
print("Socket watch id: " + str(self._watch_id))
return False # Don't run again
except Exception as e:
print("Could not connect to unix socket. " + str(e))
else:
print("Sock-File not valid: " + sock_file)
except Exception as e: # Bad habbit
print("Connect could not be established, try again later.")
self._sock.close()
@@ -153,65 +157,32 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
def __init__(self):
GObject.GObject.__init__(self)
def check_registered_paths(self, filename):
topLevelFolder = False
internalFile = False
for reg_path in socketConnect.registered_paths:
if filename == reg_path:
topLevelFolder = True
break
if filename.startswith(reg_path):
internalFile = True
# you can't have a registered path below another so it is save to break here
break
return (topLevelFolder, internalFile)
def get_file_items(self, window, files):
# Show the menu extension to share a file or folder
#
# Show if file is OK.
# Ignore top level folders.
# Also show extension for folders
# if there is a OK or SYNC underneath.
# This is only
if len(files) != 1:
return
file = files[0]
items = []
filename = get_local_path(file.get_uri())
# Check if its a folder (ends with an /), if yes add a "/"
# otherwise it will not find the entry in the table
isDir = os.path.isdir(filename + os.sep)
if isDir:
filename += os.sep
# Internal or external file?!
syncedFile = False
for reg_path in socketConnect.registered_paths:
topLevelFolder = False
filename = get_local_path(file.get_uri())
# Check if its a folder (ends with an /), if yes add a "/"
# otherwise it will not find the entry in the table
if os.path.isdir(filename + "/"):
filename += "/"
# Check if toplevel folder, we need to ignore those as they cannot be shared
if filename == reg_path:
topLevelFolder=True
# Only show the menu extension if the file is synced and the sync
# status is ok. Not for ignored files etc.
# ignore top level folders
if filename.startswith(reg_path) and topLevelFolder == False and socketConnect.nautilusVFSFile_table[filename]['state'].startswith('OK'):
syncedFile = True
# Check if toplevel folder, we need to ignore those as they cannot be shared
topLevelFolder, internalFile = self.check_registered_paths(filename)
if topLevelFolder or not internalFile:
return items
entry = socketConnect.nautilusVFSFile_table.get(filename)
if not entry:
return items
shareable = False
state = entry['state']
state_ok = state.startswith('OK')
state_sync = state.startswith('SYNC')
if state_ok:
shareable = True
elif state_sync and isDir:
# some file below is OK or SYNC
for key, value in socketConnect.nautilusVFSFile_table.items():
if key != filename and key.startswith(filename):
state = value['state']
if state.startswith('OK') or state.startswith('SYNC'):
shareable = True
break
if not shareable:
# If it is neither in a synced folder or is a directory
if not syncedFile:
return items
# Create a menu item
@@ -332,7 +303,7 @@ class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.Info
filename = get_local_path(item.get_uri())
if item.is_directory():
filename += os.sep
filename += '/'
inScope = False
for reg_path in socketConnect.registered_paths:
+6
Ver Arquivo
@@ -58,6 +58,7 @@ struct CmdOptions {
QString password;
QString proxy;
bool silent;
bool bundleRequests;
bool trustSSL;
bool useNetrc;
bool interactive;
@@ -157,6 +158,7 @@ void help()
std::cout << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " --silent, -s Don't be so verbose" << std::endl;
std::cout << " --bundle-requests, -b Bundle Requests if supported" << std::endl;
std::cout << " --httpproxy [proxy] Specify a http proxy to use." << std::endl;
std::cout << " Proxy is http://server:port" << std::endl;
std::cout << " --trust Trust the SSL certification." << std::endl;
@@ -224,6 +226,8 @@ void parseOptions( const QStringList& app_args, CmdOptions *options )
options->silent = true;
} else if( option == "--trust") {
options->trustSSL = true;
} else if( option == "-b" || option == "--bundle-requests") {
options->bundleRequests = true;
} else if( option == "-n") {
options->useNetrc = true;
} else if( option == "-h") {
@@ -292,6 +296,7 @@ int main(int argc, char **argv) {
CmdOptions options;
options.silent = false;
options.bundleRequests = false;
options.trustSSL = false;
options.useNetrc = false;
options.interactive = true;
@@ -416,6 +421,7 @@ int main(int argc, char **argv) {
loop.exec();
#endif
account->setBundleRequestsIfCapable(options.bundleRequests);
// much lower age than the default since this utility is usually made to be run right after a change in the tests
SyncEngine::minimumFileAgeForUpload = 0;
-5
Ver Arquivo
@@ -99,7 +99,6 @@ set(client_SRCS
wizard/owncloudadvancedsetuppage.cpp
wizard/owncloudconnectionmethoddialog.cpp
wizard/owncloudhttpcredspage.cpp
wizard/owncloudbrowsercredspage.cpp
wizard/owncloudsetuppage.cpp
wizard/owncloudwizardcommon.cpp
wizard/owncloudwizard.cpp
@@ -200,9 +199,6 @@ IF( WIN32 )
${CMAKE_CURRENT_BINARY_DIR}/version.rc
@ONLY)
set(client_version ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
IF(NOT MSVC)
set(client_manifest ${CMAKE_CURRENT_SOURCE_DIR}/manifest-mingw.rc)
ENDIF()
ENDIF()
set( final_src
@@ -210,7 +206,6 @@ set( final_src
${client_SRCS}
${client_UI_SRCS}
${client_version}
${client_manifest}
${guiMoc}
${client_I18N}
${3rdparty_SRC}
+3 -122
Ver Arquivo
@@ -15,14 +15,9 @@
#include <QInputDialog>
#include <QLabel>
#include <QDesktopServices>
#include <QNetworkReply>
#include <QTimer>
#include <QBuffer>
#include "creds/httpcredentialsgui.h"
#include "theme.h"
#include "account.h"
#include <json.h>
using namespace QKeychain;
@@ -31,31 +26,11 @@ namespace OCC
void HttpCredentialsGui::askFromUser()
{
_asyncAuth.reset(new AsyncAuth(_account, this));
connect(_asyncAuth.data(), SIGNAL(result(AsyncAuth::Result,QString)),
this, SLOT(asyncAuthResult(AsyncAuth::Result,QString)));
_asyncAuth->start();
// The rest of the code assumes that this will be done asynchronously
QMetaObject::invokeMethod(this, "askFromUserAsync", Qt::QueuedConnection);
}
void HttpCredentialsGui::asyncAuthResult(AsyncAuth::Result r, const QString& token)
{
if (r == AsyncAuth::Waiting) {
return;
}
if (r == AsyncAuth::NotSupported) {
// We will re-enter the event loop, so better wait the next iteration
QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection);
return;
}
if (r == AsyncAuth::LoggedIn) {
_password = token;
_ready = true;
persist();
}
emit asked();
}
void HttpCredentialsGui::showDialog()
void HttpCredentialsGui::askFromUserAsync()
{
QString msg = tr("Please enter %1 password:<br>"
"<br>"
@@ -104,99 +79,5 @@ QString HttpCredentialsGui::requestAppPasswordText(const Account* account)
.arg(account->url().toString() + QLatin1String("/index.php/settings/personal?section=apppasswords"));
}
AsyncAuth::~AsyncAuth()
{
delete _reply;
}
void AsyncAuth::start()
{
QNetworkRequest req;
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
auto buffer = new QBuffer();
buffer->setData("name=" + Utility::userAgentString().toPercentEncoding());
QNetworkReply *reply = _account->davRequest("POST",
Utility::concatUrlPath(_account->url(), QLatin1String("/index.php/auth/start")),
req, buffer);
QObject::connect(reply, SIGNAL(finished()), this, SLOT(startFinished()));
buffer->setParent(reply);
QTimer::singleShot(30*1000, reply, SLOT(abort()));
_reply = reply;
// FIXME! timeout
// _state = AsyncAuth::QueryUrlState;
}
// We get a reply from /index.php/auth/start
void AsyncAuth::startFinished()
{
auto reply = qobject_cast<QNetworkReply *>(sender());
Q_ASSERT(reply);
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
qDebug() << "auth/start returned error" << reply->errorString();
// FIXME! we should make a difference between network errors and file not found error.
emit result(NotSupported, QString());
return;
}
const QString replyData = reply->readAll();
bool success;
QVariantMap json = QtJson::parse(replyData, success).toMap();
if (!success) {
qDebug() << "could not parse json" << replyData;
emit result(NotSupported, QString());
return;
}
auto clientUrl = QUrl::fromEncoded(json[QLatin1String("clientUrl")].toByteArray());
_pollUrl = QUrl::fromEncoded(json[QLatin1String("pollUrl")].toByteArray());
if (!_pollUrl.isValid() || !clientUrl.isValid()) {
qWarning() << "invalid urls from server" << json;
emit result(NotSupported, QString());
return;
}
if (!QDesktopServices::openUrl(clientUrl)) {
// We cannot open the browser, then we claim we don't support it.
emit result(NotSupported, QString());
return;
}
emit result(Waiting, QString());
QTimer::singleShot(1000, this, SLOT(poll()));
}
void AsyncAuth::poll()
{
QNetworkReply *reply = _account->davRequest("POST", _pollUrl, QNetworkRequest());
QObject::connect(reply, SIGNAL(finished()), this, SLOT(pollFinished()));
QTimer::singleShot(5*60*1000, reply, SLOT(abort()));
_reply = reply;
}
void AsyncAuth::pollFinished()
{
auto reply = qobject_cast<QNetworkReply *>(sender());
Q_ASSERT(reply);
const QString replyData = reply->readAll();
reply->deleteLater();
bool success;
QVariantMap json = QtJson::parse(replyData, success).toMap();
if (!success || json["status"].toUInt() != 1) {
QTimer::singleShot(4*1000, this, SLOT(poll()));
return;
}
auto token = json["token"].toString();
emit result(LoggedIn, token);
}
} // namespace OCC
+1 -40
Ver Arquivo
@@ -15,45 +15,10 @@
#pragma once
#include "creds/httpcredentials.h"
#include <QPointer>
namespace OCC
{
class AsyncAuth : public QObject
{
Q_OBJECT
public:
AsyncAuth(Account *account, QObject *parent)
: QObject(parent), _account(account)
{ }
~AsyncAuth();
enum Result { Waiting, NotSupported, LoggedIn };
void start();
public slots:
void startFinished();
void poll();
void pollFinished();
signals:
/**
* The state has changed.
* when logged in, token has the value of the token.
*/
void result(AsyncAuth::Result result, const QString &token);
private:
Account *_account;
QUrl _pollUrl;
// Makes sure the reply is destroyed when this object is closed
QPointer<QNetworkReply> _reply;
// State _state;
};
/**
* @brief The HttpCredentialsGui class
* @ingroup gui
@@ -64,13 +29,9 @@ public:
explicit HttpCredentialsGui() : HttpCredentials() {}
HttpCredentialsGui(const QString& user, const QString& password, const QString& certificatePath, const QString& certificatePasswd) : HttpCredentials(user, password, certificatePath, certificatePasswd) {}
void askFromUser() Q_DECL_OVERRIDE;
Q_INVOKABLE void askFromUserAsync();
static QString requestAppPasswordText(const Account *account);
private slots:
void asyncAuthResult(AsyncAuth::Result, const QString &token);
void showDialog();
private:
QScopedPointer<AsyncAuth> _asyncAuth;
};
} // namespace OCC
-4
Ver Arquivo
@@ -295,11 +295,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
pBRect.setHeight(barHeight);
pBRect.setWidth( overallWidth - 2 * margin );
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
QStyleOptionProgressBarV2 pBarOpt;
#else
QStyleOptionProgressBar pBarOpt;
#endif
pBarOpt.state = option.state | QStyle::State_Horizontal;
pBarOpt.minimum = 0;
+11 -27
Ver Arquivo
@@ -38,12 +38,6 @@ FolderStatusModel::FolderStatusModel(QObject *parent)
FolderStatusModel::~FolderStatusModel()
{ }
static bool sortByFolderHeader(const FolderStatusModel::SubFolderInfo& lhs, const FolderStatusModel::SubFolderInfo& rhs)
{
return QString::compare(lhs._folder->shortGuiRemotePathOrAppName(),
rhs._folder->shortGuiRemotePathOrAppName(),
Qt::CaseInsensitive) < 0;
}
void FolderStatusModel::setAccountState(const AccountState* accountState)
{
@@ -64,6 +58,7 @@ void FolderStatusModel::setAccountState(const AccountState* accountState)
if (f->accountState() != accountState)
continue;
SubFolderInfo info;
info._pathIdx << _folders.size();
info._name = f->alias();
info._path = "/";
info._folder = f;
@@ -74,14 +69,6 @@ void FolderStatusModel::setAccountState(const AccountState* accountState)
connect(f, SIGNAL(newBigFolderDiscovered(QString)), this, SLOT(slotNewBigFolder()), Qt::UniqueConnection);
}
// Sort by header text
qSort(_folders.begin(), _folders.end(), sortByFolderHeader);
// Set the root _pathIdx after the sorting
for (int i = 0; i < _folders.size(); ++i) {
_folders[i]._pathIdx << i;
}
endResetModel();
emit dirtyChanged();
}
@@ -388,13 +375,12 @@ QModelIndex FolderStatusModel::indexForPath(Folder *f, const QString& path) cons
if (slashPos == -1) {
// first level folder
for (int i = 0; i < _folders.size(); ++i) {
auto& info = _folders.at(i);
if (info._folder == f) {
if (_folders.at(i)._folder == f) {
if( path.isEmpty() ) { // the folder object
return index(i, 0);
}
for (int j = 0; j < info._subs.size(); ++j) {
const QString subName = info._subs.at(j)._name;
for (int j = 0; j < _folders.at(i)._subs.size(); ++j) {
const QString subName = _folders.at(i)._subs.at(j)._name;
if (subName == path) {
return index(j, 0, index(i));
}
@@ -1000,12 +986,10 @@ void FolderStatusModel::slotFolderSyncStateChange(Folder *f)
}
if (folderIndex < 0) { return; }
auto& pi = _folders[folderIndex]._progress;
SyncResult::Status state = f->syncResult().status();
if (f->syncPaused()) {
// Reset progress info.
pi = SubFolderInfo::Progress();
_folders[folderIndex]._progress = SubFolderInfo::Progress();
} else if (state == SyncResult::NotYetStarted) {
FolderMan* folderMan = FolderMan::instance();
int pos = folderMan->scheduleQueue().indexOf(f);
@@ -1019,16 +1003,16 @@ void FolderStatusModel::slotFolderSyncStateChange(Folder *f)
} else {
message = tr("Waiting for %n other folder(s)...", "", pos);
}
pi = SubFolderInfo::Progress();
pi._overallSyncString = message;
_folders[folderIndex]._progress = SubFolderInfo::Progress();
_folders[folderIndex]._progress._overallSyncString = message;
} else if (state == SyncResult::SyncPrepare) {
pi = SubFolderInfo::Progress();
pi._overallSyncString = tr("Preparing to sync...");
_folders[folderIndex]._progress = SubFolderInfo::Progress();
_folders[folderIndex]._progress._overallSyncString = tr("Preparing to sync...");
} else if (state == SyncResult::Problem || state == SyncResult::Success) {
// Reset the progress info after a sync.
pi = SubFolderInfo::Progress();
_folders[folderIndex]._progress = SubFolderInfo::Progress();
} else if (state == SyncResult::Error) {
pi = SubFolderInfo::Progress();
_folders[folderIndex]._progress = SubFolderInfo::Progress();
}
// update the icon etc. now
-3
Ver Arquivo
@@ -1,3 +0,0 @@
#include "winuser.h"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "owncloud.exe.manifest-mingw"
-10
Ver Arquivo
@@ -1,10 +0,0 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level='asInvoker' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
-3
Ver Arquivo
@@ -903,9 +903,6 @@ void ownCloudGui::setPauseOnAllFoldersHelper(bool pause)
foreach (Folder* f, FolderMan::instance()->map()) {
if (accounts.contains(f->accountState())) {
f->setSyncPaused(pause);
if (pause) {
f->slotTerminateSync();
}
}
}
}
+5 -27
Ver Arquivo
@@ -35,8 +35,6 @@
#include "creds/credentialsfactory.h"
#include "creds/abstractcredentials.h"
#include "creds/dummycredentials.h"
#include "creds/httpcredentialsgui.h"
#include "wizard/owncloudbrowsercredspage.h"
namespace OCC {
@@ -195,31 +193,11 @@ void OwncloudSetupWizard::slotOwnCloudFoundAuth(const QUrl& url, const QVariantM
qDebug() << Q_FUNC_INFO << " was redirected to" << redirectedUrl.toString();
}
/* Start by trying a browser authentication if the server supports it.
* If it does not support it, asyncAuthResult will fallback to the legacy wizard page */
_asyncAuth.reset(new AsyncAuth(_ocWizard->account().data(), this));
connect(_asyncAuth.data(), SIGNAL(result(AsyncAuth::Result,QString)),
this, SLOT(asyncAuthResult(AsyncAuth::Result,QString)));
_asyncAuth->start();
}
void OwncloudSetupWizard::asyncAuthResult(AsyncAuth::Result r,const QString &token)
{
if (r == AsyncAuth::NotSupported) {
_asyncAuth.reset(0);
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this);
job->setIgnoreCredentialFailure(true);
connect(job, SIGNAL(authType(WizardCommon::AuthType)),
_ocWizard, SLOT(setAuthType(WizardCommon::AuthType)));
job->start();
} else if (r == AsyncAuth::Waiting) {
_ocWizard->setAuthType(WizardCommon::Browser);
} else if (r == AsyncAuth::LoggedIn) {
_ocWizard->_browserCredsPage->_token = token;
slotConnectToOCUrl(_ocWizard->account()->url().toString());
}
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this);
job->setIgnoreCredentialFailure(true);
connect(job, SIGNAL(authType(WizardCommon::AuthType)),
_ocWizard, SLOT(setAuthType(WizardCommon::AuthType)));
job->start();
}
void OwncloudSetupWizard::slotNoOwnCloudFoundAuth(QNetworkReply *reply)
-3
Ver Arquivo
@@ -26,7 +26,6 @@
#include "networkjobs.h"
#include "wizard/owncloudwizardcommon.h"
#include "creds/httpcredentialsgui.h"
namespace OCC {
@@ -81,7 +80,6 @@ private slots:
void slotCreateRemoteFolderFinished(QNetworkReply::NetworkError);
void slotAssistantFinished( int );
void slotSkipFolderConfiguration();
void asyncAuthResult(AsyncAuth::Result, const QString &);
private:
explicit OwncloudSetupWizard(QObject *parent = 0 );
@@ -97,7 +95,6 @@ private:
OwncloudWizard* _ocWizard;
QString _initLocalFolder;
QString _remoteFolder;
QScopedPointer<AsyncAuth> _asyncAuth;
};
}
-81
Ver Arquivo
@@ -1,81 +0,0 @@
/*
* Copyright (C) by Krzesimir Nowak <krzesimir@endocode.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; version 2 of the License.
*
* 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 <QVariant>
#include "wizard/owncloudbrowsercredspage.h"
#include "theme.h"
#include "account.h"
#include "cookiejar.h"
#include "wizard/owncloudwizardcommon.h"
#include "wizard/owncloudwizard.h"
#include "creds/httpcredentialsgui.h"
namespace OCC
{
OwncloudBrowserCredsPage::OwncloudBrowserCredsPage()
: AbstractCredentialsWizardPage(),
_afterInitialSetup(false)
{}
void OwncloudBrowserCredsPage::setVisible(bool visible)
{
if (!_afterInitialSetup) {
QWizardPage::setVisible(visible);
return;
}
if (isVisible() == visible) {
return;
}
if (visible) {
wizard()->hide();
} else {
wizard()->show();
}
}
void OwncloudBrowserCredsPage::initializePage()
{
_afterInitialSetup = true;
}
int OwncloudBrowserCredsPage::nextId() const
{
return WizardCommon::Page_AdvancedSetup;
}
void OwncloudBrowserCredsPage::setConnected()
{
wizard()->show();
}
AbstractCredentials* OwncloudBrowserCredsPage::getCredentials() const
{
OwncloudWizard* ocWizard = qobject_cast< OwncloudWizard* >(wizard());
Q_ASSERT(ocWizard);
// FIXME TODO !!!!!!!
QString user = QLatin1String("owncloud"); // FIXME! username;
return new HttpCredentialsGui(user, _token, ocWizard->ownCloudCertificatePath, ocWizard->ownCloudCertificatePasswd);
}
void OwncloudBrowserCredsPage::slotBrowserRejected()
{
wizard()->back();
wizard()->show();
}
} // namespace OCC
-55
Ver Arquivo
@@ -1,55 +0,0 @@
/*
* Copyright (C) by Krzesimir Nowak <krzesimir@endocode.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; version 2 of the License.
*
* 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.
*/
#pragma once
#include <QList>
#include <QMap>
#include <QNetworkCookie>
#include <QUrl>
#include <QPointer>
#include "wizard/abstractcredswizardpage.h"
#include "accountfwd.h"
namespace OCC {
class ShibbolethWebView;
/**
* @brief The OwncloudBrowserCredsPage class
* @ingroup gui
*/
class OwncloudBrowserCredsPage : public AbstractCredentialsWizardPage
{
Q_OBJECT
public:
OwncloudBrowserCredsPage();
AbstractCredentials* getCredentials() const Q_DECL_OVERRIDE;
void initializePage() Q_DECL_OVERRIDE;
int nextId() const Q_DECL_OVERRIDE;
void setConnected();
public Q_SLOTS:
void setVisible(bool visible) Q_DECL_OVERRIDE;
void slotBrowserRejected();
private:
bool _afterInitialSetup;
public:
QString _token;
};
} // namespace OCC
-2
Ver Arquivo
@@ -205,8 +205,6 @@ int OwncloudSetupPage::nextId() const
{
if (_authType == WizardCommon::HttpCreds) {
return WizardCommon::Page_HttpCreds;
} else if (_authType == WizardCommon::Browser) {
return WizardCommon::Page_BrowserCreds;
} else {
return WizardCommon::Page_ShibbolethCreds;
}
+1 -10
Ver Arquivo
@@ -20,7 +20,6 @@
#include "wizard/owncloudwizard.h"
#include "wizard/owncloudsetuppage.h"
#include "wizard/owncloudhttpcredspage.h"
#include "wizard/owncloudbrowsercredspage.h"
#ifndef NO_SHIBBOLETH
#include "wizard/owncloudshibbolethcredspage.h"
#endif
@@ -42,7 +41,6 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
_account(0),
_setupPage(new OwncloudSetupPage(this)),
_httpCredsPage(new OwncloudHttpCredsPage(this)),
_browserCredsPage(new OwncloudBrowserCredsPage),
#ifndef NO_SHIBBOLETH
_shibbolethCredsPage(new OwncloudShibbolethCredsPage),
#endif
@@ -54,7 +52,6 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setPage(WizardCommon::Page_ServerSetup, _setupPage);
setPage(WizardCommon::Page_HttpCreds, _httpCredsPage);
setPage(WizardCommon::Page_BrowserCreds, _browserCredsPage);
#ifndef NO_SHIBBOLETH
setPage(WizardCommon::Page_ShibbolethCreds, _shibbolethCredsPage);
#endif
@@ -139,10 +136,6 @@ void OwncloudWizard::successfulStep()
_httpCredsPage->setConnected();
break;
case WizardCommon::Page_BrowserCreds:
_browserCredsPage->setConnected();
break;
#ifndef NO_SHIBBOLETH
case WizardCommon::Page_ShibbolethCreds:
_shibbolethCredsPage->setConnected();
@@ -170,9 +163,7 @@ void OwncloudWizard::setAuthType(WizardCommon::AuthType type)
_credentialsPage = _shibbolethCredsPage;
} else
#endif
if (type == WizardCommon::Browser) {
_credentialsPage = _browserCredsPage;
} else {
{
_credentialsPage = _httpCredsPage;
}
next();
-4
Ver Arquivo
@@ -25,7 +25,6 @@ namespace OCC {
class OwncloudSetupPage;
class OwncloudHttpCredsPage;
class OwncloudBrowserCredsPage;
#ifndef NO_SHIBBOLETH
class OwncloudShibbolethCredsPage;
#endif
@@ -91,7 +90,6 @@ private:
AccountPtr _account;
OwncloudSetupPage* _setupPage;
OwncloudHttpCredsPage* _httpCredsPage;
OwncloudBrowserCredsPage* _browserCredsPage;
#ifndef NO_SHIBBOLETH
OwncloudShibbolethCredsPage* _shibbolethCredsPage;
#endif
@@ -100,8 +98,6 @@ private:
AbstractCredentialsWizardPage* _credentialsPage;
QStringList _setupLog;
friend class OwncloudSetupWizard;
};
} // namespace OCC
+1 -3
Ver Arquivo
@@ -32,8 +32,7 @@ void initErrorLabel(QLabel* errorLabel);
enum AuthType {
HttpCreds,
Shibboleth,
Browser
Shibboleth
};
enum SyncMode {
@@ -45,7 +44,6 @@ enum Pages {
Page_ServerSetup,
Page_HttpCreds,
Page_ShibbolethCreds,
Page_BrowserCreds,
Page_AdvancedSetup,
Page_Result
};
-2
Ver Arquivo
@@ -50,8 +50,6 @@ set(libsync_SRCS
propagatorjobs.cpp
propagatedownload.cpp
propagateupload.cpp
propagateuploadv1.cpp
propagateuploadng.cpp
propagateremotedelete.cpp
propagateremotemove.cpp
propagateremotemkdir.cpp
+5
Ver Arquivo
@@ -128,6 +128,11 @@ QNetworkReply *AbstractNetworkJob::davRequest(const QByteArray &verb, const QUrl
return addTimer(_account->davRequest(verb, url, req, data));
}
QNetworkReply *AbstractNetworkJob::multipartRequest(const QString &relPath, QNetworkRequest req, QHttpMultiPart *multiPart)
{
return addTimer(_account->multipartRequest(relPath, req, multiPart));
}
QNetworkReply* AbstractNetworkJob::getRequest(const QString &relPath)
{
return addTimer(_account->getRequest(relPath));
+2
Ver Arquivo
@@ -26,6 +26,7 @@
#include "accountfwd.h"
class QUrl;
class QHttpMultiPart;
namespace OCC {
@@ -78,6 +79,7 @@ protected:
QNetworkReply* headRequest(const QString &relPath);
QNetworkReply* headRequest(const QUrl &url);
QNetworkReply* deleteRequest(const QUrl &url);
QNetworkReply* multipartRequest(const QString &relPath, QNetworkRequest req, QHttpMultiPart *multiPart);
int maxRedirects() const { return 10; }
virtual bool finished() = 0;
-4
Ver Arquivo
@@ -63,10 +63,6 @@ QNetworkReply* AccessManager::createRequest(QNetworkAccessManager::Operation op,
}
newRequest.setRawHeader(QByteArray("User-Agent"), Utility::userAgentString());
// Some firewalls reject requests that have a "User-Agent" but no "Accept" header
newRequest.setRawHeader(QByteArray("Accept"), "*/*");
QByteArray verb = newRequest.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray();
// For PROPFIND (assumed to be a WebDAV op), set xml/utf8 as content type/encoding
// This needs extension
+30 -8
Ver Arquivo
@@ -39,6 +39,8 @@ Account::Account(QObject *parent)
: QObject(parent)
, _capabilities(QVariantMap())
, _davPath( Theme::instance()->webDavPath() )
, _wasMigrated(false)
, _bundleRequests(true)
{
qRegisterMetaType<AccountPtr>("AccountPtr");
}
@@ -75,19 +77,18 @@ AccountPtr Account::sharedFromThis()
return _sharedThis.toStrongRef();
}
QString Account::user() const
QString Account::davFilesPath() const
{
return _user.isEmpty() ? _credentials->user() : _user;
}
void Account::setUser(const QString &user)
{
_user = user;
//TODO DO NOT HARCODE PATH, GET IT FROM THE SERVER!!!!
QString dfp("remote.php/dav/files/");
dfp.append(_credentials->user());
return dfp;
}
QString Account::displayName() const
{
QString dn = QString("%1@%2").arg(user(), _url.host());
auto user = _credentials->user();
QString dn = QString("%1@%2").arg(user, _url.host());
int port = url().port();
if (port > 0 && port != 80 && port != 443) {
dn.append(QLatin1Char(':'));
@@ -236,6 +237,20 @@ QNetworkReply *Account::davRequest(const QByteArray &verb, const QUrl &url, QNet
return _am->sendCustomRequest(req, verb, data);
}
QNetworkReply *Account::multipartRequest(const QString &relPath, QNetworkRequest req, QHttpMultiPart *multiPart)
{
return multipartRequest(Utility::concatUrlPath(url(), relPath), req, multiPart);
}
QNetworkReply *Account::multipartRequest(const QUrl &url, QNetworkRequest req, QHttpMultiPart *multiPart)
{
req.setUrl(url);
#if QT_VERSION > QT_VERSION_CHECK(4, 8, 4)
req.setSslConfiguration(this->getOrCreateSslConfig());
#endif
return _am->post(req, multiPart);
}
void Account::setCertificate(const QByteArray certficate, const QString privateKey)
{
_pemCertificate=certficate;
@@ -474,6 +489,13 @@ void Account::setNonShib(bool nonShib)
}
}
void Account::setBundleRequestsIfCapable(bool bundleRequests){
_bundleRequests = bundleRequests;
}
bool Account::bundledRequestsEnabled()
{
return (_bundleRequests && _capabilities.bundledRequest()) ? true : false;
}
} // namespace OCC
+25 -14
Ver Arquivo
@@ -33,6 +33,7 @@ class QSettings;
class QNetworkReply;
class QUrl;
class QNetworkAccessManager;
class QHttpMultiPart;
namespace OCC {
@@ -63,15 +64,31 @@ public:
class OWNCLOUDSYNC_EXPORT Account : public QObject {
Q_OBJECT
public:
/**
* @brief The possibly themed dav path for the account. It has
* a trailing slash.
* @returns the (themeable) dav path for the account.
*/
QString davPath() const;
/**
* @brief The possibly themed files dav path for the account. It has
* a trailing slash.
* @returns the (themeable) files dav path for the account.
*/
QString davFilesPath() const;
void setDavPath(const QString&s) { _davPath = s; }
void setNonShib(bool nonShib);
/// Functions for bundle support
void setBundleRequestsIfCapable(bool bundleRequests);
bool bundledRequestsEnabled();
static AccountPtr create();
~Account();
AccountPtr sharedFromThis();
/// The user that can be used in dav url
QString user() const;
void setUser(const QString &user);
/// The name of the account as shown in the toolbar
QString displayName() const;
@@ -82,15 +99,6 @@ public:
void setUrl(const QUrl &url);
QUrl url() const { return _url; }
/**
* @brief The possibly themed dav path for the account. It has
* a trailing slash.
* @returns the (themeable) dav path for the account.
*/
QString davPath() const;
void setDavPath(const QString&s) { _davPath = s; }
void setNonShib(bool nonShib);
/** Returns webdav entry URL, based on url() */
QUrl davUrl() const;
@@ -107,6 +115,8 @@ public:
QNetworkReply* deleteRequest( const QUrl &url);
QNetworkReply* davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data = 0);
QNetworkReply* davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data = 0);
QNetworkReply* multipartRequest(const QString &relPath, QNetworkRequest req, QHttpMultiPart *multiPart);
QNetworkReply* multipartRequest(const QUrl &url, QNetworkRequest req, QHttpMultiPart *multiPart);
/** The ssl configuration during the first connection */
@@ -202,7 +212,6 @@ private:
QWeakPointer<Account> _sharedThis;
QString _id;
QString _user;
QMap<QString, QVariant> _settingsMap;
QUrl _url;
QList<QSslCertificate> _approvedCerts;
@@ -221,6 +230,8 @@ private:
QByteArray _pemCertificate;
QString _pemPrivateKey;
QString _davPath; // defaults to value from theme, might be overwritten in brandings
bool _wasMigrated;
bool _bundleRequests;
friend class AccountManager;
};
+6 -6
Ver Arquivo
@@ -46,6 +46,12 @@ bool Capabilities::sharePublicLink() const
}
}
bool Capabilities::bundledRequest() const
{
bool bundle = _capabilities["dav"].toMap()["bundlerequest"].toBool();
return bundle;
}
bool Capabilities::sharePublicLinkAllowUpload() const
{
return _capabilities["files_sharing"].toMap()["public"].toMap()["upload"].toBool();
@@ -107,10 +113,4 @@ QByteArray Capabilities::uploadChecksumType() const
return QByteArray();
}
bool Capabilities::chunkingNg() const
{
return _capabilities["dav"].toMap()["chunking"].toByteArray() >= "1.0";
}
}
+1 -1
Ver Arquivo
@@ -39,7 +39,7 @@ public:
bool sharePublicLinkEnforceExpireDate() const;
int sharePublicLinkExpireDateDays() const;
bool shareResharing() const;
bool chunkingNg() const;
bool bundledRequest() const;
/// returns true if the capabilities report notifications
bool notificationsAvailable() const;
+2 -18
Ver Arquivo
@@ -229,27 +229,11 @@ void ConnectionValidator::slotCapabilitiesRecieved(const QVariantMap &json)
auto caps = json.value("ocs").toMap().value("data").toMap().value("capabilities");
qDebug() << "Server capabilities" << caps;
_account->setCapabilities(caps.toMap());
fetchUser();
}
void ConnectionValidator::fetchUser()
{
JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/user"), this);
job->setTimeout(timeoutToUseMsec);
QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)), this, SLOT(slotUserFetched(QVariantMap)));
job->start();
}
void ConnectionValidator::slotUserFetched(const QVariantMap &json)
{
QString user = json.value("ocs").toMap().value("data").toMap().value("id").toString();
if (!user.isEmpty()) {
_account->setUser(user);
}
reportResult(Connected);
return;
}
void ConnectionValidator::reportResult(Status status)
{
emit connectionResult(status, _errors);
+2 -12
Ver Arquivo
@@ -27,7 +27,7 @@ namespace OCC {
* This is a job-like class to check that the server is up and that we are connected.
* There are two entry points: checkServerAndAuth and checkAuthentication
* checkAuthentication is the quick version that only does the propfind
* while checkServerAndAuth is doing the 4 calls.
* while checkServerAndAuth is doing the 3 calls.
*
* We cannot use the capabilites call to test the login and the password because of
* https://github.com/owncloud/core/issues/12930
@@ -60,15 +60,7 @@ namespace OCC {
+-> checkServerCapabilities (cloud/capabilities)
JsonApiJob
|
+-> slotCapabilitiesRecieved -+
|
+-----------------------------------+
|
+-> fetchUser
PropfindJob
|
+-> slotUserFetched --> X
+-> slotCapabilitiesRecieved --> X
\endcode
*/
class OWNCLOUDSYNC_EXPORT ConnectionValidator : public QObject
@@ -117,12 +109,10 @@ protected slots:
void slotAuthSuccess();
void slotCapabilitiesRecieved(const QVariantMap&);
void slotUserFetched(const QVariantMap &);
private:
void reportResult(Status status);
void checkServerCapabilities();
void fetchUser();
QStringList _errors;
AccountPtr _account;
-2
Ver Arquivo
@@ -17,7 +17,6 @@
#define MIRALL_CREDS_HTTP_CREDENTIALS_H
#include <QMap>
#include <QUrl>
#include "creds/abstractcredentials.h"
@@ -75,7 +74,6 @@ private:
QString _certificatePasswd;
};
} // namespace OCC
#endif
+2 -20
Ver Arquivo
@@ -20,7 +20,6 @@
#include <QUrl>
#include "account.h"
#include <QFileInfo>
#include <cstring>
namespace OCC {
@@ -237,8 +236,7 @@ void DiscoverySingleDirectoryJob::start()
QList<QByteArray> props;
props << "resourcetype" << "getlastmodified" << "getcontentlength" << "getetag"
<< "http://owncloud.org/ns:id" << "http://owncloud.org/ns:downloadURL"
<< "http://owncloud.org/ns:dDC" << "http://owncloud.org/ns:permissions"
<< "http://owncloud.org/ns:share-types";
<< "http://owncloud.org/ns:dDC" << "http://owncloud.org/ns:permissions";
if (_isRootPath)
props << "http://owncloud.org/ns:data-fingerprint";
@@ -310,25 +308,9 @@ static csync_vio_file_stat_t* propertyMapToFileStat(const QMap<QString,QString>
} else {
qWarning() << "permissions too large" << v;
}
} else if (property == "share-types" && !value.isEmpty()) {
// Since QMap is sorted, "share-types" is always "permissions".
if (file_stat->remotePerm[0] == '\0' || !(file_stat->fields & CSYNC_VIO_FILE_STAT_FIELDS_PERM)) {
qWarning() << "Server returned a share type, but no permissions?";
} else {
// S means shared with me.
// But for our purpose, we want to know if the file is shared. It does not matter
// if we are the owner or not.
// Piggy back on the persmission field 'S'
if (!std::strchr(file_stat->remotePerm, 'S')) {
if (std::strlen(file_stat->remotePerm) < sizeof(file_stat->remotePerm)-1) {
std::strcat(file_stat->remotePerm, "S");
} else {
qWarning() << "permissions too large" << file_stat->remotePerm;
}
}
}
}
}
return file_stat;
}
+2 -18
Ver Arquivo
@@ -106,24 +106,14 @@ MkColJob::MkColJob(AccountPtr account, const QString &path, QObject *parent)
{
}
MkColJob::MkColJob(AccountPtr account, const QUrl &url,
const QMap<QByteArray, QByteArray> &extraHeaders, QObject *parent)
: AbstractNetworkJob(account, QString(), parent), _url(url), _extraHeaders(extraHeaders)
{
}
void MkColJob::start()
{
// add 'Content-Length: 0' header (see https://github.com/owncloud/client/issues/3256)
QNetworkRequest req;
req.setRawHeader("Content-Length", "0");
for(auto it = _extraHeaders.constBegin(); it != _extraHeaders.constEnd(); ++it) {
req.setRawHeader(it.key(), it.value());
}
// assumes ownership
QNetworkReply *reply = _url.isValid() ? davRequest("MKCOL", _url, req)
: davRequest("MKCOL", path(), req);
QNetworkReply *reply = davRequest("MKCOL", path(), req);
setReply(reply);
setupConnections(reply);
AbstractNetworkJob::start();
@@ -274,11 +264,6 @@ LsColJob::LsColJob(AccountPtr account, const QString &path, QObject *parent)
{
}
LsColJob::LsColJob(AccountPtr account, const QUrl &url, QObject *parent)
: AbstractNetworkJob(account, QString(), parent), _url(url)
{
}
void LsColJob::setProperties(QList<QByteArray> properties)
{
_properties = properties;
@@ -322,8 +307,7 @@ void LsColJob::start()
QBuffer *buf = new QBuffer(this);
buf->setData(xml);
buf->open(QIODevice::ReadOnly);
QNetworkReply *reply = _url.isValid() ? davRequest("PROPFIND", _url, req, buf)
: davRequest("PROPFIND", path(), req, buf);
QNetworkReply *reply = davRequest("PROPFIND", path(), req, buf);
buf->setParent(reply);
setReply(reply);
setupConnections(reply);
-6
Ver Arquivo
@@ -62,7 +62,6 @@ class OWNCLOUDSYNC_EXPORT LsColJob : public AbstractNetworkJob {
Q_OBJECT
public:
explicit LsColJob(AccountPtr account, const QString &path, QObject *parent = 0);
explicit LsColJob(AccountPtr account, const QUrl &url, QObject *parent = 0);
void start() Q_DECL_OVERRIDE;
QHash<QString, qint64> _sizes;
@@ -88,7 +87,6 @@ private slots:
private:
QList<QByteArray> _properties;
QUrl _url; // Used instead of path() if the url is specified in the constructor
};
/**
@@ -172,12 +170,8 @@ private:
*/
class OWNCLOUDSYNC_EXPORT MkColJob : public AbstractNetworkJob {
Q_OBJECT
QUrl _url; // Only used if the constructor taking a url is taken.
QMap<QByteArray, QByteArray> _extraHeaders;
public:
explicit MkColJob(AccountPtr account, const QString &path, QObject *parent = 0);
explicit MkColJob(AccountPtr account, const QUrl &url,
const QMap<QByteArray, QByteArray> &extraHeaders, QObject *parent = 0);
void start() Q_DECL_OVERRIDE;
signals:
+32 -16
Ver Arquivo
@@ -270,14 +270,7 @@ PropagateItemJob* OwncloudPropagator::createJob(const SyncFileItemPtr &item) {
job->setDeleteExistingFolder(deleteExisting);
return job;
} else {
PropagateUploadFileCommon *job = 0;
static const auto chunkng = qgetenv("OWNCLOUD_CHUNKING_NG");
if (item->_size > chunkSize()
&& (account()->capabilities().chunkingNg() || chunkng == "1") && chunkng != "0") {
job = new PropagateUploadFileNG(this, item);
} else {
job = new PropagateUploadFileV1(this, item);
}
auto job = new PropagateUploadFile(this, item);
job->setDeleteExisting(deleteExisting);
return job;
}
@@ -310,6 +303,13 @@ void OwncloudPropagator::start(const SyncFileItemVector& items)
directories.push(qMakePair(QString(), _rootJob.data()));
QVector<PropagatorJob*> directoriesToRemove;
QString removedDirectory;
PropagateBundle *bundledUploadRequestsJob = new PropagateBundle(this);
quint64 chunkSize = OwncloudPropagator::chunkSize();
// TODO: here we should also check somehow if bundle is not broken for specific sync - bug recovery to standard PUTs
bool bundledRequestsEnabled = _account->bundledRequestsEnabled();
foreach(const SyncFileItemPtr &item, items) {
if (!removedDirectory.isEmpty() && item->_file.startsWith(removedDirectory)) {
@@ -386,6 +386,12 @@ void OwncloudPropagator::start(const SyncFileItemVector& items)
currentDirJob->append(dir);
}
directories.push(qMakePair(item->destination() + "/" , dir));
} else if (bundledRequestsEnabled
&& (item->_instruction == CSYNC_INSTRUCTION_NEW)
&& (item->_direction == SyncFileItem::Up)
&& (item->_size < chunkSize)) {
//this will create list of bundle files to sync for that bundlejob
bundledUploadRequestsJob->append(item);
} else if (PropagateItemJob* current = createJob(item)) {
if (item->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE) {
// will delete directories, so defer execution
@@ -401,6 +407,21 @@ void OwncloudPropagator::start(const SyncFileItemVector& items)
_rootJob->append(it);
}
// Root job is DirectoryJob with FullParallelizm, so we need to halt bundle till all other jobs are finished.
// If there are no running jobs in root folder it means we can start with sending the bundle
// If there are running jobs, we halt and wait for finish of the other job to trigger sending
if (bundledRequestsEnabled && !bundledUploadRequestsJob->empty()) {
bundledUploadRequestsJob->_item->_direction = SyncFileItem::Direction::Up;
bundledUploadRequestsJob->_item->_type = SyncFileItem::Type::RequestsContainer;
bundledUploadRequestsJob->_item->_instruction = CSYNC_INSTRUCTION_NEW;
bundledUploadRequestsJob->_item->_size = bundledUploadRequestsJob->syncItemsSize();
bundledUploadRequestsJob->_item->_originalFile = tr("%1 file(s)").arg(bundledUploadRequestsJob->syncItemsNumber());
_rootJob->append(bundledUploadRequestsJob);
}
else{
delete bundledUploadRequestsJob;
}
connect(_rootJob.data(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)),
this, SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)));
connect(_rootJob.data(), SIGNAL(progress(const SyncFileItem &,quint64)), this, SIGNAL(progress(const SyncFileItem &,quint64)));
@@ -613,15 +634,9 @@ bool PropagateDirectory::scheduleNextJob()
return false;
}
// cache the value of first unfinished subjob
bool stopAtDirectory = false;
int i = _firstUnfinishedSubJob;
int subJobsCount = _subJobs.count();
while (i < subJobsCount && _subJobs.at(i)->_state == Finished) {
_firstUnfinishedSubJob = ++i;
}
for (int i = _firstUnfinishedSubJob; i < subJobsCount; ++i) {
// FIXME: use the cached value of finished job
for (int i = 0; i < _subJobs.count(); ++i) {
if (_subJobs.at(i)->_state == Finished) {
continue;
}
@@ -637,6 +652,7 @@ bool PropagateDirectory::scheduleNextJob()
Q_ASSERT(_subJobs.at(i)->_state == Running);
auto paral = _subJobs.at(i)->parallelism();
if (paral == WaitForFinished) {
return false;
}
+7 -4
Ver Arquivo
@@ -195,11 +195,10 @@ public:
int _jobsFinished; // number of jobs that have completed
int _runningNow; // number of subJobs running right now
SyncFileItem::Status _hasError; // NoStatus, or NormalError / SoftError if there was an error
int _firstUnfinishedSubJob;
explicit PropagateDirectory(OwncloudPropagator *propagator, const SyncFileItemPtr &item = SyncFileItemPtr(new SyncFileItem))
: PropagatorJob(propagator)
, _firstJob(0), _item(item), _jobsFinished(0), _runningNow(0), _hasError(SyncFileItem::NoStatus), _firstUnfinishedSubJob(0)
, _firstJob(0), _item(item), _jobsFinished(0), _runningNow(0), _hasError(SyncFileItem::NoStatus)
{ }
virtual ~PropagateDirectory() {
@@ -325,6 +324,10 @@ public:
emitFinished(SyncFileItem::NormalError);
}
int runningNowAtRootJob() const {
return _rootJob->_runningNow;
}
// timeout in seconds
static int httpTimeout();
@@ -380,11 +383,11 @@ private:
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
// access to signals which are protected in Qt4
friend class PropagateDownloadFile;
friend class PropagateUploadFile;
friend class PropagateLocalMkdir;
friend class PropagateLocalRename;
friend class PropagateRemoteMove;
friend class PropagateUploadFileV1;
friend class PropagateUploadFileNG;
friend class PropagateBundle;
#endif
};
+3 -4
Ver Arquivo
@@ -44,10 +44,9 @@ QString ownCloudTheme::configFileName() const
QString ownCloudTheme::about() const
{
QString devString;
devString = trUtf8("<p>Version %2. For more information visit <a href=\"%3\">https://%4</a></p>"
"<p>For known issues and help, please visit: <a href=\"https://central.owncloud.org/c/help/desktop-file-sync\">https://central.owncloud.org</a></p>"
"<p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, "
" Jan-Christoph Borchardt, and others.</small></p>"
devString = trUtf8("<p>Version %2. For more information visit <a href=\"%3\">%4</a></p>"
"<p><small>By Klaas Freitag, Daniel Molkentin, Jan-Christoph Borchardt, "
"Olivier Goffart, Markus Götz and others.</small></p>"
"<p>Copyright ownCloud GmbH</p>"
"<p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>"
"ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH "
+7 -5
Ver Arquivo
@@ -28,11 +28,13 @@ QString Progress::asResultString( const SyncFileItem& item)
case CSYNC_INSTRUCTION_SYNC:
case CSYNC_INSTRUCTION_NEW:
case CSYNC_INSTRUCTION_TYPE_CHANGE:
if (item._direction != SyncFileItem::Up) {
return QCoreApplication::translate( "progress", "Downloaded");
} else {
return QCoreApplication::translate( "progress", "Uploaded");
}
if (item._type != SyncFileItem::Type::RequestsContainer) {
if (item._direction != SyncFileItem::Up) {
return QCoreApplication::translate( "progress", "Downloaded");
} else
return QCoreApplication::translate( "progress", "Uploaded");
}
return QCoreApplication::translate( "progress", "Processed Bundle");
case CSYNC_INSTRUCTION_CONFLICT:
return QCoreApplication::translate( "progress", "Server version downloaded, copied changed local file into conflict file");
case CSYNC_INSTRUCTION_REMOVE:
+2 -3
Ver Arquivo
@@ -593,7 +593,7 @@ void PropagateDownloadFile::deleteExistingFolder()
}
QString conflictDir = FileSystem::makeConflictFileName(
existingDir, Utility::qDateTimeFromTime_t(FileSystem::getModTime(existingDir)));
existingDir, Utility::qDateTimeFromTime_t(_item->_modtime));
emit _propagator->touchedFile(existingDir);
emit _propagator->touchedFile(conflictDir);
@@ -722,8 +722,7 @@ void PropagateDownloadFile::downloadFinished()
&& !FileSystem::fileEquals(fn, _tmpFile.fileName());
if (isConflict) {
QString renameError;
QString conflictFileName = FileSystem::makeConflictFileName(
fn, Utility::qDateTimeFromTime_t(FileSystem::getModTime(fn)));
QString conflictFileName = FileSystem::makeConflictFileName(fn, Utility::qDateTimeFromTime_t(_item->_modtime));
if (!FileSystem::rename(fn, conflictFileName, &renameError)) {
// If the rename fails, don't replace it.
+1 -4
Ver Arquivo
@@ -22,14 +22,11 @@ DeleteJob::DeleteJob(AccountPtr account, const QString& path, QObject* parent)
: AbstractNetworkJob(account, path, parent)
{ }
DeleteJob::DeleteJob(AccountPtr account, const QUrl& url, QObject* parent)
: AbstractNetworkJob(account, QString(), parent), _url(url)
{ }
void DeleteJob::start()
{
QNetworkRequest req;
setReply(_url.isValid() ? davRequest("DELETE", _url, req) : davRequest("DELETE", path(), req));
setReply(davRequest("DELETE", path(), req));
setupConnections(reply());
if( reply()->error() != QNetworkReply::NoError ) {
-2
Ver Arquivo
@@ -24,10 +24,8 @@ namespace OCC {
*/
class DeleteJob : public AbstractNetworkJob {
Q_OBJECT
QUrl _url; // Only used if the constructor taking a url is taken.
public:
explicit DeleteJob(AccountPtr account, const QString& path, QObject* parent = 0);
explicit DeleteJob(AccountPtr account, const QUrl& url, QObject* parent = 0);
void start() Q_DECL_OVERRIDE;
bool finished() Q_DECL_OVERRIDE;
+1 -9
Ver Arquivo
@@ -28,20 +28,12 @@ MoveJob::MoveJob(AccountPtr account, const QString& path,
: AbstractNetworkJob(account, path, parent), _destination(destination)
{ }
MoveJob::MoveJob(AccountPtr account, const QUrl& url, const QString &destination,
QMap<QByteArray, QByteArray> extraHeaders, QObject* parent)
: AbstractNetworkJob(account, QString(), parent), _destination(destination), _url(url)
, _extraHeaders(extraHeaders)
{ }
void MoveJob::start()
{
QNetworkRequest req;
req.setRawHeader("Destination", QUrl::toPercentEncoding(_destination, "/"));
for(auto it = _extraHeaders.constBegin(); it != _extraHeaders.constEnd(); ++it) {
req.setRawHeader(it.key(), it.value());
}
setReply(_url.isValid() ? davRequest("MOVE", _url, req) : davRequest("MOVE", path(), req));
setReply(davRequest("MOVE", path(), req));
setupConnections(reply());
if( reply()->error() != QNetworkReply::NoError ) {
-4
Ver Arquivo
@@ -25,12 +25,8 @@ namespace OCC {
class MoveJob : public AbstractNetworkJob {
Q_OBJECT
const QString _destination;
const QUrl _url; // Only used (instead of path) when the constructor taking an URL is used
QMap<QByteArray, QByteArray> _extraHeaders;
public:
explicit MoveJob(AccountPtr account, const QString& path, const QString &destination, QObject* parent = 0);
explicit MoveJob(AccountPtr account, const QUrl& url, const QString &destination,
QMap<QByteArray, QByteArray> _extraHeaders, QObject* parent = 0);
void start() Q_DECL_OVERRIDE;
bool finished() Q_DECL_OVERRIDE;
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+133 -128
Ver Arquivo
@@ -20,7 +20,6 @@
#include <QFile>
#include <QDebug>
namespace OCC {
class BandwidthManager;
@@ -89,17 +88,12 @@ private:
QScopedPointer<QIODevice> _device;
QMap<QByteArray, QByteArray> _headers;
QString _errorString;
QUrl _url;
public:
// Takes ownership of the device
explicit PUTFileJob(AccountPtr account, const QString& path, QIODevice *device,
const QMap<QByteArray, QByteArray> &headers, int chunk, QObject* parent = 0)
: AbstractNetworkJob(account, path, parent), _device(device), _headers(headers), _chunk(chunk) {}
explicit PUTFileJob(AccountPtr account, const QUrl& url, QIODevice *device,
const QMap<QByteArray, QByteArray> &headers, int chunk, QObject* parent = 0)
: AbstractNetworkJob(account, QString(), parent), _device(device), _headers(headers)
, _url(url), _chunk(chunk) {}
~PUTFileJob();
int _chunk;
@@ -128,6 +122,51 @@ private slots:
#endif
};
/**
* @brief The MultipartJob class
* @ingroup libsync
*/
class MultipartJob : public AbstractNetworkJob {
Q_OBJECT
private:
QVector<SyncFileItemPtr> _syncItems;
QHttpMultiPart* _multipart;
QString _errorString;
public:
QElapsedTimer _duration;
// Takes ownership of the device
explicit MultipartJob(AccountPtr account, const QString& path, QHttpMultiPart* multiPart, const QVector<SyncFileItemPtr> &syncItems,QObject* parent = 0)
: AbstractNetworkJob(account, path, parent), _syncItems(syncItems), _multipart(multiPart), _errorString(QString()) {}
~MultipartJob();
virtual void start() Q_DECL_OVERRIDE;
virtual bool finished() Q_DECL_OVERRIDE {
emit finishedSignal();
return true;
}
QString errorString() {
return _errorString.isEmpty() ? reply()->errorString() : _errorString;
}
QVector<SyncFileItemPtr> syncItems() const {
return _syncItems;
}
virtual void slotTimeout() Q_DECL_OVERRIDE;
signals:
void finishedSignal();
private slots:
#if QT_VERSION < 0x050402
void slotSoftAbort();
#endif
};
/**
* @brief This job implements the asynchronous PUT
*
@@ -160,90 +199,10 @@ signals:
};
/**
* @brief The PropagateUploadFileCommon class is the code common between all chunking algorithms
* @brief The PropagateUploadFile class
* @ingroup libsync
*
* State Machine:
*
* +---> start() --> (delete job) -------+
* | |
* +--> slotComputeContentChecksum() <---+
* |
* v
* slotComputeTransmissionChecksum()
* |
* v
* slotStartUpload() -> doStartUpload()
* .
* .
* v
* finalize() or abortWithError() or startPollJob()
*/
class PropagateUploadFileCommon : public PropagateItemJob {
Q_OBJECT
protected:
QElapsedTimer _duration;
QVector<AbstractNetworkJob*> _jobs; /// network jobs that are currently in transit
bool _finished; /// Tells that all the jobs have been finished
bool _deleteExisting;
// measure the performance of checksum calc and upload
Utility::StopWatch _stopWatch;
QByteArray _transmissionChecksum;
QByteArray _transmissionChecksumType;
public:
PropagateUploadFileCommon(OwncloudPropagator* propagator,const SyncFileItemPtr& item)
: PropagateItemJob(propagator, item), _finished(false), _deleteExisting(false) {}
/**
* Whether an existing entity with the same name may be deleted before
* the upload.
*
* Default: false.
*/
void setDeleteExisting(bool enabled);
void start() Q_DECL_OVERRIDE;
bool isLikelyFinishedQuickly() Q_DECL_OVERRIDE { return _item->_size < 100*1024; }
private slots:
void slotComputeContentChecksum();
// Content checksum computed, compute the transmission checksum
void slotComputeTransmissionChecksum(const QByteArray& contentChecksumType, const QByteArray& contentChecksum);
// transmission checksum computed, prepare the upload
void slotStartUpload(const QByteArray& transmissionChecksumType, const QByteArray& transmissionChecksum);
public:
virtual void doStartUpload() = 0;
void startPollJob(const QString& path);
void finalize();
void abortWithError(SyncFileItem::Status status, const QString &error);
public slots:
void abort() Q_DECL_OVERRIDE;
void slotJobDestroyed(QObject *job);
private slots:
void slotPollFinished();
protected:
// Bases headers that need to be sent with every chunk
QMap<QByteArray, QByteArray> headers();
};
/**
* @ingroup libsync
*
* Propagation job, impementing the old chunking agorithm
*
*/
class PropagateUploadFileV1 : public PropagateUploadFileCommon {
class PropagateUploadFile : public PropagateItemJob {
Q_OBJECT
private:
@@ -261,66 +220,112 @@ private:
int _currentChunk;
int _chunkCount; /// Total number of chunks for this file
int _transferId; /// transfer id (part of the url)
QElapsedTimer _duration;
QVector<AbstractNetworkJob*> _jobs; /// network jobs that are currently in transit
bool _finished; // Tells that all the jobs have been finished
// measure the performance of checksum calc and upload
Utility::StopWatch _stopWatch;
QByteArray _transmissionChecksum;
QByteArray _transmissionChecksumType;
bool _deleteExisting;
quint64 chunkSize() const { return _propagator->chunkSize(); }
public:
PropagateUploadFileV1(OwncloudPropagator* propagator,const SyncFileItemPtr& item) :
PropagateUploadFileCommon(propagator,item) {}
PropagateUploadFile(OwncloudPropagator* propagator,const SyncFileItemPtr& item)
: PropagateItemJob(propagator, item), _startChunk(0), _currentChunk(0), _chunkCount(0), _transferId(0), _finished(false), _deleteExisting(false) {}
void start() Q_DECL_OVERRIDE;
void doStartUpload() Q_DECL_OVERRIDE;
bool isLikelyFinishedQuickly() Q_DECL_OVERRIDE { return _item->_size < 100*1024; }
/**
* Whether an existing entity with the same name may be deleted before
* the upload.
*
* Default: false.
*/
void setDeleteExisting(bool enabled);
private slots:
void startNextChunk();
void slotPutFinished();
void slotPollFinished();
void slotUploadProgress(qint64,qint64);
void abort() Q_DECL_OVERRIDE;
void startNextChunk();
void finalize(const SyncFileItem&);
void slotJobDestroyed(QObject *job);
void slotStartUpload(const QByteArray& transmissionChecksumType, const QByteArray& transmissionChecksum);
void slotComputeTransmissionChecksum(const QByteArray& contentChecksumType, const QByteArray& contentChecksum);
void slotComputeContentChecksum();
private:
void startPollJob(const QString& path);
void abortWithError(SyncFileItem::Status status, const QString &error);
};
/**
* @brief The PropagateBundle class
* @ingroup libsync
*
* Propagation job, impementing the new chunking agorithm
*
*/
class PropagateUploadFileNG : public PropagateUploadFileCommon {
class PropagateBundle : public PropagateItemJob {
Q_OBJECT
private:
quint64 _sent; /// amount of data (bytes) that was already sent
uint _transferId; /// transfer id (part of the url)
int _currentChunk; /// Id of the next chunk that will be sent
bool _removeJobError; /// If not null, there was an error removing the job
// Map chunk number with its size from the PROPFIND on resume.
// (Only used from slotPropfindIterate/slotPropfindFinished because the LsColJob use signals to report data.)
QMap<int, quint64> _serverChunks;
quint64 chunkSize() const { return _propagator->chunkSize(); }
/**
* Return the URL of a chunk.
* If chunk == -1, returns the URL of the parent folder containing the chunks
*/
QUrl chunkUrl(int chunk = -1);
QLinkedList<SyncFileItemPtr> _itemsToSync;
QLinkedList<SyncFileItemPtr> _itemsToChecksum;
QVector<MultipartJob*> _jobs; /// network jobs that are currently in transit
bool _running; // Tells that all the jobs have been finished
bool _preparingBundle; // Tells that all the jobs have been finished
quint64 _size; // Tells what is the total size of _itemsToSync inside the bundle
quint64 _currentBundleSize;
quint64 _currentRequestsNumber;
quint64 _totalFiles;
// measure the performance of checksum calc and upload
Utility::StopWatch _stopWatch;
public:
PropagateUploadFileNG(OwncloudPropagator* propagator,const SyncFileItemPtr& item) :
PropagateUploadFileCommon(propagator,item) {}
void doStartUpload() Q_DECL_OVERRIDE;
private:
void startNewUpload();
void startNextChunk();
PropagateBundle(OwncloudPropagator* propagator)
: PropagateItemJob(propagator, SyncFileItemPtr(new SyncFileItem)), _running(false), _preparingBundle(true), _size(0), _currentBundleSize(0), _currentRequestsNumber(0) {}
void start() Q_DECL_OVERRIDE;
void startBundle();
void append(const SyncFileItemPtr &bundledFile);
QByteArray getRemotePath(QString filePath);
bool empty();
bool scheduleNextJob() Q_DECL_OVERRIDE {
if (_running != true && _propagator->runningNowAtRootJob() == 1){
_running = true;
QMetaObject::invokeMethod(this, "start"); // We could be in a different thread (neon jobs)
}
if (_state == NotYetStarted){
_state = Running;
return true;
}
return false;
}
quint64 syncItemsSize() const { return _size; }
quint64 syncItemsNumber() const { return _itemsToChecksum.count(); }
private slots:
void slotPropfindFinished();
void slotPropfindFinishedWithError();
void slotPropfindIterate(const QString &name, const QMap<QString,QString> &properties);
void slotDeleteJobFinished();
void slotMkColFinished(QNetworkReply::NetworkError);
void slotPutFinished();
void slotMoveJobFinished();
void slotUploadProgress(qint64,qint64);
};
void slotComputeTransmissionChecksum();
void slotStartUpload(const QByteArray& transmissionChecksumType, const QByteArray& transmissionChecksum);
void slotMultipartFinished();
void slotJobDestroyed(QObject *job);
void abortWithError(SyncFileItem::Status status, const QString &error);
private:
quint64 checkBundledRequestsLimits()
{
//TODO: obtain this value from the server or by other means
quint64 maximumBundledFiles = 500;
return (maximumBundledFiles/_propagator->maximumActiveJob());
}
quint64 chunkSize() const { return _propagator->chunkSize(); }
int getHttpStatusCode(const QString &status);
void slotItemFinished(const SyncFileItemPtr &item, QMap<QString, QMap<QString, QString> > &responseObjectsProperties);
void itemDone(SyncFileItemPtr item, SyncFileItem::Status status, const QString &errorString = QString());
};
}
-488
Ver Arquivo
@@ -1,488 +0,0 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@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 "config.h"
#include "propagateupload.h"
#include "owncloudpropagator_p.h"
#include "networkjobs.h"
#include "account.h"
#include "syncjournaldb.h"
#include "syncjournalfilerecord.h"
#include "utility.h"
#include "filesystem.h"
#include "propagatorjobs.h"
#include "syncengine.h"
#include "propagateremotemove.h"
#include "propagateremotedelete.h"
#include <QNetworkAccessManager>
#include <QFileInfo>
#include <QDir>
#include <cmath>
#include <cstring>
namespace OCC {
QUrl PropagateUploadFileNG::chunkUrl(int chunk)
{
QString path = QLatin1String("remote.php/dav/uploads/")
+ _propagator->account()->user()
+ QLatin1Char('/') + QString::number(_transferId);
if (chunk >= 0) {
path += QLatin1Char('/') + QString::number(chunk);
}
return Utility::concatUrlPath(_propagator->account()->url(), path);
}
/*
State machine:
*----> doStartUpload()
Check the db: is there an entry?
/ \
no yes
/ \
/ PROPFIND
startNewUpload() <-+ +----------------------------\
| | | \
MKCOL + slotPropfindFinishedWithError() slotPropfindFinished()
| Is there stale files to remove?
slotMkColFinished() | |
| no yes
| | |
| | DeleteJob
| | |
+-----+<------------------------------------------------------+<--- slotDeleteJobFinished()
|
+----> startNextChunk() ---finished? --+
^ | |
+---------------+ |
|
+----------------------------------------+
|
+-> MOVE ------> moveJobFinished() ---> finalize()
*/
void PropagateUploadFileNG::doStartUpload()
{
_duration.start();
_propagator->_activeJobList.append(this);
const SyncJournalDb::UploadInfo progressInfo = _propagator->_journal->getUploadInfo(_item->_file);
if (progressInfo._valid && Utility::qDateTimeToTime_t(progressInfo._modtime) == _item->_modtime ) {
_transferId = progressInfo._transferid;
auto url = chunkUrl();
auto job = new LsColJob(_propagator->account(), url, this);
_jobs.append(job);
job->setProperties(QList<QByteArray>() << "resourcetype" << "getcontentlength");
connect(job, SIGNAL(finishedWithoutError()), this, SLOT(slotPropfindFinished()));
connect(job, SIGNAL(finishedWithError(QNetworkReply*)),
this, SLOT(slotPropfindFinishedWithError()));
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
connect(job, SIGNAL(directoryListingIterated(QString,QMap<QString,QString>)),
this, SLOT(slotPropfindIterate(QString,QMap<QString,QString>)));
job->start();
return;
}
startNewUpload();
}
void PropagateUploadFileNG::slotPropfindIterate(const QString &name, const QMap<QString,QString> &properties)
{
if (name == chunkUrl().path()) {
return; // skip the info about the path itself
}
bool ok = false;
auto chunkId = name.mid(name.lastIndexOf('/')+1).toUInt(&ok);
if (ok) {
_serverChunks[chunkId] = properties["getcontentlength"].toULongLong();
}
}
void PropagateUploadFileNG::slotPropfindFinished()
{
auto job = qobject_cast<LsColJob *>(sender());
slotJobDestroyed(job); // remove it from the _jobs list
_propagator->_activeJobList.removeOne(this);
_currentChunk = 0;
_sent = 0;
while (_serverChunks.contains(_currentChunk)) {
_sent += _serverChunks[_currentChunk];
_serverChunks.remove(_currentChunk);
++_currentChunk;
}
if (_sent > _item->_size) {
// Normally this can't happen because the size is xor'ed with the transfer id, and it is
// therefore impossible that there is more data on the server than on the file.
qWarning() << "Inconsistency while resuming " << _item->_file
<< ": the size on the server (" << _sent << ") is bigger than the size of the file ("
<< _item->_size << ")";
startNewUpload();
return;
}
qDebug() << "Resuming "<< _item->_file << " from chunk " << _currentChunk << "; sent ="<< _sent;
if (!_serverChunks.isEmpty()) {
qDebug() << "To Delete" << _serverChunks;
_propagator->_activeJobList.append(this);
_removeJobError = false;
// Make sure that if there is a "hole" and then a few more chunks, on the server
// we should remove the later chunks. Otherwise when we do dynamic chunk sizing, we may end up
// with corruptions if there are too many chunks, or if we abort and there are still stale chunks.
for (auto it = _serverChunks.begin(); it != _serverChunks.end(); ++it) {
auto job = new DeleteJob(_propagator->account(), Utility::concatUrlPath(chunkUrl(), QString::number(it.key())), this);
QObject::connect(job, SIGNAL(finishedSignal()), this, SLOT(slotDeleteJobFinished()));
_jobs.append(job);
job->start();
}
return;
}
startNextChunk();
}
void PropagateUploadFileNG::slotPropfindFinishedWithError()
{
auto job = qobject_cast<LsColJob *>(sender());
slotJobDestroyed(job); // remove it from the _jobs list
QNetworkReply::NetworkError err = job->reply()->error();
auto httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
auto status = classifyError(err, httpErrorCode, &_propagator->_anotherSyncNeeded);
if (status == SyncFileItem::FatalError) {
_propagator->_activeJobList.removeOne(this);
QString errorString = errorMessage(job->reply()->errorString(), job->reply()->readAll());
abortWithError(status, errorString);
return;
}
startNewUpload();
}
void PropagateUploadFileNG::slotDeleteJobFinished()
{
auto job = qobject_cast<DeleteJob *>(sender());
Q_ASSERT(job);
_jobs.remove(_jobs.indexOf(job));
QNetworkReply::NetworkError err = job->reply()->error();
if (err != QNetworkReply::NoError && err != QNetworkReply::ContentNotFoundError) {
const int httpStatus = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
SyncFileItem::Status status = classifyError(err, httpStatus);
if (status == SyncFileItem::FatalError) {
abortWithError(status, job->errorString());
return;
} else {
qWarning() << "DeleteJob errored out" << job->errorString() << job->reply()->url();
_removeJobError = true;
// Let the other jobs finish
}
}
if (_jobs.isEmpty()) {
_propagator->_activeJobList.removeOne(this);
if (_removeJobError) {
// There was an error removing some files, just start over
startNewUpload();
} else {
startNextChunk();
}
}
}
void PropagateUploadFileNG::startNewUpload()
{
Q_ASSERT(_propagator->_activeJobList.count(this) == 1);
_transferId = qrand() ^ _item->_modtime ^ (_item->_size << 16) ^ qHash(_item->_file);
_sent = 0;
_currentChunk = 0;
emit progress(*_item, 0);
SyncJournalDb::UploadInfo pi;
pi._valid = true;
pi._transferid = _transferId;
pi._modtime = Utility::qDateTimeFromTime_t(_item->_modtime);
_propagator->_journal->setUploadInfo(_item->_file, pi);
_propagator->_journal->commit("Upload info");
QMap<QByteArray, QByteArray> headers;
headers["OC-Total-Length"] = QByteArray::number(_item->_size);
auto job = new MkColJob(_propagator->account(), chunkUrl(), headers, this);
connect(job, SIGNAL(finished(QNetworkReply::NetworkError)),
this, SLOT(slotMkColFinished(QNetworkReply::NetworkError)));
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
job->start();
}
void PropagateUploadFileNG::slotMkColFinished(QNetworkReply::NetworkError)
{
_propagator->_activeJobList.removeOne(this);
auto job = qobject_cast<MkColJob *>(sender());
slotJobDestroyed(job); // remove it from the _jobs list
QNetworkReply::NetworkError err = job->reply()->error();
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (err != QNetworkReply::NoError || _item->_httpErrorCode != 201) {
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
&_propagator->_anotherSyncNeeded);
QString errorString = errorMessage(job->reply()->errorString(), job->reply()->readAll());
if (job->reply()->hasRawHeader("OC-ErrorString")) {
errorString = job->reply()->rawHeader("OC-ErrorString");
}
abortWithError(status, errorString);
return;
}
startNextChunk();
}
void PropagateUploadFileNG::startNextChunk()
{
if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
return;
quint64 fileSize = _item->_size;
Q_ASSERT(fileSize >= _sent);
quint64 currentChunkSize = qMin(chunkSize(), fileSize - _sent);
if (currentChunkSize == 0) {
Q_ASSERT(_jobs.isEmpty()); // There should be no running job anymore
_finished = true;
// Finish with a MOVE
QString destination = _propagator->account()->url().path()
+ QLatin1String("/remote.php/dav/files/") + _propagator->account()->user()
+ _propagator->_remoteFolder + _item->_file;
auto headers = PropagateUploadFileCommon::headers();
// "If-Match applies to the source, but we are interested in comparing the etag of the destination
auto ifMatch = headers.take("If-Match");
if (!ifMatch.isEmpty()) {
headers["If"] = "<" + destination.toUtf8() + "> ([" + ifMatch + "])";
}
if (!_transmissionChecksumType.isEmpty()) {
headers[checkSumHeaderC] = makeChecksumHeader(
_transmissionChecksumType, _transmissionChecksum);
}
auto job = new MoveJob(_propagator->account(), Utility::concatUrlPath(chunkUrl(), "/.file"),
destination, headers, this);
_jobs.append(job);
connect(job, SIGNAL(finishedSignal()), this, SLOT(slotMoveJobFinished()));
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
_propagator->_activeJobList.append(this);
job->start();
return;
}
auto device = new UploadDevice(&_propagator->_bandwidthManager);
const QString fileName = _propagator->getFilePath(_item->_file);
if (! device->prepareAndOpen(fileName, _sent, currentChunkSize)) {
qDebug() << "ERR: Could not prepare upload device: " << device->errorString();
// If the file is currently locked, we want to retry the sync
// when it becomes available again.
if (FileSystem::isFileLocked(fileName)) {
emit _propagator->seenLockedFile(fileName);
}
// Soft error because this is likely caused by the user modifying his files while syncing
abortWithError( SyncFileItem::SoftError, device->errorString() );
return;
}
QMap<QByteArray, QByteArray> headers;
headers["OC-Chunk-Offset"] = QByteArray::number(_sent);
_sent += currentChunkSize;
QUrl url = chunkUrl(_currentChunk);
// job takes ownership of device via a QScopedPointer. Job deletes itself when finishing
PUTFileJob* job = new PUTFileJob(_propagator->account(), url, device, headers, _currentChunk);
_jobs.append(job);
connect(job, SIGNAL(finishedSignal()), this, SLOT(slotPutFinished()));
connect(job, SIGNAL(uploadProgress(qint64,qint64)),
this, SLOT(slotUploadProgress(qint64,qint64)));
connect(job, SIGNAL(uploadProgress(qint64,qint64)),
device, SLOT(slotJobUploadProgress(qint64,qint64)));
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
job->start();
_propagator->_activeJobList.append(this);
_currentChunk++;
// FIXME! parallel chunk?
}
void PropagateUploadFileNG::slotPutFinished()
{
PUTFileJob *job = qobject_cast<PUTFileJob *>(sender());
Q_ASSERT(job);
slotJobDestroyed(job); // remove it from the _jobs list
qDebug() << job->reply()->request().url() << "FINISHED WITH STATUS"
<< job->reply()->error()
<< (job->reply()->error() == QNetworkReply::NoError ? QLatin1String("") : job->reply()->errorString())
<< job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute)
<< job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
_propagator->_activeJobList.removeOne(this);
if (_finished) {
// We have sent the finished signal already. We don't need to handle any remaining jobs
return;
}
QNetworkReply::NetworkError err = job->reply()->error();
#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2)
if (err == QNetworkReply::OperationCanceledError && job->reply()->property("owncloud-should-soft-cancel").isValid()) {
// Abort the job and try again later.
// This works around a bug in QNAM wich might reuse a non-empty buffer for the next request.
qDebug() << "Forcing job abort on HTTP connection reset with Qt < 5.4.2.";
_propagator->_anotherSyncNeeded = true;
abortWithError(SyncFileItem::SoftError, tr("Forcing job abort on HTTP connection reset with Qt < 5.4.2."));
return;
}
#endif
if (err != QNetworkReply::NoError) {
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QByteArray replyContent = job->reply()->readAll();
qDebug() << replyContent; // display the XML error in the debug
QString errorString = errorMessage(job->errorString(), replyContent);
if (job->reply()->hasRawHeader("OC-ErrorString")) {
errorString = job->reply()->rawHeader("OC-ErrorString");
}
// FIXME! can this happen for the chunks?
if (_item->_httpErrorCode == 412) {
// Precondition Failed: Maybe the bad etag is in the database, we need to clear the
// parent folder etag so we won't read from DB next sync.
_propagator->_journal->avoidReadFromDbOnNextSync(_item->_file);
_propagator->_anotherSyncNeeded = true;
}
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
&_propagator->_anotherSyncNeeded);
abortWithError(status, errorString);
return;
}
Q_ASSERT(_sent <= _item->_size);
bool finished = _sent == _item->_size;
// Check if the file still exists
const QString fullFilePath(_propagator->getFilePath(_item->_file));
if( !FileSystem::fileExists(fullFilePath) ) {
if (!finished) {
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
return;
} else {
_propagator->_anotherSyncNeeded = true;
}
}
// Check whether the file changed since discovery.
if (! FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
_propagator->_anotherSyncNeeded = true;
if( !finished ) {
abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync."));
return;
}
}
if (!finished) {
// Deletes an existing blacklist entry on successful chunk upload
if (_item->_hasBlacklistEntry) {
_propagator->_journal->wipeErrorBlacklistEntry(_item->_file);
_item->_hasBlacklistEntry = false;
}
}
startNextChunk();
}
void PropagateUploadFileNG::slotMoveJobFinished()
{
_propagator->_activeJobList.removeOne(this);
auto job = qobject_cast<MoveJob *>(sender());
slotJobDestroyed(job); // remove it from the _jobs list
QNetworkReply::NetworkError err = job->reply()->error();
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (err != QNetworkReply::NoError) {
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
&_propagator->_anotherSyncNeeded);
QString errorString = errorMessage(job->errorString(), job->reply()->readAll());
abortWithError(status, errorString);
return;
}
if (_item->_httpErrorCode != 201 && _item->_httpErrorCode != 204) {
abortWithError(SyncFileItem::NormalError, tr("Unexpected return code from server (%1)").arg(_item->_httpErrorCode));
return;
}
QByteArray fid = job->reply()->rawHeader("OC-FileID");
if(fid.isEmpty()) {
qWarning() << "Server did not return a OC-FileID" << _item->_file;
abortWithError(SyncFileItem::NormalError, tr("Missing File ID from server"));
return;
} else {
// the old file id should only be empty for new files uploaded
if( !_item->_fileId.isEmpty() && _item->_fileId != fid ) {
qDebug() << "WARN: File ID changed!" << _item->_fileId << fid;
}
_item->_fileId = fid;
}
_item->_etag = getEtagFromReply(job->reply());;
if (_item->_etag.isEmpty()) {
qWarning() << "Server did not return an ETAG" << _item->_file;
abortWithError(SyncFileItem::NormalError, tr("Missing ETag from server"));
return;
}
_item->_responseTimeStamp = job->responseTimestamp();
// performance logging
_item->_requestDuration = _stopWatch.stop();
qDebug() << "*==* duration UPLOAD" << _item->_size
<< _stopWatch.durationOfLap(QLatin1String("ContentChecksum"))
<< _stopWatch.durationOfLap(QLatin1String("TransmissionChecksum"))
<< _item->_requestDuration;
// The job might stay alive for the whole sync, release this tiny bit of memory.
_stopWatch.reset();
finalize();
}
void PropagateUploadFileNG::slotUploadProgress(qint64 sent, qint64 total)
{
// Completion is signaled with sent=0, total=0; avoid accidentally
// resetting progress due to the sent being zero by ignoring it.
// finishedSignal() is bound to be emitted soon anyway.
// See https://bugreports.qt.io/browse/QTBUG-44782.
if (sent == 0 && total == 0) {
return;
}
emit progress(*_item, _sent + sent - total);
}
}
-375
Ver Arquivo
@@ -1,375 +0,0 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@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 "config.h"
#include "propagateupload.h"
#include "owncloudpropagator_p.h"
#include "networkjobs.h"
#include "account.h"
#include "syncjournaldb.h"
#include "syncjournalfilerecord.h"
#include "utility.h"
#include "filesystem.h"
#include "propagatorjobs.h"
#include "checksums.h"
#include "syncengine.h"
#include "propagateremotedelete.h"
#include <json.h>
#include <QNetworkAccessManager>
#include <QFileInfo>
#include <QDir>
#include <cmath>
#include <cstring>
namespace OCC {
void PropagateUploadFileV1::doStartUpload()
{
_chunkCount = std::ceil(_item->_size / double(chunkSize()));
_startChunk = 0;
_transferId = qrand() ^ _item->_modtime ^ (_item->_size << 16);
const SyncJournalDb::UploadInfo progressInfo = _propagator->_journal->getUploadInfo(_item->_file);
if (progressInfo._valid && Utility::qDateTimeToTime_t(progressInfo._modtime) == _item->_modtime ) {
_startChunk = progressInfo._chunk;
_transferId = progressInfo._transferid;
qDebug() << Q_FUNC_INFO << _item->_file << ": Resuming from chunk " << _startChunk;
}
_currentChunk = 0;
_duration.start();
emit progress(*_item, 0);
startNextChunk();
}
void PropagateUploadFileV1::startNextChunk()
{
if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
return;
if (! _jobs.isEmpty() && _currentChunk + _startChunk >= _chunkCount - 1) {
// Don't do parallel upload of chunk if this might be the last chunk because the server cannot handle that
// https://github.com/owncloud/core/issues/11106
// We return now and when the _jobs are finished we will proceed with the last chunk
// NOTE: Some other parts of the code such as slotUploadProgress also assume that the last chunk
// is sent last.
return;
}
quint64 fileSize = _item->_size;
auto headers = PropagateUploadFileCommon::headers();
headers["OC-Total-Length"] = QByteArray::number(fileSize);
headers["OC-Chunk-Size"]= QByteArray::number(quint64(chunkSize()));
QString path = _item->_file;
UploadDevice *device = new UploadDevice(&_propagator->_bandwidthManager);
qint64 chunkStart = 0;
qint64 currentChunkSize = fileSize;
bool isFinalChunk = false;
if (_chunkCount > 1) {
int sendingChunk = (_currentChunk + _startChunk) % _chunkCount;
// XOR with chunk size to make sure everything goes well if chunk size changes between runs
uint transid = _transferId ^ chunkSize();
qDebug() << "Upload chunk" << sendingChunk << "of" << _chunkCount << "transferid(remote)=" << transid;
path += QString("-chunking-%1-%2-%3").arg(transid).arg(_chunkCount).arg(sendingChunk);
headers["OC-Chunked"] = "1";
chunkStart = chunkSize() * quint64(sendingChunk);
currentChunkSize = chunkSize();
if (sendingChunk == _chunkCount - 1) { // last chunk
currentChunkSize = (fileSize % chunkSize());
if( currentChunkSize == 0 ) { // if the last chunk pretends to be 0, its actually the full chunk size.
currentChunkSize = chunkSize();
}
isFinalChunk = true;
}
} else {
// if there's only one chunk, it's the final one
isFinalChunk = true;
}
if (isFinalChunk && !_transmissionChecksumType.isEmpty()) {
headers[checkSumHeaderC] = makeChecksumHeader(
_transmissionChecksumType, _transmissionChecksum);
}
const QString fileName = _propagator->getFilePath(_item->_file);
if (! device->prepareAndOpen(fileName, chunkStart, currentChunkSize)) {
qDebug() << "ERR: Could not prepare upload device: " << device->errorString();
// If the file is currently locked, we want to retry the sync
// when it becomes available again.
if (FileSystem::isFileLocked(fileName)) {
emit _propagator->seenLockedFile(fileName);
}
// Soft error because this is likely caused by the user modifying his files while syncing
abortWithError( SyncFileItem::SoftError, device->errorString() );
delete device;
return;
}
// job takes ownership of device via a QScopedPointer. Job deletes itself when finishing
PUTFileJob* job = new PUTFileJob(_propagator->account(), _propagator->_remoteFolder + path, device, headers, _currentChunk);
_jobs.append(job);
connect(job, SIGNAL(finishedSignal()), this, SLOT(slotPutFinished()));
connect(job, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(slotUploadProgress(qint64,qint64)));
connect(job, SIGNAL(uploadProgress(qint64,qint64)), device, SLOT(slotJobUploadProgress(qint64,qint64)));
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
job->start();
_propagator->_activeJobList.append(this);
_currentChunk++;
bool parallelChunkUpload = true;
QByteArray env = qgetenv("OWNCLOUD_PARALLEL_CHUNK");
if (!env.isEmpty()) {
parallelChunkUpload = env != "false" && env != "0";
} else {
int versionNum = _propagator->account()->serverVersionInt();
if (versionNum < 0x080003) {
// Disable parallel chunk upload severs older than 8.0.3 to avoid too many
// internal sever errors (#2743, #2938)
parallelChunkUpload = false;
}
}
if (_currentChunk + _startChunk >= _chunkCount - 1) {
// Don't do parallel upload of chunk if this might be the last chunk because the server cannot handle that
// https://github.com/owncloud/core/issues/11106
parallelChunkUpload = false;
}
if (parallelChunkUpload && (_propagator->_activeJobList.count() < _propagator->maximumActiveJob())
&& _currentChunk < _chunkCount ) {
startNextChunk();
}
if (!parallelChunkUpload || _chunkCount - _currentChunk <= 0) {
emit ready();
}
}
void PropagateUploadFileV1::slotPutFinished()
{
PUTFileJob *job = qobject_cast<PUTFileJob *>(sender());
Q_ASSERT(job);
slotJobDestroyed(job); // remove it from the _jobs list
qDebug() << Q_FUNC_INFO << job->reply()->request().url() << "FINISHED WITH STATUS"
<< job->reply()->error()
<< (job->reply()->error() == QNetworkReply::NoError ? QLatin1String("") : job->reply()->errorString())
<< job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute)
<< job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
_propagator->_activeJobList.removeOne(this);
if (_finished) {
// We have sent the finished signal already. We don't need to handle any remaining jobs
return;
}
QNetworkReply::NetworkError err = job->reply()->error();
#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2)
if (err == QNetworkReply::OperationCanceledError && job->reply()->property("owncloud-should-soft-cancel").isValid()) { // Abort the job and try again later.
// This works around a bug in QNAM wich might reuse a non-empty buffer for the next request.
qDebug() << "Forcing job abort on HTTP connection reset with Qt < 5.4.2.";
_propagator->_anotherSyncNeeded = true;
abortWithError(SyncFileItem::SoftError, tr("Forcing job abort on HTTP connection reset with Qt < 5.4.2."));
return;
}
#endif
if (err != QNetworkReply::NoError) {
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(checkForProblemsWithShared(_item->_httpErrorCode,
tr("The file was edited locally but is part of a read only share. "
"It is restored and your edit is in the conflict file."))) {
return;
}
QByteArray replyContent = job->reply()->readAll();
qDebug() << replyContent; // display the XML error in the debug
QString errorString = errorMessage(job->errorString(), replyContent);
if (job->reply()->hasRawHeader("OC-ErrorString")) {
errorString = job->reply()->rawHeader("OC-ErrorString");
}
if (_item->_httpErrorCode == 412) {
// Precondition Failed: Maybe the bad etag is in the database, we need to clear the
// parent folder etag so we won't read from DB next sync.
_propagator->_journal->avoidReadFromDbOnNextSync(_item->_file);
_propagator->_anotherSyncNeeded = true;
}
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
&_propagator->_anotherSyncNeeded);
abortWithError(status, errorString);
return;
}
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// The server needs some time to process the request and provide us with a poll URL
if (_item->_httpErrorCode == 202) {
_finished = true;
QString path = QString::fromUtf8(job->reply()->rawHeader("OC-Finish-Poll"));
if (path.isEmpty()) {
done(SyncFileItem::NormalError, tr("Poll URL missing"));
return;
}
startPollJob(path);
return;
}
// Check the file again post upload.
// Two cases must be considered separately: If the upload is finished,
// the file is on the server and has a changed ETag. In that case,
// the etag has to be properly updated in the client journal, and because
// of that we can bail out here with an error. But we can reschedule a
// sync ASAP.
// But if the upload is ongoing, because not all chunks were uploaded
// yet, the upload can be stopped and an error can be displayed, because
// the server hasn't registered the new file yet.
QByteArray etag = getEtagFromReply(job->reply());
bool finished = etag.length() > 0;
// Check if the file still exists
const QString fullFilePath(_propagator->getFilePath(_item->_file));
if( !FileSystem::fileExists(fullFilePath) ) {
if (!finished) {
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
return;
} else {
_propagator->_anotherSyncNeeded = true;
}
}
// Check whether the file changed since discovery.
if (! FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
_propagator->_anotherSyncNeeded = true;
if( !finished ) {
abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync."));
// FIXME: the legacy code was retrying for a few seconds.
// and also checking that after the last chunk, and removed the file in case of INSTRUCTION_NEW
return;
}
}
if (!finished) {
// Proceed to next chunk.
if (_currentChunk >= _chunkCount) {
if (!_jobs.empty()) {
// just wait for the other job to finish.
return;
}
_finished = true;
done(SyncFileItem::NormalError, tr("The server did not acknowledge the last chunk. (No e-tag was present)"));
return;
}
// Deletes an existing blacklist entry on successful chunk upload
if (_item->_hasBlacklistEntry) {
_propagator->_journal->wipeErrorBlacklistEntry(_item->_file);
_item->_hasBlacklistEntry = false;
}
SyncJournalDb::UploadInfo pi;
pi._valid = true;
auto currentChunk = job->_chunk;
foreach (auto *job, _jobs) {
// Take the minimum finished one
if (auto putJob = qobject_cast<PUTFileJob*>(job)) {
currentChunk = qMin(currentChunk, putJob->_chunk - 1);
}
}
pi._chunk = (currentChunk + _startChunk + 1) % _chunkCount ; // next chunk to start with
pi._transferid = _transferId;
pi._modtime = Utility::qDateTimeFromTime_t(_item->_modtime);
_propagator->_journal->setUploadInfo(_item->_file, pi);
_propagator->_journal->commit("Upload info");
startNextChunk();
return;
}
// the following code only happens after all chunks were uploaded.
_finished = true;
// the file id should only be empty for new files up- or downloaded
QByteArray fid = job->reply()->rawHeader("OC-FileID");
if( !fid.isEmpty() ) {
if( !_item->_fileId.isEmpty() && _item->_fileId != fid ) {
qDebug() << "WARN: File ID changed!" << _item->_fileId << fid;
}
_item->_fileId = fid;
}
_item->_etag = etag;
_item->_responseTimeStamp = job->responseTimestamp();
if (job->reply()->rawHeader("X-OC-MTime") != "accepted") {
// X-OC-MTime is supported since owncloud 5.0. But not when chunking.
// Normally Owncloud 6 always puts X-OC-MTime
qWarning() << "Server does not support X-OC-MTime" << job->reply()->rawHeader("X-OC-MTime");
// Well, the mtime was not set
done(SyncFileItem::SoftError, "Server does not support X-OC-MTime");
}
// performance logging
_item->_requestDuration = _stopWatch.stop();
qDebug() << "*==* duration UPLOAD" << _item->_size
<< _stopWatch.durationOfLap(QLatin1String("ContentChecksum"))
<< _stopWatch.durationOfLap(QLatin1String("TransmissionChecksum"))
<< _item->_requestDuration;
// The job might stay alive for the whole sync, release this tiny bit of memory.
_stopWatch.reset();
finalize();
}
void PropagateUploadFileV1::slotUploadProgress(qint64 sent, qint64 total)
{
// Completion is signaled with sent=0, total=0; avoid accidentally
// resetting progress due to the sent being zero by ignoring it.
// finishedSignal() is bound to be emitted soon anyway.
// See https://bugreports.qt.io/browse/QTBUG-44782.
if (sent == 0 && total == 0) {
return;
}
int progressChunk = _currentChunk + _startChunk - 1;
if (progressChunk >= _chunkCount)
progressChunk = _currentChunk - 1;
// amount is the number of bytes already sent by all the other chunks that were sent
// not including this one.
// FIXME: this assumes all chunks have the same size, which is true only if the last chunk
// has not been finished (which should not happen because the last chunk is sent sequentially)
quint64 amount = progressChunk * chunkSize();
sender()->setProperty("byteWritten", sent);
if (_jobs.count() > 1) {
amount -= (_jobs.count() -1) * chunkSize();
foreach (QObject *j, _jobs) {
amount += j->property("byteWritten").toULongLong();
}
} else {
// sender() is the only current job, no need to look at the byteWritten properties
amount += sent;
}
emit progress(*_item, amount);
}
}
+3 -20
Ver Arquivo
@@ -334,16 +334,6 @@ int SyncEngine::treewalkRemote( TREE_WALK_FILE* file, void *data )
return static_cast<SyncEngine*>(data)->treewalkFile( file, true );
}
/**
* The main function in the post-reconcile phase.
*
* Called on each entry in the local and remote trees by
* csync_walk_local_tree()/csync_walk_remote_tree().
*
* It merges the two csync rbtrees into a single map of SyncFileItems.
*
* See doc/dev/sync-algorithm.md for an overview.
*/
int SyncEngine::treewalkFile( TREE_WALK_FILE *file, bool remote )
{
if( ! file ) return -1;
@@ -599,15 +589,11 @@ int SyncEngine::treewalkFile( TREE_WALK_FILE *file, bool remote )
// This counts as a NONE for detecting if all the files on the server were changed
_hasNoneFiles = true;
} else if (!isDirectory) {
auto difftime = std::difftime(file->modtime, file->other.modtime);
if (difftime < -3600*2) {
if (std::difftime(file->modtime, file->other.modtime) < 0) {
// We are going back on time
// We only increment if the difference is more than two hours to avoid clock skew
// issues or DST changes. (We simply ignore files that goes in the past less than
// two hours for the backup detection heuristics.)
_backInTimeFiles++;
qDebug() << file->path << "has a timestamp earlier than the local file";
} else if (difftime > 0) {
} else {
_hasForwardInTimeFiles = true;
}
}
@@ -942,10 +928,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
&& _discoveryMainThread->_dataFingerprint != databaseFingerprint) {
qDebug() << "data fingerprint changed, assume restore from backup" << databaseFingerprint << _discoveryMainThread->_dataFingerprint;
restoreOldFiles();
} else if (!_hasForwardInTimeFiles && _backInTimeFiles >= 2 && _account->serverVersionInt() < 0x090100) {
// The server before ownCloud 9.1 did not have the data-fingerprint property. So in that
// case we use heuristics to detect restored backup. This is disabled with newer version
// because this causes troubles to the user and is not as reliable as the data-fingerprint.
} else if (!_hasForwardInTimeFiles && _backInTimeFiles >= 2) {
qDebug() << "All the changes are bringing files in the past, asking the user";
// this typically happen when a backup is restored on the server
bool restore = false;
+1
Ver Arquivo
@@ -45,6 +45,7 @@ public:
enum Type {
UnknownType = 0,
RequestsContainer = 1,
File = CSYNC_FTW_TYPE_FILE,
Directory = CSYNC_FTW_TYPE_DIR,
SoftLink = CSYNC_FTW_TYPE_SLINK
+7 -7
Ver Arquivo
@@ -17,12 +17,12 @@
namespace OCC {
SyncFileStatus::SyncFileStatus()
:_tag(StatusNone), _shared(false)
:_tag(StatusNone), _sharedWithMe(false)
{
}
SyncFileStatus::SyncFileStatus(SyncFileStatusTag tag)
:_tag(tag), _shared(false)
:_tag(tag), _sharedWithMe(false)
{
}
@@ -37,14 +37,14 @@ SyncFileStatus::SyncFileStatusTag SyncFileStatus::tag() const
return _tag;
}
void SyncFileStatus::setShared(bool isShared)
void SyncFileStatus::setSharedWithMe(bool isShared)
{
_shared = isShared;
_sharedWithMe = isShared;
}
bool SyncFileStatus::shared() const
bool SyncFileStatus::sharedWithMe() const
{
return _shared;
return _sharedWithMe;
}
QString SyncFileStatus::toSocketAPIString() const
@@ -72,7 +72,7 @@ QString SyncFileStatus::toSocketAPIString() const
statusString = QLatin1String("ERROR");
break;
}
if(canBeShared && _shared) {
if(canBeShared && _sharedWithMe) {
statusString += QLatin1String("+SWM");
}
+4 -4
Ver Arquivo
@@ -43,18 +43,18 @@ public:
void set(SyncFileStatusTag tag);
SyncFileStatusTag tag() const;
void setShared( bool isShared );
bool shared() const;
void setSharedWithMe( bool isShared );
bool sharedWithMe() const;
QString toSocketAPIString() const;
private:
SyncFileStatusTag _tag;
bool _shared;
bool _sharedWithMe;
};
inline bool operator==(const SyncFileStatus &a, const SyncFileStatus &b) {
return a.tag() == b.tag() && a.shared() == b.shared();
return a.tag() == b.tag() && a.sharedWithMe() == b.sharedWithMe();
}
inline bool operator!=(const SyncFileStatus &a, const SyncFileStatus &b) {
+1 -1
Ver Arquivo
@@ -272,7 +272,7 @@ SyncFileStatus SyncFileStatusTracker::resolveSyncAndErrorStatus(const QString &r
}
if (isShared)
status.setShared(true);
status.setSharedWithMe(true);
return status;
}
-1
Ver Arquivo
@@ -47,7 +47,6 @@ owncloud_add_test(ExcludedFiles "")
if(HAVE_QT5 AND NOT BUILD_WITH_QT4)
owncloud_add_test(SyncEngine "syncenginetestutils.h")
owncloud_add_test(SyncFileStatusTracker "syncenginetestutils.h")
owncloud_add_test(ChunkingNg "syncenginetestutils.h")
endif(HAVE_QT5 AND NOT BUILD_WITH_QT4)
SET(FolderMan_SRC ../src/gui/folderman.cpp)
+204 -139
Ver Arquivo
@@ -18,20 +18,7 @@
#include <QtTest>
static const QUrl sRootUrl("owncloud://somehost/owncloud/remote.php/webdav/");
static const QUrl sRootUrl2("owncloud://somehost/owncloud/remote.php/dav/files/admin/");
static const QUrl sUploadUrl("owncloud://somehost/owncloud/remote.php/dav/uploads/admin/");
inline QString getFilePathFromUrl(const QUrl &url) {
QString path = url.path();
if (path.startsWith(sRootUrl.path()))
return path.mid(sRootUrl.path().length());
if (path.startsWith(sRootUrl2.path()))
return path.mid(sRootUrl2.path().length());
if (path.startsWith(sUploadUrl.path()))
return path.mid(sUploadUrl.path().length());
return {};
}
static const QUrl sBundleRootUrl("owncloud://somehost/remote.php/dav/files/");
inline QString generateEtag() {
return QString::number(QDateTime::currentDateTime().toMSecsSinceEpoch(), 16);
@@ -82,15 +69,10 @@ public:
QFile file{_rootDir.filePath(relativePath)};
QVERIFY(!file.exists());
file.open(QFile::WriteOnly);
QByteArray buf(1024, contentChar);
for (int x = 0; x < size/buf.size(); ++x) {
file.write(buf);
}
file.write(buf.data(), size % buf.size());
file.write(QByteArray{}.fill(contentChar, size));
file.close();
// Set the mtime 30 seconds in the past, for some tests that need to make sure that the mtime differs.
OCC::FileSystem::setModTime(file.fileName(), OCC::Utility::qDateTimeToTime_t(QDateTime::currentDateTime().addSecs(-30)));
QCOMPARE(file.size(), size);
}
void setContents(const QString &relativePath, char contentChar) override {
QFile file{_rootDir.filePath(relativePath)};
@@ -263,7 +245,6 @@ public:
QDateTime lastModified = QDateTime::currentDateTime().addDays(-7);
QString etag = generateEtag();
QByteArray fileId = generateFileId();
QByteArray extraDavProperties;
qint64 size = 0;
char contentChar = 'W';
@@ -303,12 +284,6 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isNull()); // for root, it should be empty
const FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
Q_ASSERT(fileInfo);
QString prefix = request.url().path().left(request.url().path().size() - fileName.size());
// Don't care about the request and just return a full propfind
const QString davUri{QStringLiteral("DAV:")};
const QString ocUri{QStringLiteral("http://owncloud.org/ns")};
@@ -322,7 +297,7 @@ public:
auto writeFileResponse = [&](const FileInfo &fileInfo) {
xml.writeStartElement(davUri, QStringLiteral("response"));
xml.writeTextElement(davUri, QStringLiteral("href"), prefix + fileInfo.path());
xml.writeTextElement(davUri, QStringLiteral("href"), "/owncloud/remote.php/webdav/" + fileInfo.path());
xml.writeStartElement(davUri, QStringLiteral("propstat"));
xml.writeStartElement(davUri, QStringLiteral("prop"));
@@ -340,13 +315,17 @@ public:
xml.writeTextElement(davUri, QStringLiteral("getetag"), fileInfo.etag);
xml.writeTextElement(ocUri, QStringLiteral("permissions"), fileInfo.isShared ? QStringLiteral("SRDNVCKW") : QStringLiteral("RDNVCKW"));
xml.writeTextElement(ocUri, QStringLiteral("id"), fileInfo.fileId);
buffer.write(fileInfo.extraDavProperties);
xml.writeEndElement(); // prop
xml.writeTextElement(davUri, QStringLiteral("status"), "HTTP/1.1 200 OK");
xml.writeEndElement(); // propstat
xml.writeEndElement(); // response
};
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
const FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
Q_ASSERT(fileInfo);
writeFileResponse(*fileInfo);
foreach(const FileInfo &childFileInfo, fileInfo->children)
writeFileResponse(childFileInfo);
@@ -390,8 +369,8 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isEmpty());
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
if ((fileInfo = remoteRootFileInfo.find(fileName))) {
fileInfo->size = putPayload.size();
fileInfo->contentChar = putPayload.at(0);
@@ -408,7 +387,6 @@ public:
}
Q_INVOKABLE void respond() {
emit uploadProgress(fileInfo->size, fileInfo->size);
setRawHeader("OC-ETag", fileInfo->etag.toLatin1());
setRawHeader("ETag", fileInfo->etag.toLatin1());
setRawHeader("X-OC-MTime", "accepted"); // Prevents Q_ASSERT(!_runningNow) since we'll call PropagateItemJob::done twice in that case.
@@ -433,8 +411,8 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isEmpty());
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
fileInfo = remoteRootFileInfo.createDir(fileName);
if (!fileInfo) {
@@ -466,8 +444,8 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isEmpty());
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
remoteRootFileInfo.remove(fileName);
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
}
@@ -493,10 +471,11 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isEmpty());
QString dest = getFilePathFromUrl(QUrl::fromEncoded(request.rawHeader("Destination")));
Q_ASSERT(!dest.isEmpty());
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
QString destPath = request.rawHeader("Destination");
Q_ASSERT(destPath.startsWith(sRootUrl.path()));
QString dest = destPath.mid(sRootUrl.path().length());
remoteRootFileInfo.rename(fileName, dest);
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
}
@@ -525,8 +504,8 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isEmpty());
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
fileInfo = remoteRootFileInfo.find(fileName);
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
}
@@ -556,78 +535,6 @@ public:
};
class FakeChunkMoveReply : public QNetworkReply
{
Q_OBJECT
FileInfo *fileInfo;
public:
FakeChunkMoveReply(FileInfo &uploadsFileInfo, FileInfo &remoteRootFileInfo,
QNetworkAccessManager::Operation op, const QNetworkRequest &request,
QObject *parent) : QNetworkReply{parent} {
setRequest(request);
setUrl(request.url());
setOperation(op);
open(QIODevice::ReadOnly);
QString source = getFilePathFromUrl(request.url());
Q_ASSERT(!source.isEmpty());
Q_ASSERT(source.endsWith("/.file"));
source = source.left(source.length() - qstrlen("/.file"));
auto sourceFolder = uploadsFileInfo.find(source);
Q_ASSERT(sourceFolder);
Q_ASSERT(sourceFolder->isDir);
int count = 0;
int size = 0;
char payload = '*';
do {
if (!sourceFolder->children.contains(QString::number(count)))
break;
auto &x = sourceFolder->children[QString::number(count)];
Q_ASSERT(!x.isDir);
Q_ASSERT(x.size > 0); // There should not be empty chunks
size += x.size;
payload = x.contentChar;
++count;
} while(true);
Q_ASSERT(count > 1); // There should be at least two chunks, otherwise why would we use chunking?
QCOMPARE(sourceFolder->children.count(), count); // There should not be holes or extra files
QString fileName = getFilePathFromUrl(QUrl::fromEncoded(request.rawHeader("Destination")));
Q_ASSERT(!fileName.isEmpty());
if ((fileInfo = remoteRootFileInfo.find(fileName))) {
QCOMPARE(request.rawHeader("If"), QByteArray("<" + request.rawHeader("Destination") + "> ([\"" + fileInfo->etag.toLatin1() + "\"])"));
fileInfo->size = size;
fileInfo->contentChar = payload;
} else {
Q_ASSERT(!request.hasRawHeader("If"));
// Assume that the file is filled with the same character
fileInfo = remoteRootFileInfo.create(fileName, size, payload);
}
if (!fileInfo) {
abort();
return;
}
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
}
Q_INVOKABLE void respond() {
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201);
setRawHeader("OC-ETag", fileInfo->etag.toLatin1());
setRawHeader("ETag", fileInfo->etag.toLatin1());
setRawHeader("OC-FileId", fileInfo->fileId);
emit metaDataChanged();
emit finished();
}
void abort() override { }
qint64 readData(char *, qint64) override { return 0; }
};
class FakeErrorReply : public QNetworkReply
{
Q_OBJECT
@@ -651,45 +558,202 @@ public:
qint64 readData(char *, qint64) override { return 0; }
};
class FakeBundlePOSTReply : public QNetworkReply
{
Q_OBJECT
FileInfo *fileInfo;
QByteArray payload;
public:
FakeBundlePOSTReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &postPayload, QObject *parent)
: QNetworkReply{parent} {
setRequest(request);
QUrl rawUrl = request.url();
QString bundlePath(sBundleRootUrl.path()+rawUrl.userName());
setUrl(rawUrl);
setOperation(op);
open(QIODevice::ReadOnly);
const QString davUri{QStringLiteral("DAV:")};
const QString ocUri{QStringLiteral("http://owncloud.org/ns")};
const QString sabUri{QStringLiteral("http://sabredav.org/ns")};
QBuffer buffer{&payload};
buffer.open(QIODevice::WriteOnly);
QXmlStreamWriter xml( &buffer );
xml.writeNamespace(davUri, "d");
xml.writeNamespace(ocUri, "o");
xml.writeNamespace(sabUri, "s");
auto writeFileResponse = [&](const FileInfo &fileInfo) {
xml.writeStartElement(davUri, QStringLiteral("response"));
//TODO: no need for X-OC-PATH, href could contain that, fix client/server
xml.writeTextElement(davUri, QStringLiteral("href"), bundlePath);
xml.writeStartElement(davUri, QStringLiteral("propstat"));
xml.writeStartElement(davUri, QStringLiteral("prop"));
xml.writeTextElement(davUri, QStringLiteral("oc-etag"), fileInfo.etag);
xml.writeTextElement(davUri, QStringLiteral("etag"), fileInfo.etag);
xml.writeTextElement(davUri, QStringLiteral("oc-fileid"), fileInfo.fileId);
xml.writeTextElement(davUri, QStringLiteral("x-oc-mtime"), QStringLiteral("accepted"));
//TODO: this slash to be fixed on client/server
xml.writeTextElement(davUri, QStringLiteral("oc-path"), "/"+fileInfo.path());
xml.writeEndElement(); // prop
xml.writeTextElement(davUri, QStringLiteral("status"), "HTTP/1.1 200 OK");
xml.writeEndElement(); // propstat
xml.writeEndElement(); // response
};
auto writeFileErrorResponse = [&](const FileInfo &fileInfo, const QString &exception, const QString &message, const QString &status) {
xml.writeStartElement(davUri, QStringLiteral("response"));
//TODO: no need for X-OC-PATH, href could contain that, fix client/server
xml.writeTextElement(davUri, QStringLiteral("href"), bundlePath);
xml.writeStartElement(davUri, QStringLiteral("propstat"));
xml.writeStartElement(davUri, QStringLiteral("prop"));
xml.writeStartElement(davUri, QStringLiteral("error"));
xml.writeTextElement(sabUri, QStringLiteral("exception"), exception);
xml.writeTextElement(sabUri, QStringLiteral("message"), message);
xml.writeEndElement(); // error
//TODO: this slash to be fixed on client/server
xml.writeTextElement(davUri, QStringLiteral("oc-path"), "/"+fileInfo.path());
xml.writeEndElement(); // prop
xml.writeTextElement(davUri, QStringLiteral("status"), status);
xml.writeEndElement(); // propstat
xml.writeEndElement(); // response
};
if ("erroruser" == rawUrl.userName()) {
xml.writeStartDocument();
xml.writeStartElement(davUri, QStringLiteral("error"));
xml.writeTextElement(sabUri, QStringLiteral("exception"), QStringLiteral("OCA\\DAV\\Connector\\Sabre\\Exception\\Forbidden"));
xml.writeTextElement(sabUri, QStringLiteral("message"), QStringLiteral("URL endpoint has to be instance of \\OCA\\DAV\\Files\\FilesHome"));
xml.writeTextElement(ocUri, QStringLiteral("retry"), QStringLiteral("false"));
xml.writeTextElement(ocUri, QStringLiteral("reason"), QStringLiteral("URL endpoint has to be instance of \\OCA\\DAV\\Files\\FilesHome"));
xml.writeEndElement(); // error
xml.writeEndDocument();
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 403);
} else {
Q_ASSERT(request.url().path().endsWith(bundlePath));
xml.writeStartDocument();
xml.writeStartElement(davUri, QStringLiteral("multistatus"));
//multipart parsing
QString headerSectEnd = "\r\n\r\n";
QString headerEnd = "\r\n";
QString headerOcMethod = "X-OC-Method: ";
QString headerConLen = "Content-Length: ";
QString headerOcPath = "X-OC-Path: ";
int indexOfBody = 0;
QChar contentChar;
while(postPayload.indexOf(headerSectEnd,indexOfBody) + headerSectEnd.length() >=indexOfBody) {
//find oc-method
int indexOfheaderEnd = postPayload.indexOf(headerOcMethod,indexOfBody) + headerOcMethod.length();
int indexOfheaderBodyEnd = postPayload.indexOf(headerEnd,indexOfheaderEnd);
Q_ASSERT(postPayload.mid(indexOfheaderEnd,indexOfheaderBodyEnd-indexOfheaderEnd) == QString("PUT"));
//find oc-path
indexOfheaderEnd = postPayload.indexOf(headerOcPath,indexOfBody) + headerOcPath.length();
indexOfheaderBodyEnd = postPayload.indexOf(headerEnd,indexOfheaderEnd)-1;
QString filePath(postPayload.mid(indexOfheaderEnd+1,indexOfheaderBodyEnd-indexOfheaderEnd));
//find content-length
indexOfheaderEnd = postPayload.indexOf(headerConLen,indexOfBody) + headerConLen.length();
indexOfheaderBodyEnd = postPayload.indexOf(headerEnd,indexOfheaderEnd);
QString fileSize(postPayload.mid(indexOfheaderEnd,indexOfheaderBodyEnd-indexOfheaderEnd));
//find body content and extract first letter
indexOfheaderEnd = postPayload.indexOf(headerSectEnd,indexOfBody) + headerSectEnd.length();
indexOfBody = indexOfheaderEnd+1;
contentChar = postPayload.at(indexOfheaderEnd+1);
if ((fileInfo = remoteRootFileInfo.find(filePath))) {
fileInfo->size = fileSize.toInt();
fileInfo->contentChar = contentChar.toAscii();
} else {
// Assume that the file is filled with the same character
fileInfo = remoteRootFileInfo.create(filePath, fileSize.toInt(), contentChar.toAscii());
}
if (!fileInfo) {
abort();
return;
}
if (filePath.endsWith("normalerrorfile")){
writeFileErrorResponse(*fileInfo, QStringLiteral("Sabre\\DAV\\Exception\\BadRequest"), QStringLiteral("Method not allowed - file exists - update of the file is not supported!"), QStringLiteral("HTTP/1.1 400 Bad Request"));
} else if (filePath.endsWith("fatalerrorfile")){
writeFileErrorResponse(*fileInfo, QStringLiteral("Sabre\\DAV\\Exception\\ServiceUnavailable"), QStringLiteral("Failed to check file size"), QStringLiteral("HTTP/1.1 503 Service Unavailable"));
} else if (filePath.endsWith("softerrorfile")){
writeFileErrorResponse(*fileInfo, QStringLiteral("OCA\\DAV\\Connector\\Sabre\\Exception\\FileLocked"), QStringLiteral("Target file is locked by another process."), QStringLiteral("HTTP/1.1 423 Locked (WebDAV; RFC 4918)"));
} else {
writeFileResponse(*fileInfo);
}
}
xml.writeEndElement(); // multistatus
xml.writeEndDocument();
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 207);
setFinished(true);
}
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
}
Q_INVOKABLE void respond() {
setHeader(QNetworkRequest::ContentTypeHeader, "application/xml; charset=utf-8");
setHeader(QNetworkRequest::ContentLengthHeader, payload.size());
emit metaDataChanged();
if (bytesAvailable())
emit readyRead();
emit finished();
}
void abort() override { }
qint64 bytesAvailable() const override { return payload.size() + QIODevice::bytesAvailable(); }
qint64 readData(char *data, qint64 maxlen) override {
qint64 len = std::min(qint64{payload.size()}, maxlen);
strncpy(data, payload.constData(), len);
payload.remove(0, len);
return len;
}
};
class FakeQNAM : public QNetworkAccessManager
{
FileInfo _remoteRootFileInfo;
FileInfo _uploadFileInfo;
QStringList _errorPaths;
public:
FakeQNAM(FileInfo initialRoot) : _remoteRootFileInfo{std::move(initialRoot)} { }
FileInfo &currentRemoteState() { return _remoteRootFileInfo; }
FileInfo &uploadState() { return _uploadFileInfo; }
QStringList &errorPaths() { return _errorPaths; }
protected:
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request,
QIODevice *outgoingData = 0) {
const QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isNull());
const QString fileName = request.url().path().mid(sRootUrl.path().length());
if (_errorPaths.contains(fileName))
return new FakeErrorReply{op, request, this};
bool isUpload = request.url().path().startsWith(sUploadUrl.path());
FileInfo &info = isUpload ? _uploadFileInfo : _remoteRootFileInfo;
auto verb = request.attribute(QNetworkRequest::CustomVerbAttribute);
if (verb == QLatin1String("PROPFIND"))
// Ignore outgoingData always returning somethign good enough, works for now.
return new FakePropfindReply{info, op, request, this};
return new FakePropfindReply{_remoteRootFileInfo, op, request, this};
else if (verb == QLatin1String("GET"))
return new FakeGetReply{info, op, request, this};
return new FakeGetReply{_remoteRootFileInfo, op, request, this};
else if (verb == QLatin1String("PUT"))
return new FakePutReply{info, op, request, outgoingData->readAll(), this};
return new FakePutReply{_remoteRootFileInfo, op, request, outgoingData->readAll(), this};
else if (verb == QLatin1String("MKCOL"))
return new FakeMkcolReply{info, op, request, this};
return new FakeMkcolReply{_remoteRootFileInfo, op, request, this};
else if (verb == QLatin1String("DELETE"))
return new FakeDeleteReply{info, op, request, this};
else if (verb == QLatin1String("MOVE") && !isUpload)
return new FakeMoveReply{info, op, request, this};
else if (verb == QLatin1String("MOVE") && isUpload)
return new FakeChunkMoveReply{info, _remoteRootFileInfo, op, request, this};
else {
return new FakeDeleteReply{_remoteRootFileInfo, op, request, this};
else if (verb == QLatin1String("MOVE"))
return new FakeMoveReply{_remoteRootFileInfo, op, request, this};
else if (op == QNetworkAccessManager::PostOperation) {
return new FakeBundlePOSTReply{_remoteRootFileInfo, op, request, outgoingData->readAll(), this};
} else {
qDebug() << verb << outgoingData;
Q_UNREACHABLE();
}
@@ -751,7 +815,7 @@ public:
OCC::SyncEngine &syncEngine() const { return *_syncEngine; }
FileModifier &localModifier() { return _localModifier; }
FileInfo &remoteModifier() { return _fakeQnam->currentRemoteState(); }
FileModifier &remoteModifier() { return _fakeQnam->currentRemoteState(); }
FileInfo currentLocalState() {
QDir rootDir{_tempDir.path()};
FileInfo rootTemplate;
@@ -760,7 +824,6 @@ public:
}
FileInfo currentRemoteState() { return _fakeQnam->currentRemoteState(); }
FileInfo &uploadState() { return _fakeQnam->uploadState(); }
QStringList &serverErrorPaths() { return _fakeQnam->errorPaths(); }
@@ -797,16 +860,18 @@ public:
QVERIFY(false);
}
bool execUntilFinished() {
void execUntilFinished() {
QSignalSpy spy(_syncEngine.get(), SIGNAL(finished(bool)));
bool ok = spy.wait(60000);
Q_ASSERT(ok && "Sync timed out");
return spy[0][0].toBool();
QVERIFY(spy.wait());
}
bool syncOnce() {
void syncOnce() {
scheduleSync();
return execUntilFinished();
execUntilFinished();
}
OCC::AccountPtr getAccount() {
return _account;
}
private:
-78
Ver Arquivo
@@ -1,78 +0,0 @@
/*
* 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>
using namespace OCC;
class TestChunkingNG : public QObject
{
Q_OBJECT
private slots:
void testFileUpload() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"chunking", "1.0"} } } });
const int size = 300 * 1000 * 1000; // 300 MB
fakeFolder.localModifier().insert("A/a0", size);
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(fakeFolder.uploadState().children.count(), 1); // the transfer was done with chunking
QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size);
// Check that another upload of the same file also work.
fakeFolder.localModifier().appendByte("A/a0");
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(fakeFolder.uploadState().children.count(), 2); // the transfer was done with chunking
}
void testResume () {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"chunking", "1.0"} } } });
const int size = 300 * 1000 * 1000; // 300 MB
fakeFolder.localModifier().insert("A/a0", size);
// Abort when the upload is at 1/3
int sizeWhenAbort = -1;
auto con = QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::transmissionProgress,
[&](const ProgressInfo &progress) {
if (progress.completedSize() > (progress.totalSize() /3 )) {
sizeWhenAbort = progress.completedSize();
fakeFolder.syncEngine().abort();
}
});
QVERIFY(!fakeFolder.syncOnce()); // there should have been an error
QObject::disconnect(con);
QVERIFY(sizeWhenAbort > 0);
QVERIFY(sizeWhenAbort < size);
QCOMPARE(fakeFolder.uploadState().children.count(), 1); // the transfer was done with chunking
auto upStateChildren = fakeFolder.uploadState().children.first().children;
QCOMPARE(sizeWhenAbort, std::accumulate(upStateChildren.cbegin(), upStateChildren.cend(), 0,
[](int s, const FileInfo &i) { return s + i.size; }));
// Add a fake file to make sure it gets deleted
fakeFolder.uploadState().children.first().insert("10000", size);
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(fakeFolder.uploadState().children.count(), 1); // The same chunk id was re-used
QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size);
}
};
QTEST_GUILESS_MAIN(TestChunkingNG)
#include "testchunkingng.moc"
+79
Ver Arquivo
@@ -31,6 +31,16 @@ bool itemDidCompleteSuccessfully(const QSignalSpy &spy, const QString &path)
return false;
}
SyncFileItem::Status itemDidCompleteWithStatus(const QSignalSpy &spy, const QString &path)
{
for(const QList<QVariant> &args : spy) {
SyncFileItem item = args[0].value<SyncFileItem>();
if (item.destination() == path)
return item._status;
}
return SyncFileItem::NoStatus;
}
class TestSyncEngine : public QObject
{
Q_OBJECT
@@ -54,6 +64,75 @@ private slots:
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void testFileUploadBundled() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
QVariantMap capBundle;
capBundle["bundlerequest"] = true;
QVariantMap caps;
caps["dav"] = capBundle;
fakeFolder.getAccount()->setCapabilities(caps);
testFileUploadBundledAllFilesOK(fakeFolder);
testFileUploadBundledErrorForFile(fakeFolder);
//TODO unfinished, cannot generate NetworkError
//testFileUploadBundledNotHomeCollection(fakeFolder);
}
void testFileUploadBundledAllFilesOK(FakeFolder &fakeFolder) {
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)));
fakeFolder.localModifier().insert("A/a3");
fakeFolder.localModifier().insert("A/a4");
fakeFolder.localModifier().insert("B/b0");
fakeFolder.syncOnce();
//check separate files
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a3"));
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a4"));
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "B/b0"));
//check whole bundle
QVERIFY(itemDidCompleteSuccessfully(completeSpy, ""));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void testFileUploadBundledErrorForFile(FakeFolder &fakeFolder) {
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)));
fakeFolder.localModifier().insert("A/a5");
fakeFolder.localModifier().insert("A/normalerrorfile");
fakeFolder.localModifier().insert("A/fatalerrorfile");
fakeFolder.localModifier().insert("A/softerrorfile");
fakeFolder.localModifier().insert("B/b3");
fakeFolder.syncOnce();
//check separate files
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a5"));
QVERIFY(SyncFileItem::NormalError == itemDidCompleteWithStatus(completeSpy, "A/normalerrorfile"));
QVERIFY(SyncFileItem::FatalError == itemDidCompleteWithStatus(completeSpy, "A/fatalerrorfile"));
QVERIFY(SyncFileItem::SoftError == itemDidCompleteWithStatus(completeSpy, "A/softerrorfile"));
QVERIFY(itemDidCompleteSuccessfully(completeSpy, "B/b3"));
//check whole bundle
QVERIFY(itemDidCompleteSuccessfully(completeSpy, ""));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void testFileUploadBundledNotHomeCollection(FakeFolder &fakeFolder) {
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)));
fakeFolder.localModifier().insert("A/a7");
fakeFolder.localModifier().insert("A/a8");
fakeFolder.localModifier().insert("B/b4");
//add the user "erroruser" which is not a FilesHomeCollection
fakeFolder.getAccount()->setUrl(QUrl(QStringLiteral("http://erroruser:admin@localhost/owncloud")));
fakeFolder.syncOnce();
//check whole bundle
QVERIFY(itemDidCompleteSuccessfully(completeSpy, ""));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void testDirDownload() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)));
+1 -7
Ver Arquivo
@@ -371,15 +371,11 @@ private slots:
void sharedStatus() {
SyncFileStatus sharedUpToDateStatus(SyncFileStatus::StatusUpToDate);
sharedUpToDateStatus.setShared(true);
sharedUpToDateStatus.setSharedWithMe(true);
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.remoteModifier().insert("S/s0");
fakeFolder.remoteModifier().appendByte("S/s1");
fakeFolder.remoteModifier().insert("B/b3");
fakeFolder.remoteModifier().find("B/b3")->extraDavProperties
= "<oc:share-types><oc:share-type>0</oc:share-type></oc:share-types>";
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
@@ -399,8 +395,6 @@ private slots:
QEXPECT_FAIL("", "We currently only know if a new file is shared on the second sync, after a PROPFIND.", Continue);
QCOMPARE(statusSpy.statusOf("S/s0"), sharedUpToDateStatus);
QCOMPARE(statusSpy.statusOf("S/s1"), sharedUpToDateStatus);
QCOMPARE(statusSpy.statusOf("B/b1").shared(), false);
QCOMPARE(statusSpy.statusOf("B/b3"), sharedUpToDateStatus);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
+1 -1
Ver Arquivo
@@ -80,7 +80,7 @@ private slots:
quint64 sec = 1000;
quint64 hour = 3600 * sec;
QDateTime current = QDateTime::currentDateTimeUtc();
QDateTime current = QDateTime::currentDateTime();
QCOMPARE(durationToDescriptiveString2(0), QString("0 second(s)") );
QCOMPARE(durationToDescriptiveString2(5), QString("0 second(s)") );
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff