Merge pull request #90 from nextcloud/oc_up

Merge upstream
This commit is contained in:
Roeland Jago Douma 2017-12-14 11:18:54 +01:00 committed by GitHub
commit 09fa5966da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
143 changed files with 5287 additions and 4780 deletions

View file

@ -246,6 +246,7 @@ set(WITH_TESTING ${UNIT_TESTING})
if(BUILD_CLIENT)
add_subdirectory(src)
if(NOT BUILD_LIBRARIES_ONLY)
add_subdirectory(man)
add_subdirectory(doc)
add_subdirectory(doc/dev)
if(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/admin)

View file

@ -1,9 +1,10 @@
ChangeLog
=========
version 2.4.0 (2017-11-XX)
version 2.4.0 (2017-12-XX)
* If you're using 2.4.0 alpha1, please upgrade as the alpha1 had an issue with hidden files!
* OAuth2 authentication support by opening external browser (#5668)
* Shibboleth: Change to use OAuth2 if supported (#6198)
* Sharing: Add support for multiple public link shares (#5655)
* Sharing: Add option to copy/email private links (#5023, #5627)
* Sharing: Add option "show file listing" (#5837)
@ -61,6 +62,7 @@ version 2.4.0 (2017-11-XX)
* Sync: Introduce overall errors that are not tied to a file (#5746)
* Sync: Better messaging for 507 Insufficient Storage (#5537)
* Sync: Create conflicts by comparing the hash of files with identical mtime/size (#5589)
* Sync: Avoid downloads by comparing the hash of files with identical mtime/size (#6153)
* Sync: Upload conflict files if OWNCLOUD_UPLOAD_CONFLICT_FILES environment variable is set (#6038)
* Sync: Blacklist: Don't let errors become warnings (#5516)
* Sync: Check etag again after active sync (#4116)

View file

@ -98,7 +98,7 @@ set(QT_RCC_EXECUTABLE "${Qt5Core_RCC_EXECUTABLE}")
#Enable deprecated symbols
add_definitions("-DQT_DISABLE_DEPRECATED_BEFORE=0")
add_definitions("-DQT_DEPRECATED_WARNINGS")
add_definitions("-DQT_USE_QSTRINGBUILDER") #optimize string concatenation
add_definitions("-DQT_MESSAGELOGCONTEXT") #enable function name and line number in debug output

View file

@ -4,7 +4,6 @@ if(SPHINX_FOUND)
set(SPHINX_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_doctrees")
# HTML output directory
set(SPHINX_HTML_DIR "${CMAKE_CURRENT_BINARY_DIR}/html")
set(SPHINX_MAN_DIR "${CMAKE_CURRENT_BINARY_DIR}/man1")
set(SPHINX_PDF_DIR "${CMAKE_CURRENT_BINARY_DIR}/latex")
set(SPHINX_QCH_DIR "${CMAKE_CURRENT_BINARY_DIR}/qthelp")
set(SPHINX_HTMLHELP_DIR "${CMAKE_CURRENT_BINARY_DIR}/htmlhelp")
@ -17,8 +16,6 @@ if(SPHINX_FOUND)
install(DIRECTORY ${SPHINX_PDF_DIR} DESTINATION ${APPLICATION_DOC_DIR} OPTIONAL)
install(DIRECTORY ${SPHINX_QCH_DIR} DESTINATION ${APPLICATION_DOC_DIR} OPTIONAL)
install(DIRECTORY ${SPHINX_MAN_DIR} DESTINATION ${CMAKE_INSTALL_MANDIR} OPTIONAL)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in" conf.py @ONLY)
if(WITH_DOC)
@ -79,11 +76,6 @@ if(SPHINX_FOUND)
-D html_theme=owncloud_com
${CMAKE_CURRENT_SOURCE_DIR}
${SPHINX_HTML_DIR}/com )
add_custom_target( doc-man ${SPHINX_EXECUTABLE}
-q -c . -b man
-d ${SPHINX_CACHE_DIR}/man
${CMAKE_CURRENT_SOURCE_DIR}
${SPHINX_MAN_DIR} )
## Building CHM files requires HTML Help Workshop. Since it requires wine
## with special dependencies, it's impossible to write a cmake check for it.
@ -98,4 +90,4 @@ if(SPHINX_FOUND)
${SPHINX_HTMLHELP_DIR} )
add_custom_target( doc-chm pushd ${SPHINX_HTMLHELP_DIR}; ${MSHTML_COMPILER} *.hhp; popd
DEPENDS doc-chm-sphinx )
endif(SPHINX_FOUND)
endif(SPHINX_FOUND)

View file

@ -119,7 +119,7 @@ Preventing Automatic Updates in Linux Environments
Because the Linux client does not provide automatic updating functionality, there is no
need to remove the automatic-update check. However, if you want to disable it edit your desktop
client configuration file, ``$HOME/.local/share/data/ownCloud/owncloud.cfg``.
client configuration file, ``$HOME/.config/ownCloud/owncloud.cfg``.
Add this line to the [General] section::
skipUpdateCheck=true

View file

@ -1,13 +1,13 @@
The ownCloud Client reads a configuration file. You can locate this configuration file as follows:
On Linux distributions:
``$HOME/.local/share/data/ownCloud/owncloud.cfg``
``$HOME/.config/ownCloud/owncloud.cfg``
On Microsoft Windows systems:
``%LOCALAPPDATA%\ownCloud\owncloud.cfg``
``%APPDATA%\ownCloud\owncloud.cfg``
On MAC OS X systems:
``$HOME/Library/Application Support/ownCloud/owncloud.cfg``
``$HOME/Library/Preferences/ownCloud/owncloud.cfg``
The configuration file contains settings using the Microsoft Windows .ini file

14
man/CMakeLists.txt Normal file
View file

@ -0,0 +1,14 @@
if(SPHINX_FOUND)
# Sphinx cache with pickled ReST documents
set(SPHINX_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_doctrees")
# HTML output directory
set(SPHINX_MAN_DIR "${CMAKE_CURRENT_BINARY_DIR}/man1")
install(DIRECTORY ${SPHINX_MAN_DIR} DESTINATION ${CMAKE_INSTALL_MANDIR} OPTIONAL)
add_custom_target( doc-man ${SPHINX_EXECUTABLE}
-c ${CMAKE_SOURCE_DIR}/doc -b man
-d ${SPHINX_CACHE_DIR}/man
${CMAKE_CURRENT_SOURCE_DIR}
${SPHINX_MAN_DIR} )
endif(SPHINX_FOUND)

1
man/index.rst Normal file
View file

@ -0,0 +1 @@

33
man/owncloud.1.rst Normal file
View file

@ -0,0 +1,33 @@
:orphan:
owncloud(1)
————
SYNOPSIS
========
*owncloud* [`OPTIONS`...]
DESCRIPTION
===========
The ownCloud Client is a file synchronization desktop utility. It synchronizes files on your local computer, tablet, or handheld device with an ownCloud Server. If you make a change to the files on one device, the change is propagated to all other synchronized devices using the desktop synchronization clients.
Normally, you start the client by clicking on the desktop icon or by starting it from the client application menu. After starting, an ownCloud icon appears in the computer system tray or on your tablet or handheld device.
Options
=======
.. include:: ../doc/options.rst
Config File
===========
.. include:: ../doc/conffile.rst
BUGS
====
Please report bugs at https://github.com/owncloud/client/issues.
SEE ALSO
========
:manpage:`owncloudcmd(1)`

97
man/owncloudcmd.1.rst Normal file
View file

@ -0,0 +1,97 @@
:orphan:
owncloudcmd(1)
—————
SYNOPSIS
========
*owncloudcmd* [`OPTIONS`...] sourcedir owncloudurl
DESCRIPTION
===========
owncloudcmd is the command line tool used for the ownCloud file synchronization
desktop utility.
Contrary to the :manpage:`owncloud(1)` GUI client, `owncloudcmd` only performs
a single sync run and then exits. In so doing, `owncloudcmd` replaces the
`ocsync` binary used for the same purpose in earlier releases.
A *sync run* synchronizes a single local directory using a WebDAV share on a
remote ownCloud server.
To invoke the command line client, provide the local and the remote repository:
The first parameter is the local directory. The second parameter is
the server URL.
.. note:: Prior to the 1.6 release of owncloudcmd, the tool only accepted
``owncloud://`` or ``ownclouds://`` in place of ``http://`` and ``https://`` as
a scheme. See ``Examples`` for details.
OPTIONS
=======
``—user``, ``-u`` ``[user]``
Use ``user`` as the login name.
``—password``, ``-p`` ``[password]``
Use ``password`` as the password.
``-n``
Use ``netrc (5)`` for login.
``—non-interactive``
Do not prompt for questions.
``—silent``, ``—s``
Inhibits verbose log output.
``—trust``
Trust any SSL certificate, including invalid ones.
``—httpproxy http://[user@pass:]<server>:<port>``
Uses ``server`` as HTTP proxy.
``—nonshib``
Uses Non Shibboleth WebDAV Authentication
``—davpath [path]``
Overrides the WebDAV Path with ``path``
``—exclude [file]``
Exclude list file
``—unsyncedfolders [file]``
File containing the list of unsynced folders (selective sync)
``—max-sync-retries [n]``
Retries maximum n times (defaults to 3)
``-h``
Sync hidden files,do not ignore them
Example
=======
To synchronize the ownCloud directory ``Music`` to the local directory ``media/music``
through a proxy listening on port ``8080`` on the gateway machine ``192.168.178.1``,
the command line would be::
$ owncloudcmd —httpproxy http://192.168.178.1:8080 \
$HOME/media/music \
https://server/owncloud/remote.php/webdav/Music
``owncloudcmd`` will enquire user name and password, unless they have
been specified on the command line or ``-n`` (see `netrc(5)`) has been passed.
Using the legacy scheme, it would be::
$ owncloudcmd —httpproxy http://192.168.178.1:8080 \
$HOME/media/music \
ownclouds://server/owncloud/remote.php/webdav/Music
BUGS
====
Please report bugs at https://github.com/owncloud/client/issues.
SEE ALSO
========
:manpage:`owncloud(1)`

View file

@ -8,253 +8,6 @@ GenericName=Folder Sync
Icon=@APPLICATION_EXECUTABLE@
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
X-GNOME-Autostart-Delay=3
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations

View file

@ -22,7 +22,7 @@
#define OVERLAY_GUID_WARNING L"{0960F096-F328-48A3-B746-276B1E3C3722}"
#define OVERLAY_GUID_WARNING_SHARED L"{0960F097-F328-48A3-B746-276B1E3C3722}"
#define OVERLAY_GENERIC_NAME L"OC Overlay Handler"
#define OVERLAY_GENERIC_NAME L"ownCloud overlay handler"
// two spaces to put us ahead of the competition :/
#define OVERLAY_NAME_ERROR L" OCError"
@ -47,4 +47,4 @@
#define GET_FILE_OVERLAY_ID L"getFileIconId"
#define PORT 34001
#define PORT 34001

View file

@ -180331,7 +180331,7 @@ static int sessionBindRow(
** iterator pIter points to to the SELECT and attempts to seek to the table
** entry. If a row is found, the SELECT statement left pointing at the row
** and SQLITE_ROW is returned. Otherwise, if no row is found and no error
** has occured, the statement is reset and SQLITE_OK is returned. If an
** has occurred, the statement is reset and SQLITE_OK is returned. If an
** error occurs, the statement is reset and an SQLite error code is returned.
**
** If this function returns SQLITE_ROW, the caller must eventually reset()

View file

@ -8,20 +8,29 @@ if(NOT TOKEN_AUTH_ONLY)
find_package(Qt5Keychain REQUIRED)
endif()
if(WIN32)
# Enable DEP & ASLR
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
elseif(UNIX AND NOT APPLE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector-strong")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-strong")
if(NOT MSVC)
if(NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "^(parisc|hppa)"))
if((CMAKE_CXX_COMPILER_ID MATCHES "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9))
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector --param=ssp-buffer-size=4")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector --param=ssp-buffer-size=4")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector-strong")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-strong")
endif()
endif()
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FORTIFY_SOURCE=2")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2")
endif()
endif()
if(WIN32)
# Enable DEP & ASLR
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
elseif(UNIX AND NOT APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
endif()

View file

@ -31,7 +31,7 @@ endif()
if(NOT BUILD_LIBRARIES_ONLY)
add_executable(${cmd_NAME} ${cmd_SRC})
qt5_use_modules(${cmd_NAME} Network Sql)
qt5_use_modules(${cmd_NAME} Network )
set_target_properties(${cmd_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} )
set_target_properties(${cmd_NAME} PROPERTIES

View file

@ -53,7 +53,7 @@
using namespace OCC;
static void nullMessageHandler(QtMsgType, const char *)
static void nullMessageHandler(QtMsgType, const QMessageLogContext &, const QString &)
{
}
@ -333,7 +333,7 @@ int main(int argc, char **argv)
csync_set_log_level(options.silent ? 1 : 11);
if (options.silent) {
qInstallMsgHandler(nullMessageHandler);
qInstallMessageHandler(nullMessageHandler);
} else {
qSetMessagePattern("%{time MM-dd hh:mm:ss:zzz} [ %{type} %{category} ]%{if-debug}\t[ %{function} ]%{endif}:\t%{message}");
}
@ -534,7 +534,7 @@ restart_sync:
engine.excludedFiles().addExcludeFilePath(systemExcludeFile);
}
if (!engine.excludedFiles().reloadExcludes()) {
if (!engine.excludedFiles().reloadExcludeFiles()) {
qFatal("Cannot load system exclude list or list supplied via --exclude");
return EXIT_FAILURE;
}

View file

@ -11,7 +11,6 @@
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "configfile.h"
#include "common/utility.h"
#include "account.h"
#include "simplesslerrorhandler.h"

View file

@ -344,6 +344,7 @@ void SqlQuery::bindValue(int pos, const QVariant &value)
break;
case QVariant::UInt:
case QVariant::LongLong:
case QVariant::ULongLong:
res = sqlite3_bind_int64(_stmt, pos, value.toLongLong());
break;
case QVariant::DateTime: {

View file

@ -45,7 +45,7 @@ Q_LOGGING_CATEGORY(lcDb, "sync.database", QtInfoMsg)
static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &query)
{
rec._path = query.baValue(0);
rec._inode = query.intValue(1);
rec._inode = query.int64Value(1);
rec._modtime = query.int64Value(2);
rec._type = query.intValue(3);
rec._etag = query.baValue(4);

View file

@ -296,9 +296,7 @@ namespace {
QString description(quint64 value) const
{
return QCoreApplication::translate(
"Utility", name, 0, QCoreApplication::UnicodeUTF8,
value);
return QCoreApplication::translate("Utility", name, 0, value);
}
};
// QTBUG-3945 and issue #4855: QT_TRANSLATE_NOOP does not work with plural form because lupdate
@ -520,11 +518,11 @@ void Utility::sortFilenames(QStringList &fileNames)
QCollator collator;
collator.setNumericMode(true);
collator.setCaseSensitivity(Qt::CaseInsensitive);
qSort(fileNames.begin(), fileNames.end(), collator);
std::sort(fileNames.begin(), fileNames.end(), collator);
}
QUrl Utility::concatUrlPath(const QUrl &url, const QString &concatPath,
const QList<QPair<QString, QString>> &queryItems)
const QUrlQuery &queryItems)
{
QString path = url.path();
if (!concatPath.isEmpty()) {
@ -540,9 +538,7 @@ QUrl Utility::concatUrlPath(const QUrl &url, const QString &concatPath,
QUrl tmpUrl = url;
tmpUrl.setPath(path);
if (queryItems.size() > 0) {
tmpUrl.setQueryItems(queryItems);
}
tmpUrl.setQuery(queryItems);
return tmpUrl;
}

View file

@ -28,6 +28,7 @@
#include <QLoggingCategory>
#include <QMap>
#include <QUrl>
#include <QUrlQuery>
#include <functional>
#include <memory>
@ -70,7 +71,7 @@ namespace Utility {
* @param unit an optional unit that is appended if present.
* @return the formatted string.
*/
OCSYNC_EXPORT QString compactFormatDouble(double value, int prec, const QString &unit = QString::null);
OCSYNC_EXPORT QString compactFormatDouble(double value, int prec, const QString &unit = QString());
// porting methods
OCSYNC_EXPORT QString escape(const QString &);
@ -175,7 +176,7 @@ namespace Utility {
/** Appends concatPath and queryItems to the url */
OCSYNC_EXPORT QUrl concatUrlPath(
const QUrl &url, const QString &concatPath,
const QList<QPair<QString, QString>> &queryItems = (QList<QPair<QString, QString>>()));
const QUrlQuery &queryItems = {});
/** Returns a new settings pre-set in a specific group. The Settings will be created
with the given parent. If no parent is specified, the caller must destroy the settings */

View file

@ -58,7 +58,7 @@ bool hasLaunchOnStartup_private(const QString &)
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
CFURLRef itemUrlRef = NULL;
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr) {
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
returnValue = true;
@ -100,7 +100,7 @@ void setLaunchOnStartup_private(const QString &appName, const QString &guiName,
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
CFURLRef itemUrlRef = NULL;
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr) {
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
LSSharedFileListItemRemove(loginItems, item); // remove it!

View file

@ -77,7 +77,7 @@ int csync_update(CSYNC *ctx) {
csync_memstat_check();
if (!ctx->excludes) {
if (!ctx->exclude_traversal_fn) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "No exclude file loaded or defined!");
}
@ -211,14 +211,14 @@ static int _csync_treewalk_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
if (other_file_it == other_tree->cend()) {
/* Check the renamed path as well. */
QByteArray renamed_path = csync_rename_adjust_path(ctx, cur->path);
QByteArray renamed_path = csync_rename_adjust_parent_path(ctx, cur->path);
if (renamed_path != cur->path)
other_file_it = other_tree->find(renamed_path);
}
if (other_file_it == other_tree->cend()) {
/* Check the source path as well. */
QByteArray renamed_path = csync_rename_adjust_path_source(ctx, cur->path);
QByteArray renamed_path = csync_rename_adjust_parent_path_source(ctx, cur->path);
if (renamed_path != cur->path)
other_file_it = other_tree->find(renamed_path);
}
@ -314,6 +314,9 @@ int csync_s::reinitialize() {
local.files.clear();
remote.files.clear();
renames.folder_renamed_from.clear();
renames.folder_renamed_to.clear();
local_discovery_style = LocalDiscoveryStyle::FilesystemOnly;
locally_touched_dirs.clear();

View file

@ -108,7 +108,8 @@ enum csync_status_codes_e {
CSYNC_STATUS_INDIVIDUAL_STAT_FAILED,
CSYNC_STATUS_FORBIDDEN,
CSYNC_STATUS_INDIVIDUAL_TOO_DEEP,
CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE
CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE,
CSYNC_STATUS_INDIVIDUAL_CANNOT_ENCODE
};
typedef enum csync_status_codes_e CSYNC_STATUS;

View file

@ -40,6 +40,7 @@
#include "common/utility.h"
#include <QString>
#include <QFileInfo>
#ifdef _WIN32
#include <io.h>
@ -50,25 +51,6 @@
#define CSYNC_LOG_CATEGORY_NAME "csync.exclude"
#include "csync_log.h"
#ifndef WITH_TESTING
static
#endif
int _csync_exclude_add(c_strlist_t **inList, const char *string) {
size_t i = 0;
// We never want duplicates, so check whether the string is already
// in the list first.
if (*inList) {
for (i = 0; i < (*inList)->count; ++i) {
char *pattern = (*inList)->vector[i];
if (c_streq(pattern, string)) {
return 1;
}
}
}
return c_strlist_add_grow(inList, string);
}
/** Expands C-like escape sequences.
*
* The returned string is heap-allocated and owned by the caller.
@ -87,7 +69,6 @@ static const char *csync_exclude_expand_escapes(const char * input)
case '\'': out[o++] = '\''; break;
case '"': out[o++] = '"'; break;
case '?': out[o++] = '?'; break;
case '\\': out[o++] = '\\'; break;
case '#': out[o++] = '#'; break;
case 'a': out[o++] = '\a'; break;
case 'b': out[o++] = '\b'; break;
@ -97,6 +78,9 @@ static const char *csync_exclude_expand_escapes(const char * input)
case 't': out[o++] = '\t'; break;
case 'v': out[o++] = '\v'; break;
default:
// '\*' '\?' '\[' '\\' will be processed during regex translation
// '\\' is intentionally not expanded here (to avoid '\\*' and '\*'
// ending up meaning the same thing)
out[o++] = input[i];
out[o++] = input[i+1];
break;
@ -109,7 +93,8 @@ static const char *csync_exclude_expand_escapes(const char * input)
return out;
}
int csync_exclude_load(const char *fname, c_strlist_t **list) {
/** Loads patterns from a file and adds them to excludes */
int csync_exclude_load(const char *fname, QList<QByteArray> *excludes) {
int fd = -1;
int i = 0;
int rc = -1;
@ -162,14 +147,9 @@ int csync_exclude_load(const char *fname, c_strlist_t **list) {
buf[i] = '\0';
if (*entry != '#') {
const char *unescaped = csync_exclude_expand_escapes(entry);
rc = _csync_exclude_add(list, unescaped);
if( rc == 0 ) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Adding entry: %s", unescaped);
}
excludes->append(unescaped);
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Adding entry: %s", unescaped);
SAFE_FREE(unescaped);
if (rc < 0) {
goto out;
}
}
}
entry = buf + i + 1;
@ -193,6 +173,11 @@ static const char *win_reserved_words_4[] = {
};
static const char *win_reserved_words_n[] = { "CLOCK$", "$Recycle.Bin" };
/**
* @brief Checks if filename is considered reserved by Windows
* @param file_name filename
* @return true if file is reserved, false otherwise
*/
bool csync_is_windows_reserved_word(const char *filename)
{
size_t len_filename = strlen(filename);
@ -233,14 +218,12 @@ bool csync_is_windows_reserved_word(const char *filename)
return false;
}
static CSYNC_EXCLUDE_TYPE _csync_excluded_common(c_strlist_t *excludes, const char *path, int filetype, bool check_leading_dirs) {
size_t i = 0;
static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const char *path)
{
const char *bname = NULL;
size_t blen = 0;
int rc = -1;
CSYNC_EXCLUDE_TYPE match = CSYNC_NOT_EXCLUDED;
CSYNC_EXCLUDE_TYPE type = CSYNC_NOT_EXCLUDED;
c_strlist_t *path_components = NULL;
/* split up the path */
bname = strrchr(path, '/');
@ -322,10 +305,12 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(c_strlist_t *excludes, const ch
#endif
/* We create a desktop.ini on Windows for the sidebar icon, make sure we don't sync them. */
rc = csync_fnmatch("Desktop.ini", bname, 0);
if (rc == 0) {
match = CSYNC_FILE_SILENTLY_EXCLUDED;
goto out;
if (blen == 11) {
rc = csync_fnmatch("Desktop.ini", bname, 0);
if (rc == 0) {
match = CSYNC_FILE_SILENTLY_EXCLUDED;
goto out;
}
}
if (!OCC::Utility::shouldUploadConflictFiles()) {
@ -335,192 +320,393 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(c_strlist_t *excludes, const ch
}
}
if( ! excludes ) {
goto out;
}
if (check_leading_dirs) {
/* Build a list of path components to check. */
path_components = c_strlist_new(32);
char *path_split = strdup(path);
size_t len = strlen(path_split);
for (i = len; ; --i) {
// read backwards until a path separator is found
if (i != 0 && path_split[i-1] != '/') {
continue;
}
// check 'basename', i.e. for "/foo/bar/fi" we'd check 'fi', 'bar', 'foo'
if (path_split[i] != 0) {
c_strlist_add_grow(&path_components, path_split + i);
}
if (i == 0) {
break;
}
// check 'dirname', i.e. for "/foo/bar/fi" we'd check '/foo/bar', '/foo'
path_split[i-1] = '\0';
c_strlist_add_grow(&path_components, path_split);
}
SAFE_FREE(path_split);
}
/* Loop over all exclude patterns and evaluate the given path */
for (i = 0; match == CSYNC_NOT_EXCLUDED && i < excludes->count; i++) {
bool match_dirs_only = false;
char *pattern = excludes->vector[i];
type = CSYNC_FILE_EXCLUDE_LIST;
if (!pattern[0]) { /* empty pattern */
continue;
}
/* Excludes starting with ']' means it can be cleanup */
if (pattern[0] == ']') {
++pattern;
if (filetype == CSYNC_FTW_TYPE_FILE) {
type = CSYNC_FILE_EXCLUDE_AND_REMOVE;
}
}
/* Check if the pattern applies to pathes only. */
if (pattern[strlen(pattern)-1] == '/') {
if (!check_leading_dirs && filetype == CSYNC_FTW_TYPE_FILE) {
continue;
}
match_dirs_only = true;
pattern[strlen(pattern)-1] = '\0'; /* Cut off the slash */
}
/* check if the pattern contains a / and if, compare to the whole path */
if (strchr(pattern, '/')) {
rc = csync_fnmatch(pattern, path, FNM_PATHNAME);
if( rc == 0 ) {
match = type;
}
/* if the pattern requires a dir, but path is not, its still not excluded. */
if (match_dirs_only && filetype != CSYNC_FTW_TYPE_DIR) {
match = CSYNC_NOT_EXCLUDED;
}
}
/* if still not excluded, check each component and leading directory of the path */
if (match == CSYNC_NOT_EXCLUDED && check_leading_dirs) {
size_t j = 0;
if (match_dirs_only && filetype == CSYNC_FTW_TYPE_FILE) {
j = 1; // skip the first entry, which is bname
}
for (; j < path_components->count; ++j) {
rc = csync_fnmatch(pattern, path_components->vector[j], 0);
if (rc == 0) {
match = type;
break;
}
}
} else if (match == CSYNC_NOT_EXCLUDED && !check_leading_dirs) {
rc = csync_fnmatch(pattern, bname, 0);
if (rc == 0) {
match = type;
}
}
if (match_dirs_only) {
/* restore the '/' */
pattern[strlen(pattern)] = '/';
}
}
c_strlist_destroy(path_components);
out:
return match;
}
/* Only for bnames (not paths) */
static QString convertToBnameRegexpSyntax(QString exclude)
using namespace OCC;
ExcludedFiles::ExcludedFiles()
{
QString s = QRegularExpression::escape(exclude).replace("\\*", ".*").replace("\\?", ".");
return s;
}
void csync_exclude_traversal_prepare(CSYNC *ctx)
ExcludedFiles::~ExcludedFiles()
{
ctx->parsed_traversal_excludes.prepare(ctx->excludes);
}
void csync_s::TraversalExcludes::prepare(c_strlist_t *excludes)
void ExcludedFiles::addExcludeFilePath(const QString &path)
{
c_strlist_destroy(list_patterns_fnmatch);
list_patterns_fnmatch = nullptr;
_excludeFiles.insert(path);
}
// Start out with regexes that would match nothing
QString exclude_only = "a^";
QString exclude_and_remove = "a^";
void ExcludedFiles::addManualExclude(const QByteArray &expr)
{
_manualExcludes.append(expr);
_allExcludes.append(expr);
prepare();
}
size_t exclude_count = excludes ? excludes->count : 0;
for (unsigned int i = 0; i < exclude_count; i++) {
char *exclude = excludes->vector[i];
QString *builderToUse = & exclude_only;
if (exclude[0] == '\n') continue; // empty line
if (exclude[0] == '\r') continue; // empty line
void ExcludedFiles::clearManualExcludes()
{
_manualExcludes.clear();
reloadExcludeFiles();
}
/* If an exclude entry contains some fnmatch-ish characters, we use the C-style codepath without QRegularEpression */
if (strchr(exclude, '/') || strchr(exclude, '[') || strchr(exclude, '{') || strchr(exclude, '\\')) {
_csync_exclude_add(&list_patterns_fnmatch, exclude);
continue;
}
bool ExcludedFiles::reloadExcludeFiles()
{
_allExcludes.clear();
bool success = true;
foreach (const QString &file, _excludeFiles) {
if (csync_exclude_load(file.toUtf8(), &_allExcludes) < 0)
success = false;
}
_allExcludes.append(_manualExcludes);
prepare();
return success;
}
/* Those will attempt to use QRegularExpression */
if (exclude[0] == ']'){
exclude++;
builderToUse = &exclude_and_remove;
}
if (builderToUse->size() > 0) {
builderToUse->append("|");
}
builderToUse->append(convertToBnameRegexpSyntax(exclude));
bool ExcludedFiles::isExcluded(
const QString &filePath,
const QString &basePath,
bool excludeHidden) const
{
if (!filePath.startsWith(basePath, Utility::fsCasePreserving() ? Qt::CaseInsensitive : Qt::CaseSensitive)) {
// Mark paths we're not responsible for as excluded...
return true;
}
QString pattern = "^(" + exclude_only + ")$|^(" + exclude_and_remove + ")$";
regexp_exclude.setPattern(pattern);
QRegularExpression::PatternOptions patternOptions = QRegularExpression::OptimizeOnFirstUsageOption;
if (OCC::Utility::fsCasePreserving())
patternOptions |= QRegularExpression::CaseInsensitiveOption;
regexp_exclude.setPatternOptions(patternOptions);
regexp_exclude.optimize();
}
CSYNC_EXCLUDE_TYPE csync_excluded_traversal(CSYNC *ctx, const char *path, int filetype) {
CSYNC_EXCLUDE_TYPE match = CSYNC_NOT_EXCLUDED;
/* Check only static patterns and only with the reduced list which is empty usually */
match = _csync_excluded_common(ctx->parsed_traversal_excludes.list_patterns_fnmatch, path, filetype, false);
if (match != CSYNC_NOT_EXCLUDED) {
return match;
}
if (ctx->excludes) {
/* Now check with our optimized regexps */
const char *bname = NULL;
/* split up the path */
bname = strrchr(path, '/');
if (bname) {
bname += 1; // don't include the /
} else {
bname = path;
}
QString p = QString::fromUtf8(bname);
auto m = ctx->parsed_traversal_excludes.regexp_exclude.match(p);
if (m.hasMatch()) {
if (!m.captured(1).isEmpty()) {
match = CSYNC_FILE_EXCLUDE_LIST;
} else if (!m.captured(2).isEmpty()) {
match = CSYNC_FILE_EXCLUDE_AND_REMOVE;
if (excludeHidden) {
QString path = filePath;
// Check all path subcomponents, but to *not* check the base path:
// We do want to be able to sync with a hidden folder as the target.
while (path.size() > basePath.size()) {
QFileInfo fi(path);
if (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.'))) {
return true;
}
// Get the parent path
path = fi.absolutePath();
}
}
QFileInfo fi(filePath);
csync_ftw_type_e type = CSYNC_FTW_TYPE_FILE;
if (fi.isDir()) {
type = CSYNC_FTW_TYPE_DIR;
}
QString relativePath = filePath.mid(basePath.size());
if (relativePath.endsWith(QLatin1Char('/'))) {
relativePath.chop(1);
}
return fullPatternMatch(relativePath.toUtf8(), type) != CSYNC_NOT_EXCLUDED;
}
CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, int filetype) const
{
auto match = _csync_excluded_common(path);
if (match != CSYNC_NOT_EXCLUDED)
return match;
if (_allExcludes.isEmpty())
return CSYNC_NOT_EXCLUDED;
// Check the bname part of the path to see whether the full
// regex should be run.
const char *bname = strrchr(path, '/');
if (bname) {
bname += 1; // don't include the /
} else {
bname = path;
}
QString bnameStr = QString::fromUtf8(bname);
QRegularExpressionMatch m;
if (filetype == CSYNC_FTW_TYPE_DIR) {
m = _bnameActivationRegexDir.match(bnameStr);
} else {
m = _bnameActivationRegexFile.match(bnameStr);
}
if (!m.hasMatch())
return match;
// Now run the full match
QString pathStr = QString::fromUtf8(path);
if (filetype == CSYNC_FTW_TYPE_DIR) {
m = _fullRegexDir.match(pathStr);
} else {
m = _fullRegexFile.match(pathStr);
}
if (m.hasMatch()) {
if (!m.captured(1).isEmpty()) {
match = CSYNC_FILE_EXCLUDE_LIST;
} else if (!m.captured(2).isEmpty()) {
match = CSYNC_FILE_EXCLUDE_AND_REMOVE;
}
}
return match;
}
CSYNC_EXCLUDE_TYPE csync_excluded_no_ctx(c_strlist_t *excludes, const char *path, int filetype) {
return _csync_excluded_common(excludes, path, filetype, true);
CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const char *path, int filetype) const
{
auto match = _csync_excluded_common(path);
if (match != CSYNC_NOT_EXCLUDED)
return match;
if (_allExcludes.isEmpty())
return CSYNC_NOT_EXCLUDED;
QString p = QString::fromUtf8(path);
QRegularExpressionMatch m;
if (filetype == CSYNC_FTW_TYPE_DIR) {
m = _fullRegexDir.match(p);
} else {
m = _fullRegexFile.match(p);
}
if (m.hasMatch()) {
if (!m.captured(1).isEmpty()) {
return CSYNC_FILE_EXCLUDE_LIST;
} else if (!m.captured(2).isEmpty()) {
return CSYNC_FILE_EXCLUDE_AND_REMOVE;
}
}
return CSYNC_NOT_EXCLUDED;
}
auto ExcludedFiles::csyncTraversalMatchFun() const
-> std::function<CSYNC_EXCLUDE_TYPE(const char *path, int filetype)>
{
return [this](const char *path, int filetype) { return this->traversalPatternMatch(path, filetype); };
}
static QString convertToRegexpSyntax(QString exclude)
{
// Translate *, ?, [...] to their regex variants.
// The escape sequences \*, \?, \[. \\ have a special meaning,
// the other ones have already been expanded before
// (like "\\n" being replaced by "\n").
//
// QString being UTF-16 makes unicode-correct escaping tricky.
// If we escaped each UTF-16 code unit we'd end up splitting 4-byte
// code points. To avoid problems we delegate as much work as possible to
// QRegularExpression::escape(): It always receives as long a sequence
// as code units as possible.
QString regex;
int i = 0;
int charsToEscape = 0;
auto flush = [&]() {
regex.append(QRegularExpression::escape(exclude.mid(i - charsToEscape, charsToEscape)));
charsToEscape = 0;
};
auto len = exclude.size();
for (; i < len; ++i) {
switch (exclude[i].unicode()) {
case '*':
flush();
regex.append("[^/]*");
break;
case '?':
flush();
regex.append("[^/]");
break;
case '[': {
flush();
// Find the end of the bracket expression
auto j = i + 1;
for (; j < len; ++j) {
if (exclude[j] == ']')
break;
if (j != len - 1 && exclude[j] == '\\' && exclude[j + 1] == ']')
++j;
}
if (j == len) {
// no matching ], just insert the escaped [
regex.append("\\[");
break;
}
// Translate [! to [^
QString bracketExpr = exclude.mid(i, j - i + 1);
if (bracketExpr.startsWith("[!"))
bracketExpr[1] = '^';
regex.append(bracketExpr);
i = j;
break;
}
case '\\':
flush();
if (i == len - 1) {
regex.append("\\\\");
break;
}
// '\*' -> '\*', but '\z' -> '\\z'
switch (exclude[i + 1].unicode()) {
case '*':
case '?':
case '[':
case '\\':
regex.append(QRegularExpression::escape(exclude.mid(i + 1, 1)));
break;
default:
charsToEscape += 2;
break;
}
++i;
break;
default:
++charsToEscape;
break;
}
}
flush();
return regex;
}
void ExcludedFiles::prepare()
{
// Build regular expressions for the different cases.
//
// To compose the _bnameActivationRegex and _fullRegex patterns we
// collect several subgroups of patterns here.
//
// * The "full" group will contain all patterns that contain a non-trailing
// slash. They only make sense in the fullRegex.
// * The "bname" group contains all patterns without a non-trailing slash.
// These need separate handling in the _fullRegex (slash-containing
// patterns must be anchored to the front, these don't need it)
// * The "bnameTrigger" group contains the bname part of all patterns in the
// "full" group. These and the "bname" group become _bnameActivationRegex.
//
// To complicate matters, the exclude patterns have two binary attributes
// meaning we'll end up with 4 variants:
// * "]" patterns mean "EXCLUDE_AND_REMOVE", they get collected in the
// pattern strings ending in "Remove". The others go to "Keep".
// * trailing-slash patterns match directories only. They get collected
// in the pattern strings saying "Dir", the others go into "FileDir"
// because they match files and directories.
QString fullFileDirKeep;
QString fullFileDirRemove;
QString fullDirKeep;
QString fullDirRemove;
QString bnameFileDirKeep;
QString bnameFileDirRemove;
QString bnameDirKeep;
QString bnameDirRemove;
QString bnameTriggerFileDir;
QString bnameTriggerDir;
auto regexAppend = [](QString &fileDirPattern, QString &dirPattern, const QString &appendMe, bool dirOnly) {
QString &pattern = dirOnly ? dirPattern : fileDirPattern;
if (!pattern.isEmpty())
pattern.append("|");
pattern.append(appendMe);
};
for (auto exclude : _allExcludes) {
if (exclude[0] == '\n')
continue; // empty line
if (exclude[0] == '\r')
continue; // empty line
bool matchDirOnly = exclude.endsWith('/');
if (matchDirOnly)
exclude = exclude.left(exclude.size() - 1);
bool removeExcluded = (exclude[0] == ']');
if (removeExcluded)
exclude = exclude.mid(1);
bool fullPath = exclude.contains('/');
/* Use QRegularExpression, append to the right pattern */
auto &bnameFileDir = removeExcluded ? bnameFileDirRemove : bnameFileDirKeep;
auto &bnameDir = removeExcluded ? bnameDirRemove : bnameDirKeep;
auto &fullFileDir = removeExcluded ? fullFileDirRemove : fullFileDirKeep;
auto &fullDir = removeExcluded ? fullDirRemove : fullDirKeep;
auto regexExclude = convertToRegexpSyntax(QString::fromUtf8(exclude));
if (!fullPath) {
regexAppend(bnameFileDir, bnameDir, regexExclude, matchDirOnly);
} else {
regexAppend(fullFileDir, fullDir, regexExclude, matchDirOnly);
// for activation, trigger on the 'bname' part of the full pattern
auto bnameExclude = exclude.mid(exclude.lastIndexOf('/') + 1);
auto regexBname = convertToRegexpSyntax(bnameExclude);
regexAppend(bnameTriggerFileDir, bnameTriggerDir, regexBname, matchDirOnly);
}
}
// The empty pattern would match everything - change it to match-nothing
auto emptyMatchNothing = [](QString &pattern) {
if (pattern.isEmpty())
pattern = "a^";
};
emptyMatchNothing(fullFileDirKeep);
emptyMatchNothing(fullFileDirRemove);
emptyMatchNothing(fullDirKeep);
emptyMatchNothing(fullDirRemove);
emptyMatchNothing(bnameFileDirKeep);
emptyMatchNothing(bnameFileDirRemove);
emptyMatchNothing(bnameDirKeep);
emptyMatchNothing(bnameDirRemove);
emptyMatchNothing(bnameTriggerFileDir);
emptyMatchNothing(bnameTriggerDir);
// The bname activation regexe is applied to the bname only, so must be
// anchored in the beginning and in the end. It has the explicit triggers
// plus the bname-only patterns. Here we don't care about the remove/keep
// distinction.
_bnameActivationRegexFile.setPattern(
"^(?:" + bnameFileDirKeep + "|" + bnameFileDirRemove + "|" + bnameTriggerFileDir + ")$");
_bnameActivationRegexDir.setPattern(
"^(?:" + bnameFileDirKeep + "|" + bnameFileDirRemove
+ "|" + bnameDirKeep + "|" + bnameFileDirRemove
+ "|" + bnameTriggerFileDir + "|" + bnameTriggerDir + ")$");
// The full regex has two captures, it's basic form is "(...)|(...)". The first
// capture has the keep/exclude-only patterns, the second the remove/exclude-and-remove
// patterns.
_fullRegexFile.setPattern(
QLatin1String("(")
// Full patterns are anchored to the beginning
+ "^(?:" + fullFileDirKeep + ")(?:$|/)" + "|"
// Simple bname patterns can be any path component
+ "(?:^|/)(?:" + bnameFileDirKeep + ")(?:$|/)" + "|"
// When checking a file for exclusion we must check all parent paths
// against the dir-only patterns as well.
+ "(?:^|/)(?:" + bnameDirKeep + ")/"
+ ")|("
+ "^(?:" + fullFileDirRemove + ")(?:$|/)" + "|"
+ "(?:^|/)(?:" + bnameFileDirRemove + ")(?:$|/)" + "|"
+ "(?:^|/)(?:" + bnameDirRemove + ")/"
+ ")");
_fullRegexDir.setPattern(
QLatin1String("(")
+ "^(?:" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)" + "|"
+ "(?:^|/)(?:" + bnameFileDirKeep + "|" + bnameDirKeep + ")(?:$|/)"
+ ")|("
+ "^(?:" + fullFileDirRemove + "|" + fullDirRemove + ")(?:$|/)" + "|"
+ "(?:^|/)(?:" + bnameFileDirRemove + "|" + bnameDirRemove + ")(?:$|/)"
+ ")");
QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption;
if (OCC::Utility::fsCasePreserving())
patternOptions |= QRegularExpression::CaseInsensitiveOption;
_bnameActivationRegexFile.setPatternOptions(patternOptions);
_bnameActivationRegexFile.optimize();
_bnameActivationRegexDir.setPatternOptions(patternOptions);
_bnameActivationRegexDir.optimize();
_fullRegexFile.setPatternOptions(patternOptions);
_fullRegexFile.optimize();
_fullRegexDir.setPatternOptions(patternOptions);
_fullRegexDir.optimize();
}

View file

@ -23,6 +23,13 @@
#include "ocsynclib.h"
#include "csync.h"
#include <QObject>
#include <QSet>
#include <QString>
#include <QRegularExpression>
enum csync_exclude_type_e {
CSYNC_NOT_EXCLUDED = 0,
CSYNC_FILE_SILENTLY_EXCLUDED,
@ -33,67 +40,156 @@ enum csync_exclude_type_e {
CSYNC_FILE_EXCLUDE_LONG_FILENAME,
CSYNC_FILE_EXCLUDE_HIDDEN,
CSYNC_FILE_EXCLUDE_STAT_FAILED,
CSYNC_FILE_EXCLUDE_CONFLICT
CSYNC_FILE_EXCLUDE_CONFLICT,
CSYNC_FILE_EXCLUDE_CANNOT_ENCODE
};
typedef enum csync_exclude_type_e CSYNC_EXCLUDE_TYPE;
#ifdef WITH_TESTING
int OCSYNC_EXPORT _csync_exclude_add(c_strlist_t **inList, const char *string);
#endif
class ExcludedFilesTest;
/**
* @brief Load exclude list
* Manages file/directory exclusion.
*
* @param ctx The context of the synchronizer.
* @param fname The filename to load.
* Most commonly exclude patterns are loaded from files. See
* addExcludeFilePath() and reloadExcludeFiles().
*
* @return 0 on success, -1 if an error occurred with errno set.
* Excluded files are primarily relevant for sync runs, and for
* file watcher filtering.
*
* Excluded files and ignored files are the same thing. But the
* selective sync blacklist functionality is a different thing
* entirely.
*/
int OCSYNC_EXPORT csync_exclude_load(const char *fname, c_strlist_t **list);
class OCSYNC_EXPORT ExcludedFiles : public QObject
{
Q_OBJECT
public:
ExcludedFiles();
~ExcludedFiles();
/**
* @brief When all list loads and list are done
*
* Used to initialize internal data structures that build upon the loaded excludes.
*
* @param ctx
*/
void OCSYNC_EXPORT csync_exclude_traversal_prepare(CSYNC *ctx);
/**
* Adds a new path to a file containing exclude patterns.
*
* Does not load the file. Use reloadExcludeFiles() afterwards.
*/
void addExcludeFilePath(const QString &path);
/**
* @brief Check if the given path should be excluded in a traversal situation.
*
* It does only part of the work that csync_excluded does because it's assumed
* that all leading directories have been run through csync_excluded_traversal()
* before. This can be significantly faster.
*
* That means for '/foo/bar/file' only ('/foo/bar/file', 'file') is checked
* against the exclude patterns.
*
* @param ctx The synchronizer context.
* @param path The patch to check.
*
* @return 2 if excluded and needs cleanup, 1 if excluded, 0 if not.
*/
CSYNC_EXCLUDE_TYPE OCSYNC_EXPORT csync_excluded_traversal(CSYNC *ctx, const char *path, int filetype);
/**
* Checks whether a file or directory should be excluded.
*
* @param filePath the absolute path to the file
* @param basePath folder path from which to apply exclude rules, ends with a /
*/
bool isExcluded(
const QString &filePath,
const QString &basePath,
bool excludeHidden) const;
/**
* Adds an exclude pattern.
*
* Primarily used in tests. Patterns added this way are preserved when
* reloadExcludeFiles() is called.
*/
void addManualExclude(const QByteArray &expr);
/**
* Removes all manually added exclude patterns.
*
* Primarily used in tests.
*/
void clearManualExcludes();
/**
* Generate a hook for traversal exclude pattern matching
* that csync can use.
*
* Careful: The function will only be valid for as long as this
* ExcludedFiles instance stays alive.
*/
auto csyncTraversalMatchFun() const
-> std::function<CSYNC_EXCLUDE_TYPE(const char *path, int filetype)>;
public slots:
/**
* Reloads the exclude patterns from the registered paths.
*/
bool reloadExcludeFiles();
private:
/**
* @brief Match the exclude pattern against the full path.
*
* @param Path is folder-relative, should not start with a /.
*
* Note that this only matches patterns. It does not check whether the file
* or directory pointed to is hidden (or whether it even exists).
*/
CSYNC_EXCLUDE_TYPE fullPatternMatch(const char *path, int filetype) const;
/**
* @brief Check if the given path should be excluded in a traversal situation.
*
* It does only part of the work that full() does because it's assumed
* that all leading directories have been run through traversal()
* before. This can be significantly faster.
*
* That means for 'foo/bar/file' only ('foo/bar/file', 'file') is checked
* against the exclude patterns.
*
* @param Path is folder-relative, should not start with a /.
*
* Note that this only matches patterns. It does not check whether the file
* or directory pointed to is hidden (or whether it even exists).
*/
CSYNC_EXCLUDE_TYPE traversalPatternMatch(const char *path, int filetype) const;
/**
* Generate optimized regular expressions for the exclude patterns.
*
* The optimization works in two steps: First, all supported patterns are put
* into _fullRegexFile/_fullRegexDir. These regexes can be applied to the full
* path to determine whether it is excluded or not.
*
* The second is a performance optimization. The particularly common use
* case for excludes during a sync run is "traversal": Instead of checking
* the full path every time, we check each parent path with the traversal
* function incrementally.
*
* Example: When the sync run eventually arrives at "a/b/c it can assume
* that the traversal matching has already been run on "a", "a/b"
* and just needs to run the traversal matcher on "a/b/c".
*
* The full matcher is equivalent to or-combining the traversal match results
* of all parent paths:
* full("a/b/c/d") == traversal("a") || traversal("a/b") || traversal("a/b/c")
*
* The traversal matcher can be extremely fast because it has a fast early-out
* case: It checks the bname part of the path against _bnameActivationRegex
* and only runs the full regex if the bname activation was triggered.
*
* Note: The traversal matcher will return not-excluded on some paths that the
* full matcher would exclude. Example: "b" is excluded. traversal("b/c")
* returns not-excluded because "c" isn't a bname activation pattern.
*/
void prepare();
/// Files to load excludes from
QSet<QString> _excludeFiles;
/// Exclude patterns added with addManualExclude()
QList<QByteArray> _manualExcludes;
/// List of all active exclude patterns
QList<QByteArray> _allExcludes;
/// see prepare()
QRegularExpression _bnameActivationRegexFile;
QRegularExpression _bnameActivationRegexDir;
QRegularExpression _fullRegexFile;
QRegularExpression _fullRegexDir;
friend class ExcludedFilesTest;
};
/**
* @brief Checks all path components if the whole path should be excluded
*
* @param excludes
* @param path
* @param filetype
* @return
*/
CSYNC_EXCLUDE_TYPE OCSYNC_EXPORT csync_excluded_no_ctx(c_strlist_t *excludes, const char *path, int filetype);
#endif /* _CSYNC_EXCLUDE_H */
/**
* @brief Checks if filename is considered reserved by Windows
* @param file_name filename
* @return true if file is reserved, false otherwise
*/
bool csync_is_windows_reserved_word(const char *file_name);
/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */

View file

@ -39,6 +39,7 @@
#include <sqlite3.h>
#include <map>
#include <set>
#include <functional>
#include "common/syncjournaldb.h"
#include "config_csync.h"
@ -46,11 +47,9 @@
#include "std/c_private.h"
#include "csync.h"
#include "csync_misc.h"
#include "csync_exclude.h"
#include "csync_macros.h"
#include <QRegularExpression>
/**
* How deep to scan directories.
*/
@ -148,17 +147,13 @@ struct OCSYNC_EXPORT csync_s {
OCC::SyncJournalDb *statedb;
c_strlist_t *excludes = nullptr; /* list of individual patterns collected from all exclude files */
struct TraversalExcludes {
~TraversalExcludes() {
c_strlist_destroy(list_patterns_fnmatch);
}
void prepare(c_strlist_t *excludes);
QRegularExpression regexp_exclude;
c_strlist_t *list_patterns_fnmatch = nullptr;
} parsed_traversal_excludes;
/**
* Function used to determine whether an item is excluded
* during the update phase.
*
* See ExcludedFiles in csync_exclude.
*/
std::function<CSYNC_EXCLUDE_TYPE(const char *path, int filetype)> exclude_traversal_fn;
struct {
std::unordered_map<ByteArrayRef, QByteArray, ByteArrayRefHash> folder_renamed_to; // map from->to

View file

@ -113,7 +113,7 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
if (!other) {
/* Check the renamed path as well. */
other = other_tree->findFile(csync_rename_adjust_path(ctx, cur->path));
other = other_tree->findFile(csync_rename_adjust_parent_path(ctx, cur->path));
}
if (!other) {
/* Check if it is ignored */
@ -147,24 +147,25 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
cur->instruction = CSYNC_INSTRUCTION_NEW;
bool processedRename = false;
auto renameCandidateProcessing = [&](const OCC::SyncJournalFileRecord &base) {
auto renameCandidateProcessing = [&](const QByteArray &basePath) {
if (processedRename)
return;
if (!base.isValid())
if (basePath.isEmpty())
return;
/* First, check that the file is NOT in our tree (another file with the same name was added) */
if (our_tree->findFile(base._path)) {
qCDebug(lcReconcile, "Origin found in our tree : %s", base._path.constData());
if (our_tree->findFile(basePath)) {
other = nullptr;
qCDebug(lcReconcile, "Origin found in our tree : %s", basePath.constData());
} else {
/* Find the potential rename source file in the other tree.
* If the renamed file could not be found in the opposite tree, that is because it
* is not longer existing there, maybe because it was renamed or deleted.
* The journal is cleaned up later after propagation.
*/
other = other_tree->findFile(base._path);
other = other_tree->findFile(basePath);
qCDebug(lcReconcile, "Rename origin in other tree (%s) %s",
base._path.constData(), other ? "found" : "not found");
basePath.constData(), other ? "found" : "not found");
}
if(!other) {
@ -197,7 +198,7 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
cur->instruction = CSYNC_INSTRUCTION_NONE;
// We have consumed 'other': exit this loop to not consume another one.
processedRename = true;
} else if (our_tree->findFile(csync_rename_adjust_path(ctx, other->path)) == cur) {
} else if (our_tree->findFile(csync_rename_adjust_parent_path(ctx, other->path)) == cur) {
// If we're here, that means that the other side's reconcile will be able
// to work against cur: The filename itself didn't change, only a parent
// directory was renamed! In that case it's safe to ignore the rename
@ -225,12 +226,34 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
qCDebug(lcReconcile, "Finding rename origin through inode %" PRIu64 "",
cur->inode);
ctx->statedb->getFileRecordByInode(cur->inode, &base);
renameCandidateProcessing(base);
renameCandidateProcessing(base._path);
} else {
ASSERT(ctx->current == REMOTE_REPLICA);
qCDebug(lcReconcile, "Finding rename origin through file ID %s",
cur->file_id.constData());
ctx->statedb->getFileRecordsByFileId(cur->file_id, renameCandidateProcessing);
// The update phase has already mapped out all dir->dir renames, check the
// path that is consistent with that first. Otherwise update mappings and
// reconcile mappings might disagree, leading to odd situations down the
// line.
auto basePath = csync_rename_adjust_full_path_source(ctx, cur->path);
if (basePath != cur->path) {
qCDebug(lcReconcile, "Trying rename origin by csync_rename mapping %s",
basePath.constData());
// We go through getFileRecordsByFileId to ensure the basePath
// computed in this way also has the expected fileid.
ctx->statedb->getFileRecordsByFileId(cur->file_id,
[&](const OCC::SyncJournalFileRecord &base) {
if (base._path == basePath)
renameCandidateProcessing(basePath);
});
}
// Also feed all the other files with the same fileid if necessary
if (!processedRename) {
qCDebug(lcReconcile, "Finding rename origin through file ID %s",
cur->file_id.constData());
ctx->statedb->getFileRecordsByFileId(cur->file_id,
[&](const OCC::SyncJournalFileRecord &base) { renameCandidateProcessing(base._path); });
}
}
break;

View file

@ -36,7 +36,7 @@ void csync_rename_record(CSYNC* ctx, const QByteArray &from, const QByteArray &t
ctx->renames.folder_renamed_from[to] = from;
}
QByteArray csync_rename_adjust_path(CSYNC* ctx, const QByteArray &path)
QByteArray csync_rename_adjust_parent_path(CSYNC *ctx, const QByteArray &path)
{
if (ctx->renames.folder_renamed_to.empty())
return path;
@ -50,11 +50,25 @@ QByteArray csync_rename_adjust_path(CSYNC* ctx, const QByteArray &path)
return path;
}
QByteArray csync_rename_adjust_path_source(CSYNC* ctx, const QByteArray &path)
QByteArray csync_rename_adjust_parent_path_source(CSYNC *ctx, const QByteArray &path)
{
if (ctx->renames.folder_renamed_from.empty())
return path;
for (auto p = _parentDir(path); !p.isEmpty(); p = _parentDir(p)) {
for (ByteArrayRef p = _parentDir(path); !p.isEmpty(); p = _parentDir(p)) {
auto it = ctx->renames.folder_renamed_from.find(p);
if (it != ctx->renames.folder_renamed_from.end()) {
QByteArray rep = it->second + path.mid(p.length());
return rep;
}
}
return path;
}
QByteArray csync_rename_adjust_full_path_source(CSYNC *ctx, const QByteArray &path)
{
if (ctx->renames.folder_renamed_from.empty())
return path;
for (ByteArrayRef p = path; !p.isEmpty(); p = _parentDir(p)) {
auto it = ctx->renames.folder_renamed_from.find(p);
if (it != ctx->renames.folder_renamed_from.end()) {
QByteArray rep = it->second + path.mid(p.length());

View file

@ -22,10 +22,19 @@
#include "csync.h"
/* Return the final destination path of a given patch in case of renames */
QByteArray OCSYNC_EXPORT csync_rename_adjust_path(CSYNC *ctx, const QByteArray &path);
/* Return the final destination path of a given patch in case of renames
*
* Does only map the parent directories. If the directory "A" is renamed to
* "B" then this function will not map "A" to "B". Only "A/foo" -> "B/foo".
*/
QByteArray OCSYNC_EXPORT csync_rename_adjust_parent_path(CSYNC *ctx, const QByteArray &path);
/* Return the source of a given path in case of renames */
QByteArray OCSYNC_EXPORT csync_rename_adjust_path_source(CSYNC *ctx, const QByteArray &path);
QByteArray OCSYNC_EXPORT csync_rename_adjust_parent_path_source(CSYNC *ctx, const QByteArray &path);
/* like the parent_path variant, but applying to the full path */
QByteArray OCSYNC_EXPORT csync_rename_adjust_full_path_source(CSYNC *ctx, const QByteArray &path);
void OCSYNC_EXPORT csync_rename_record(CSYNC *ctx, const QByteArray &from, const QByteArray &to);
/* Return the amount of renamed item recorded */
bool OCSYNC_EXPORT csync_rename_count(CSYNC *ctx);

View file

@ -46,6 +46,8 @@
#include "common/utility.h"
#include "common/asserts.h"
#include <QtCore/QTextCodec>
// Needed for PRIu64 on MinGW in C++ mode.
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
@ -107,7 +109,7 @@ static bool _csync_mtime_equal(time_t a, time_t b)
*/
static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> fs) {
OCC::SyncJournalFileRecord base;
CSYNC_EXCLUDE_TYPE excluded;
CSYNC_EXCLUDE_TYPE excluded = CSYNC_NOT_EXCLUDED;
if (fs == NULL) {
errno = EINVAL;
@ -118,8 +120,9 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
if (fs->type == CSYNC_FTW_TYPE_SKIP) {
excluded =CSYNC_FILE_EXCLUDE_STAT_FAILED;
} else {
/* Check if file is excluded */
excluded = csync_excluded_traversal(ctx, fs->path, fs->type);
/* Check if file is excluded */
if (ctx->exclude_traversal_fn)
excluded = ctx->exclude_traversal_fn(fs->path, fs->type);
}
if( excluded == CSYNC_NOT_EXCLUDED ) {
@ -148,6 +151,14 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
}
}
if (ctx->current == REMOTE_REPLICA && QTextCodec::codecForLocale()->mibEnum() != 106) {
/* If the locale codec is not UTF-8, we must check that the filename from the server can
* be encoded in the local file system. */
if (!QTextCodec::codecForLocale()->canEncode(QString::fromUtf8(fs->path))) {
excluded = CSYNC_FILE_EXCLUDE_CANNOT_ENCODE;
}
}
if (fs->type == CSYNC_FTW_TYPE_FILE ) {
if (fs->modtime == 0) {
qCDebug(lcUpdate, "file: %s - mtime is zero!", fs->path.constData());
@ -375,6 +386,8 @@ out:
fs->error_status = CSYNC_STATUS_INDIVIDUAL_STAT_FAILED;
} else if (excluded == CSYNC_FILE_EXCLUDE_CONFLICT) {
fs->error_status = CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE;
} else if (excluded == CSYNC_FILE_EXCLUDE_CANNOT_ENCODE) {
fs->error_status = CSYNC_STATUS_INDIVIDUAL_CANNOT_ENCODE;
}
}
}
@ -486,7 +499,9 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri)
/* Check for exclusion from the tree.
* Note that this is only a safety net in case the ignore list changes
* without a full remote discovery being triggered. */
CSYNC_EXCLUDE_TYPE excluded = csync_excluded_traversal(ctx, st->path, st->type);
CSYNC_EXCLUDE_TYPE excluded = CSYNC_NOT_EXCLUDED;
if (ctx->exclude_traversal_fn)
excluded = ctx->exclude_traversal_fn(st->path, st->type);
if (excluded != CSYNC_NOT_EXCLUDED) {
qDebug(lcUpdate, "%s excluded (%d)", st->path.constData(), excluded);

View file

@ -89,7 +89,7 @@ int c_streq(const char *a, const char *b);
*
* @param size Size to allocate.
*
* @return Pointer to the newly allocated stringlist. NULL if an error occured.
* @return Pointer to the newly allocated stringlist. NULL if an error occurred.
*/
c_strlist_t *c_strlist_new(size_t size);
@ -99,7 +99,7 @@ c_strlist_t *c_strlist_new(size_t size);
* @param strlist Stringlist to expand
* @param size New size of the strlinglist to expand
*
* @return Pointer to the expanded stringlist. NULL if an error occured.
* @return Pointer to the expanded stringlist. NULL if an error occurred.
*/
c_strlist_t *c_strlist_expand(c_strlist_t *strlist, size_t size);
@ -111,7 +111,7 @@ c_strlist_t *c_strlist_expand(c_strlist_t *strlist, size_t size);
* @param strlist Stringlist to add the string.
* @param string String to add.
*
* @return 0 on success, less than 0 and errno set if an error occured.
* @return 0 on success, less than 0 and errno set if an error occurred.
* ENOBUFS if the list is full.
*/
int c_strlist_add(c_strlist_t *strlist, const char *string);
@ -125,7 +125,7 @@ int c_strlist_add(c_strlist_t *strlist, const char *string);
* @param strlist Stringlist to add the string.
* @param string String to add.
*
* @return 0 on success, less than 0 and errno set if an error occured.
* @return 0 on success, less than 0 and errno set if an error occurred.
*/
int c_strlist_add_grow(c_strlist_t **strlist, const char *string);

View file

@ -263,7 +263,6 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
}
if (_model->classify(index) == FolderStatusModel::SubFolder) {
QTreeView *tv = ui->_folderList;
QMenu *menu = new QMenu(tv);
menu->setAttribute(Qt::WA_DeleteOnClose);
@ -275,8 +274,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
ac->setEnabled(false);
}
menu->exec(QCursor::pos());
menu->popup(tv->mapToGlobal(pos));
return;
}
@ -291,6 +289,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
auto folderMan = FolderMan::instance();
QMenu *menu = new QMenu(tv);
menu->setAttribute(Qt::WA_DeleteOnClose);
QAction *ac = menu->addAction(tr("Open folder"));
@ -316,7 +315,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
ac = menu->addAction(tr("Remove folder sync connection"));
connect(ac, &QAction::triggered, this, &AccountSettings::slotRemoveCurrentFolder);
menu->exec(tv->mapToGlobal(pos));
menu->popup(tv->mapToGlobal(pos));
}
void AccountSettings::slotFolderListClicked(const QModelIndex &indx)
@ -688,14 +687,22 @@ void AccountSettings::slotAccountStateChanged()
/* Allow to expand the item if the account is connected. */
ui->_folderList->setItemsExpandable(state == AccountState::Connected);
/* check if there are expanded root items, if so, close them, if the state is different from being Connected. */
if (state != AccountState::Connected) {
/* check if there are expanded root items, if so, close them */
int i;
for (i = 0; i < _model->rowCount(); ++i) {
if (ui->_folderList->isExpanded(_model->index(i)))
ui->_folderList->setExpanded(_model->index(i), false);
}
} else if (_model->isDirty()) {
// If we connect and have pending changes, show the list.
doExpand();
}
// Disabling expansion of folders might require hiding the selective
// sync user interface buttons.
refreshSelectiveSyncStatus();
/* set the correct label for the Account toolbox button */
if (_accountState) {
if (_accountState->isSignedOut()) {
@ -748,7 +755,7 @@ AccountSettings::~AccountSettings()
void AccountSettings::refreshSelectiveSyncStatus()
{
bool shouldBeVisible = _model->isDirty();
bool shouldBeVisible = _model->isDirty() && _accountState->isConnected();
QString msg;
int cnt = 0;

View file

@ -128,9 +128,9 @@ void ActivityListModel::startFetchJob(AccountState *s)
this, &ActivityListModel::slotActivitiesReceived);
job->setProperty("AccountStatePtr", QVariant::fromValue<QPointer<AccountState>>(s));
QList<QPair<QString, QString>> params;
params.append(qMakePair(QString::fromLatin1("page"), QString::fromLatin1("0")));
params.append(qMakePair(QString::fromLatin1("pagesize"), QString::fromLatin1("100")));
QUrlQuery params;
params.addQueryItem(QLatin1String("page"), QLatin1String("0"));
params.addQueryItem(QLatin1String("pagesize"), QLatin1String("100"));
job->addQueryParams(params);
_currentlyFetching.insert(s);

View file

@ -429,14 +429,13 @@ void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCod
}
endNotificationRequest(job->widget(), replyCode);
// FIXME: remove the widget after a couple of seconds
qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply;
// if the notification was successful start a timer that triggers
// removal of the done widgets in a few seconds
// Add 200 millisecs to the predefined value to make sure that the timer in
// widget's method readyToClose() has elapsed.
if (replyCode == OCS_SUCCESS_STATUS_CODE) {
if (replyCode == OCS_SUCCESS_STATUS_CODE || replyCode == OCS_SUCCESS_STATUS_CODE_V2) {
scheduleWidgetToRemove(job->widget());
}
}

View file

@ -35,7 +35,6 @@
#include "accountmanager.h"
#include "creds/abstractcredentials.h"
#include "updater/ocupdater.h"
#include "excludedfiles.h"
#include "owncloudsetupwizard.h"
#include "version.h"
@ -52,6 +51,7 @@
#include <QTranslator>
#include <QMenu>
#include <QMessageBox>
#include <QDesktopServices>
class QSocket;
@ -120,10 +120,29 @@ Application::Application(int &argc, char **argv)
// TODO: Can't set this without breaking current config paths
// setOrganizationName(QLatin1String(APPLICATION_VENDOR));
setOrganizationDomain(QLatin1String(APPLICATION_REV_DOMAIN));
setApplicationName(_theme->appNameGUI());
setApplicationName(_theme->appName());
setWindowIcon(_theme->applicationIcon());
setAttribute(Qt::AA_UseHighDpiPixmaps, true);
auto confDir = ConfigFile().configPath();
if (!QFileInfo(confDir).exists()) {
// Migrate from version <= 2.4
setApplicationName(_theme->appNameGUI());
QString oldDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
setApplicationName(_theme->appName());
if (QFileInfo(oldDir).isDir()) {
qCInfo(lcApplication) << "Migrating old config from" << oldDir << "to" << confDir;
if (!QFile::rename(oldDir, confDir)) {
qCWarning(lcApplication) << "Failed to move the old config file to its new location (" << oldDir << "to" << confDir << ")";
} else {
#ifndef Q_OS_WIN
// Create a symbolic link so a downgrade of the client would still find the config.
QFile::link(confDir, oldDir);
#endif
}
}
}
parseOptions(arguments());
//no need to waste time;
if (_helpOnly || _versionOnly)
@ -140,13 +159,10 @@ Application::Application(int &argc, char **argv)
setupLogging();
setupTranslations();
// Setup global excludes
qCInfo(lcApplication) << "Loading global exclude list";
// The timeout is initialized with an environment variable, if not, override with the value from the config
ConfigFile cfg;
ExcludedFiles &excludes = ExcludedFiles::instance();
excludes.addExcludeFilePath(cfg.excludeFile(ConfigFile::SystemScope));
excludes.addExcludeFilePath(cfg.excludeFile(ConfigFile::UserScope));
excludes.reloadExcludes();
if (!AbstractNetworkJob::httpTimeout)
AbstractNetworkJob::httpTimeout = cfg.timeout();
_folderManager.reset(new FolderMan);

View file

@ -156,12 +156,13 @@ void OAuth::start()
QUrl OAuth::authorisationLink() const
{
Q_ASSERT(_server.isListening());
QUrl url = Utility::concatUrlPath(_account->url(), QLatin1String("/index.php/apps/oauth2/authorize"),
{ { QLatin1String("response_type"), QLatin1String("code") },
{ QLatin1String("client_id"), Theme::instance()->oauthClientId() },
{ QLatin1String("redirect_uri"), QLatin1String("http://localhost:") + QString::number(_server.serverPort()) } });
QUrlQuery query;
query.setQueryItems({ { QLatin1String("response_type"), QLatin1String("code") },
{ QLatin1String("client_id"), Theme::instance()->oauthClientId() },
{ QLatin1String("redirect_uri"), QLatin1String("http://localhost:") + QString::number(_server.serverPort()) } });
if (!_expectedUser.isNull())
url.addQueryItem("user", _expectedUser);
query.addQueryItem("user", _expectedUser);
QUrl url = Utility::concatUrlPath(_account->url(), QLatin1String("/index.php/apps/oauth2/authorize"), query);
return url;
}

View file

@ -23,6 +23,7 @@
#include "creds/shibbolethcredentials.h"
#include "shibboleth/shibbolethuserjob.h"
#include "creds/credentialscommon.h"
#include "creds/httpcredentialsgui.h"
#include "accessmanager.h"
#include "account.h"
@ -151,7 +152,31 @@ void ShibbolethCredentials::fetchFromKeychainHelper()
void ShibbolethCredentials::askFromUser()
{
showLoginWindow();
// First, we do a DetermineAuthTypeJob to make sure that the server is still using shibboleth and did not upgrade to oauth
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
connect(job, &DetermineAuthTypeJob::authType, [this, job](DetermineAuthTypeJob::AuthType type) {
if (type == DetermineAuthTypeJob::Shibboleth) {
// Normal case, still shibboleth
showLoginWindow();
} else if (type == DetermineAuthTypeJob::OAuth) {
// Hack: upgrade to oauth
auto newCred = new HttpCredentialsGui;
job->setParent(0);
job->deleteLater();
auto account = this->_account;
auto user = this->_user;
account->setCredentials(newCred); // delete this
account->setCredentialSetting(QLatin1String("user"), user);
newCred->fetchUser();
newCred->askFromUser();
} else {
// Basic auth or unkown. Since it may be unkown it might be a temporary failure, don't replace the credentials here
// Still show the login window in that case not to break the flow.
showLoginWindow();
}
});
job->start();
}
bool ShibbolethCredentials::stillValid(QNetworkReply *reply)

View file

@ -30,7 +30,6 @@
#include "socketapi.h"
#include "theme.h"
#include "filesystem.h"
#include "excludedfiles.h"
#include "creds/abstractcredentials.h"
@ -79,7 +78,8 @@ Folder::Folder(const FolderDefinition &definition,
// pass the setting if hidden files are to be ignored, will be read in csync_update
_engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles);
if (!setIgnoredFiles())
ConfigFile::setupDefaultExcludeFilePaths(_engine->excludedFiles());
if (!reloadExcludes())
qCWarning(lcFolder, "Could not read system exclude file");
connect(_accountState.data(), &AccountState::isConnectedChanged, this, &Folder::canSyncChanged);
@ -595,24 +595,9 @@ void Folder::wipe()
FolderMan::instance()->socketApi()->slotRegisterPath(alias());
}
bool Folder::setIgnoredFiles()
bool Folder::reloadExcludes()
{
// Note: Doing this on each sync run and on Folder construction is
// unnecessary, because _engine->excludedFiles() persists between
// sync runs. This is not a big problem because ExcludedFiles maintains
// a QSet of files to load.
ConfigFile cfg;
QString systemList = cfg.excludeFile(ConfigFile::SystemScope);
qCInfo(lcFolder) << "Adding system ignore list to csync:" << systemList;
_engine->excludedFiles().addExcludeFilePath(systemList);
QString userList = cfg.excludeFile(ConfigFile::UserScope);
if (QFile::exists(userList)) {
qCInfo(lcFolder) << "Adding user defined ignore list to csync:" << userList;
_engine->excludedFiles().addExcludeFilePath(userList);
}
return _engine->excludedFiles().reloadExcludes();
return _engine->excludedFiles().reloadExcludeFiles();
}
void Folder::setProxyDirty(bool value)
@ -647,7 +632,7 @@ void Folder::startSync(const QStringList &pathList)
_fileLog->start(path());
if (!setIgnoredFiles()) {
if (!reloadExcludes()) {
slotSyncError(tr("Could not read system exclude file"));
QMetaObject::invokeMethod(this, "slotSyncFinished", Qt::QueuedConnection, Q_ARG(bool, false));
return;

View file

@ -323,7 +323,7 @@ private slots:
void slotNextSyncFullLocalDiscovery();
private:
bool setIgnoredFiles();
bool reloadExcludes();
void showSyncResultPopup();
@ -342,7 +342,7 @@ private:
};
void createGuiLog(const QString &filename, LogStatus status, int count,
const QString &renameTarget = QString::null);
const QString &renameTarget = QString());
AccountStatePtr _accountState;
FolderDefinition _definition;

View file

@ -1018,7 +1018,7 @@ QString FolderMan::getBackupName(QString fullPathName) const
fullPathName.chop(1);
if (fullPathName.isEmpty())
return QString::null;
return QString();
QString newName = fullPathName + tr(" (backup)");
QFileInfo fi(newName);

View file

@ -59,7 +59,7 @@ QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option,
auto classif = static_cast<const FolderStatusModel *>(index.model())->classify(index);
if (classif == FolderStatusModel::AddButton) {
const int margins = aliasFm.height(); // same as 2*aliasMargin of paint
QFontMetrics fm(option.font);
QFontMetrics fm(qApp->font("QPushButton"));
QStyleOptionButton opt;
static_cast<QStyleOption &>(opt) = option;
opt.text = addFolderText();
@ -138,7 +138,10 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
opt.rect.setWidth(qMin(opt.rect.width(), hint.width()));
opt.rect.adjust(0, aliasMargin, 0, -aliasMargin);
opt.rect = QStyle::visualRect(option.direction, option.rect, opt.rect);
painter->save();
painter->setFont(qApp->font("QPushButton"));
QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, painter, option.widget);
painter->restore();
return;
}

View file

@ -90,7 +90,7 @@ void FolderStatusModel::setAccountState(const AccountState *accountState)
}
// Sort by header text
qSort(_folders.begin(), _folders.end(), sortByFolderHeader);
std::sort(_folders.begin(), _folders.end(), sortByFolderHeader);
// Set the root _pathIdx after the sorting
for (int i = 0; i < _folders.size(); ++i) {
@ -951,8 +951,7 @@ void FolderStatusModel::slotSetProgress(const ProgressInfo &progress)
//: Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated))
fileProgressString.append(tr("download %1/s").arg(Utility::octetsToString(estimatedDownBw)));
#else
fileProgressString.append(trUtf8("\u2193"
" %1/s")
fileProgressString.append(tr("\u2193 %1/s")
.arg(Utility::octetsToString(estimatedDownBw)));
#endif
}
@ -962,8 +961,7 @@ void FolderStatusModel::slotSetProgress(const ProgressInfo &progress)
//: Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated))
fileProgressString.append(tr("upload %1/s").arg(Utility::octetsToString(estimatedUpBw)));
#else
fileProgressString.append(trUtf8("\u2191"
" %1/s")
fileProgressString.append(tr("\u2191 %1/s")
.arg(Utility::octetsToString(estimatedUpBw)));
#endif
}

View file

@ -32,7 +32,6 @@
#include "folderwatcher_linux.h"
#endif
#include "excludedfiles.h"
#include "folder.h"
namespace OCC {

View file

@ -35,6 +35,7 @@
#include <QWizardPage>
#include <QTreeWidget>
#include <QVBoxLayout>
#include <QEvent>
#include <stdlib.h>
@ -106,7 +107,7 @@ bool FolderWizardLocalPath::isComplete() const
_ui.warnLabel->setWordWrap(true);
if (isOk) {
_ui.warnLabel->hide();
_ui.warnLabel->setText(QString::null);
_ui.warnLabel->setText(QString());
} else {
_ui.warnLabel->show();
QString warnings = formatWarnings(warnStrings);
@ -117,7 +118,7 @@ bool FolderWizardLocalPath::isComplete() const
void FolderWizardLocalPath::slotChooseLocalFolder()
{
QString sf = QDesktopServices::storageLocation(QDesktopServices::HomeLocation);
QString sf = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
QDir d(sf);
// open the first entry of the home dir. Otherwise the dir picker comes
@ -536,9 +537,11 @@ FolderWizard::FolderWizard(AccountPtr account, QWidget *parent)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setPage(Page_Source, _folderWizardSourcePage);
_folderWizardSourcePage->installEventFilter(this);
if (!Theme::instance()->singleSyncFolder()) {
_folderWizardTargetPage = new FolderWizardRemotePath(account);
setPage(Page_Target, _folderWizardTargetPage);
_folderWizardTargetPage->installEventFilter(this);
}
setPage(Page_SelectiveSync, _folderWizardSelectiveSyncPage);
@ -551,5 +554,25 @@ FolderWizard::~FolderWizard()
{
}
bool FolderWizard::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::LayoutRequest) {
// Workaround QTBUG-3396: forces QWizardPrivate::updateLayout()
QTimer::singleShot(0, this, [this] { setTitleFormat(titleFormat()); });
}
return QWizard::eventFilter(watched, event);
}
void FolderWizard::resizeEvent(QResizeEvent *event)
{
QWizard::resizeEvent(event);
// workaround for QTBUG-22819: when the error label word wrap, the minimum height is not adjusted
int hfw = currentPage()->heightForWidth(currentPage()->width());
if (currentPage()->height() < hfw) {
currentPage()->setMinimumSize(currentPage()->minimumSizeHint().width(), hfw);
setTitleFormat(titleFormat()); // And another workaround for QTBUG-3396
}
}
} // end namespace

View file

@ -149,6 +149,9 @@ public:
explicit FolderWizard(AccountPtr account, QWidget *parent = 0);
~FolderWizard();
bool eventFilter(QObject *watched, QEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private:
FolderWizardLocalPath *_folderWizardSourcePage;
FolderWizardRemotePath *_folderWizardTargetPage;

View file

@ -19,6 +19,7 @@
#include <QDesktopServices>
#include <QLoggingCategory>
#include <QMessageBox>
#include <QUrlQuery>
using namespace OCC;
@ -45,8 +46,10 @@ bool Utility::openBrowser(const QUrl &url, QWidget *errorWidgetParent)
bool Utility::openEmailComposer(const QString &subject, const QString &body, QWidget *errorWidgetParent)
{
QUrl url(QLatin1String("mailto:"));
url.setQueryItems({ { QLatin1String("subject"), subject },
QUrlQuery query;
query.setQueryItems({ { QLatin1String("subject"), subject },
{ QLatin1String("body"), body } });
url.setQuery(query);
if (!QDesktopServices::openUrl(url)) {
if (errorWidgetParent) {

View file

@ -17,7 +17,6 @@
#include "ignorelisteditor.h"
#include "folderman.h"
#include "ui_ignorelisteditor.h"
#include "excludedfiles.h"
#include <QFile>
#include <QDir>
@ -61,7 +60,7 @@ IgnoreListEditor::IgnoreListEditor(QWidget *parent)
connect(ui->addPushButton, &QAbstractButton::clicked, this, &IgnoreListEditor::slotAddPattern);
ui->tableWidget->resizeColumnsToContents();
ui->tableWidget->horizontalHeader()->setResizeMode(patternCol, QHeaderView::Stretch);
ui->tableWidget->horizontalHeader()->setSectionResizeMode(patternCol, QHeaderView::Stretch);
ui->tableWidget->verticalHeader()->setVisible(false);
ui->syncHiddenFilesCheckBox->setChecked(!FolderMan::instance()->ignoreHiddenFiles());
@ -135,8 +134,6 @@ void IgnoreListEditor::slotUpdateLocalIgnoreList()
folder->journalDb()->forceRemoteDiscoveryNextSync();
folderMan->scheduleFolder(folder);
}
ExcludedFiles::instance().reloadExcludes();
}
void IgnoreListEditor::slotAddPattern()

View file

@ -32,6 +32,7 @@
#include "common/syncjournalfilerecord.h"
#include "elidedlabel.h"
#include "ui_issueswidget.h"
#include <climits>
@ -54,6 +55,9 @@ IssuesWidget::IssuesWidget(QWidget *parent)
connect(_ui->_treeWidget, &QTreeWidget::itemActivated, this, &IssuesWidget::slotOpenFile);
connect(_ui->copyIssuesButton, &QAbstractButton::clicked, this, &IssuesWidget::copyToClipboard);
_ui->_treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(_ui->_treeWidget, &QTreeWidget::customContextMenuRequested, this, &IssuesWidget::slotItemContextMenu);
connect(_ui->showIgnores, &QAbstractButton::toggled, this, &IssuesWidget::slotRefreshIssues);
connect(_ui->showWarnings, &QAbstractButton::toggled, this, &IssuesWidget::slotRefreshIssues);
connect(_ui->filterAccount, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &IssuesWidget::slotRefreshIssues);
@ -85,7 +89,7 @@ IssuesWidget::IssuesWidget(QWidget *parent)
_ui->_treeWidget->setHeaderLabels(header);
int timestampColumnWidth =
ActivityItemDelegate::rowHeight() // icon
+ _ui->_treeWidget->fontMetrics().width(ProtocolWidget::timeString(QDateTime::currentDateTime()))
+ _ui->_treeWidget->fontMetrics().width(ProtocolItem::timeString(QDateTime::currentDateTime()))
+ timestampColumnExtra;
_ui->_treeWidget->setColumnWidth(0, timestampColumnWidth);
_ui->_treeWidget->setColumnWidth(1, 180);
@ -198,7 +202,7 @@ void IssuesWidget::slotItemCompleted(const QString &folder, const SyncFileItemPt
{
if (!item->hasErrorStatus())
return;
QTreeWidgetItem *line = ProtocolWidget::createCompletedTreewidgetItem(folder, *item);
QTreeWidgetItem *line = ProtocolItem::create(folder, *item);
if (!line)
return;
addItem(line);
@ -233,6 +237,15 @@ void IssuesWidget::slotAccountRemoved(AccountState *account)
updateAccountChoiceVisibility();
}
void IssuesWidget::slotItemContextMenu(const QPoint &pos)
{
auto item = _ui->_treeWidget->itemAt(pos);
if (!item)
return;
auto globalPos = _ui->_treeWidget->viewport()->mapToGlobal(pos);
ProtocolItem::openContextMenu(globalPos, item, this);
}
void IssuesWidget::updateAccountChoiceVisibility()
{
bool visible = _ui->filterAccount->count() > 2;
@ -373,8 +386,8 @@ void IssuesWidget::addError(const QString &folderAlias, const QString &message,
QStringList columns;
QDateTime timestamp = QDateTime::currentDateTime();
const QString timeStr = ProtocolWidget::timeString(timestamp);
const QString longTimeStr = ProtocolWidget::timeString(timestamp, QLocale::LongFormat);
const QString timeStr = ProtocolItem::timeString(timestamp);
const QString longTimeStr = ProtocolItem::timeString(timestamp, QLocale::LongFormat);
columns << timeStr;
columns << ""; // no "File" entry
@ -383,7 +396,7 @@ void IssuesWidget::addError(const QString &folderAlias, const QString &message,
QIcon icon = Theme::instance()->syncStateIcon(SyncResult::Error);
QTreeWidgetItem *twitem = new SortedTreeWidgetItem(columns);
QTreeWidgetItem *twitem = new ProtocolItem(columns);
twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight()));
twitem->setData(0, Qt::UserRole, timestamp);
twitem->setIcon(0, icon);

View file

@ -68,6 +68,7 @@ private slots:
void slotUpdateFolderFilters();
void slotAccountAdded(AccountState *account);
void slotAccountRemoved(AccountState *account);
void slotItemContextMenu(const QPoint &pos);
private:
void updateAccountChoiceVisibility();

View file

@ -128,8 +128,8 @@ void NotificationWidget::slotNotificationRequestFinished(int statusCode)
QString timeStr = locale.toString(QTime::currentTime());
// the ocs API returns stat code 100 if it succeeded.
if (statusCode != OCS_SUCCESS_STATUS_CODE) {
// the ocs API returns stat code 100 or 200 inside the xml if it succeeded.
if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) {
qCWarning(lcNotifications) << "Notification Request to Server failed, leave button visible.";
for (i = 0; i < _buttons.count(); i++) {
_buttons.at(i)->setEnabled(true);

View file

@ -28,6 +28,7 @@ OcsJob::OcsJob(AccountPtr account)
: AbstractNetworkJob(account, "")
{
_passStatusCodes.append(OCS_SUCCESS_STATUS_CODE);
_passStatusCodes.append(OCS_SUCCESS_STATUS_CODE_V2);
setIgnoreCredentialFailure(true);
}
@ -51,15 +52,16 @@ void OcsJob::appendPath(const QString &id)
setPath(path() + QLatin1Char('/') + id);
}
static QList<QPair<QByteArray, QByteArray>>
percentEncodeQueryItems(
static QUrlQuery percentEncodeQueryItems(
const QList<QPair<QString, QString>> &items)
{
QList<QPair<QByteArray, QByteArray>> result;
QUrlQuery result;
// Note: QUrlQuery::setQueryItems() does not fully percent encode
// the query items, see #5042
foreach (const auto &item, items) {
result.append(qMakePair(
result.addQueryItem(
QUrl::toPercentEncoding(item.first),
QUrl::toPercentEncoding(item.second)));
QUrl::toPercentEncoding(item.second));
}
return result;
}
@ -70,13 +72,11 @@ void OcsJob::start()
req.setRawHeader("Ocs-APIREQUEST", "true");
req.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
QUrl url = Utility::concatUrlPath(account()->url(), path());
QBuffer *buffer = new QBuffer;
QUrlQuery queryItems;
if (_verb == "GET") {
// Note: QUrl::setQueryItems() does not fully percent encode
// the query items, see #5042
url.setEncodedQueryItems(percentEncodeQueryItems(_params));
queryItems = percentEncodeQueryItems(_params);
} else if (_verb == "POST" || _verb == "PUT") {
// Url encode the _postParams and put them in a buffer.
QByteArray postData;
@ -90,12 +90,8 @@ void OcsJob::start()
}
buffer->setData(postData);
}
//We want json data
auto queryItems = url.encodedQueryItems();
queryItems.append(qMakePair(QByteArray("format"), QByteArray("json")));
url.setEncodedQueryItems(queryItems);
queryItems.addQueryItem(QLatin1String("format"), QLatin1String("json"));
QUrl url = Utility::concatUrlPath(account()->url(), path(), queryItems);
sendRequest(_verb, url, req, buffer);
AbstractNetworkJob::start();
}

View file

@ -24,6 +24,8 @@
#include <QUrl>
#define OCS_SUCCESS_STATUS_CODE 100
// Apparantly the v2.php URLs can return that
#define OCS_SUCCESS_STATUS_CODE_V2 200
class QJsonDocument;

View file

@ -25,6 +25,8 @@
#include "folder.h"
#include "openfilemanager.h"
#include "activityitemdelegate.h"
#include "guiutility.h"
#include "accountstate.h"
#include "ui_protocolwidget.h"
@ -32,7 +34,115 @@
namespace OCC {
bool SortedTreeWidgetItem::operator<(const QTreeWidgetItem &other) const
QString ProtocolItem::timeString(QDateTime dt, QLocale::FormatType format)
{
const QLocale loc = QLocale::system();
QString dtFormat = loc.dateTimeFormat(format);
static const QRegExp re("(HH|H|hh|h):mm(?!:s)");
dtFormat.replace(re, "\\1:mm:ss");
return loc.toString(dt, dtFormat);
}
ProtocolItem *ProtocolItem::create(const QString &folder, const SyncFileItem &item)
{
auto f = FolderMan::instance()->folder(folder);
if (!f) {
return 0;
}
QStringList columns;
QDateTime timestamp = QDateTime::currentDateTime();
const QString timeStr = timeString(timestamp);
const QString longTimeStr = timeString(timestamp, QLocale::LongFormat);
columns << timeStr;
columns << Utility::fileNameForGuiUse(item._originalFile);
columns << f->shortGuiLocalPath();
// If the error string is set, it's prefered because it is a useful user message.
QString message = item._errorString;
if (message.isEmpty()) {
message = Progress::asResultString(item);
}
columns << message;
QIcon icon;
if (item._status == SyncFileItem::NormalError
|| item._status == SyncFileItem::FatalError
|| item._status == SyncFileItem::DetailError
|| item._status == SyncFileItem::BlacklistedError) {
icon = Theme::instance()->syncStateIcon(SyncResult::Error);
} else if (Progress::isWarningKind(item._status)) {
icon = Theme::instance()->syncStateIcon(SyncResult::Problem);
}
if (ProgressInfo::isSizeDependent(item)) {
columns << Utility::octetsToString(item._size);
}
ProtocolItem *twitem = new ProtocolItem(columns);
// Warning: The data and tooltips on the columns define an implicit
// interface and can only be changed with care.
twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight()));
twitem->setData(0, Qt::UserRole, timestamp);
twitem->setIcon(0, icon);
twitem->setToolTip(0, longTimeStr);
twitem->setToolTip(1, item._file);
twitem->setData(2, Qt::UserRole, folder);
twitem->setToolTip(3, message);
twitem->setData(3, Qt::UserRole, item._status);
return twitem;
}
SyncJournalFileRecord ProtocolItem::syncJournalRecord(QTreeWidgetItem *item)
{
SyncJournalFileRecord rec;
auto f = folder(item);
if (!f)
return rec;
f->journalDb()->getFileRecord(item->toolTip(1), &rec);
return rec;
}
Folder *ProtocolItem::folder(QTreeWidgetItem *item)
{
return FolderMan::instance()->folder(item->data(2, Qt::UserRole).toString());
}
void ProtocolItem::openContextMenu(QPoint globalPos, QTreeWidgetItem *item, QWidget *parent)
{
auto f = ProtocolItem::folder(item);
if (!f)
return;
AccountPtr account = f->accountState()->account();
auto rec = ProtocolItem::syncJournalRecord(item);
// rec might not be valid
auto menu = new QMenu(parent);
if (rec.isValid()) {
// "Open in Browser" action
auto openInBrowser = menu->addAction(ProtocolWidget::tr("Open in browser"));
QObject::connect(openInBrowser, &QAction::triggered, parent, [parent, account, rec]() {
fetchPrivateLinkUrl(account, rec._path, rec.numericFileId(), parent,
[parent](const QString &url) {
Utility::openBrowser(url, parent);
});
});
}
// More actions will be conditionally added to the context menu here later
if (menu->actions().isEmpty()) {
delete menu;
return;
}
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->popup(globalPos);
}
bool ProtocolItem::operator<(const QTreeWidgetItem &other) const
{
int column = treeWidget()->sortColumn();
if (column != 0) {
@ -56,6 +166,9 @@ ProtocolWidget::ProtocolWidget(QWidget *parent)
connect(_ui->_treeWidget, &QTreeWidget::itemActivated, this, &ProtocolWidget::slotOpenFile);
_ui->_treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
connect(_ui->_treeWidget, &QTreeWidget::customContextMenuRequested, this, &ProtocolWidget::slotItemContextMenu);
// Adjust copyToClipboard() when making changes here!
QStringList header;
header << tr("Time");
@ -71,7 +184,7 @@ ProtocolWidget::ProtocolWidget(QWidget *parent)
_ui->_treeWidget->setHeaderLabels(header);
int timestampColumnWidth =
_ui->_treeWidget->fontMetrics().width(timeString(QDateTime::currentDateTime()))
_ui->_treeWidget->fontMetrics().width(ProtocolItem::timeString(QDateTime::currentDateTime()))
+ timestampColumnExtra;
_ui->_treeWidget->setColumnWidth(0, timestampColumnWidth);
_ui->_treeWidget->setColumnWidth(1, 180);
@ -119,14 +232,13 @@ void ProtocolWidget::hideEvent(QHideEvent *ev)
QWidget::hideEvent(ev);
}
QString ProtocolWidget::timeString(QDateTime dt, QLocale::FormatType format)
void ProtocolWidget::slotItemContextMenu(const QPoint &pos)
{
const QLocale loc = QLocale::system();
QString dtFormat = loc.dateTimeFormat(format);
static const QRegExp re("(HH|H|hh|h):mm(?!:s)");
dtFormat.replace(re, "\\1:mm:ss");
return loc.toString(dt, dtFormat);
auto item = _ui->_treeWidget->itemAt(pos);
if (!item)
return;
auto globalPos = _ui->_treeWidget->viewport()->mapToGlobal(pos);
ProtocolItem::openContextMenu(globalPos, item, this);
}
void ProtocolWidget::slotOpenFile(QTreeWidgetItem *item, int)
@ -144,60 +256,11 @@ void ProtocolWidget::slotOpenFile(QTreeWidgetItem *item, int)
}
}
QTreeWidgetItem *ProtocolWidget::createCompletedTreewidgetItem(const QString &folder, const SyncFileItem &item)
{
auto f = FolderMan::instance()->folder(folder);
if (!f) {
return 0;
}
QStringList columns;
QDateTime timestamp = QDateTime::currentDateTime();
const QString timeStr = timeString(timestamp);
const QString longTimeStr = timeString(timestamp, QLocale::LongFormat);
columns << timeStr;
columns << Utility::fileNameForGuiUse(item._originalFile);
columns << f->shortGuiLocalPath();
// If the error string is set, it's prefered because it is a useful user message.
QString message = item._errorString;
if (message.isEmpty()) {
message = Progress::asResultString(item);
}
columns << message;
QIcon icon;
if (item._status == SyncFileItem::NormalError
|| item._status == SyncFileItem::FatalError
|| item._status == SyncFileItem::DetailError
|| item._status == SyncFileItem::BlacklistedError) {
icon = Theme::instance()->syncStateIcon(SyncResult::Error);
} else if (Progress::isWarningKind(item._status)) {
icon = Theme::instance()->syncStateIcon(SyncResult::Problem);
}
if (ProgressInfo::isSizeDependent(item)) {
columns << Utility::octetsToString(item._size);
}
QTreeWidgetItem *twitem = new SortedTreeWidgetItem(columns);
twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight()));
twitem->setData(0, Qt::UserRole, timestamp);
twitem->setIcon(0, icon);
twitem->setToolTip(0, longTimeStr);
twitem->setToolTip(1, item._file);
twitem->setData(2, Qt::UserRole, folder);
twitem->setToolTip(3, message);
twitem->setData(3, Qt::UserRole, item._status);
return twitem;
}
void ProtocolWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
{
if (item->hasErrorStatus())
return;
QTreeWidgetItem *line = createCompletedTreewidgetItem(folder, *item);
QTreeWidgetItem *line = ProtocolItem::create(folder, *item);
if (line) {
// Limit the number of items
int itemCnt = _ui->_treeWidget->topLevelItemCount();

View file

@ -35,16 +35,25 @@ namespace Ui {
class Application;
/**
* A QTreeWidgetItem with special sorting.
* The items used in the protocol and issue QTreeWidget
*
* It allows items for global entries to be moved to the top if the
* Special sorting: It allows items for global entries to be moved to the top if the
* sorting section is the "Time" column.
*/
class SortedTreeWidgetItem : public QTreeWidgetItem
class ProtocolItem : public QTreeWidgetItem
{
public:
using QTreeWidgetItem::QTreeWidgetItem;
// Shared with IssueWidget
static ProtocolItem *create(const QString &folder, const SyncFileItem &item);
static QString timeString(QDateTime dt, QLocale::FormatType format = QLocale::NarrowFormat);
static SyncJournalFileRecord syncJournalRecord(QTreeWidgetItem *item);
static Folder *folder(QTreeWidgetItem *item);
static void openContextMenu(QPoint globalPos, QTreeWidgetItem *item, QWidget *parent);
private:
bool operator<(const QTreeWidgetItem &other) const override;
};
@ -63,10 +72,6 @@ public:
void storeSyncActivity(QTextStream &ts);
// Shared with IssueWidget
static QTreeWidgetItem *createCompletedTreewidgetItem(const QString &folder, const SyncFileItem &item);
static QString timeString(QDateTime dt, QLocale::FormatType format = QLocale::NarrowFormat);
public slots:
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
void slotOpenFile(QTreeWidgetItem *item, int);
@ -75,6 +80,9 @@ protected:
void showEvent(QShowEvent *);
void hideEvent(QHideEvent *);
private slots:
void slotItemContextMenu(const QPoint &pos);
signals:
void copyToClipboard();

View file

@ -14,7 +14,6 @@
#include "selectivesyncdialog.h"
#include "folder.h"
#include "account.h"
#include "excludedfiles.h"
#include "networkjobs.h"
#include "theme.h"
#include "folderman.h"
@ -95,6 +94,9 @@ SelectiveSyncWidget::SelectiveSyncWidget(AccountPtr account, QWidget *parent)
_folderTree->header()->setStretchLastSection(true);
_folderTree->headerItem()->setText(0, tr("Name"));
_folderTree->headerItem()->setText(1, tr("Size"));
ConfigFile::setupDefaultExcludeFilePaths(_excludedFiles);
_excludedFiles.reloadExcludeFiles();
}
QSize SelectiveSyncWidget::sizeHint() const
@ -204,7 +206,7 @@ void SelectiveSyncWidget::slotUpdateDirectories(QStringList list)
QMutableListIterator<QString> it(list);
while (it.hasNext()) {
it.next();
if (ExcludedFiles::instance().isExcluded(it.value(), pathToRemove, FolderMan::instance()->ignoreHiddenFiles()))
if (_excludedFiles.isExcluded(it.value(), pathToRemove, FolderMan::instance()->ignoreHiddenFiles()))
it.remove();
}

View file

@ -17,6 +17,8 @@
#include <QTreeWidget>
#include "accountfwd.h"
#include "csync_exclude.h"
class QTreeWidgetItem;
class QTreeWidget;
class QNetworkReply;
@ -72,6 +74,10 @@ private:
QLabel *_loading;
QTreeWidget *_folderTree;
// During account setup we want to filter out excluded folders from the
// view without having a Folder.SyncEngine.ExcludedFiles instance.
ExcludedFiles _excludedFiles;
};
/**

View file

@ -24,6 +24,8 @@ namespace OCC {
Q_LOGGING_CATEGORY(lcServerNotification, "gui.servernotification", QtInfoMsg)
const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications");
ServerNotificationHandler::ServerNotificationHandler(QObject *parent)
: QObject(parent)
{
@ -47,7 +49,7 @@ void ServerNotificationHandler::slotFetchNotifications(AccountState *ptr)
}
// if the previous notification job has finished, start next.
_notificationJob = new JsonApiJob(ptr->account(), QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications"), this);
_notificationJob = new JsonApiJob(ptr->account(), notificationsPath, this);
QObject::connect(_notificationJob.data(), &JsonApiJob::jsonReceived,
this, &ServerNotificationHandler::slotNotificationsReceived);
_notificationJob->setProperty("AccountStatePtr", QVariant::fromValue<AccountState *>(ptr));
@ -94,6 +96,16 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
a._links.append(al);
}
// Add another action to dismiss notification on server
// https://github.com/owncloud/notifications/blob/master/docs/ocs-endpoint-v1.md#deleting-a-notification-for-a-user
ActivityLink al;
al._label = tr("Dismiss");
al._link = Utility::concatUrlPath(ai->account()->url(), notificationsPath + "/" + json.value("notification_id").toString()).toString();
al._verb = "DELETE";
al._isPrimary = false;
a._links.append(al);
list.append(a);
}
emit newNotificationList(list);

View file

@ -57,23 +57,6 @@ namespace OCC {
#include "settingsdialogcommon.cpp"
static QIcon circleMask(const QImage &avatar)
{
int dim = avatar.width();
QPixmap fixedImage(dim, dim);
fixedImage.fill(Qt::transparent);
QPainter imgPainter(&fixedImage);
QPainterPath clip;
clip.addEllipse(0, 0, dim, dim);
imgPainter.setClipPath(clip);
imgPainter.drawImage(0, 0, avatar);
imgPainter.end();
return QIcon(fixedImage);
}
//
// Whenever you change something here check both settingsdialog.cpp and settingsdialogmac.cpp !
//
@ -232,7 +215,7 @@ void SettingsDialog::accountAdded(AccountState *s)
accountAction = createColorAwareAction(QLatin1String(":/client/resources/account.png"),
actionText);
} else {
QIcon icon = circleMask(avatar);
QIcon icon(QPixmap::fromImage(AvatarJob::makeCircularAvatar(avatar)));
accountAction = createActionWithIcon(icon, actionText);
}
@ -265,7 +248,7 @@ void SettingsDialog::slotAccountAvatarChanged()
if (action) {
QImage pix = account->avatar();
if (!pix.isNull()) {
action->setIcon(circleMask(pix));
action->setIcon(QPixmap::fromImage(AvatarJob::makeCircularAvatar(pix)));
}
}
}
@ -325,7 +308,7 @@ void SettingsDialog::customizeStyle()
QString altBase(palette().alternateBase().color().name());
QString dark(palette().dark().color().name());
QString background(palette().base().color().name());
_toolBar->setStyleSheet(QString::fromAscii(TOOLBAR_CSS).arg(background, dark, highlightColor, altBase));
_toolBar->setStyleSheet(QString::fromLatin1(TOOLBAR_CSS).arg(background, dark, highlightColor, altBase));
Q_FOREACH (QAction *a, _actionGroup->actions()) {
QIcon icon = createColorAwareIcon(a->property("iconPath").toString());

View file

@ -81,8 +81,9 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
connect(_ui->pushButton_setPassword, &QAbstractButton::clicked, this, &ShareLinkWidget::slotPasswordReturnPressed);
connect(_ui->checkBox_expire, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCheckBoxExpireClicked);
connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareLinkWidget::slotExpireDateChanged);
connect(_ui->checkBox_editing, &QAbstractButton::clicked, this, &ShareLinkWidget::slotPermissionsCheckboxClicked);
connect(_ui->checkBox_fileListing, &QAbstractButton::clicked, this, &ShareLinkWidget::slotPermissionsCheckboxClicked);
connect(_ui->radio_readOnly, &QAbstractButton::clicked, this, &ShareLinkWidget::slotPermissionsClicked);
connect(_ui->radio_readWrite, &QAbstractButton::clicked, this, &ShareLinkWidget::slotPermissionsClicked);
connect(_ui->radio_uploadOnly, &QAbstractButton::clicked, this, &ShareLinkWidget::slotPermissionsClicked);
_ui->errorLabel->hide();
@ -149,7 +150,7 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
// File can't have public upload set; we also hide it if the capability isn't there
_ui->widget_editing->setVisible(
!_isFile && _account->capabilities().sharePublicLinkAllowUpload());
_ui->checkBox_fileListing->setVisible(
_ui->radio_uploadOnly->setVisible(
_account->capabilities().sharePublicLinkSupportsUploadOnly());
@ -158,12 +159,12 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
_linkContextMenu = new QMenu(this);
connect(_linkContextMenu, &QMenu::triggered,
this, &ShareLinkWidget::slotLinkContextMenuActionTriggered);
_deleteLinkAction = _linkContextMenu->addAction(tr("Delete"));
_openLinkAction = _linkContextMenu->addAction(tr("Open link in browser"));
_copyLinkAction = _linkContextMenu->addAction(tr("Copy link to clipboard"));
_copyDirectLinkAction = _linkContextMenu->addAction(tr("Copy link to clipboard (direct download)"));
_emailLinkAction = _linkContextMenu->addAction(tr("Send link by email"));
_emailDirectLinkAction = _linkContextMenu->addAction(tr("Send link by email (direct download)"));
_deleteLinkAction = _linkContextMenu->addAction(tr("Delete"));
/*
* Create the share manager and connect it properly
@ -282,7 +283,9 @@ void ShareLinkWidget::slotShareSelectionChanged()
auto share = selectedShare();
if (!share) {
_ui->shareProperties->setEnabled(false);
_ui->checkBox_editing->setChecked(false);
_ui->radio_readOnly->setChecked(false);
_ui->radio_readWrite->setChecked(false);
_ui->radio_uploadOnly->setChecked(false);
_ui->checkBox_expire->setChecked(false);
_ui->checkBox_password->setChecked(false);
return;
@ -292,8 +295,11 @@ void ShareLinkWidget::slotShareSelectionChanged()
_ui->checkBox_password->setEnabled(!_passwordRequired);
_ui->checkBox_expire->setEnabled(!_expiryRequired);
_ui->checkBox_editing->setEnabled(
_account->capabilities().sharePublicLinkAllowUpload());
_ui->widget_editing->setEnabled(true);
if (!_account->capabilities().sharePublicLinkAllowUpload()) {
_ui->radio_readWrite->setEnabled(false);
_ui->radio_uploadOnly->setEnabled(false);
}
// Password state
_ui->checkBox_password->setText(tr("P&assword protect"));
@ -324,9 +330,15 @@ void ShareLinkWidget::slotShareSelectionChanged()
// Public upload state (box is hidden for files)
if (!_isFile) {
_ui->checkBox_editing->setChecked(share->getPublicUpload());
_ui->checkBox_fileListing->setChecked(share->getShowFileListing());
_ui->checkBox_fileListing->setEnabled(share->getPublicUpload());
if (share->getPublicUpload()) {
if (share->getShowFileListing()) {
_ui->radio_readWrite->setChecked(true);
} else {
_ui->radio_uploadOnly->setChecked(true);
}
} else {
_ui->radio_readOnly->setChecked(true);
}
}
}
@ -456,7 +468,7 @@ void ShareLinkWidget::slotCreateShareRequiresPassword(const QString &message)
_ui->checkBox_password->setEnabled(false);
_ui->checkBox_password->setText(tr("Public sh&aring requires a password"));
_ui->checkBox_expire->setEnabled(false);
_ui->checkBox_editing->setEnabled(false);
_ui->widget_editing->setEnabled(false);
if (!message.isEmpty()) {
_ui->errorLabel->setText(message);
_ui->errorLabel->show();
@ -579,19 +591,18 @@ void ShareLinkWidget::slotDeleteShareClicked()
confirmAndDeleteShare(share);
}
void ShareLinkWidget::slotPermissionsCheckboxClicked()
void ShareLinkWidget::slotPermissionsClicked()
{
if (auto current = selectedShare()) {
_ui->checkBox_editing->setEnabled(false);
_ui->checkBox_fileListing->setEnabled(false);
_ui->widget_editing->setEnabled(false);
_pi_editing->startAnimation();
_ui->errorLabel->hide();
SharePermissions perm = SharePermissionRead;
if (_ui->checkBox_editing->isChecked() && _ui->checkBox_fileListing->isChecked()) {
if (_ui->radio_readWrite->isChecked()) {
perm = SharePermissionRead | SharePermissionCreate
| SharePermissionUpdate | SharePermissionDelete;
} else if (_ui->checkBox_editing->isChecked() && !_ui->checkBox_fileListing->isChecked()) {
} else if (_ui->radio_uploadOnly->isChecked()) {
perm = SharePermissionCreate;
}
current->setPermissions(perm);

View file

@ -65,7 +65,7 @@ private slots:
void slotCheckBoxPasswordClicked();
void slotCheckBoxExpireClicked();
void slotPasswordReturnPressed();
void slotPermissionsCheckboxClicked();
void slotPermissionsClicked();
void slotExpireDateChanged(const QDate &date);
void slotPasswordChanged(const QString &newText);
void slotNameEdited(QTableWidgetItem *item);

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>376</width>
<height>493</height>
<width>394</width>
<height>534</height>
</rect>
</property>
<property name="windowTitle">
@ -48,7 +48,7 @@
<item>
<widget class="QTableWidget" name="linkShares">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -191,36 +191,110 @@
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QCheckBox" name="checkBox_editing">
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Allow editing</string>
<string>Users can view and download contents.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer">
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
<width>20</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="checkBox_fileListing">
<item row="6" column="0">
<widget class="QRadioButton" name="radio_uploadOnly">
<property name="text">
<string>Show file listing</string>
<string>Upload only (File Drop)</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Receive files from others without revealing the contents of the folder.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="radio_readOnly">
<property name="text">
<string>Read only</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QRadioButton" name="radio_readWrite">
<property name="text">
<string>Read &amp;&amp; Write</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Users can view, download, edit and upload contents.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
@ -287,7 +361,6 @@
<tabstop>nameLineEdit</tabstop>
<tabstop>createShareButton</tabstop>
<tabstop>linkShares</tabstop>
<tabstop>checkBox_editing</tabstop>
<tabstop>checkBox_password</tabstop>
<tabstop>lineEdit_password</tabstop>
<tabstop>pushButton_setPassword</tabstop>

View file

@ -379,9 +379,9 @@ QSharedPointer<LinkShare> ShareManager::parseLinkShare(const QJsonObject &data)
// From ownCloud server version 8 on, a different share link scheme is used.
url = QUrl(Utility::concatUrlPath(_account->url(), QLatin1String("index.php/s/") + data.value("token").toString())).toString();
} else {
QList<QPair<QString, QString>> queryArgs;
queryArgs.append(qMakePair(QString("service"), QString("files")));
queryArgs.append(qMakePair(QString("t"), data.value("token").toString()));
QUrlQuery queryArgs;
queryArgs.addQueryItem(QLatin1String("service"), QLatin1String("files"));
queryArgs.addQueryItem(QLatin1String("t"), data.value("token").toString());
url = QUrl(Utility::concatUrlPath(_account->url(), QLatin1String("public.php"), queryArgs).toString());
}

View file

@ -41,6 +41,9 @@
#include <QAction>
#include <QDesktopServices>
#include <QMessageBox>
#include <QCryptographicHash>
#include <QColor>
#include <QPainter>
namespace OCC {
@ -178,31 +181,34 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
auto newViewPort = new QWidget(scrollArea);
auto layout = new QVBoxLayout(newViewPort);
layout->setMargin(0);
layout->setSpacing(0);
QSize minimumSize = newViewPort->sizeHint();
int x = 0;
if (shares.isEmpty()) {
foreach (const auto &share, shares) {
// We don't handle link shares
if (share->getShareType() == Share::TypeLink) {
continue;
}
ShareUserLine *s = new ShareUserLine(share, _maxSharingPermissions, _isFile, _ui->scrollArea);
connect(s, &ShareUserLine::resizeRequested, this, &ShareUserGroupWidget::slotAdjustScrollWidgetSize);
connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares);
s->setBackgroundRole(layout->count() % 2 == 0 ? QPalette::Base : QPalette::AlternateBase);
layout->addWidget(s);
x++;
if (x <= 3) {
minimumSize = newViewPort->sizeHint();
} else {
minimumSize.rwidth() = qMax(newViewPort->sizeHint().width(), minimumSize.width());
}
}
if (layout->isEmpty()) {
layout->addWidget(new QLabel(tr("The item is not shared with any users or groups")));
} else {
foreach (const auto &share, shares) {
// We don't handle link shares
if (share->getShareType() == Share::TypeLink) {
continue;
}
ShareUserLine *s = new ShareUserLine(share, _maxSharingPermissions, _isFile, _ui->scrollArea);
connect(s, &ShareUserLine::resizeRequested, this, &ShareUserGroupWidget::slotAdjustScrollWidgetSize);
connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares);
layout->addWidget(s);
x++;
if (x <= 3) {
minimumSize = newViewPort->sizeHint();
} else {
minimumSize.rwidth() = qMax(newViewPort->sizeHint().width(), minimumSize.width());
}
}
layout->addStretch(1);
}
@ -416,6 +422,65 @@ ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
if (!share->account()->capabilities().shareResharing()) {
_ui->permissionShare->hide();
}
loadAvatar();
}
void ShareUserLine::loadAvatar()
{
const int avatarSize = 36;
// Set size of the placeholder
_ui->avatar->setMinimumHeight(avatarSize);
_ui->avatar->setMinimumWidth(avatarSize);
_ui->avatar->setMaximumHeight(avatarSize);
_ui->avatar->setMaximumWidth(avatarSize);
_ui->avatar->setAlignment(Qt::AlignCenter);
/* Create the fallback avatar.
*
* This will be shown until the avatar image data arrives.
*/
const QByteArray hash = QCryptographicHash::hash(_ui->sharedWith->text().toUtf8(), QCryptographicHash::Md5);
double hue = static_cast<quint8>(hash[0]) / 255.;
// See core/js/placeholder.js for details on colors and styling
const QColor bg = QColor::fromHslF(hue, 0.7, 0.68);
const QString style = QString(R"(* {
color: #fff;
background-color: %1;
border-radius: %2px;
text-align: center;
line-height: %2px;
font-size: %2px;
})").arg(bg.name(), QString::number(avatarSize / 2));
_ui->avatar->setStyleSheet(style);
// The avatar label is the first character of the user name.
const QString text = _share->getShareWith()->displayName();
_ui->avatar->setText(text.at(0).toUpper());
/* Start the network job to fetch the avatar data.
*
* Currently only regular users can have avatars.
*/
if (_share->getShareWith()->type() == Sharee::User) {
AvatarJob *job = new AvatarJob(_share->account(), _share->getShareWith()->shareWith(), avatarSize, this);
connect(job, &AvatarJob::avatarPixmap, this, &ShareUserLine::slotAvatarLoaded);
job->start();
}
}
void ShareUserLine::slotAvatarLoaded(QImage avatar)
{
if (avatar.isNull())
return;
avatar = AvatarJob::makeCircularAvatar(avatar);
_ui->avatar->setPixmap(QPixmap::fromImage(avatar));
// Remove the stylesheet for the fallback avatar
_ui->avatar->setStyleSheet("");
}
void ShareUserLine::on_deleteShareButton_clicked()

View file

@ -131,8 +131,11 @@ private slots:
void slotShareDeleted();
void slotPermissionsSet();
void slotAvatarLoaded(QImage avatar);
private:
void displayPermissions();
void loadAvatar();
Ui::ShareUserLine *_ui;
QSharedPointer<Share> _share;

View file

@ -10,27 +10,25 @@
<x>0</x>
<y>0</y>
<width>468</width>
<height>64</height>
<height>46</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="avatar">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sharedWith">
<property name="text">
@ -55,36 +53,24 @@
</spacer>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<widget class="QCheckBox" name="permissionShare">
<property name="text">
<string>can share</string>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</widget>
</item>
<item>
<widget class="QCheckBox" name="permissionsEdit">
<property name="text">
<string>can edit</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="permissionToolButton">
<property name="text">
<string>...</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QCheckBox" name="permissionsEdit">
<property name="text">
<string>can edit</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="permissionShare">
<property name="text">
<string>can share</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="permissionToolButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>

View file

@ -65,7 +65,7 @@ static inline QString removeTrailingSlash(QString path)
return path;
}
static QString buildMessage(const QString &verb, const QString &path, const QString &status = QString::null)
static QString buildMessage(const QString &verb, const QString &path, const QString &status = QString())
{
QString msg(verb);
@ -270,7 +270,7 @@ void SocketApi::slotReadSocket()
QString line = QString::fromUtf8(socket->readLine()).normalized(QString::NormalizationForm_C);
line.chop(1); // remove the '\n'
qCInfo(lcSocketApi) << "Received SocketAPI message <--" << line << "from" << socket;
QByteArray command = line.split(":").value(0).toAscii();
QByteArray command = line.split(":").value(0).toLatin1();
QByteArray functionWithArguments = "command_" + command + "(QString,SocketListener*)";
int indexOfMethod = staticMetaObject.indexOfMethod(functionWithArguments);
@ -307,7 +307,7 @@ void SocketApi::slotUnregisterPath(const QString &alias)
Folder *f = FolderMan::instance()->folder(alias);
if (f)
broadcastMessage(buildMessage(QLatin1String("UNREGISTER_PATH"), removeTrailingSlash(f->path()), QString::null), true);
broadcastMessage(buildMessage(QLatin1String("UNREGISTER_PATH"), removeTrailingSlash(f->path()), QString()), true);
_registeredAliases.remove(alias);
}
@ -492,7 +492,7 @@ void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listen
}
// Fetches the private link url asynchronously and then calls the target slot
void fetchPrivateLinkUrl(const QString &localFile, SocketApi *target, void (SocketApi::*targetFun)(const QString &url) const)
static void fetchPrivateLinkUrlHelper(const QString &localFile, SocketApi *target, void (SocketApi::*targetFun)(const QString &url) const)
{
Folder *shareFolder = FolderMan::instance()->folderForPath(localFile);
if (!shareFolder) {
@ -505,45 +505,23 @@ void fetchPrivateLinkUrl(const QString &localFile, SocketApi *target, void (Sock
AccountPtr account = shareFolder->accountState()->account();
// Generate private link ourselves: used as a fallback
SyncJournalFileRecord rec;
if (!shareFolder->journalDb()->getFileRecord(file, &rec) || !rec.isValid())
return;
const QString oldUrl =
account->deprecatedPrivateLinkUrl(rec.numericFileId()).toString(QUrl::FullyEncoded);
// Retrieve the new link or numeric file id by PROPFIND
PropfindJob *job = new PropfindJob(account, file, target);
job->setProperties(
QList<QByteArray>()
<< "http://owncloud.org/ns:fileid" // numeric file id for fallback private link generation
<< "http://owncloud.org/ns:privatelink");
job->setTimeout(10 * 1000);
QObject::connect(job, &PropfindJob::result, target, [=](const QVariantMap &result) {
auto privateLinkUrl = result["privatelink"].toString();
auto numericFileId = result["fileid"].toByteArray();
if (!privateLinkUrl.isEmpty()) {
(target->*targetFun)(privateLinkUrl);
} else if (!numericFileId.isEmpty()) {
(target->*targetFun)(account->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded));
} else {
(target->*targetFun)(oldUrl);
}
fetchPrivateLinkUrl(account, file, rec.numericFileId(), target, [=](const QString &url) {
(target->*targetFun)(url);
});
QObject::connect(job, &PropfindJob::finishedWithError, target, [=](QNetworkReply *) {
(target->*targetFun)(oldUrl);
});
job->start();
}
void SocketApi::command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *)
{
fetchPrivateLinkUrl(localFile, this, &SocketApi::copyPrivateLinkToClipboard);
fetchPrivateLinkUrlHelper(localFile, this, &SocketApi::copyPrivateLinkToClipboard);
}
void SocketApi::command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *)
{
fetchPrivateLinkUrl(localFile, this, &SocketApi::emailPrivateLink);
fetchPrivateLinkUrlHelper(localFile, this, &SocketApi::emailPrivateLink);
}
void SocketApi::copyPrivateLinkToClipboard(const QString &link) const

View file

@ -39,21 +39,6 @@ SslButton::SslButton(QWidget *parent)
this, &SslButton::slotUpdateMenu);
}
QString SslButton::protoToString(QSsl::SslProtocol proto)
{
switch (proto) {
break;
case QSsl::SslV2:
return QLatin1String("SSL v2");
case QSsl::SslV3:
return QLatin1String("SSL v3");
case QSsl::TlsV1:
return QLatin1String("TLS");
default:
return QString();
}
}
static QString addCertDetailsField(const QString &key, const QString &value)
{
if (value.isEmpty())
@ -92,7 +77,7 @@ QMenu *SslButton::buildCertMenu(QMenu *parent, const QSslCertificate &cert,
QString serial = QString::fromUtf8(cert.serialNumber());
QString effectiveDate = cert.effectiveDate().date().toString();
QString expiryDate = cert.expiryDate().date().toString();
QString sna = QStringList(cert.alternateSubjectNames().values()).join(" ");
QString sna = QStringList(cert.subjectAlternativeNames().values()).join(" ");
QString details;
QTextStream stream(&details);
@ -144,7 +129,7 @@ QMenu *SslButton::buildCertMenu(QMenu *parent, const QSslCertificate &cert,
QString certId = cn.isEmpty() ? ou : cn;
if (QSslSocket::systemCaCertificates().contains(cert)) {
if (QSslConfiguration::systemCaCertificates().contains(cert)) {
txt += certId;
} else {
if (isSelfSigned(cert)) {
@ -224,16 +209,18 @@ void SslButton::slotUpdateMenu()
_menu->addAction(tr("Certificate information:"))->setEnabled(false);
const auto systemCerts = QSslConfiguration::systemCaCertificates();
QList<QSslCertificate> tmpChain;
foreach (QSslCertificate cert, chain) {
tmpChain << cert;
if (QSslSocket::systemCaCertificates().contains(cert))
if (systemCerts.contains(cert))
break;
}
chain = tmpChain;
// find trust anchor (informational only, verification is done by QSslSocket!)
foreach (QSslCertificate rootCA, QSslSocket::systemCaCertificates()) {
for (const QSslCertificate &rootCA : systemCerts) {
if (rootCA.issuerInfo(QSslCertificate::CommonName) == chain.last().issuerInfo(QSslCertificate::CommonName)
&& rootCA.issuerInfo(QSslCertificate::Organization) == chain.last().issuerInfo(QSslCertificate::Organization)) {
chain.append(rootCA);

View file

@ -36,7 +36,6 @@ class SslButton : public QToolButton
Q_OBJECT
public:
explicit SslButton(QWidget *parent = 0);
QString protoToString(QSsl::SslProtocol proto);
void updateAccountState(AccountState *accountState);
public slots:

View file

@ -136,7 +136,7 @@ void SyncRunFileLog::logItem(const SyncFileItem &item)
if (item._direction == SyncFileItem::None) {
return;
}
QString ts = QString::fromAscii(item._responseTimeStamp);
QString ts = QString::fromLatin1(item._responseTimeStamp);
if (ts.length() > 6) {
QRegExp rx("(\\d\\d:\\d\\d:\\d\\d)");
if (ts.contains(rx)) {

View file

@ -41,7 +41,7 @@ Updater *Updater::instance()
QUrl Updater::addQueryParams(const QUrl &url)
{
QUrl paramUrl = url;
QUrlQuery query;
Theme *theme = Theme::instance();
QString platform = QLatin1String("stranger");
if (Utility::isLinux()) {
@ -56,23 +56,25 @@ QUrl Updater::addQueryParams(const QUrl &url)
QString sysInfo = getSystemInfo();
if (!sysInfo.isEmpty()) {
paramUrl.addQueryItem(QLatin1String("client"), sysInfo);
query.addQueryItem(QLatin1String("client"), sysInfo);
}
paramUrl.addQueryItem(QLatin1String("version"), clientVersion());
paramUrl.addQueryItem(QLatin1String("platform"), platform);
paramUrl.addQueryItem(QLatin1String("oem"), theme->appName());
query.addQueryItem(QLatin1String("version"), clientVersion());
query.addQueryItem(QLatin1String("platform"), platform);
query.addQueryItem(QLatin1String("oem"), theme->appName());
QString suffix = QString::fromLatin1(MIRALL_STRINGIFY(MIRALL_VERSION_SUFFIX));
paramUrl.addQueryItem(QLatin1String("versionsuffix"), suffix);
query.addQueryItem(QLatin1String("versionsuffix"), suffix);
if (suffix.startsWith("nightly")
|| suffix.startsWith("alpha")
|| suffix.startsWith("rc")
|| suffix.startsWith("beta")) {
paramUrl.addQueryItem(QLatin1String("channel"), "beta");
query.addQueryItem(QLatin1String("channel"), "beta");
// FIXME: Provide a checkbox in UI to enable regular versions to switch
// to beta channel
}
QUrl paramUrl = url;
paramUrl.setQuery(query);
return paramUrl;
}
@ -90,7 +92,7 @@ QString Updater::getSystemInfo()
return QString::fromLocal8Bit(output.toBase64());
#else
return QString::null;
return QString();
#endif
}

View file

@ -230,7 +230,7 @@ bool OwncloudAdvancedSetupPage::isConfirmBigFolderChecked() const
bool OwncloudAdvancedSetupPage::validatePage()
{
if (!_created) {
setErrorString(QString::null);
setErrorString(QString());
_checking = true;
startSpinner();
emit completeChanged();

View file

@ -43,7 +43,7 @@
<item>
<widget class="QLabel" name="errorLabel">
<property name="text">
<string>An error occured while connecting. Please try again.</string>
<string>An error occurred while connecting. Please try again.</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>

View file

@ -221,7 +221,7 @@ QString OwncloudSetupPage::url() const
bool OwncloudSetupPage::validatePage()
{
if (!_authTypeKnown) {
setErrorString(QString::null, false);
setErrorString(QString(), false);
_checking = true;
startSpinner();
emit completeChanged();

View file

@ -195,7 +195,7 @@ void OwncloudWizard::slotCurrentPageChanged(int id)
if (id == WizardCommon::Page_Result) {
disconnect(this, &QDialog::finished, this, &OwncloudWizard::basicSetupFinished);
emit basicSetupFinished(QDialog::Accepted);
appendToConfigurationLog(QString::null);
appendToConfigurationLog(QString());
// Immediately close on show, we currently don't want this page anymore
done(Accepted);
}

View file

@ -70,7 +70,7 @@ bool OwncloudWizardResultPage::isComplete() const
void OwncloudWizardResultPage::initializePage()
{
_ui.localFolderLabel->setText(QString::null);
_ui.localFolderLabel->setText(QString());
}
void OwncloudWizardResultPage::setRemoteFolder(const QString &remoteFolder)
@ -81,7 +81,7 @@ void OwncloudWizardResultPage::setRemoteFolder(const QString &remoteFolder)
void OwncloudWizardResultPage::setupCustomization()
{
// set defaults for the customize labels.
_ui.topLabel->setText(QString::null);
_ui.topLabel->setText(QString());
_ui.topLabel->hide();
QVariant variant = Theme::instance()->customMedia(Theme::oCSetupResultTop);

View file

@ -56,7 +56,6 @@ set(libsync_SRCS
syncfilestatustracker.cpp
syncresult.cpp
theme.cpp
excludedfiles.cpp
creds/dummycredentials.cpp
creds/abstractcredentials.cpp
creds/credentialscommon.cpp

View file

@ -40,6 +40,9 @@ namespace OCC {
Q_LOGGING_CATEGORY(lcNetworkJob, "sync.networkjob", QtInfoMsg)
// If not set, it is overwritten by the Application constructor with the value from the config
int AbstractNetworkJob::httpTimeout = qEnvironmentVariableIntValue("OWNCLOUD_TIMEOUT");
AbstractNetworkJob::AbstractNetworkJob(AccountPtr account, const QString &path, QObject *parent)
: QObject(parent)
, _timedout(false)
@ -51,7 +54,7 @@ AbstractNetworkJob::AbstractNetworkJob(AccountPtr account, const QString &path,
, _redirectCount(0)
{
_timer.setSingleShot(true);
_timer.setInterval(OwncloudPropagator::httpTimeout() * 1000); // default to 5 minutes.
_timer.setInterval((httpTimeout ? httpTimeout : 300) * 1000); // default to 5 minutes.
connect(&_timer, &QTimer::timeout, this, &AbstractNetworkJob::slotTimeout);
connect(this, &AbstractNetworkJob::networkActivity, this, &AbstractNetworkJob::resetTimeout);
@ -326,7 +329,7 @@ QString extractErrorMessage(const QByteArray &errorResponse)
QXmlStreamReader reader(errorResponse);
reader.readNextStartElement();
if (reader.name() != "error") {
return QString::null;
return QString();
}
QString exception;

View file

@ -88,6 +88,10 @@ public:
*/
QString errorStringParsingBody(QByteArray *body = 0);
/** static variable the HTTP timeout (in seconds). If set to 0, the default will be used
*/
static int httpTimeout;
public slots:
void setTimeout(qint64 msec);
void resetTimeout();

View file

@ -15,7 +15,6 @@
#include "account.h"
#include "cookiejar.h"
#include "networkjobs.h"
#include "configfile.h"
#include "accessmanager.h"
#include "creds/abstractcredentials.h"
#include "capabilities.h"
@ -33,6 +32,7 @@
#include <QDir>
#include <QSslKey>
#include <QAuthenticator>
#include <QStandardPaths>
namespace OCC {
@ -93,6 +93,7 @@ void Account::setDavUser(const QString &newDavUser)
_davUser = newDavUser;
}
#ifndef TOKEN_AUTH_ONLY
QImage Account::avatar() const
{
return _avatarImg;
@ -102,6 +103,7 @@ void Account::setAvatar(const QImage &img)
_avatarImg = img;
emit accountChangedAvatar();
}
#endif
QString Account::displayName() const
{
@ -204,8 +206,7 @@ void Account::lendCookieJarTo(QNetworkAccessManager *guest)
QString Account::cookieJarPath()
{
ConfigFile cfg;
return cfg.configPath() + "/cookies" + id() + ".db";
return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/cookies" + id() + ".db";
}
void Account::resetNetworkAccessManager()

View file

@ -26,7 +26,10 @@
#include <QSslCipher>
#include <QSslError>
#include <QSharedPointer>
#ifndef TOKEN_AUTH_ONLY
#include <QPixmap>
#endif
#include "common/utility.h"
#include <memory>
@ -86,8 +89,10 @@ public:
QString davDisplayName() const;
void setDavDisplayName(const QString &newDisplayName);
#ifndef TOKEN_AUTH_ONLY
QImage avatar() const;
void setAvatar(const QImage &img);
#endif
/// The name of the account as shown in the toolbar
QString displayName() const;
@ -266,7 +271,9 @@ private:
QString _id;
QString _davUser;
QString _displayName;
#ifndef TOKEN_AUTH_ONLY
QImage _avatarImg;
#endif
QMap<QString, QVariant> _settingsMap;
QUrl _url;

View file

@ -14,8 +14,6 @@
#include "capabilities.h"
#include "configfile.h"
#include <QVariantMap>
namespace OCC {

View file

@ -21,10 +21,11 @@
#include "creds/abstractcredentials.h"
#include "csync_exclude.h"
#ifndef TOKEN_AUTH_ONLY
#include <QWidget>
#include <QHeaderView>
#include <QDesktopServices>
#endif
#include <QCoreApplication>
@ -34,6 +35,7 @@
#include <QLoggingCategory>
#include <QSettings>
#include <QNetworkProxy>
#include <QStandardPaths>
#define DEFAULT_REMOTE_POLL_INTERVAL 30000 // default remote poll time in milliseconds
#define DEFAULT_FULL_LOCAL_DISCOVERY_INTERVAL (60 * 60 * 1000) // 1 hour
@ -82,7 +84,7 @@ static const char maxLogLinesC[] = "Logging/maxLogLines";
const char certPath[] = "http_certificatePath";
const char certPasswd[] = "http_certificatePasswd";
QString ConfigFile::_confDir = QString::null;
QString ConfigFile::_confDir = QString();
bool ConfigFile::_askedUser = false;
ConfigFile::ConfigFile()
@ -250,13 +252,11 @@ QVariant ConfigFile::getPolicySetting(const QString &setting, const QVariant &de
QString ConfigFile::configPath() const
{
#ifndef TOKEN_AUTH_ONLY
if (_confDir.isEmpty()) {
// Qt 5's QStandardPaths::writableLocation gives us wrong results (without /data/),
// so we'll have to use the deprecated version for now
_confDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
// On Unix, use the AppConfigLocation for the settings, that's configurable with the XDG_CONFIG_HOME env variable.
// On Windows, use AppDataLocation, that's where the roaming data is and where we should store the config file
_confDir = QStandardPaths::writableLocation(Utility::isWindows() ? QStandardPaths::AppDataLocation : QStandardPaths::AppConfigLocation);
}
#endif
QString dir = _confDir;
if (!dir.endsWith(QLatin1Char('/')))
@ -264,12 +264,6 @@ QString ConfigFile::configPath() const
return dir;
}
QString ConfigFile::configPathWithAppName() const
{
//HACK
return QFileInfo(configFile()).dir().absolutePath().append("/");
}
static const QLatin1String exclFile("sync-exclude.lst");
QString ConfigFile::excludeFile(Scope scope) const
@ -607,12 +601,12 @@ QString ConfigFile::proxyPassword() const
int ConfigFile::useUploadLimit() const
{
return getValue(useUploadLimitC, QString::null, 0).toInt();
return getValue(useUploadLimitC, QString(), 0).toInt();
}
int ConfigFile::useDownloadLimit() const
{
return getValue(useDownloadLimitC, QString::null, 0).toInt();
return getValue(useDownloadLimitC, QString(), 0).toInt();
}
void ConfigFile::setUseUploadLimit(int val)
@ -627,12 +621,12 @@ void ConfigFile::setUseDownloadLimit(int val)
int ConfigFile::uploadLimit() const
{
return getValue(uploadLimitC, QString::null, 10).toInt();
return getValue(uploadLimitC, QString(), 10).toInt();
}
int ConfigFile::downloadLimit() const
{
return getValue(downloadLimitC, QString::null, 80).toInt();
return getValue(downloadLimitC, QString(), 80).toInt();
}
void ConfigFile::setUploadLimit(int kbytes)
@ -748,4 +742,17 @@ std::unique_ptr<QSettings> ConfigFile::settingsWithGroup(const QString &group, Q
return settings;
}
void ConfigFile::setupDefaultExcludeFilePaths(ExcludedFiles &excludedFiles)
{
ConfigFile cfg;
QString systemList = cfg.excludeFile(ConfigFile::SystemScope);
qCInfo(lcConfigFile) << "Adding system ignore list to csync:" << systemList;
excludedFiles.addExcludeFilePath(systemList);
QString userList = cfg.excludeFile(ConfigFile::UserScope);
if (QFile::exists(userList)) {
qCInfo(lcConfigFile) << "Adding user defined ignore list to csync:" << userList;
excludedFiles.addExcludeFilePath(userList);
}
}
}

View file

@ -24,6 +24,7 @@
class QWidget;
class QHeaderView;
class ExcludedFiles;
namespace OCC {
@ -42,7 +43,6 @@ public:
SystemScope };
QString configPath() const;
QString configPathWithAppName() const;
QString configFile() const;
QString excludeFile(Scope scope) const;
static QString excludeFileFromSystem(); // doesn't access config dir
@ -55,7 +55,7 @@ public:
QByteArray caCerts();
void setCaCerts(const QByteArray &);
bool passwordStorageAllowed(const QString &connection = QString::null);
bool passwordStorageAllowed(const QString &connection = QString());
// max count of lines in the log window
int maxLogLines() const;
@ -153,6 +153,9 @@ public:
with the given parent. If no parent is specified, the caller must destroy the settings */
static std::unique_ptr<QSettings> settingsWithGroup(const QString &group, QObject *parent = 0);
/// Add the system and user exclude file path to the ExcludedFiles instance.
static void setupDefaultExcludeFilePaths(ExcludedFiles &excludedFiles);
protected:
QVariant getPolicySetting(const QString &policy, const QVariant &defaultValue = QVariant()) const;
void storeData(const QString &group, const QString &key, const QVariant &value);
@ -161,7 +164,7 @@ protected:
bool dataExists(const QString &group, const QString &key) const;
private:
QVariant getValue(const QString &param, const QString &group = QString::null,
QVariant getValue(const QString &param, const QString &group = QString(),
const QVariant &defaultValue = QVariant()) const;
void setValue(const QString &key, const QVariant &value);

View file

@ -17,7 +17,6 @@
#include <QLoggingCategory>
#include <QNetworkReply>
#include <QNetworkProxyFactory>
#include <QPixmap>
#include <QXmlStreamReader>
#include "connectionvalidator.h"
@ -333,24 +332,28 @@ void ConnectionValidator::slotUserFetched(const QJsonDocument &json)
QString user = json.object().value("ocs").toObject().value("data").toObject().value("id").toString();
if (!user.isEmpty()) {
_account->setDavUser(user);
AvatarJob *job = new AvatarJob(_account, this);
job->setTimeout(20 * 1000);
QObject::connect(job, &AvatarJob::avatarPixmap, this, &ConnectionValidator::slotAvatarImage);
job->start();
}
QString displayName = json.object().value("ocs").toObject().value("data").toObject().value("display-name").toString();
if (!displayName.isEmpty()) {
_account->setDavDisplayName(displayName);
}
#ifndef TOKEN_AUTH_ONLY
AvatarJob *job = new AvatarJob(_account, _account->davUser(), 128, this);
job->setTimeout(20 * 1000);
QObject::connect(job, &AvatarJob::avatarPixmap, this, &ConnectionValidator::slotAvatarImage);
job->start();
#else
reportResult(Connected);
#endif
}
#ifndef TOKEN_AUTH_ONLY
void ConnectionValidator::slotAvatarImage(const QImage &img)
{
_account->setAvatar(img);
reportResult(Connected);
}
#endif
void ConnectionValidator::reportResult(Status status)
{

View file

@ -123,7 +123,9 @@ protected slots:
void slotCapabilitiesRecieved(const QJsonDocument &);
void slotUserFetched(const QJsonDocument &);
#ifndef TOKEN_AUTH_ONLY
void slotAvatarImage(const QImage &img);
#endif
private:
void reportResult(Status status);

View file

@ -40,11 +40,11 @@ QString AbstractCredentials::keychainKey(const QString &url, const QString &user
QString u(url);
if (u.isEmpty()) {
qCWarning(lcCredentials) << "Empty url in keyChain, error!";
return QString::null;
return QString();
}
if (user.isEmpty()) {
qCWarning(lcCredentials) << "Error: User is empty!";
return QString::null;
return QString();
}
if (!u.endsWith(QChar('/'))) {

View file

@ -102,7 +102,7 @@ QString TokenCredentials::password() const
return _password;
}
QNetworkAccessManager *TokenCredentials::getQNAM() const
QNetworkAccessManager *TokenCredentials::createQNAM() const
{
AccessManager *qnam = new TokenCredentialsAccessManager(this);

View file

@ -40,7 +40,7 @@ public:
TokenCredentials(const QString &user, const QString &password, const QString &token);
QString authType() const Q_DECL_OVERRIDE;
QNetworkAccessManager *getQNAM() const Q_DECL_OVERRIDE;
QNetworkAccessManager *createQNAM() const Q_DECL_OVERRIDE;
bool ready() const Q_DECL_OVERRIDE;
void askFromUser() Q_DECL_OVERRIDE;
void fetchFromKeychain() Q_DECL_OVERRIDE;

View file

@ -75,7 +75,7 @@ bool DiscoveryJob::isInSelectiveSyncBlackList(const QByteArray &path) const
// Also try to adjust the path if there was renames
if (csync_rename_count(_csync_ctx)) {
QByteArray adjusted = csync_rename_adjust_path_source(_csync_ctx, path);
QByteArray adjusted = csync_rename_adjust_parent_path_source(_csync_ctx, path);
if (adjusted != path) {
return findPathInList(_selectiveSyncBlackList, QString::fromUtf8(adjusted));
}
@ -698,8 +698,6 @@ void DiscoveryJob::start()
_csync_ctx->callbacks.remote_closedir_hook = remote_vio_closedir_hook;
_csync_ctx->callbacks.vio_userdata = this;
csync_exclude_traversal_prepare(_csync_ctx); // Converts the flat exclude list to optimized regexps
csync_set_log_callback(_log_callback);
csync_set_log_level(_log_level);
_lastUpdateProgressCallbackCall.invalidate();

View file

@ -24,6 +24,7 @@
#include <QWaitCondition>
#include <QLinkedList>
#include <deque>
#include "syncoptions.h"
namespace OCC {
@ -35,52 +36,6 @@ class Account;
* if the files are new, or changed.
*/
struct SyncOptions
{
SyncOptions()
: _newBigFolderSizeLimit(-1)
, _confirmExternalStorage(false)
, _initialChunkSize(10 * 1000 * 1000) // 10 MB
, _minChunkSize(1 * 1000 * 1000) // 1 MB
, _maxChunkSize(100 * 1000 * 1000) // 100 MB
, _targetChunkUploadDuration(60 * 1000) // 1 minute
, _parallelNetworkJobs(true)
{
}
/** Maximum size (in Bytes) a folder can have without asking for confirmation.
* -1 means infinite */
qint64 _newBigFolderSizeLimit;
/** If a confirmation should be asked for external storages */
bool _confirmExternalStorage;
/** The initial un-adjusted chunk size in bytes for chunked uploads, both
* for old and new chunking algorithm, which classifies the item to be chunked
*
* In chunkingNG, when dynamic chunk size adjustments are done, this is the
* starting value and is then gradually adjusted within the
* minChunkSize / maxChunkSize bounds.
*/
quint64 _initialChunkSize;
/** The minimum chunk size in bytes for chunked uploads */
quint64 _minChunkSize;
/** The maximum chunk size in bytes for chunked uploads */
quint64 _maxChunkSize;
/** The target duration of chunk uploads for dynamic chunk sizing.
*
* Set to 0 it will disable dynamic chunk sizing.
*/
quint64 _targetChunkUploadDuration;
/** Whether parallel network jobs are allowed. */
bool _parallelNetworkJobs;
};
struct DiscoveryDirectoryResult
{
QString path;

View file

@ -1,107 +0,0 @@
/*
* Copyright (C) by Christian Kamm <mail@ckamm.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 "excludedfiles.h"
#include "common/utility.h"
#include <QFileInfo>
#include "std/c_string.h"
#include "csync.h"
#include "csync_exclude.h"
using namespace OCC;
ExcludedFiles::ExcludedFiles(c_strlist_t **excludesPtr)
: _excludesPtr(excludesPtr)
{
}
ExcludedFiles::~ExcludedFiles()
{
c_strlist_destroy(*_excludesPtr);
}
ExcludedFiles &ExcludedFiles::instance()
{
static c_strlist_t *globalExcludes;
static ExcludedFiles inst(&globalExcludes);
return inst;
}
void ExcludedFiles::addExcludeFilePath(const QString &path)
{
_excludeFiles.insert(path);
}
#ifdef WITH_TESTING
void ExcludedFiles::addExcludeExpr(const QString &expr)
{
_csync_exclude_add(_excludesPtr, expr.toLatin1().constData());
}
#endif
bool ExcludedFiles::reloadExcludes()
{
c_strlist_destroy(*_excludesPtr);
*_excludesPtr = NULL;
bool success = true;
foreach (const QString &file, _excludeFiles) {
if (csync_exclude_load(file.toUtf8(), _excludesPtr) < 0)
success = false;
}
// The csync_exclude_traversal_prepare is called implicitely at sync start.
return success;
}
bool ExcludedFiles::isExcluded(
const QString &filePath,
const QString &basePath,
bool excludeHidden) const
{
if (!filePath.startsWith(basePath, Utility::fsCasePreserving() ? Qt::CaseInsensitive : Qt::CaseSensitive)) {
// Mark paths we're not responsible for as excluded...
return true;
}
if (excludeHidden) {
QString path = filePath;
// Check all path subcomponents, but to *not* check the base path:
// We do want to be able to sync with a hidden folder as the target.
while (path.size() > basePath.size()) {
QFileInfo fi(path);
if (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.'))) {
return true;
}
// Get the parent path
path = fi.absolutePath();
}
}
QFileInfo fi(filePath);
csync_ftw_type_e type = CSYNC_FTW_TYPE_FILE;
if (fi.isDir()) {
type = CSYNC_FTW_TYPE_DIR;
}
QString relativePath = filePath.mid(basePath.size());
if (relativePath.endsWith(QLatin1Char('/'))) {
relativePath.chop(1);
}
return csync_excluded_no_ctx(*_excludesPtr, relativePath.toUtf8(), type) != CSYNC_NOT_EXCLUDED;
}

View file

@ -1,76 +0,0 @@
/*
* Copyright (C) by Christian Kamm <mail@ckamm.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.
*/
#pragma once
#include "owncloudlib.h"
#include <QObject>
#include <QSet>
#include <QString>
#include "std/c_string.h"
#include "csync.h"
#include "csync_exclude.h" // for CSYNC_EXCLUDE_TYPE
namespace OCC {
/**
* Manages the global system and user exclude lists.
*/
class OWNCLOUDSYNC_EXPORT ExcludedFiles : public QObject
{
Q_OBJECT
public:
static ExcludedFiles &instance();
ExcludedFiles(c_strlist_t **excludesPtr);
~ExcludedFiles();
/**
* Adds a new path to a file containing exclude patterns.
*
* Does not load the file. Use reloadExcludes() afterwards.
*/
void addExcludeFilePath(const QString &path);
/**
* Checks whether a file or directory should be excluded.
*
* @param filePath the absolute path to the file
* @param basePath folder path from which to apply exclude rules
*/
bool isExcluded(
const QString &filePath,
const QString &basePath,
bool excludeHidden) const;
#ifdef WITH_TESTING
void addExcludeExpr(const QString &expr);
#endif
public slots:
/**
* Reloads the exclude patterns from the registered paths.
*/
bool reloadExcludes();
private:
// This is a pointer to the csync exclude list, its is owned by this class
// but the pointer can be in a csync_context so that it can itself also query the list.
c_strlist_t **_excludesPtr;
QSet<QString> _excludeFiles;
};
} // namespace OCC

View file

@ -27,9 +27,9 @@
#include <QTimer>
#include <QMutex>
#include <QCoreApplication>
#include <QPixmap>
#include <QJsonDocument>
#include <QJsonObject>
#include <QPainter>
#include "networkjobs.h"
#include "account.h"
@ -626,10 +626,11 @@ bool PropfindJob::finished()
/*********************************************************************************************/
AvatarJob::AvatarJob(AccountPtr account, QObject *parent)
#ifndef TOKEN_AUTH_ONLY
AvatarJob::AvatarJob(AccountPtr account, const QString &userId, int size, QObject *parent)
: AbstractNetworkJob(account, QString(), parent)
{
_avatarUrl = Utility::concatUrlPath(account->url(), QString("remote.php/dav/avatars/%1/128.png").arg(account->davUser()));
_avatarUrl = Utility::concatUrlPath(account->url(), QString("remote.php/dav/avatars/%1/%2.png").arg(userId, QString::number(size)));
}
void AvatarJob::start()
@ -639,6 +640,26 @@ void AvatarJob::start()
AbstractNetworkJob::start();
}
QImage AvatarJob::makeCircularAvatar(const QImage &baseAvatar)
{
int dim = baseAvatar.width();
QImage avatar(dim, dim, baseAvatar.format());
avatar.fill(Qt::transparent);
QPainter painter(&avatar);
painter.setRenderHint(QPainter::Antialiasing);
QPainterPath path;
path.addEllipse(0, 0, dim, dim);
painter.setClipPath(path);
painter.drawImage(0, 0, baseAvatar);
painter.end();
return avatar;
}
bool AvatarJob::finished()
{
int http_result_code = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@ -656,6 +677,7 @@ bool AvatarJob::finished()
emit(avatarPixmap(avImage));
return true;
}
#endif
/*********************************************************************************************/
@ -758,7 +780,7 @@ JsonApiJob::JsonApiJob(const AccountPtr &account, const QString &path, QObject *
{
}
void JsonApiJob::addQueryParams(QList<QPair<QString, QString>> params)
void JsonApiJob::addQueryParams(const QUrlQuery &params)
{
_additionalParams = params;
}
@ -767,10 +789,9 @@ void JsonApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
QUrl url = Utility::concatUrlPath(account()->url(), path());
QList<QPair<QString, QString>> params = _additionalParams;
params << qMakePair(QString::fromLatin1("format"), QString::fromLatin1("json"));
url.setQueryItems(params);
auto query = _additionalParams;
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
QUrl url = Utility::concatUrlPath(account()->url(), path(), query);
sendRequest("GET", url, req);
AbstractNetworkJob::start();
}
@ -903,4 +924,36 @@ bool SimpleNetworkJob::finished()
return true;
}
void fetchPrivateLinkUrl(AccountPtr account, const QString &remotePath,
const QByteArray &numericFileId, QObject *target,
std::function<void(const QString &url)> targetFun)
{
QString oldUrl;
if (!numericFileId.isEmpty())
oldUrl = account->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded);
// Retrieve the new link by PROPFIND
PropfindJob *job = new PropfindJob(account, remotePath, target);
job->setProperties(
QList<QByteArray>()
<< "http://owncloud.org/ns:fileid" // numeric file id for fallback private link generation
<< "http://owncloud.org/ns:privatelink");
job->setTimeout(10 * 1000);
QObject::connect(job, &PropfindJob::result, target, [=](const QVariantMap &result) {
auto privateLinkUrl = result["privatelink"].toString();
auto numericFileId = result["fileid"].toByteArray();
if (!privateLinkUrl.isEmpty()) {
targetFun(privateLinkUrl);
} else if (!numericFileId.isEmpty()) {
targetFun(account->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded));
} else {
targetFun(oldUrl);
}
});
QObject::connect(job, &PropfindJob::finishedWithError, target, [=](QNetworkReply *) {
targetFun(oldUrl);
});
job->start();
}
} // namespace OCC

View file

@ -17,6 +17,8 @@
#define NETWORKJOBS_H
#include "abstractnetworkjob.h"
#include <QUrlQuery>
#include <functional>
class QUrl;
class QJsonObject;
@ -133,11 +135,9 @@ private:
QList<QByteArray> _properties;
};
#ifndef TOKEN_AUTH_ONLY
/**
* @brief The AvatarJob class
*
* Retrieves the account users avatar from the server using a GET request.
* @brief Retrieves the account users avatar from the server using a GET request.
*
* If the server does not have the avatar, the result Pixmap is empty.
*
@ -147,15 +147,23 @@ class OWNCLOUDSYNC_EXPORT AvatarJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit AvatarJob(AccountPtr account, QObject *parent = 0);
/**
* @param userId The user for which to obtain the avatar
* @param size The size of the avatar (square so size*size)
*/
explicit AvatarJob(AccountPtr account, const QString &userId, int size, QObject *parent = 0);
void start() Q_DECL_OVERRIDE;
/** The retrieved avatar images don't have the circle shape by default */
static QImage makeCircularAvatar(const QImage &baseAvatar);
signals:
/**
* @brief avatarPixmap - returns either a valid pixmap or not.
*/
void avatarPixmap(QImage);
void avatarPixmap(const QImage &);
private slots:
virtual bool finished() Q_DECL_OVERRIDE;
@ -163,6 +171,7 @@ private slots:
private:
QUrl _avatarUrl;
};
#endif
/**
* @brief Send a Proppatch request
@ -331,7 +340,7 @@ public:
*
* This function needs to be called before start() obviously.
*/
void addQueryParams(QList<QPair<QString, QString>> params);
void addQueryParams(const QUrlQuery &params);
public slots:
void start() Q_DECL_OVERRIDE;
@ -348,7 +357,7 @@ signals:
void jsonReceived(const QJsonDocument &json, int statusCode);
private:
QList<QPair<QString, QString>> _additionalParams;
QUrlQuery _additionalParams;
};
/**
@ -402,6 +411,23 @@ private slots:
bool finished() Q_DECL_OVERRIDE;
};
/**
* @brief Runs a PROPFIND to figure out the private link url
*
* The numericFileId is used only to build the deprecatedPrivateLinkUrl
* locally as a fallback. If it's empty and the PROPFIND fails, targetFun
* will be called with an empty string.
*
* The job and signal connections are parented to the target QObject.
*
* Note: targetFun is guaranteed to be called only through the event
* loop and never directly.
*/
void OWNCLOUDSYNC_EXPORT fetchPrivateLinkUrl(
AccountPtr account, const QString &remotePath,
const QByteArray &numericFileId, QObject *target,
std::function<void(const QString &url)> targetFun);
} // namespace OCC
#endif // NETWORKJOBS_H

View file

@ -22,7 +22,6 @@
#include "propagateremotemove.h"
#include "propagateremotemkdir.h"
#include "propagatorjobs.h"
#include "configfile.h"
#include "common/utility.h"
#include "account.h"
#include "common/asserts.h"
@ -554,19 +553,6 @@ bool OwncloudPropagator::isInSharedDirectory(const QString &file)
return re;
}
int OwncloudPropagator::httpTimeout()
{
static int timeout = 0;
if (!timeout) {
timeout = qgetenv("OWNCLOUD_TIMEOUT").toUInt();
if (timeout == 0) {
ConfigFile cfg;
timeout = cfg.timeout();
}
}
return timeout;
}
bool OwncloudPropagator::localFileNameClash(const QString &relFile)
{
bool re = false;

View file

@ -30,7 +30,7 @@
#include "common/syncjournaldb.h"
#include "bandwidthmanager.h"
#include "accountfwd.h"
#include "discoveryphase.h"
#include "syncoptions.h"
namespace OCC {
@ -464,9 +464,6 @@ public:
}
}
// timeout in seconds
static int httpTimeout();
AccountPtr account() const;
enum DiskSpaceResult {

View file

@ -89,7 +89,9 @@ SyncEngine::SyncEngine(AccountPtr account, const QString &localPath,
_csync_ctx.reset(new CSYNC(localPath.toUtf8().data(), journal));
_excludedFiles.reset(new ExcludedFiles(&_csync_ctx->excludes));
_excludedFiles.reset(new ExcludedFiles);
_csync_ctx->exclude_traversal_fn = _excludedFiles->csyncTraversalMatchFun();
_syncFileStatusTracker.reset(new SyncFileStatusTracker(this));
_clearTouchedFilesTimer.setSingleShot(true);
@ -476,6 +478,9 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other,
case CSYNC_STATUS_INDIVIDUAL_TOO_DEEP:
item->_errorString = tr("Folder hierarchy is too deep");
break;
case CSYNC_STATUS_INDIVIDUAL_CANNOT_ENCODE:
item->_errorString = tr("The filename cannot be encoded on your file system.");
break;
case CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE:
item->_status = SyncFileItem::Conflict;
if (Utility::shouldUploadConflictFiles()) {

View file

@ -32,7 +32,6 @@
// when do we go away with this private/public separation?
#include <csync_private.h>
#include "excludedfiles.h"
#include "syncfileitem.h"
#include "progressdispatcher.h"
#include "common/utility.h"

Some files were not shown because too many files have changed in this diff Show more