Merge branch 'master' into patch-1

This commit is contained in:
Julius Härtl 2018-10-23 23:11:21 +02:00 committed by GitHub
commit 23883b2b60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 6281 additions and 3671 deletions

View file

@ -199,5 +199,6 @@ X-GNOME-Autostart-Delay=3
# Translations
Comment[ca]=@APPLICATION_NAME@ client de sincronització d'escriptori
Icon[ca]=@APPLICATION_ICON_NAME@
Name[ca]=@APPLICATION_NAME@ client de sincro d'escriptori
GenericName[ca]=Directori de sincronització

View file

@ -199,5 +199,6 @@ X-GNOME-Autostart-Delay=3
# Translations
Comment[ru]=Клиент синхронизации @APPLICATION_NAME@ для ПК
Icon[ru]=@APPLICATION_ICON_NAME@
Name[ru]=@APPLICATION_NAME@ клиент для ПК
GenericName[ru]=Синхронизация папок

View file

@ -32,7 +32,7 @@ you want to install system wide you could use `/usr/local` or `/opt/nextcloud/`.
##### Linux
```
$ cmake "-GVisual Studio 15 2017 Win64" .. -DCMAKE_INSTALL_PREFIX=path-to-install-folder/ -DCMAKE_BUILD_TYPE=Debug -DNO_SHIBBOLETH=1 -DQTKEYCHAIN_LIBRARY=/path-to-qt5keychain-folder/lib64/libqt5keychain.so -DQTKEYCHAIN_INCLUDE_DIR=/path-to-qt5keychain-folder/include/qt5keychain/ -DOPENSSL_ROOT_DIR=/path-to-openssl-folder/ -DOPENSSL_INCLUDE_DIR=path-to-openssl-folder/include -DOPENSSL_LIBRARIES=path-to-openssl-folder/lib
$ cmake .. -DCMAKE_INSTALL_PREFIX=path-to-install-folder/ -DCMAKE_BUILD_TYPE=Debug -DNO_SHIBBOLETH=1 -DQTKEYCHAIN_LIBRARY=/path-to-qt5keychain-folder/lib64/libqt5keychain.so -DQTKEYCHAIN_INCLUDE_DIR=/path-to-qt5keychain-folder/include/qt5keychain/ -DOPENSSL_ROOT_DIR=/path-to-openssl-folder/ -DOPENSSL_INCLUDE_DIR=path-to-openssl-folder/include -DOPENSSL_LIBRARIES=path-to-openssl-folder/lib
$ make install
```

View file

@ -24,7 +24,9 @@ The other options are:
``--logflush``
Clears (flushes) the log file after each write action.
``--logdebug``
Also output debug-level messages in the log (equivalent to setting the env var QT_LOGGING_RULES="qt.*=true;*.debug=true").
)
``--confdir`` `<dirname>`
Uses the specified configuration directory.

File diff suppressed because it is too large Load diff

View file

@ -123,9 +123,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.23.1"
#define SQLITE_VERSION_NUMBER 3023001
#define SQLITE_SOURCE_ID "2018-04-10 17:39:29 4bb2294022060e61de7da5c227a69ccd846ba330e31626ebcd59a94efd148b3b"
#define SQLITE_VERSION "3.24.0"
#define SQLITE_VERSION_NUMBER 3024000
#define SQLITE_SOURCE_ID "2018-06-04 19:24:41 c7ee0833225bfd8c5ec2f9bf62b97c4e04d03bd9566366d5221ac8fb199a87ca"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -504,6 +504,7 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8))
#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8))
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8))
#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8))
@ -511,6 +512,7 @@ SQLITE_API int sqlite3_exec(
#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8))
#define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8))
#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8))
#define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2<<8))
#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8))
#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8))
#define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3<<8))
@ -1930,6 +1932,22 @@ struct sqlite3_mem_methods {
** I/O required to support statement rollback.
** The default value for this setting is controlled by the
** [SQLITE_STMTJRNL_SPILL] compile-time option.
**
** [[SQLITE_CONFIG_SORTERREF_SIZE]]
** <dt>SQLITE_CONFIG_SORTERREF_SIZE
** <dd>The SQLITE_CONFIG_SORTERREF_SIZE option accepts a single parameter
** of type (int) - the new value of the sorter-reference size threshold.
** Usually, when SQLite uses an external sort to order records according
** to an ORDER BY clause, all fields required by the caller are present in the
** sorted records. However, if SQLite determines based on the declared type
** of a table column that its values are likely to be very large - larger
** than the configured sorter-reference size threshold - then a reference
** is stored in each sorted record and the required column values loaded
** from the database as records are returned in sorted order. The default
** value for this option is to never use this optimization. Specifying a
** negative value for this option restores the default behaviour.
** This option is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option.
** </dl>
*/
#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */
@ -1959,6 +1977,7 @@ struct sqlite3_mem_methods {
#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */
#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */
#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */
/*
** CAPI3REF: Database Connection Configuration Options
@ -2095,6 +2114,21 @@ struct sqlite3_mem_methods {
** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if
** it is not disabled, 1 if it is.
** </dd>
**
** <dt>SQLITE_DBCONFIG_RESET_DATABASE</dt>
** <dd> Set the SQLITE_DBCONFIG_RESET_DATABASE flag and then run
** [VACUUM] in order to reset a database back to an empty database
** with no schema and no content. The following process works even for
** a badly corrupted database file:
** <ol>
** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
** <li> [sqlite3_exec](db, "[VACUUM]", 0, 0, 0);
** <li> sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0);
** </ol>
** Because resetting a database is destructive and irreversible, the
** process requires the use of this obscure API and multiple steps to help
** ensure that it does not happen by accident.
** </dd>
** </dl>
*/
#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */
@ -2106,7 +2140,8 @@ struct sqlite3_mem_methods {
#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */
#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */
#define SQLITE_DBCONFIG_MAX 1008 /* Largest DBCONFIG */
#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */
#define SQLITE_DBCONFIG_MAX 1009 /* Largest DBCONFIG */
/*
** CAPI3REF: Enable Or Disable Extended Result Codes
@ -5492,6 +5527,41 @@ SQLITE_API SQLITE_EXTERN char *sqlite3_temp_directory;
*/
SQLITE_API SQLITE_EXTERN char *sqlite3_data_directory;
/*
** CAPI3REF: Win32 Specific Interface
**
** These interfaces are available only on Windows. The
** [sqlite3_win32_set_directory] interface is used to set the value associated
** with the [sqlite3_temp_directory] or [sqlite3_data_directory] variable, to
** zValue, depending on the value of the type parameter. The zValue parameter
** should be NULL to cause the previous value to be freed via [sqlite3_free];
** a non-NULL value will be copied into memory obtained from [sqlite3_malloc]
** prior to being used. The [sqlite3_win32_set_directory] interface returns
** [SQLITE_OK] to indicate success, [SQLITE_ERROR] if the type is unsupported,
** or [SQLITE_NOMEM] if memory could not be allocated. The value of the
** [sqlite3_data_directory] variable is intended to act as a replacement for
** the current directory on the sub-platforms of Win32 where that concept is
** not present, e.g. WinRT and UWP. The [sqlite3_win32_set_directory8] and
** [sqlite3_win32_set_directory16] interfaces behave exactly the same as the
** sqlite3_win32_set_directory interface except the string parameter must be
** UTF-8 or UTF-16, respectively.
*/
SQLITE_API int sqlite3_win32_set_directory(
unsigned long type, /* Identifier for directory being set or reset */
void *zValue /* New value for directory being set or reset */
);
SQLITE_API int sqlite3_win32_set_directory8(unsigned long type, const char *zValue);
SQLITE_API int sqlite3_win32_set_directory16(unsigned long type, const void *zValue);
/*
** CAPI3REF: Win32 Directory Types
**
** These macros are only available on Windows. They define the allowed values
** for the type argument to the [sqlite3_win32_set_directory] interface.
*/
#define SQLITE_WIN32_DATA_DIRECTORY_TYPE 1
#define SQLITE_WIN32_TEMP_DIRECTORY_TYPE 2
/*
** CAPI3REF: Test For Auto-Commit Mode
** KEYWORDS: {autocommit mode}
@ -6224,6 +6294,10 @@ struct sqlite3_index_info {
/*
** CAPI3REF: Virtual Table Scan Flags
**
** Virtual table implementations are allowed to set the
** [sqlite3_index_info].idxFlags field to some combination of
** these bits.
*/
#define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */
@ -6999,7 +7073,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_ALWAYS 13
#define SQLITE_TESTCTRL_RESERVE 14
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15
#define SQLITE_TESTCTRL_ISKEYWORD 16
#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */
#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18
#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */
@ -7013,6 +7087,189 @@ SQLITE_API int sqlite3_test_control(int op, ...);
#define SQLITE_TESTCTRL_PARSER_COVERAGE 26
#define SQLITE_TESTCTRL_LAST 26 /* Largest TESTCTRL */
/*
** CAPI3REF: SQL Keyword Checking
**
** These routines provide access to the set of SQL language keywords
** recognized by SQLite. Applications can uses these routines to determine
** whether or not a specific identifier needs to be escaped (for example,
** by enclosing in double-quotes) so as not to confuse the parser.
**
** The sqlite3_keyword_count() interface returns the number of distinct
** keywords understood by SQLite.
**
** The sqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and
** makes *Z point to that keyword expressed as UTF8 and writes the number
** of bytes in the keyword into *L. The string that *Z points to is not
** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns
** SQLITE_OK if N is within bounds and SQLITE_ERROR if not. If either Z
** or L are NULL or invalid pointers then calls to
** sqlite3_keyword_name(N,Z,L) result in undefined behavior.
**
** The sqlite3_keyword_check(Z,L) interface checks to see whether or not
** the L-byte UTF8 identifier that Z points to is a keyword, returning non-zero
** if it is and zero if not.
**
** The parser used by SQLite is forgiving. It is often possible to use
** a keyword as an identifier as long as such use does not result in a
** parsing ambiguity. For example, the statement
** "CREATE TABLE BEGIN(REPLACE,PRAGMA,END);" is accepted by SQLite, and
** creates a new table named "BEGIN" with three columns named
** "REPLACE", "PRAGMA", and "END". Nevertheless, best practice is to avoid
** using keywords as identifiers. Common techniques used to avoid keyword
** name collisions include:
** <ul>
** <li> Put all identifier names inside double-quotes. This is the official
** SQL way to escape identifier names.
** <li> Put identifier names inside &#91;...&#93;. This is not standard SQL,
** but it is what SQL Server does and so lots of programmers use this
** technique.
** <li> Begin every identifier with the letter "Z" as no SQL keywords start
** with "Z".
** <li> Include a digit somewhere in every identifier name.
** </ul>
**
** Note that the number of keywords understood by SQLite can depend on
** compile-time options. For example, "VACUUM" is not a keyword if
** SQLite is compiled with the [-DSQLITE_OMIT_VACUUM] option. Also,
** new keywords may be added to future releases of SQLite.
*/
SQLITE_API int sqlite3_keyword_count(void);
SQLITE_API int sqlite3_keyword_name(int,const char**,int*);
SQLITE_API int sqlite3_keyword_check(const char*,int);
/*
** CAPI3REF: Dynamic String Object
** KEYWORDS: {dynamic string}
**
** An instance of the sqlite3_str object contains a dynamically-sized
** string under construction.
**
** The lifecycle of an sqlite3_str object is as follows:
** <ol>
** <li> ^The sqlite3_str object is created using [sqlite3_str_new()].
** <li> ^Text is appended to the sqlite3_str object using various
** methods, such as [sqlite3_str_appendf()].
** <li> ^The sqlite3_str object is destroyed and the string it created
** is returned using the [sqlite3_str_finish()] interface.
** </ol>
*/
typedef struct sqlite3_str sqlite3_str;
/*
** CAPI3REF: Create A New Dynamic String Object
** CONSTRUCTOR: sqlite3_str
**
** ^The [sqlite3_str_new(D)] interface allocates and initializes
** a new [sqlite3_str] object. To avoid memory leaks, the object returned by
** [sqlite3_str_new()] must be freed by a subsequent call to
** [sqlite3_str_finish(X)].
**
** ^The [sqlite3_str_new(D)] interface always returns a pointer to a
** valid [sqlite3_str] object, though in the event of an out-of-memory
** error the returned object might be a special singleton that will
** silently reject new text, always return SQLITE_NOMEM from
** [sqlite3_str_errcode()], always return 0 for
** [sqlite3_str_length()], and always return NULL from
** [sqlite3_str_finish(X)]. It is always safe to use the value
** returned by [sqlite3_str_new(D)] as the sqlite3_str parameter
** to any of the other [sqlite3_str] methods.
**
** The D parameter to [sqlite3_str_new(D)] may be NULL. If the
** D parameter in [sqlite3_str_new(D)] is not NULL, then the maximum
** length of the string contained in the [sqlite3_str] object will be
** the value set for [sqlite3_limit](D,[SQLITE_LIMIT_LENGTH]) instead
** of [SQLITE_MAX_LENGTH].
*/
SQLITE_API sqlite3_str *sqlite3_str_new(sqlite3*);
/*
** CAPI3REF: Finalize A Dynamic String
** DESTRUCTOR: sqlite3_str
**
** ^The [sqlite3_str_finish(X)] interface destroys the sqlite3_str object X
** and returns a pointer to a memory buffer obtained from [sqlite3_malloc64()]
** that contains the constructed string. The calling application should
** pass the returned value to [sqlite3_free()] to avoid a memory leak.
** ^The [sqlite3_str_finish(X)] interface may return a NULL pointer if any
** errors were encountered during construction of the string. ^The
** [sqlite3_str_finish(X)] interface will also return a NULL pointer if the
** string in [sqlite3_str] object X is zero bytes long.
*/
SQLITE_API char *sqlite3_str_finish(sqlite3_str*);
/*
** CAPI3REF: Add Content To A Dynamic String
** METHOD: sqlite3_str
**
** These interfaces add content to an sqlite3_str object previously obtained
** from [sqlite3_str_new()].
**
** ^The [sqlite3_str_appendf(X,F,...)] and
** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf]
** functionality of SQLite to append formatted text onto the end of
** [sqlite3_str] object X.
**
** ^The [sqlite3_str_append(X,S,N)] method appends exactly N bytes from string S
** onto the end of the [sqlite3_str] object X. N must be non-negative.
** S must contain at least N non-zero bytes of content. To append a
** zero-terminated string in its entirety, use the [sqlite3_str_appendall()]
** method instead.
**
** ^The [sqlite3_str_appendall(X,S)] method appends the complete content of
** zero-terminated string S onto the end of [sqlite3_str] object X.
**
** ^The [sqlite3_str_appendchar(X,N,C)] method appends N copies of the
** single-byte character C onto the end of [sqlite3_str] object X.
** ^This method can be used, for example, to add whitespace indentation.
**
** ^The [sqlite3_str_reset(X)] method resets the string under construction
** inside [sqlite3_str] object X back to zero bytes in length.
**
** These methods do not return a result code. ^If an error occurs, that fact
** is recorded in the [sqlite3_str] object and can be recovered by a
** subsequent call to [sqlite3_str_errcode(X)].
*/
SQLITE_API void sqlite3_str_appendf(sqlite3_str*, const char *zFormat, ...);
SQLITE_API void sqlite3_str_vappendf(sqlite3_str*, const char *zFormat, va_list);
SQLITE_API void sqlite3_str_append(sqlite3_str*, const char *zIn, int N);
SQLITE_API void sqlite3_str_appendall(sqlite3_str*, const char *zIn);
SQLITE_API void sqlite3_str_appendchar(sqlite3_str*, int N, char C);
SQLITE_API void sqlite3_str_reset(sqlite3_str*);
/*
** CAPI3REF: Status Of A Dynamic String
** METHOD: sqlite3_str
**
** These interfaces return the current status of an [sqlite3_str] object.
**
** ^If any prior errors have occurred while constructing the dynamic string
** in sqlite3_str X, then the [sqlite3_str_errcode(X)] method will return
** an appropriate error code. ^The [sqlite3_str_errcode(X)] method returns
** [SQLITE_NOMEM] following any out-of-memory error, or
** [SQLITE_TOOBIG] if the size of the dynamic string exceeds
** [SQLITE_MAX_LENGTH], or [SQLITE_OK] if there have been no errors.
**
** ^The [sqlite3_str_length(X)] method returns the current length, in bytes,
** of the dynamic string under construction in [sqlite3_str] object X.
** ^The length returned by [sqlite3_str_length(X)] does not include the
** zero-termination byte.
**
** ^The [sqlite3_str_value(X)] method returns a pointer to the current
** content of the dynamic string under construction in X. The value
** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X
** and might be freed or altered by any subsequent method on the same
** [sqlite3_str] object. Applications must not used the pointer returned
** [sqlite3_str_value(X)] after any subsequent method call on the same
** object. ^Applications may change the content of the string returned
** by [sqlite3_str_value(X)] as long as they do not write into any bytes
** outside the range of 0 to [sqlite3_str_length(X)] and do not read or
** write any byte after any subsequent sqlite3_str method call.
*/
SQLITE_API int sqlite3_str_errcode(sqlite3_str*);
SQLITE_API int sqlite3_str_length(sqlite3_str*);
SQLITE_API char *sqlite3_str_value(sqlite3_str*);
/*
** CAPI3REF: SQLite Runtime Status
**
@ -8282,11 +8539,11 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *);
** method of a [virtual table], then it returns true if and only if the
** column is being fetched as part of an UPDATE operation during which the
** column value will not change. Applications might use this to substitute
** a lighter-weight value to return that the corresponding [xUpdate] method
** understands as a "no-change" value.
** a return value that is less expensive to compute and that the corresponding
** [xUpdate] method understands as a "no-change" value.
**
** If the [xColumn] method calls sqlite3_vtab_nochange() and finds that
** the column is not changed by the UPDATE statement, they the xColumn
** the column is not changed by the UPDATE statement, then the xColumn
** method can optionally return without setting a result, without calling
** any of the [sqlite3_result_int|sqlite3_result_xxxxx() interfaces].
** In that case, [sqlite3_value_nochange(X)] will return true for the
@ -8781,7 +9038,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
** values of D and S.
** The size of the database is written into *P even if the
** SQLITE_SERIALIZE_NOCOPY bit is set but no contigious copy
** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy
** of the database exists.
**
** A call to sqlite3_serialize(D,S,P,F) might return NULL even if the

View file

@ -602,7 +602,7 @@ void SyncJournalDb::close()
commitTransaction();
_db.close();
_avoidReadFromDbOnNextSyncFilter.clear();
clearEtagStorageFilter();
_metadataTableIsEmpty = false;
}
@ -824,10 +824,10 @@ bool SyncJournalDb::setFileRecord(const SyncJournalFileRecord &_record)
SyncJournalFileRecord record = _record;
QMutexLocker locker(&_mutex);
if (!_avoidReadFromDbOnNextSyncFilter.isEmpty()) {
if (!_etagStorageFilter.isEmpty()) {
// If we are a directory that should not be read from db next time, don't write the etag
QByteArray prefix = record._path + "/";
foreach (const QByteArray &it, _avoidReadFromDbOnNextSyncFilter) {
foreach (const QByteArray &it, _etagStorageFilter) {
if (it.startsWith(prefix)) {
qCInfo(lcDb) << "Filtered writing the etag of" << prefix << "because it is a prefix of" << it;
record._etag = "_invalid_";
@ -1816,7 +1816,12 @@ void SyncJournalDb::avoidReadFromDbOnNextSync(const QByteArray &fileName)
// Prevent future overwrite of the etags of this folder and all
// parent folders for this sync
argument.append('/');
_avoidReadFromDbOnNextSyncFilter.append(argument);
_etagStorageFilter.append(argument);
}
void SyncJournalDb::clearEtagStorageFilter()
{
_etagStorageFilter.clear();
}
void SyncJournalDb::forceRemoteDiscoveryNextSync()

View file

@ -178,10 +178,18 @@ public:
* Since folders in the selective sync list will not be rediscovered (csync_ftw,
* _csync_detect_update skip them), the _invalid_ marker will stay. And any
* child items in the db will be ignored when reading a remote tree from the database.
*
* Any setFileRecord() call to affected directories before the next sync run will be
* adjusted to retain the invalid etag via _etagStorageFilter.
*/
void avoidReadFromDbOnNextSync(const QString &fileName) { avoidReadFromDbOnNextSync(fileName.toUtf8()); }
void avoidReadFromDbOnNextSync(const QByteArray &fileName);
/**
* Wipe _etagStorageFilter. Also done implicitly on close().
*/
void clearEtagStorageFilter();
/**
* Ensures full remote discovery happens on the next sync.
*
@ -295,13 +303,20 @@ private:
SqlQuery _setConflictRecordQuery;
SqlQuery _deleteConflictRecordQuery;
/* This is the list of paths we called avoidReadFromDbOnNextSync on.
* It means that they should not be written to the DB in any case since doing
* that would write the etag and would void the purpose of avoidReadFromDbOnNextSync
/* Storing etags to these folders, or their parent folders, is filtered out.
*
* When avoidReadFromDbOnNextSync() is called some etags to _invalid_ in the
* database. If this is done during a sync run, a later propagation job might
* undo that by writing the correct etag to the database instead. This filter
* will prevent this write and instead guarantee the _invalid_ etag stays in
* place.
*
* The list is cleared on close() (end of sync run) and explicitly with
* clearEtagStorageFilter() (start of sync run).
*
* The contained paths have a trailing /.
*/
QList<QByteArray> _avoidReadFromDbOnNextSyncFilter;
QList<QByteArray> _etagStorageFilter;
/** The journal mode to use for the db.
*

View file

@ -166,7 +166,7 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
/* First, check that the file is NOT in our tree (another file with the same name was added) */
if (our_tree->findFile(basePath)) {
other = nullptr;
qCDebug(lcReconcile, "Origin found in our tree : %s", basePath.constData());
qCInfo(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
@ -174,7 +174,7 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
* The journal is cleaned up later after propagation.
*/
other = other_tree->findFile(basePath);
qCDebug(lcReconcile, "Rename origin in other tree (%s) %s",
qCInfo(lcReconcile, "Rename origin in other tree (%s) %s",
basePath.constData(), other ? "found" : "not found");
}
@ -185,7 +185,7 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
// Some other EVAL_RENAME already claimed other.
// We do nothing: maybe a different candidate for
// other is found as well?
qCDebug(lcReconcile, "Other has already been renamed to %s",
qCInfo(lcReconcile, "Other has already been renamed to %s",
other->rename_path.constData());
} else if (cur->type == ItemTypeDirectory
// The local replica is reconciled first, so the remote tree would
@ -197,7 +197,7 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
|| other->instruction == CSYNC_INSTRUCTION_NONE
|| other->instruction == CSYNC_INSTRUCTION_UPDATE_METADATA
|| other->instruction == CSYNC_INSTRUCTION_REMOVE) {
qCDebug(lcReconcile, "Switching %s to RENAME to %s",
qCInfo(lcReconcile, "Switching %s to RENAME to %s",
other->path.constData(), cur->path.constData());
other->instruction = CSYNC_INSTRUCTION_RENAME;
other->rename_path = cur->path;
@ -217,7 +217,7 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
// Local: The remote reconcile will be able to deal with this.
// Remote: The local replica has already dealt with this.
// See the EVAL_RENAME case when other was found directly.
qCDebug(lcReconcile, "File in a renamed directory, other side's instruction: %d",
qCInfo(lcReconcile, "File in a renamed directory, other side's instruction: %d",
other->instruction);
cur->instruction = CSYNC_INSTRUCTION_NONE;
} else {
@ -225,7 +225,7 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
// and the instruction in the local tree is NEW while cur has EVAL_RENAME
// due to a remote move of the same file. In these scenarios we just
// want the instruction to stay NEW.
qCDebug(lcReconcile, "Other already has instruction %d",
qCInfo(lcReconcile, "Other already has instruction %d",
other->instruction);
}
};
@ -233,7 +233,7 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
if (ctx->current == LOCAL_REPLICA) {
/* use the old name to find the "other" node */
OCC::SyncJournalFileRecord base;
qCDebug(lcReconcile, "Finding rename origin through inode %" PRIu64 "",
qCInfo(lcReconcile, "Finding rename origin through inode %" PRIu64 "",
cur->inode);
ctx->statedb->getFileRecordByInode(cur->inode, &base);
renameCandidateProcessing(base._path);
@ -246,7 +246,7 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
// 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",
qCInfo(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.
@ -259,7 +259,7 @@ static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx)
// Also feed all the other files with the same fileid if necessary
if (!processedRename) {
qCDebug(lcReconcile, "Finding rename origin through file ID %s",
qCInfo(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); });

View file

@ -125,12 +125,12 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
* This code should probably be in csync_exclude, but it does not have the fs parameter.
* Keep it here for now */
if (ctx->ignore_hidden_files && (fs->is_hidden)) {
qCDebug(lcUpdate, "file excluded because it is a hidden file: %s", fs->path.constData());
qCInfo(lcUpdate, "file excluded because it is a hidden file: %s", fs->path.constData());
excluded = CSYNC_FILE_EXCLUDE_HIDDEN;
}
} else {
/* File is ignored because it's matched by a user- or system exclude pattern. */
qCDebug(lcUpdate, "%s excluded (%d)", fs->path.constData(), excluded);
qCInfo(lcUpdate, "%s excluded (%d)", fs->path.constData(), excluded);
if (excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE) {
return 1;
}
@ -155,7 +155,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
*/
QTextEncoder encoder(localCodec, QTextCodec::ConvertInvalidToNull);
if (encoder.fromUnicode(QString::fromUtf8(fs->path)).contains('\0')) {
qCDebug(lcUpdate, "cannot encode %s to local encoding %d",
qCInfo(lcUpdate, "cannot encode %s to local encoding %d",
fs->path.constData(), localCodec->mibEnum());
excluded = CSYNC_FILE_EXCLUDE_CANNOT_ENCODE;
}
@ -163,7 +163,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
if (fs->type == ItemTypeFile ) {
if (fs->modtime == 0) {
qCDebug(lcUpdate, "file: %s - mtime is zero!", fs->path.constData());
qCInfo(lcUpdate, "file: %s - mtime is zero!", fs->path.constData());
}
}
@ -245,7 +245,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
checksumIdentical = fs->checksumHeader == base._checksumHeader;
}
if (checksumIdentical) {
qCDebug(lcUpdate, "NOTE: Checksums are identical, file did not actually change: %s", fs->path.constData());
qCInfo(lcUpdate, "NOTE: Checksums are identical, file did not actually change: %s", fs->path.constData());
fs->instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
goto out;
}
@ -269,7 +269,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
* The metadata comparison ensure that we fetch all the file id or permission when
* upgrading owncloud
*/
qCDebug(lcUpdate, "Reading from database: %s", fs->path.constData());
qCInfo(lcUpdate, "Reading from database: %s", fs->path.constData());
ctx->remote.read_from_db = true;
}
/* If it was remembered in the db that the remote dir has ignored files, store
@ -280,7 +280,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
}
if (metadata_differ) {
/* file id or permissions has changed. Which means we need to update them in the DB. */
qCDebug(lcUpdate, "Need to update metadata for: %s", fs->path.constData());
qCInfo(lcUpdate, "Need to update metadata for: %s", fs->path.constData());
fs->instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
} else {
fs->instruction = CSYNC_INSTRUCTION_NONE;
@ -288,7 +288,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
} else {
/* check if it's a file and has been renamed */
if (ctx->current == LOCAL_REPLICA) {
qCDebug(lcUpdate, "Checking for rename based on inode # %" PRId64 "", (uint64_t) fs->inode);
qCInfo(lcUpdate, "Checking for rename based on inode # %" PRId64 "", (uint64_t) fs->inode);
OCC::SyncJournalFileRecord base;
if(!ctx->statedb->getFileRecordByInode(fs->inode, &base)) {
@ -315,13 +315,13 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
_rel_to_abs(ctx, fs->path), base._checksumHeader,
ctx->callbacks.checksum_userdata);
if (!fs->checksumHeader.isEmpty()) {
qCDebug(lcUpdate, "checking checksum of potential rename %s %s <-> %s", fs->path.constData(), fs->checksumHeader.constData(), base._checksumHeader.constData());
qCInfo(lcUpdate, "checking checksum of potential rename %s %s <-> %s", fs->path.constData(), fs->checksumHeader.constData(), base._checksumHeader.constData());
isRename = fs->checksumHeader == base._checksumHeader;
}
}
if (isRename) {
qCDebug(lcUpdate, "pot rename detected based on inode # %" PRId64 "", (uint64_t) fs->inode);
qCInfo(lcUpdate, "pot rename detected based on inode # %" PRId64 "", (uint64_t) fs->inode);
/* inode found so the file has been renamed */
fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME;
if (fs->type == ItemTypeDirectory) {
@ -331,6 +331,8 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
goto out;
} else {
qCInfo(lcUpdate, "Checking for rename based on fileid %s", fs->file_id.constData());
/* Remote Replica Rename check */
fs->instruction = CSYNC_INSTRUCTION_NEW;
@ -376,7 +378,8 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
return;
}
qCDebug(lcUpdate, "remote rename detected based on fileid %s --> %s", qPrintable(base._path), qPrintable(fs->path.constData()));
qCInfo(lcUpdate, "remote rename detected based on fileid %s --> %s", base._path.constData(), fs->path.constData());
fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME;
done = true;
};
@ -484,11 +487,11 @@ int csync_walker(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> fs) {
}
break;
case ItemTypeSoftLink:
qCDebug(lcUpdate, "symlink: %s - not supported", fs->path.constData());
qCInfo(lcUpdate, "symlink: %s - not supported", fs->path.constData());
break;
default:
qCInfo(lcUpdate, "item: %s - item type %d not iterated", fs->path.constData(), fs->type);
return 0;
break;
}
rc = _csync_detect_update(ctx, std::move(fs));
@ -511,7 +514,7 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri)
* their correct etags again and we don't run into this case.
*/
if (rec._etag == "_invalid_") {
qCDebug(lcUpdate, "%s selective sync excluded", rec._path.constData());
qCInfo(lcUpdate, "%s selective sync excluded", rec._path.constData());
skipbase = rec._path;
skipbase += '/';
return;
@ -757,7 +760,7 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
}
csync_vio_closedir(ctx, dh);
qCDebug(lcUpdate, " <= Closing walk for %s with read_from_db %d", uri, read_from_db);
qCInfo(lcUpdate, " <= Closing walk for %s with read_from_db %d", uri, read_from_db);
return rc;

View file

@ -335,10 +335,10 @@ void AccountState::slotInvalidCredentials()
if (account()->credentials()->ready()) {
account()->credentials()->invalidateToken();
if (auto creds = qobject_cast<HttpCredentials *>(account()->credentials())) {
if (creds->refreshAccessToken())
return;
}
}
if (auto creds = qobject_cast<HttpCredentials *>(account()->credentials())) {
if (creds->refreshAccessToken())
return;
}
account()->credentials()->askFromUser();
}

View file

@ -71,7 +71,7 @@ namespace {
" --logexpire <hours> : removes logs older than <hours> hours.\n"
" (to be used with --logdir)\n"
" --logflush : flush the log file after every write.\n"
" --logdebug : also output debug-level messages in the log (equivalent to setting the env var QT_LOGGING_RULES=\"qt.*=true;*.debug=true\").\n"
" --logdebug : also output debug-level messages in the log.\n"
" --confdir <dirname> : Use the given configuration folder.\n";
QString applicationTrPath()

View file

@ -62,21 +62,16 @@ const char propertyAccountC[] = "oc_account";
ownCloudGui::ownCloudGui(Application *parent)
: QObject(parent)
, _tray(0)
,
#if defined(Q_OS_MAC)
_settingsDialog(new SettingsDialogMac(this))
,
, _settingsDialog(new SettingsDialogMac(this))
#else
_settingsDialog(new SettingsDialog(this))
,
, _settingsDialog(new SettingsDialog(this))
#endif
_logBrowser(0)
, _contextMenuVisibleOsx(false)
, _logBrowser(0)
#ifdef WITH_LIBCLOUDPROVIDERS
, _bus(QDBusConnection::sessionBus())
#endif
, _recentActionsMenu(0)
, _qdbusmenuWorkaround(false)
, _app(parent)
{
_tray = new Systray();
@ -154,7 +149,7 @@ void ownCloudGui::slotOpenSettingsDialog()
void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
{
if (_qdbusmenuWorkaround) {
if (_workaroundFakeDoubleClick) {
static QElapsedTimer last_click;
if (last_click.isValid() && last_click.elapsed() < 200) {
return;
@ -439,17 +434,19 @@ void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *men
void ownCloudGui::slotContextMenuAboutToShow()
{
// For some reason on OS X _contextMenu->isVisible returns always false
_contextMenuVisibleOsx = true;
_contextMenuVisibleManual = true;
// Update icon in sys tray, as it might change depending on the context menu state
slotComputeOverallSyncStatus();
if (!_workaroundNoAboutToShowUpdate) {
updateContextMenu();
}
}
void ownCloudGui::slotContextMenuAboutToHide()
{
// For some reason on OS X _contextMenu->isVisible returns always false
_contextMenuVisibleOsx = false;
_contextMenuVisibleManual = false;
// Update icon in sys tray, as it might change depending on the context menu state
slotComputeOverallSyncStatus();
@ -457,11 +454,11 @@ void ownCloudGui::slotContextMenuAboutToHide()
bool ownCloudGui::contextMenuVisible() const
{
#ifdef Q_OS_MAC
return _contextMenuVisibleOsx;
#else
// On some platforms isVisible doesn't work and always returns false,
// elsewhere aboutToHide is unreliable.
if (_workaroundManualVisibility)
return _contextMenuVisibleManual;
return _contextMenu->isVisible();
#endif
}
static bool minimalTrayMenu()
@ -484,12 +481,36 @@ static bool updateWhileVisible()
}
}
static QByteArray forceQDBusTrayWorkaround()
static QByteArray envForceQDBusTrayWorkaround()
{
static QByteArray var = qgetenv("OWNCLOUD_FORCE_QDBUS_TRAY_WORKAROUND");
return var;
}
static QByteArray envForceWorkaroundShowAndHideTray()
{
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_SHOW_HIDE");
return var;
}
static QByteArray envForceWorkaroundNoAboutToShowUpdate()
{
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_NO_ABOUT_TO_SHOW");
return var;
}
static QByteArray envForceWorkaroundFakeDoubleClick()
{
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_FAKE_DOUBLE_CLICK");
return var;
}
static QByteArray envForceWorkaroundManualVisibility()
{
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_MANUAL_VISIBILITY");
return var;
}
void ownCloudGui::setupContextMenu()
{
if (_contextMenu) {
@ -512,51 +533,65 @@ void ownCloudGui::setupContextMenu()
return;
}
// Enables workarounds for bugs introduced in Qt 5.5.0
// In particular QTBUG-47863 #3672 (tray menu fails to update and
// becomes unresponsive) and QTBUG-48068 #3722 (click signal is
// emitted several times)
// The Qt version check intentionally uses 5.0.0 (where platformMenu()
// was introduced) instead of 5.5.0 to avoid issues where the Qt
// version used to build is different from the one used at runtime.
// If we build with 5.6.1 or newer, we can skip this because the
// bugs should be fixed there.
#ifdef Q_OS_LINUX
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) && (QT_VERSION < QT_VERSION_CHECK(5, 6, 0))
if (qVersion() == QByteArray("5.5.0")) {
QObject *platformMenu = reinterpret_cast<QObject *>(_tray->contextMenu()->platformMenu());
if (platformMenu
&& platformMenu->metaObject()->className() == QLatin1String("QDBusPlatformMenu")) {
_qdbusmenuWorkaround = true;
qCWarning(lcApplication) << "Enabled QDBusPlatformMenu workaround";
}
}
#endif
#endif
auto applyEnvVariable = [](bool *sw, const QByteArray &value) {
if (value == "1")
*sw = true;
if (value == "0")
*sw = false;
};
if (forceQDBusTrayWorkaround() == "1") {
_qdbusmenuWorkaround = true;
} else if (forceQDBusTrayWorkaround() == "0") {
_qdbusmenuWorkaround = false;
// This is an old compound flag that people might still depend on
bool qdbusmenuWorkarounds = false;
applyEnvVariable(&qdbusmenuWorkarounds, envForceQDBusTrayWorkaround());
if (qdbusmenuWorkarounds) {
_workaroundFakeDoubleClick = true;
_workaroundNoAboutToShowUpdate = true;
_workaroundShowAndHideTray = true;
}
// When the qdbusmenuWorkaround is necessary, we can't do on-demand updates
// because the workaround is to hide and show the tray icon.
if (_qdbusmenuWorkaround) {
connect(&_workaroundBatchTrayUpdate, &QTimer::timeout, this, &ownCloudGui::updateContextMenu);
_workaroundBatchTrayUpdate.setInterval(30 * 1000);
_workaroundBatchTrayUpdate.setSingleShot(true);
} else {
// Update the context menu whenever we're about to show it
// to the user.
#ifdef Q_OS_MAC
// https://bugreports.qt.io/browse/QTBUG-54633
connect(_contextMenu.data(), SIGNAL(aboutToShow()), SLOT(slotContextMenuAboutToShow()));
connect(_contextMenu.data(), SIGNAL(aboutToHide()), SLOT(slotContextMenuAboutToHide()));
#else
connect(_contextMenu.data(), &QMenu::aboutToShow, this, &ownCloudGui::updateContextMenu);
// https://bugreports.qt.io/browse/QTBUG-54633
_workaroundNoAboutToShowUpdate = true;
_workaroundManualVisibility = true;
#endif
#ifdef Q_OS_LINUX
// For KDE sessions if the platform plugin is missing,
// neither aboutToShow() updates nor the isVisible() call
// work. At least aboutToHide is reliable.
// https://github.com/owncloud/client/issues/6545
static QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP");
static QByteArray desktopSession = qgetenv("DESKTOP_SESSION");
bool isKde =
xdgCurrentDesktop.contains("KDE")
|| desktopSession.contains("plasma")
|| desktopSession.contains("kde");
QObject *platformMenu = reinterpret_cast<QObject *>(_tray->contextMenu()->platformMenu());
if (isKde && platformMenu && platformMenu->metaObject()->className() == QLatin1String("QDBusPlatformMenu")) {
_workaroundManualVisibility = true;
_workaroundNoAboutToShowUpdate = true;
}
#endif
applyEnvVariable(&_workaroundNoAboutToShowUpdate, envForceWorkaroundNoAboutToShowUpdate());
applyEnvVariable(&_workaroundFakeDoubleClick, envForceWorkaroundFakeDoubleClick());
applyEnvVariable(&_workaroundShowAndHideTray, envForceWorkaroundShowAndHideTray());
applyEnvVariable(&_workaroundManualVisibility, envForceWorkaroundManualVisibility());
qCInfo(lcApplication) << "Tray menu workarounds:"
<< "noabouttoshow:" << _workaroundNoAboutToShowUpdate
<< "fakedoubleclick:" << _workaroundFakeDoubleClick
<< "showhide:" << _workaroundShowAndHideTray
<< "manualvisibility:" << _workaroundManualVisibility;
connect(&_delayedTrayUpdateTimer, &QTimer::timeout, this, &ownCloudGui::updateContextMenu);
_delayedTrayUpdateTimer.setInterval(2 * 1000);
_delayedTrayUpdateTimer.setSingleShot(true);
connect(_contextMenu.data(), SIGNAL(aboutToShow()), SLOT(slotContextMenuAboutToShow()));
// unfortunately aboutToHide is unreliable, it seems to work on OSX though
connect(_contextMenu.data(), SIGNAL(aboutToHide()), SLOT(slotContextMenuAboutToHide()));
// Populate the context menu now.
updateContextMenu();
@ -568,13 +603,21 @@ void ownCloudGui::updateContextMenu()
return;
}
if (_qdbusmenuWorkaround) {
// If it's visible, we can't update live, and it won't be updated lazily: reschedule
if (contextMenuVisible() && !updateWhileVisible() && _workaroundNoAboutToShowUpdate) {
if (!_delayedTrayUpdateTimer.isActive()) {
_delayedTrayUpdateTimer.start();
}
return;
}
if (_workaroundShowAndHideTray) {
// To make tray menu updates work with these bugs (see setupContextMenu)
// we need to hide and show the tray icon. We don't want to do that
// while it's visible!
if (contextMenuVisible()) {
if (!_workaroundBatchTrayUpdate.isActive()) {
_workaroundBatchTrayUpdate.start();
if (!_delayedTrayUpdateTimer.isActive()) {
_delayedTrayUpdateTimer.start();
}
return;
}
@ -667,35 +710,30 @@ void ownCloudGui::updateContextMenu()
}
_contextMenu->addAction(_actionQuit);
if (_qdbusmenuWorkaround) {
if (_workaroundShowAndHideTray) {
_tray->show();
}
}
void ownCloudGui::updateContextMenuNeeded()
{
// For the workaround case updating while visible is impossible. Instead
// occasionally update the menu when it's invisible.
if (_qdbusmenuWorkaround) {
if (!_workaroundBatchTrayUpdate.isActive()) {
_workaroundBatchTrayUpdate.start();
}
// if it's visible and we can update live: update now
if (contextMenuVisible() && updateWhileVisible()) {
// Note: don't update while visible on OSX
// https://bugreports.qt.io/browse/QTBUG-54845
updateContextMenu();
return;
}
#ifdef Q_OS_MAC
// https://bugreports.qt.io/browse/QTBUG-54845
// We cannot update on demand or while visible -> update when invisible.
if (!contextMenuVisible()) {
updateContextMenu();
// if we can't lazily update: update later
if (_workaroundNoAboutToShowUpdate) {
// Note: don't update immediately even in the invisible case
// as that can lead to extremely frequent menu updates
if (!_delayedTrayUpdateTimer.isActive()) {
_delayedTrayUpdateTimer.start();
}
return;
}
#else
if (updateWhileVisible() && contextMenuVisible())
updateContextMenu();
#endif
// If no update was done here, we might update it on-demand due to
// the aboutToShow() signal.
}
void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg)

View file

@ -140,9 +140,11 @@ private:
// tray's menu
QScopedPointer<QMenu> _contextMenu;
// Manually tracking whether the context menu is visible, but only works
// on OSX because aboutToHide is not reliable everywhere.
bool _contextMenuVisibleOsx;
// Manually tracking whether the context menu is visible via aboutToShow
// and aboutToHide. Unfortunately aboutToHide isn't reliable everywhere
// so this only gets used with _workaroundManualVisibility (when the tray's
// isVisible() is unreliable)
bool _contextMenuVisibleManual = false;
#ifdef WITH_LIBCLOUDPROVIDERS
QDBusConnection _bus;
@ -150,8 +152,11 @@ private:
QMenu *_recentActionsMenu;
QVector<QMenu *> _accountMenus;
bool _qdbusmenuWorkaround;
QTimer _workaroundBatchTrayUpdate;
bool _workaroundShowAndHideTray = false;
bool _workaroundNoAboutToShowUpdate = false;
bool _workaroundFakeDoubleClick = false;
bool _workaroundManualVisibility = false;
QTimer _delayedTrayUpdateTimer;
QMap<QString, QPointer<ShareDialog>> _shareDialogs;
QAction *_actionNewAccountWizard;

View file

@ -1,5 +1,4 @@
// This file is generated by kxml_compiler from occinfo.xml.
// All changes you do to this file will be lost.
#include "updateinfo.h"
#include "updater.h"
@ -83,24 +82,6 @@ UpdateInfo UpdateInfo::parseElement(const QDomElement &element, bool *ok)
return result;
}
void UpdateInfo::writeElement(QXmlStreamWriter &xml)
{
xml.writeStartElement(QLatin1String("owncloudclient"));
if (!version().isEmpty()) {
xml.writeTextElement(QLatin1String("version"), version());
}
if (!versionString().isEmpty()) {
xml.writeTextElement(QLatin1String("versionstring"), versionString());
}
if (!web().isEmpty()) {
xml.writeTextElement(QLatin1String("web"), web());
}
if (!downloadUrl().isEmpty()) {
xml.writeTextElement(QLatin1String("downloadurl"), web());
}
xml.writeEndElement();
}
UpdateInfo UpdateInfo::parseFile(const QString &filename, bool *ok)
{
QFile file(filename);
@ -149,23 +130,4 @@ UpdateInfo UpdateInfo::parseString(const QString &xml, bool *ok)
return c;
}
bool UpdateInfo::writeFile(const QString &filename)
{
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
qCCritical(lcUpdater) << "Unable to open file '" << filename << "'";
return false;
}
QXmlStreamWriter xml(&file);
xml.setAutoFormatting(true);
xml.setAutoFormattingIndent(2);
xml.writeStartDocument(QLatin1String("1.0"));
writeElement(xml);
xml.writeEndDocument();
file.close();
return true;
}
} // namespace OCC

View file

@ -1,5 +1,4 @@
// This file is generated by kxml_compiler from occinfo.xml.
// All changes you do to this file will be lost.
#ifndef UPDATEINFO_H
#define UPDATEINFO_H
@ -24,10 +23,8 @@ public:
Parse XML object from DOM element.
*/
static UpdateInfo parseElement(const QDomElement &element, bool *ok);
void writeElement(QXmlStreamWriter &xml);
static UpdateInfo parseFile(const QString &filename, bool *ok);
static UpdateInfo parseString(const QString &xml, bool *ok);
bool writeFile(const QString &filename);
private:
QString mVersion;

View file

@ -390,10 +390,9 @@ QByteArray decryptStringSymmetric(const QByteArray& key, const QByteArray& data)
return result;
}
QByteArray privateKeyToPem(const QSslKey key) {
QByteArray privateKeyToPem(const QByteArray key) {
BIO *privateKeyBio = BIO_new(BIO_s_mem());
QByteArray privateKeyPem = key.toPem();
BIO_write(privateKeyBio, privateKeyPem.constData(), privateKeyPem.size());
BIO_write(privateKeyBio, key.constData(), key.size());
EVP_PKEY *pkey = PEM_read_bio_PrivateKey(privateKeyBio, NULL, NULL, NULL);
BIO *pemBio = BIO_new(BIO_s_mem());
@ -694,7 +693,8 @@ void ClientSideEncryption::privateKeyFetched(Job *incoming) {
return;
}
_privateKey = QSslKey(readJob->binaryData(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
//_privateKey = QSslKey(readJob->binaryData(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
_privateKey = readJob->binaryData();
if (_privateKey.isNull()) {
getPrivateKeyFromServer();
@ -723,7 +723,7 @@ void ClientSideEncryption::mnemonicKeyFetched(QKeychain::Job *incoming) {
if (readJob->error() != NoError || readJob->textData().length() == 0) {
_certificate = QSslCertificate();
_publicKey = QSslKey();
_privateKey = QSslKey();
_privateKey = QByteArray();
getPublicKeyFromServer();
return;
}
@ -745,7 +745,7 @@ void ClientSideEncryption::writePrivateKey() {
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
job->setInsecureFallback(false);
job->setKey(kck);
job->setBinaryData(_privateKey.toPem());
job->setBinaryData(_privateKey);
connect(job, &WritePasswordJob::finished, [this](Job *incoming) {
Q_UNUSED(incoming);
qCInfo(lcCse()) << "Private key stored in keychain";
@ -791,7 +791,7 @@ void ClientSideEncryption::writeMnemonic() {
void ClientSideEncryption::forgetSensitiveData()
{
_privateKey = QSslKey();
_privateKey = QByteArray();
_certificate = QSslCertificate();
_publicKey = QSslKey();
_mnemonic = QString();
@ -859,7 +859,8 @@ void ClientSideEncryption::generateKeyPair()
return;
}
QByteArray key = BIO2ByteArray(privKey);
_privateKey = QSslKey(key, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
//_privateKey = QSslKey(key, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
_privateKey = key;
qCInfo(lcCse()) << "Keys generated correctly, sending to server.";
generateCSR(localKeyPair);
@ -1025,9 +1026,10 @@ void ClientSideEncryption::decryptPrivateKey(const QByteArray &key) {
qCInfo(lcCse()) << "Generated key:" << pass;
QByteArray privateKey = EncryptionHelper::decryptPrivateKey(pass, key2);
_privateKey = QSslKey(privateKey, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
//_privateKey = QSslKey(privateKey, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
_privateKey = privateKey;
qCInfo(lcCse()) << "Private key: " << _privateKey.toPem();
qCInfo(lcCse()) << "Private key: " << _privateKey;
if (!_privateKey.isNull()) {
writePrivateKey();
@ -1037,7 +1039,7 @@ void ClientSideEncryption::decryptPrivateKey(const QByteArray &key) {
}
} else {
_mnemonic = QString();
_privateKey = QSslKey();
_privateKey = QByteArray();
qCInfo(lcCse()) << "Cancelled";
break;
}
@ -1226,7 +1228,7 @@ QByteArray FolderMetadata::encryptMetadataKey(const QByteArray& data) const {
QByteArray FolderMetadata::decryptMetadataKey(const QByteArray& encryptedMetadata) const
{
BIO *privateKeyBio = BIO_new(BIO_s_mem());
QByteArray privateKeyPem = _account->e2e()->_privateKey.toPem();
QByteArray privateKeyPem = _account->e2e()->_privateKey;
BIO_write(privateKeyBio, privateKeyPem.constData(), privateKeyPem.size());
EVP_PKEY *key = PEM_read_bio_PrivateKey(privateKeyBio, NULL, NULL, NULL);

View file

@ -47,7 +47,7 @@ namespace EncryptionHelper {
const QByteArray& data
);
QByteArray privateKeyToPem(const QSslKey key);
QByteArray privateKeyToPem(const QByteArray key);
//TODO: change those two EVP_PKEY into QSslKey.
QByteArray encryptStringAsymmetric(
@ -122,7 +122,8 @@ private:
QMap<QString, bool> _folder2encryptedStatus;
public:
QSslKey _privateKey;
//QSslKey _privateKey;
QByteArray _privateKey;
QSslKey _publicKey;
QSslCertificate _certificate;
QString _mnemonic;

View file

@ -361,6 +361,10 @@ void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(QString file, con
}
if (map.contains("data-fingerprint")) {
_dataFingerprint = map.value("data-fingerprint").toUtf8();
if (_dataFingerprint.isEmpty()) {
// Placeholder that means that the server supports the feature even if it did not set one.
_dataFingerprint = "[empty]";
}
}
} else {
// Remove <webDAV-Url>/folder/ from <webDAV-Url>/folder/subfile.txt

View file

@ -149,6 +149,7 @@ void GETFileJob::newReplyHook(QNetworkReply *reply)
connect(reply, &QNetworkReply::metaDataChanged, this, &GETFileJob::slotMetaDataChanged);
connect(reply, &QIODevice::readyRead, this, &GETFileJob::slotReadyRead);
connect(reply, &QNetworkReply::finished, this, &GETFileJob::slotReadyRead);
connect(reply, &QNetworkReply::downloadProgress, this, &GETFileJob::downloadProgress);
}

View file

@ -837,6 +837,11 @@ void SyncEngine::startSync()
// database creation error!
}
// Functionality like selective sync might have set up etag storage
// filtering via avoidReadFromDbOnNextSync(). This *is* the next sync, so
// undo the filter to allow this sync to retrieve and store the correct etags.
_journal->clearEtagStorageFilter();
_csync_ctx->upload_conflict_files = _account->capabilities().uploadConflictFiles();
_excludedFiles->setExcludeConflictFiles(!_account->capabilities().uploadConflictFiles());
@ -1042,10 +1047,9 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
}
auto databaseFingerprint = _journal->dataFingerprint();
// If databaseFingerprint is null, this means that there was no information in the database
// (for example, upgrading from a previous version, or first sync)
// Note that an empty ("") fingerprint is valid and means it was empty on the server before.
if (!databaseFingerprint.isNull()
// If databaseFingerprint is empty, this means that there was no information in the database
// (for example, upgrading from a previous version, or first sync, or server not supporting fingerprint)
if (!databaseFingerprint.isEmpty()
&& _discoveryMainThread->_dataFingerprint != databaseFingerprint) {
qCInfo(lcEngine) << "data fingerprint changed, assume restore from backup" << databaseFingerprint << _discoveryMainThread->_dataFingerprint;
restoreOldFiles(syncItems);

View file

@ -728,9 +728,18 @@ public:
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, _httpErrorCode);
setError(InternalServerError, "Internal Server Fake Error");
emit metaDataChanged();
emit readyRead();
// finishing can come strictly after readyRead was called
QTimer::singleShot(5, this, &FakeErrorReply::slotSetFinished);
}
public slots:
void slotSetFinished() {
setFinished(true);
emit finished();
}
public:
void abort() override { }
qint64 readData(char *, qint64) override { return 0; }
@ -1001,6 +1010,23 @@ private:
}
};
/* Return the FileInfo for a conflict file for the specified relative filename */
inline const FileInfo *findConflict(FileInfo &dir, const QString &filename)
{
QFileInfo info(filename);
const FileInfo *parentDir = dir.find(info.path());
if (!parentDir)
return nullptr;
QString start = info.baseName() + " (conflicted copy";
for (const auto &item : parentDir->children) {
if (item.name.startsWith(start)) {
return &item;
}
}
return nullptr;
}
// QTest::toString overloads
namespace OCC {
inline char *toString(const SyncFileStatus &s) {

View file

@ -151,6 +151,76 @@ private slots:
QCOMPARE(fakeFolder.currentLocalState(), expectedState);
QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
}
void testDataFingetPrint_data()
{
QTest::addColumn<bool>("hasInitialFingerPrint");
QTest::newRow("initial finger print") << true;
QTest::newRow("no initial finger print") << false;
}
void testDataFingetPrint()
{
QFETCH(bool, hasInitialFingerPrint);
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
fakeFolder.remoteModifier().setContents("C/c1", 'N');
fakeFolder.remoteModifier().setModTime("C/c1", QDateTime::currentDateTimeUtc().addDays(-2));
fakeFolder.remoteModifier().remove("C/c2");
if (hasInitialFingerPrint) {
fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>initial_finger_print</oc:data-fingerprint>";
} else {
//Server support finger print, but none is set.
fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint></oc:data-fingerprint>";
}
QVERIFY(fakeFolder.syncOnce());
// First sync, we did not change the finger print, so the file should be downloaded as normal
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->contentChar, 'N');
QVERIFY(!fakeFolder.currentRemoteState().find("C/c2"));
/* Simulate a backup restoration */
// A/a1 is an old file
fakeFolder.remoteModifier().setContents("A/a1", 'O');
fakeFolder.remoteModifier().setModTime("A/a1", QDateTime::currentDateTimeUtc().addDays(-2));
// B/b1 did not exist at the time of the backup
fakeFolder.remoteModifier().remove("B/b1");
// B/b2 was uploaded by another user in the mean time.
fakeFolder.remoteModifier().setContents("B/b2", 'N');
fakeFolder.remoteModifier().setModTime("B/b2", QDateTime::currentDateTimeUtc().addDays(2));
// C/c3 was removed since we made the backup
fakeFolder.remoteModifier().insert("C/c3_removed");
// C/c4 was moved to A/a2 since we made the backup
fakeFolder.remoteModifier().rename("A/a2", "C/old_a2_location");
// The admin sets the data-fingerprint property
fakeFolder.remoteModifier().extraDavProperties = "<oc:data-fingerprint>new_finger_print</oc:data-fingerprint>";
QVERIFY(fakeFolder.syncOnce());
auto currentState = fakeFolder.currentLocalState();
// Altough the local file is kept as a conflict, the server file is downloaded
QCOMPARE(currentState.find("A/a1")->contentChar, 'O');
auto conflict = findConflict(currentState, "A/a1");
QVERIFY(conflict);
QCOMPARE(conflict->contentChar, 'W');
fakeFolder.localModifier().remove(conflict->path());
// b1 was restored (re-uploaded)
QVERIFY(currentState.find("B/b1"));
// b2 has the new content (was not restored), since its mode time goes forward in time
QCOMPARE(currentState.find("B/b2")->contentChar, 'N');
conflict = findConflict(currentState, "B/b2");
QVERIFY(conflict); // Just to be sure, we kept the old file in a conflict
QCOMPARE(conflict->contentChar, 'W');
fakeFolder.localModifier().remove(conflict->path());
// We actually do not remove files that technically should have been removed (we don't want data-loss)
QVERIFY(currentState.find("C/c3_removed"));
QVERIFY(currentState.find("C/old_a2_location"));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
};
QTEST_GUILESS_MAIN(TestAllFilesDeleted)

292
test/testpermissions.cpp Normal file
View file

@ -0,0 +1,292 @@
/*
* This software is in the public domain, furnished "as is", without technical
* support, and with no warranty, express or implied, as to its usefulness for
* any purpose.
*
*/
#include <QtTest>
#include "syncenginetestutils.h"
#include <syncengine.h>
#include "common/ownsql.h"
using namespace OCC;
static void applyPermissionsFromName(FileInfo &info) {
static QRegularExpression rx("_PERM_([^_]*)_[^/]*$");
auto m = rx.match(info.name);
if (m.hasMatch()) {
info.permissions = RemotePermissions(m.captured(1));
}
for (FileInfo &sub : info.children)
applyPermissionsFromName(sub);
}
// Check if the expected rows in the DB are non-empty. Note that in some cases they might be, then we cannot use this function
// https://github.com/owncloud/client/issues/2038
static void assertCsyncJournalOk(SyncJournalDb &journal)
{
SqlDatabase db;
QVERIFY(db.openReadOnly(journal.databaseFilePath()));
SqlQuery q("SELECT count(*) from metadata where length(fileId) == 0", db);
QVERIFY(q.exec());
QVERIFY(q.next());
QCOMPARE(q.intValue(0), 0);
#if defined(Q_OS_WIN) // Make sure the file does not appear in the FileInfo
FileSystem::setFileHidden(journal.databaseFilePath() + "-shm", true);
#endif
}
class TestPermissions : public QObject
{
Q_OBJECT
private slots:
void t7pl()
{
FakeFolder fakeFolder{ FileInfo() };
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
const int cannotBeModifiedSize = 133;
const int canBeModifiedSize = 144;
//create some files
auto insertIn = [&](const QString &dir) {
fakeFolder.remoteModifier().insert(dir + "normalFile_PERM_WVND_.data", 100 );
fakeFolder.remoteModifier().insert(dir + "cannotBeRemoved_PERM_WVN_.data", 101 );
fakeFolder.remoteModifier().insert(dir + "canBeRemoved_PERM_D_.data", 102 );
fakeFolder.remoteModifier().insert(dir + "cannotBeModified_PERM_DVN_.data", cannotBeModifiedSize , 'A');
fakeFolder.remoteModifier().insert(dir + "canBeModified_PERM_W_.data", canBeModifiedSize );
};
//put them in some directories
fakeFolder.remoteModifier().mkdir("normalDirectory_PERM_CKDNV_");
insertIn("normalDirectory_PERM_CKDNV_/");
fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_" );
insertIn("readonlyDirectory_PERM_M_/" );
fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_/subdir_PERM_CK_");
fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_");
fakeFolder.remoteModifier().insert("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data", 100);
applyPermissionsFromName(fakeFolder.remoteModifier());
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
assertCsyncJournalOk(fakeFolder.syncJournal());
qInfo("Do some changes and see how they propagate");
//1. remove the file than cannot be removed
// (they should be recovered)
fakeFolder.localModifier().remove("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data");
fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data");
//2. remove the file that can be removed
// (they should properly be gone)
auto removeReadOnly = [&] (const QString &file) {
QVERIFY(!QFileInfo(fakeFolder.localPath() + file).permission(QFile::WriteOwner));
QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner);
fakeFolder.localModifier().remove(file);
};
removeReadOnly("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data");
removeReadOnly("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data");
//3. Edit the files that cannot be modified
// (they should be recovered, and a conflict shall be created)
auto editReadOnly = [&] (const QString &file) {
QVERIFY(!QFileInfo(fakeFolder.localPath() + file).permission(QFile::WriteOwner));
QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner);
fakeFolder.localModifier().appendByte(file);
};
editReadOnly("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data");
editReadOnly("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
//4. Edit other files
// (they should be uploaded)
fakeFolder.localModifier().appendByte("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data");
fakeFolder.localModifier().appendByte("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data");
//5. Create a new file in a read write folder
// (should be uploaded)
fakeFolder.localModifier().insert("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data", 106 );
applyPermissionsFromName(fakeFolder.remoteModifier());
//do the sync
QVERIFY(fakeFolder.syncOnce());
assertCsyncJournalOk(fakeFolder.syncJournal());
auto currentLocalState = fakeFolder.currentLocalState();
//1.
// File should be recovered
QVERIFY(currentLocalState.find("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data"));
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data"));
//2.
// File should be deleted
QVERIFY(!currentLocalState.find("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data"));
QVERIFY(!currentLocalState.find("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data"));
//3.
// File should be recovered
QCOMPARE(currentLocalState.find("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data")->size, cannotBeModifiedSize);
QCOMPARE(currentLocalState.find("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")->size, cannotBeModifiedSize);
// and conflict created
auto c1 = findConflict(currentLocalState, "normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data");
QVERIFY(c1);
QCOMPARE(c1->size, cannotBeModifiedSize + 1);
auto c2 = findConflict(currentLocalState, "readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
QVERIFY(c2);
QCOMPARE(c2->size, cannotBeModifiedSize + 1);
// remove the conflicts for the next state comparison
fakeFolder.localModifier().remove(c1->path());
fakeFolder.localModifier().remove(c2->path());
//4. File should be updated, that's tested by assertLocalAndRemoteDir
QCOMPARE(currentLocalState.find("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data")->size, canBeModifiedSize + 1);
QCOMPARE(currentLocalState.find("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data")->size, canBeModifiedSize + 1);
//5.
// the file should be in the server and local
QVERIFY(currentLocalState.find("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data"));
// Both side should still be the same
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
// Next test
//6. Create a new file in a read only folder
// (they should not be uploaded)
fakeFolder.localModifier().insert("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data", 105 );
applyPermissionsFromName(fakeFolder.remoteModifier());
// error: can't upload to readonly
QVERIFY(!fakeFolder.syncOnce());
assertCsyncJournalOk(fakeFolder.syncJournal());
currentLocalState = fakeFolder.currentLocalState();
//6.
// The file should not exist on the remote, but still be there
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"));
QVERIFY(!fakeFolder.currentRemoteState().find("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"));
// remove it so next test succeed.
fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data");
// Both side should still be the same
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
//######################################################################
qInfo( "remove the read only directory" );
// -> It must be recovered
fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_");
applyPermissionsFromName(fakeFolder.remoteModifier());
QVERIFY(fakeFolder.syncOnce());
assertCsyncJournalOk(fakeFolder.syncJournal());
currentLocalState = fakeFolder.currentLocalState();
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data"));
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
//######################################################################
qInfo( "move a directory in a outside read only folder" );
//Missing directory should be restored
//new directory should be uploaded
fakeFolder.localModifier().rename("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_");
applyPermissionsFromName(fakeFolder.remoteModifier());
fakeFolder.syncOnce();
if (fakeFolder.syncEngine().isAnotherSyncNeeded() == ImmediateFollowUp) {
QVERIFY(fakeFolder.syncOnce());
}
assertCsyncJournalOk(fakeFolder.syncJournal());
currentLocalState = fakeFolder.currentLocalState();
// old name restored
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"));
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"));
// new still exist (and is uploaded)
QVERIFY(currentLocalState.find("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
//######################################################################
qInfo( "rename a directory in a read only folder and move a directory to a read-only" );
// do a sync to update the database
applyPermissionsFromName(fakeFolder.remoteModifier());
QVERIFY(fakeFolder.syncOnce());
//1. rename a directory in a read only folder
//Missing directory should be restored
//new directory should stay but not be uploaded
fakeFolder.localModifier().rename("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "readonlyDirectory_PERM_M_/newname_PERM_CK_" );
//2. move a directory from read to read only (move the directory from previous step)
fakeFolder.localModifier().rename("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_", "readonlyDirectory_PERM_M_/moved_PERM_CK_" );
// error: can't upload to readonly!
QVERIFY(!fakeFolder.syncOnce());
if (fakeFolder.syncEngine().isAnotherSyncNeeded() == ImmediateFollowUp) {
QVERIFY(!fakeFolder.syncOnce());
}
assertCsyncJournalOk(fakeFolder.syncJournal());
currentLocalState = fakeFolder.currentLocalState();
//1.
// old name restored
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
// new still exist
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/newname_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
// but is not on server: so remove it localy for the future comarison
fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/newname_PERM_CK_");
//2.
// old removed
QVERIFY(!currentLocalState.find("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"));
// new still there
QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/moved_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" ));
//but not on server
fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/moved_PERM_CK_");
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
//######################################################################
qInfo( "multiple restores of a file create different conflict files" );
editReadOnly("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
fakeFolder.localModifier().setContents("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data", 's');
//do the sync
applyPermissionsFromName(fakeFolder.remoteModifier());
QVERIFY(fakeFolder.syncOnce());
assertCsyncJournalOk(fakeFolder.syncJournal());
QThread::sleep(1); // make sure changes have different mtime
editReadOnly("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data");
fakeFolder.localModifier().setContents("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data", 'd');
//do the sync
applyPermissionsFromName(fakeFolder.remoteModifier());
QVERIFY(fakeFolder.syncOnce());
assertCsyncJournalOk(fakeFolder.syncJournal());
// there should be two conflict files
currentLocalState = fakeFolder.currentLocalState();
int count = 0;
while (auto i = findConflict(currentLocalState, "readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")) {
QVERIFY((i->contentChar == 's') || (i->contentChar == 'd'));
fakeFolder.localModifier().remove(i->path());
currentLocalState = fakeFolder.currentLocalState();
count++;
}
QCOMPARE(count, 2);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
};
QTEST_GUILESS_MAIN(TestPermissions)
#include "testpermissions.moc"

View file

@ -255,7 +255,8 @@ private slots:
} else if(item->_file == "Y/Z/d3") {
QVERIFY(item->_status != SyncFileItem::Success);
}
QVERIFY(item->_file != "Y/Z/d9"); // we should have aborted the sync before d9 starts
// We do not know about the other files - maybe the sync was aborted,
// maybe they finished before the error caused the abort.
}
}

View file

@ -388,7 +388,7 @@
<message>
<location filename="../src/gui/accountstate.cpp" line="138"/>
<source>Asking Credentials</source>
<translation type="unfinished"/>
<translation>Kysytään tilitietoja</translation>
</message>
<message>
<location filename="../src/gui/accountstate.cpp" line="140"/>
@ -1389,7 +1389,7 @@ Kohteet, joiden poisto on sallittu, poistetaan, jos ne estävät kansion poistam
<message>
<location filename="../src/gui/issueswidget.ui" line="20"/>
<source>List of issues</source>
<translation type="unfinished"/>
<translation>Lista ongelmista</translation>
</message>
<message>
<location filename="../src/gui/issueswidget.ui" line="34"/>
@ -1426,7 +1426,7 @@ Kohteet, joiden poisto on sallittu, poistetaan, jos ne estävät kansion poistam
<message>
<location filename="../src/gui/issueswidget.ui" line="155"/>
<source>Copy the issues list to the clipboard.</source>
<translation type="unfinished"/>
<translation>Kopioi ongelmalista leikepöydälle.</translation>
</message>
<message>
<location filename="../src/gui/issueswidget.ui" line="158"/>
@ -1446,7 +1446,7 @@ Kohteet, joiden poisto on sallittu, poistetaan, jos ne estävät kansion poistam
<message>
<location filename="../src/gui/issueswidget.cpp" line="84"/>
<source>Issue</source>
<translation type="unfinished"/>
<translation>Ongelma</translation>
</message>
</context>
<context>
@ -1878,7 +1878,7 @@ for additional privileges during the process.</source>
<message>
<location filename="../src/gui/wizard/owncloudoauthcredspage.cpp" line="44"/>
<source>Login in your browser</source>
<translation type="unfinished"/>
<translation>Kirjaudu selaimellasi</translation>
</message>
</context>
<context>

View file

@ -2082,7 +2082,7 @@ Il est déconseillé de l&apos;utiliser.</translation>
<message>
<location filename="../src/gui/wizard/owncloudwizard.cpp" line="93"/>
<source>Skip folders configuration</source>
<translation>Passer outre la configuration des dossiers</translation>
<translation>Ignorer la configuration des dossiers</translation>
</message>
</context>
<context>

View file

@ -415,7 +415,7 @@
<location filename="../src/gui/activitywidget.cpp" line="515"/>
<location filename="../src/gui/activitywidget.cpp" line="563"/>
<source>Server Activity</source>
<translation>Sunucu Etkinliği</translation>
<translation>Sunucu İşlemleri</translation>
</message>
<message>
<location filename="../src/gui/activitywidget.cpp" line="522"/>
@ -436,12 +436,12 @@
<message>
<location filename="../src/gui/activitywidget.cpp" line="606"/>
<source>The server activity list has been copied to the clipboard.</source>
<translation>Sunucu etkinliği listesi panoya kopyalandı.</translation>
<translation>Sunucu işlemleri listesi panoya kopyalandı.</translation>
</message>
<message>
<location filename="../src/gui/activitywidget.cpp" line="610"/>
<source>The sync activity list has been copied to the clipboard.</source>
<translation>Eşitleme etkinliği listesi panoya kopyalandı.</translation>
<translation>Eşitleme işlemi listesi panoya kopyalandı.</translation>
</message>
<message>
<location filename="../src/gui/activitywidget.cpp" line="613"/>
@ -481,7 +481,7 @@
<message>
<location filename="../src/gui/activitywidget.cpp" line="88"/>
<source>Copy the activity list to the clipboard.</source>
<translation>Etkinlik listesini panoya kopyala.</translation>
<translation>İşlem listesini panoya kopyala.</translation>
</message>
<message>
<location filename="../src/gui/activitywidget.cpp" line="135"/>
@ -736,7 +736,7 @@
<message>
<location filename="../src/gui/folder.cpp" line="414"/>
<source>Sync Activity</source>
<translation>Eşitleme Etkinliği</translation>
<translation>Eşitleme İşlemi</translation>
</message>
<message>
<location filename="../src/gui/folder.cpp" line="640"/>
@ -2526,7 +2526,7 @@ Kullanmanız önerilmez.</translation>
<message>
<location filename="../src/gui/settingsdialog.cpp" line="109"/>
<source>Activity</source>
<translation>Etkinlik</translation>
<translation>İşlem</translation>
</message>
<message>
<location filename="../src/gui/settingsdialog.cpp" line="118"/>
@ -2554,7 +2554,7 @@ Kullanmanız önerilmez.</translation>
<message>
<location filename="../src/gui/settingsdialogmac.cpp" line="97"/>
<source>Activity</source>
<translation>Etkinlik</translation>
<translation>İşlem</translation>
</message>
<message>
<location filename="../src/gui/settingsdialogmac.cpp" line="111"/>