Merge pull request #5045 from owncloud/dbjournal_per_account

Allow a folder to be synced to several accounts. This changes the path of the sync journal file!
This commit is contained in:
ckamm 2016-12-06 10:55:58 +01:00 committed by GitHub
commit 5bef1aa402
41 changed files with 600 additions and 239 deletions

View file

@ -114,7 +114,7 @@ void csync_create(CSYNC **csync, const char *local) {
*csync = ctx;
}
void csync_init(CSYNC *ctx) {
void csync_init(CSYNC *ctx, const char *db_file) {
assert(ctx);
/* Do not initialize twice */
@ -125,6 +125,9 @@ void csync_init(CSYNC *ctx) {
ctx->remote.type = REMOTE_REPLICA;
SAFE_FREE(ctx->statedb.file);
ctx->statedb.file = c_strdup(db_file);
c_rbtree_create(&ctx->local.tree, _key_cmp, _data_cmp);
c_rbtree_create(&ctx->remote.tree, _key_cmp, _data_cmp);
@ -146,19 +149,11 @@ int csync_update(CSYNC *ctx) {
}
ctx->status_code = CSYNC_STATUS_OK;
/* create/load statedb */
rc = asprintf(&ctx->statedb.file, "%s/.csync_journal.db",
ctx->local.uri);
if (rc < 0) {
ctx->status_code = CSYNC_STATUS_MEMORY_ERROR;
return rc;
}
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Journal: %s", ctx->statedb.file);
if (csync_statedb_load(ctx, ctx->statedb.file, &ctx->statedb.db) < 0) {
/* Path of database file is set in csync_init */
if (csync_statedb_load(ctx, ctx->statedb.file, &ctx->statedb.db) < 0) {
rc = -1;
return rc;
}
}
ctx->status_code = CSYNC_STATUS_OK;
@ -515,7 +510,6 @@ static void _csync_clean_ctx(CSYNC *ctx)
c_rbtree_free(ctx->local.tree);
c_rbtree_free(ctx->remote.tree);
SAFE_FREE(ctx->statedb.file);
SAFE_FREE(ctx->remote.root_perms);
}
@ -572,6 +566,7 @@ int csync_destroy(CSYNC *ctx) {
_csync_clean_ctx(ctx);
SAFE_FREE(ctx->statedb.file);
SAFE_FREE(ctx->local.uri);
SAFE_FREE(ctx->error_string);

View file

@ -326,7 +326,7 @@ void OCSYNC_EXPORT csync_create(CSYNC **csync, const char *local);
*
* @param ctx The context to initialize.
*/
void OCSYNC_EXPORT csync_init(CSYNC *ctx);
void OCSYNC_EXPORT csync_init(CSYNC *ctx, const char *db_file);
/**
* @brief Update detection

View file

@ -230,6 +230,11 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(c_strlist_t *excludes, const ch
}
blen = strlen(bname);
rc = csync_fnmatch("._sync_*.db*", bname, 0);
if (rc == 0) {
match = CSYNC_FILE_SILENTLY_EXCLUDED;
goto out;
}
rc = csync_fnmatch(".csync_journal.db*", bname, 0);
if (rc == 0) {
match = CSYNC_FILE_SILENTLY_EXCLUDED;

View file

@ -704,8 +704,6 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
}
while ((dirent = csync_vio_readdir(ctx, dh))) {
const char *path = NULL;
size_t ulen = 0;
int flen;
int flag;
@ -744,35 +742,6 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
goto error;
}
/* Create relative path: For local replica, we need to remove the base path. */
path = filename;
if (ctx->current == LOCAL_REPLICA) {
ulen = strlen(ctx->local.uri) + 1;
if (((size_t)flen) < ulen) {
csync_vio_file_stat_destroy(dirent);
dirent = NULL;
ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL;
goto error;
}
path += ulen;
}
/* skip ".csync_journal.db" and ".csync_journal.db.ctmp" */
/* Isn't this done via csync_exclude already? */
if (c_streq(path, ".csync_journal.db")
|| c_streq(path, ".csync_journal.db.ctmp")
|| c_streq(path, ".csync_journal.db.ctmp-journal")
|| c_streq(path, ".csync-progressdatabase")
|| c_streq(path, ".csync_journal.db-shm")
|| c_streq(path, ".csync_journal.db-wal")
|| c_streq(path, ".csync_journal.db-journal")) {
csync_vio_file_stat_destroy(dirent);
dirent = NULL;
SAFE_FREE(filename);
continue;
}
/* Only for the local replica we have to stat(), for the remote one we have all data already */
if (ctx->replica == LOCAL_REPLICA) {
res = csync_vio_stat(ctx, filename, dirent);

View file

@ -23,7 +23,7 @@
#include "csync_private.h"
static void setup(void **state) {
static int setup(void **state) {
CSYNC *csync;
int rc;
@ -33,9 +33,11 @@ static void setup(void **state) {
csync_create(&csync, "/tmp/check_csync1");
*state = csync;
return 0;
}
static void setup_module(void **state) {
static int setup_module(void **state) {
CSYNC *csync;
int rc;
@ -44,11 +46,13 @@ static void setup_module(void **state) {
csync_create(&csync, "/tmp/check_csync1");
csync_init(csync);
csync_init(csync, "foo");
*state = csync;
return 0;
}
static void teardown(void **state) {
static int teardown(void **state) {
CSYNC *csync = *state;
int rc;
@ -61,6 +65,8 @@ static void teardown(void **state) {
assert_int_equal(rc, 0);
*state = NULL;
return 0;
}
static void check_csync_commit(void **state)
@ -88,10 +94,10 @@ static void check_csync_commit_dummy(void **state)
int torture_run_tests(void)
{
const UnitTest tests[] = {
unit_test_setup_teardown(check_csync_commit, setup, teardown),
unit_test_setup_teardown(check_csync_commit_dummy, setup_module, teardown),
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(check_csync_commit, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_commit_dummy, setup_module, teardown),
};
return run_tests(tests);
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -50,11 +50,11 @@ static void check_csync_create(void **state)
int torture_run_tests(void)
{
const UnitTest tests[] = {
unit_test(check_csync_destroy_null),
unit_test(check_csync_create),
const struct CMUnitTest tests[] = {
cmocka_unit_test(check_csync_destroy_null),
cmocka_unit_test(check_csync_create),
};
return run_tests(tests);
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -29,15 +29,16 @@
#define EXCLUDE_LIST_FILE SOURCEDIR"/../sync-exclude.lst"
static void setup(void **state) {
static int setup(void **state) {
CSYNC *csync;
csync_create(&csync, "/tmp/check_csync1");
*state = csync;
return 0;
}
static void setup_init(void **state) {
static int setup_init(void **state) {
CSYNC *csync;
int rc;
@ -59,9 +60,10 @@ static void setup_init(void **state) {
assert_int_equal(rc, 0);
*state = csync;
return 0;
}
static void teardown(void **state) {
static int teardown(void **state) {
CSYNC *csync = *state;
int rc;
@ -74,6 +76,8 @@ static void teardown(void **state) {
assert_int_equal(rc, 0);
*state = NULL;
return 0;
}
static void check_csync_exclude_add(void **state)
@ -143,6 +147,17 @@ static void check_csync_excluded(void **state)
assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED);
rc = csync_excluded_no_ctx(csync->excludes, "subdir/.csync_journal.db", CSYNC_FTW_TYPE_FILE);
assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED);
/* also the new form of the database name */
rc = csync_excluded_no_ctx(csync->excludes, "._sync_5bdd60bdfcfa.db", CSYNC_FTW_TYPE_FILE);
assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED);
rc = csync_excluded_no_ctx(csync->excludes, "._sync_5bdd60bdfcfa.db.ctmp", CSYNC_FTW_TYPE_FILE);
assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED);
rc = csync_excluded_no_ctx(csync->excludes, "._sync_5bdd60bdfcfa.db-shm", CSYNC_FTW_TYPE_FILE);
assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED);
rc = csync_excluded_no_ctx(csync->excludes, "subdir/._sync_5bdd60bdfcfa.db", CSYNC_FTW_TYPE_FILE);
assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED);
/* pattern ]*.directory - ignore and remove */
rc = csync_excluded_no_ctx(csync->excludes, "my.~directory", CSYNC_FTW_TYPE_FILE);
@ -380,16 +395,16 @@ static void check_csync_exclude_expand_escapes(void **state)
int torture_run_tests(void)
{
const UnitTest tests[] = {
unit_test_setup_teardown(check_csync_exclude_add, setup, teardown),
unit_test_setup_teardown(check_csync_exclude_load, setup, teardown),
unit_test_setup_teardown(check_csync_excluded, setup_init, teardown),
unit_test_setup_teardown(check_csync_excluded_traversal, setup_init, teardown),
unit_test_setup_teardown(check_csync_pathes, setup_init, teardown),
unit_test_setup_teardown(check_csync_is_windows_reserved_word, setup_init, teardown),
unit_test_setup_teardown(check_csync_excluded_performance, setup_init, teardown),
unit_test(check_csync_exclude_expand_escapes),
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(check_csync_exclude_add, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_exclude_load, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_excluded, setup_init, teardown),
cmocka_unit_test_setup_teardown(check_csync_excluded_traversal, setup_init, teardown),
cmocka_unit_test_setup_teardown(check_csync_pathes, setup_init, teardown),
cmocka_unit_test_setup_teardown(check_csync_is_windows_reserved_word, setup_init, teardown),
cmocka_unit_test_setup_teardown(check_csync_excluded_performance, setup_init, teardown),
cmocka_unit_test(check_csync_exclude_expand_escapes),
};
return run_tests(tests);
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -23,7 +23,7 @@
#include "csync_private.h"
static void setup(void **state) {
static int setup(void **state) {
CSYNC *csync;
int rc;
@ -33,9 +33,10 @@ static void setup(void **state) {
csync_create(&csync, "/tmp/check_csync1");
*state = csync;
return 0;
}
static void setup_module(void **state) {
static int setup_module(void **state) {
CSYNC *csync;
int rc;
@ -45,9 +46,10 @@ static void setup_module(void **state) {
csync_create(&csync, "/tmp/check_csync1");
*state = csync;
return 0;
}
static void teardown(void **state) {
static int teardown(void **state) {
CSYNC *csync = *state;
int rc;
@ -60,24 +62,27 @@ static void teardown(void **state) {
assert_int_equal(rc, 0);
*state = NULL;
return 0;
}
static void check_csync_init(void **state)
{
CSYNC *csync = *state;
csync_init(csync);
csync_init(csync, "");
assert_int_equal(csync->status & CSYNC_STATUS_INIT, 1);
}
int torture_run_tests(void)
{
const UnitTest tests[] = {
unit_test_setup_teardown(check_csync_init, setup, teardown),
unit_test_setup_teardown(check_csync_init, setup_module, teardown),
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(check_csync_init, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_init, setup_module, teardown),
};
return run_tests(tests);
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -26,7 +26,7 @@
#include "csync_log.c"
#include "c_private.h"
static void setup(void **state) {
static int setup(void **state) {
CSYNC *csync;
int rc;
@ -36,9 +36,11 @@ static void setup(void **state) {
csync_create(&csync, "/tmp/check_csync1");
*state = csync;
return 0;
}
static void teardown(void **state) {
static int teardown(void **state) {
CSYNC *csync = *state;
int rc;
@ -51,6 +53,8 @@ static void teardown(void **state) {
assert_int_equal(rc, 0);
*state = NULL;
return 0;
}
static void check_log_callback(int verbosity,
@ -134,11 +138,11 @@ static void check_logging(void **state)
int torture_run_tests(void)
{
const UnitTest tests[] = {
unit_test(check_set_log_level),
unit_test(check_set_auth_callback),
unit_test_setup_teardown(check_logging, setup, teardown),
const struct CMUnitTest tests[] = {
cmocka_unit_test(check_set_log_level),
cmocka_unit_test(check_set_auth_callback),
cmocka_unit_test_setup_teardown(check_logging, setup, teardown),
};
return run_tests(tests);
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -48,10 +48,10 @@ static void check_csync_normalize_etag(void **state)
int torture_run_tests(void)
{
const UnitTest tests[] = {
unit_test(check_csync_normalize_etag),
const struct CMUnitTest tests[] = {
cmocka_unit_test(check_csync_normalize_etag),
};
return run_tests(tests);
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -26,7 +26,7 @@
#define TESTDB "/tmp/check_csync1/test.db"
static void setup(void **state) {
static int setup(void **state) {
CSYNC *csync;
int rc;
@ -47,9 +47,11 @@ static void setup(void **state) {
rc = sqlite3_close(db);
assert_int_equal(rc, SQLITE_OK);
return 0;
}
static void teardown(void **state) {
static int teardown(void **state) {
CSYNC *csync = *state;
int rc;
@ -60,6 +62,8 @@ static void teardown(void **state) {
assert_int_equal(rc, 0);
*state = NULL;
return 0;
}
static void check_csync_statedb_load(void **state)
@ -116,11 +120,11 @@ static void check_csync_statedb_close(void **state)
int torture_run_tests(void)
{
const UnitTest tests[] = {
unit_test_setup_teardown(check_csync_statedb_load, setup, teardown),
unit_test_setup_teardown(check_csync_statedb_close, setup, teardown),
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(check_csync_statedb_load, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_statedb_close, setup, teardown),
};
return run_tests(tests);
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -27,7 +27,7 @@
static void setup(void **state)
static int setup(void **state)
{
CSYNC *csync;
int rc = 0;
@ -39,7 +39,7 @@ static void setup(void **state)
rc = system("mkdir -p /tmp/check_csync");
assert_int_equal(rc, 0);
csync_create(&csync, "/tmp/check_csync1");
csync_init(csync);
csync_init(csync, TESTDB);
sqlite3 *db = NULL;
rc = sqlite3_open_v2(TESTDB, &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, NULL);
@ -51,9 +51,11 @@ static void setup(void **state)
assert_int_equal(rc, 0);
*state = csync;
return 0;
}
static void setup_db(void **state)
static int setup_db(void **state)
{
char *errmsg;
int rc = 0;
@ -89,10 +91,12 @@ static void setup_db(void **state)
assert_int_equal(rc, SQLITE_OK);
sqlite3_close(db);
return 0;
}
static void teardown(void **state) {
static int teardown(void **state) {
CSYNC *csync = *state;
int rc = 0;
@ -104,6 +108,8 @@ static void teardown(void **state) {
assert_int_equal(rc, 0);
*state = NULL;
return 0;
}
@ -204,15 +210,15 @@ static void check_csync_statedb_get_stat_by_inode_not_found(void **state)
int torture_run_tests(void)
{
const UnitTest tests[] = {
unit_test_setup_teardown(check_csync_statedb_query_statement, setup, teardown),
unit_test_setup_teardown(check_csync_statedb_drop_tables, setup, teardown),
unit_test_setup_teardown(check_csync_statedb_insert_metadata, setup, teardown),
unit_test_setup_teardown(check_csync_statedb_write, setup, teardown),
unit_test_setup_teardown(check_csync_statedb_get_stat_by_hash_not_found, setup_db, teardown),
unit_test_setup_teardown(check_csync_statedb_get_stat_by_inode_not_found, setup_db, teardown),
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(check_csync_statedb_query_statement, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_statedb_drop_tables, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_statedb_insert_metadata, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_statedb_write, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_statedb_get_stat_by_hash_not_found, setup_db, teardown),
cmocka_unit_test_setup_teardown(check_csync_statedb_get_stat_by_inode_not_found, setup_db, teardown),
};
return run_tests(tests);
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -81,7 +81,7 @@ static void statedb_insert_metadata(sqlite3 *db)
}
}
static void setup(void **state)
static int setup(void **state)
{
CSYNC *csync;
int rc;
@ -92,7 +92,7 @@ static void setup(void **state)
rc = system("mkdir -p /tmp/check_csync1");
assert_int_equal(rc, 0);
csync_create(&csync, "/tmp/check_csync1");
csync_init(csync);
csync_init(csync, TESTDB);
/* Create a new db with metadata */
sqlite3 *db;
@ -109,9 +109,11 @@ static void setup(void **state)
assert_int_equal(rc, 0);
*state = csync;
return 0;
}
static void setup_ftw(void **state)
static int setup_ftw(void **state)
{
CSYNC *csync;
int rc;
@ -121,7 +123,7 @@ static void setup_ftw(void **state)
rc = system("mkdir -p /tmp/check_csync1");
assert_int_equal(rc, 0);
csync_create(&csync, "/tmp");
csync_init(csync);
csync_init(csync, TESTDB);
sqlite3 *db = NULL;
rc = sqlite3_open_v2(TESTDB, &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, NULL);
@ -135,9 +137,11 @@ static void setup_ftw(void **state)
csync->statedb.file = c_strdup( TESTDB );
*state = csync;
return 0;
}
static void teardown(void **state)
static int teardown(void **state)
{
CSYNC *csync = *state;
int rc;
@ -147,9 +151,11 @@ static void teardown(void **state)
assert_int_equal(rc, 0);
*state = NULL;
return 0;
}
static void teardown_rm(void **state) {
static int teardown_rm(void **state) {
int rc;
teardown(state);
@ -158,6 +164,8 @@ static void teardown_rm(void **state) {
assert_int_equal(rc, 0);
rc = system("rm -rf /tmp/check_csync1");
assert_int_equal(rc, 0);
return 0;
}
/* create a file stat, caller must free memory */
@ -424,19 +432,19 @@ static void check_csync_ftw_failing_fn(void **state)
int torture_run_tests(void)
{
const UnitTest tests[] = {
unit_test_setup_teardown(check_csync_detect_update, setup, teardown_rm),
unit_test_setup_teardown(check_csync_detect_update_db_none, setup, teardown),
unit_test_setup_teardown(check_csync_detect_update_db_eval, setup, teardown),
unit_test_setup_teardown(check_csync_detect_update_db_rename, setup, teardown),
unit_test_setup_teardown(check_csync_detect_update_db_new, setup, teardown_rm),
unit_test_setup_teardown(check_csync_detect_update_null, setup, teardown_rm),
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(check_csync_detect_update, setup, teardown_rm),
cmocka_unit_test_setup_teardown(check_csync_detect_update_db_none, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_detect_update_db_eval, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_detect_update_db_rename, setup, teardown),
cmocka_unit_test_setup_teardown(check_csync_detect_update_db_new, setup, teardown_rm),
cmocka_unit_test_setup_teardown(check_csync_detect_update_null, setup, teardown_rm),
unit_test_setup_teardown(check_csync_ftw, setup_ftw, teardown_rm),
unit_test_setup_teardown(check_csync_ftw_empty_uri, setup_ftw, teardown_rm),
unit_test_setup_teardown(check_csync_ftw_failing_fn, setup_ftw, teardown_rm),
cmocka_unit_test_setup_teardown(check_csync_ftw, setup_ftw, teardown_rm),
cmocka_unit_test_setup_teardown(check_csync_ftw_empty_uri, setup_ftw, teardown_rm),
cmocka_unit_test_setup_teardown(check_csync_ftw_failing_fn, setup_ftw, teardown_rm),
};
return run_tests(tests);
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -43,11 +43,11 @@ static void check_csync_memstat(void **state)
int torture_run_tests(void)
{
const UnitTest tests[] = {
unit_test(check_csync_instruction_str),
unit_test(check_csync_memstat),
const struct CMUnitTest tests[] = {
cmocka_unit_test(check_csync_instruction_str),
cmocka_unit_test(check_csync_memstat),
};
return run_tests(tests);
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -457,6 +457,7 @@ sub traverse( $$;$ )
$isHere = 1 if( $acceptConflicts && !$isHere && $f =~ /_conflict/ );
$isHere = 1 if( $f =~ /\.csync/ );
$isHere = 1 if( $f =~ /\._sync_/ );
assert( $isHere, "Filename local, but not remote: $f" );
}

View file

@ -176,7 +176,7 @@ assertLocalAndRemoteDir( 'remoteToLocal1', 1);
printInfo("simulate a owncloud 5 update by removing all the fileid");
## simulate a owncloud 5 update by removing all the fileid
system( "sqlite3 " . localDir() . ".csync_journal.db \"UPDATE metadata SET fileid='';\"");
system( "sqlite3 " . localDir() . "._sync_*.db \"UPDATE metadata SET fileid='';\"");
#refresh the ids
csync();
assertLocalAndRemoteDir( 'remoteToLocal1', 1);

View file

@ -61,7 +61,7 @@ sub getETagFromJournal($$)
{
my ($name,$num) = @_;
my $sql = "sqlite3 " . localDir() . ".csync_journal.db \"SELECT md5 FROM metadata WHERE path='$name';\"";
my $sql = "sqlite3 " . localDir() . "._sync_*.db \"SELECT md5 FROM metadata WHERE path='$name';\"";
open(my $fh, '-|', $sql) or die $!;
my $etag = <$fh>;
close $fh;

View file

@ -37,8 +37,8 @@ sub assertCsyncJournalOk {
my $path = $_[0];
# FIXME: should test also remoteperm but it's not working with owncloud6
# my $cmd = 'sqlite3 ' . $path . '.csync_journal.db "SELECT count(*) from metadata where length(remotePerm) == 0 or length(fileId) == 0"';
my $cmd = 'sqlite3 ' . $path . '.csync_journal.db "SELECT count(*) from metadata where length(fileId) == 0"';
# my $cmd = 'sqlite3 ' . $path . '._sync_*.db "SELECT count(*) from metadata where length(remotePerm) == 0 or length(fileId) == 0"';
my $cmd = 'sqlite3 ' . $path . '._sync_*.db "SELECT count(*) from metadata where length(fileId) == 0"';
my $result = `$cmd`;
assert($result == "0");
}
@ -170,14 +170,14 @@ assertLocalAndRemoteDir( '', 0);
#######################################################################
printInfo( "move a directory in a outside read only folder" );
system("sqlite3 " . localDir().'.csync_journal.db .dump');
system("sqlite3 " . localDir().'._sync_*.db .dump');
#Missing directory should be restored
#new directory should be uploaded
system("mv " . localDir().'readonlyDirectory_PERM_M_/subdir_PERM_CK_ ' . localDir().'normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_' );
csync();
system("sqlite3 " . localDir().'.csync_journal.db .dump');
system("sqlite3 " . localDir().'._sync_*.db .dump');
assertCsyncJournalOk(localDir());
# old name restored
@ -229,7 +229,7 @@ system("rm -r " . localDir(). "readonlyDirectory_PERM_M_/moved_PERM_CK_");
assertLocalAndRemoteDir( '', 0);
system("sqlite3 " . localDir().'.csync_journal.db .dump');
system("sqlite3 " . localDir().'._sync_*.db .dump');
#######################################################################

View file

@ -153,7 +153,8 @@ By default, the ownCloud Client ignores the following files:
* Files matched by one of the patterns defined in the Ignored Files Editor
* Files containing characters that do not work on certain file systems ``(`\, /, :, ?, *, ", >, <, |`)``.
* Files starting with ``.csync_journal.db``, as these files are reserved for journalling.
* Files starting with ``._sync_xxxxxxx.db`` and the old format ``.csync_journal.db``,
as these files are reserved for journalling.
If a pattern selected using a checkbox in the `ignoredFilesEditor-label` (or if
a line in the exclude file starts with the character ``]`` directly followed by

View file

@ -278,7 +278,6 @@ void selectiveSyncFixup(OCC::SyncJournalDb *journal, const QStringList &newList)
}
}
int main(int argc, char **argv) {
QCoreApplication app(argc, argv);
@ -374,11 +373,20 @@ int main(int argc, char **argv) {
QByteArray remUrl = options.target_url.toUtf8();
// Find the folder and the original owncloud url
QStringList splitted = url.path().split(account->davPath());
QStringList splitted = url.path().split("/" + account->davPath());
url.setPath(splitted.value(0));
url.setScheme(url.scheme().replace("owncloud", "http"));
QString folder = splitted.value(1);
QUrl credentialFreeUrl = url;
credentialFreeUrl.setUserName(QString());
credentialFreeUrl.setPassword(QString());
// Remote folders typically start with a / and don't end with one
QString folder = "/" + splitted.value(1);
if (folder.endsWith("/") && folder != "/") {
folder.chop(1);
}
SimpleSslErrorHandler *sslErrorHandler = new SimpleSslErrorHandler;
@ -461,7 +469,9 @@ restart_sync:
}
Cmd cmd;
SyncJournalDb db(options.source_dir);
QString dbPath = options.source_dir + SyncJournalDb::makeDbName(credentialFreeUrl, folder, user);
SyncJournalDb db(dbPath);
if (!selectiveSyncList.empty()) {
selectiveSyncFixup(&db, selectiveSyncList);
}

View file

@ -295,7 +295,8 @@ void AccountSettings::slotFolderWizardAccepted()
FolderDefinition definition;
definition.localPath = FolderDefinition::prepareLocalPath(
folderWizard->field(QLatin1String("sourceFolder")).toString());
definition.targetPath = folderWizard->property("targetPath").toString();
definition.targetPath = FolderDefinition::prepareTargetPath(
folderWizard->property("targetPath").toString());
{
QDir dir(definition.localPath);

View file

@ -46,6 +46,7 @@
namespace OCC {
const char oldJournalPath[] = ".csync_journal.db";
Folder::Folder(const FolderDefinition& definition,
AccountState* accountState,
@ -60,8 +61,9 @@ Folder::Folder(const FolderDefinition& definition,
, _lastSyncDuration(0)
, _consecutiveFailingSyncs(0)
, _consecutiveFollowUpSyncs(0)
, _journal(definition.localPath)
, _journal(_definition.absoluteJournalPath())
, _fileLog(new SyncRunFileLog)
, _saveBackwardsCompatible(false)
{
qRegisterMetaType<SyncFileItemVector>("SyncFileItemVector");
qRegisterMetaType<SyncFileItem::Direction>("SyncFileItem::Direction");
@ -124,6 +126,7 @@ Folder::~Folder()
_engine.reset();
}
void Folder::checkLocalPath()
{
const QFileInfo fi(_definition.localPath);
@ -203,7 +206,7 @@ void Folder::setIgnoreHiddenFiles(bool ignore)
_definition.ignoreHiddenFiles = ignore;
}
QString Folder::cleanPath()
QString Folder::cleanPath() const
{
QString cleanedPath = QDir::cleanPath(_canonicalLocalPath);
@ -584,8 +587,33 @@ void Folder::slotThreadTreeWalkResult(const SyncFileItemVector& items)
void Folder::saveToSettings() const
{
// Remove first to make sure we don't get duplicates
removeFromSettings();
auto settings = _accountState->settings();
settings->beginGroup(QLatin1String("Folders"));
// The folder is saved to backwards-compatible "Folders"
// section only if it has the migrate flag set (i.e. was in
// there before) or if the folder is the only one for the
// given target path.
// This ensures that older clients will not read a configuration
// where two folders for different accounts point at the same
// local folders.
bool oneAccountOnly = true;
foreach (Folder* other, FolderMan::instance()->map()) {
if (other != this && other->cleanPath() == this->cleanPath()) {
oneAccountOnly = false;
break;
}
}
bool compatible = _saveBackwardsCompatible || oneAccountOnly;
if (compatible) {
settings->beginGroup(QLatin1String("Folders"));
} else {
settings->beginGroup(QLatin1String("Multifolders"));
}
FolderDefinition::save(*settings, _definition);
settings->sync();
@ -594,9 +622,12 @@ void Folder::saveToSettings() const
void Folder::removeFromSettings() const
{
auto settings = _accountState->settings();
auto settings = _accountState->settings();
settings->beginGroup(QLatin1String("Folders"));
settings->remove(FolderMan::escapeAlias(_definition.alias));
settings->endGroup();
settings->beginGroup(QLatin1String("Multifolders"));
settings->remove(FolderMan::escapeAlias(_definition.alias));
}
bool Folder::isFileExcludedAbsolute(const QString& fullPath) const
@ -628,12 +659,12 @@ void Folder::slotTerminateSync()
// local folder is synced to the same ownCloud.
void Folder::wipe()
{
QString stateDbFile = path()+QLatin1String(".csync_journal.db");
QString stateDbFile = _engine->journal()->databaseFilePath();
// Delete files that have been partially downloaded.
slotDiscardDownloadProgress();
//Unregister the socket API so it does not keep the .sync_journal file open
//Unregister the socket API so it does not keep the ._sync_journal file open
FolderMan::instance()->socketApi()->slotUnregisterPath(alias());
_journal.close(); // close the sync journal
@ -953,6 +984,11 @@ void Folder::scheduleThisFolderSoon()
}
}
void Folder::setSaveBackwardsCompatible(bool save)
{
_saveBackwardsCompatible = save;
}
void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool *cancel)
{
ConfigFile cfgFile;
@ -1005,6 +1041,7 @@ void FolderDefinition::save(QSettings& settings, const FolderDefinition& folder)
{
settings.beginGroup(FolderMan::escapeAlias(folder.alias));
settings.setValue(QLatin1String("localPath"), folder.localPath);
settings.setValue(QLatin1String("journalPath"), folder.journalPath);
settings.setValue(QLatin1String("targetPath"), folder.targetPath);
settings.setValue(QLatin1String("paused"), folder.paused);
settings.setValue(QLatin1String("ignoreHiddenFiles"), folder.ignoreHiddenFiles);
@ -1017,6 +1054,7 @@ bool FolderDefinition::load(QSettings& settings, const QString& alias,
settings.beginGroup(alias);
folder->alias = FolderMan::unescapeAlias(alias);
folder->localPath = settings.value(QLatin1String("localPath")).toString();
folder->journalPath = settings.value(QLatin1String("journalPath")).toString();
folder->targetPath = settings.value(QLatin1String("targetPath")).toString();
folder->paused = settings.value(QLatin1String("paused")).toBool();
folder->ignoreHiddenFiles = settings.value(QLatin1String("ignoreHiddenFiles"), QVariant(true)).toBool();
@ -1026,6 +1064,9 @@ bool FolderDefinition::load(QSettings& settings, const QString& alias,
// code we assum /, so clean it up now.
folder->localPath = prepareLocalPath(folder->localPath);
// Target paths also have a convention
folder->targetPath = prepareTargetPath(folder->targetPath);
return true;
}
@ -1038,5 +1079,29 @@ QString FolderDefinition::prepareLocalPath(const QString& path)
return p;
}
QString FolderDefinition::prepareTargetPath(const QString &path)
{
QString p = path;
if (p.endsWith(QLatin1Char('/'))) {
p.chop(1);
}
// Doing this second ensures the empty string or "/" come
// out as "/".
if (!p.startsWith(QLatin1Char('/'))) {
p.prepend(QLatin1Char('/'));
}
return p;
}
QString FolderDefinition::absoluteJournalPath() const
{
return QDir(localPath).filePath(journalPath);
}
QString FolderDefinition::defaultJournalPath(AccountPtr account)
{
return SyncJournalDb::makeDbName(account->url(), targetPath, account->credentials()->user());
}
} // namespace OCC

View file

@ -53,6 +53,8 @@ public:
QString alias;
/// path on local machine
QString localPath;
/// path to the journal, usually relative to localPath
QString journalPath;
/// path on remote
QString targetPath;
/// whether the folder is paused
@ -69,6 +71,15 @@ public:
/// Ensure / as separator and trailing /.
static QString prepareLocalPath(const QString& path);
/// Ensure starting / and no ending /.
static QString prepareTargetPath(const QString& path);
/// journalPath relative to localPath.
QString absoluteJournalPath() const;
/// Returns the relative journal path that's appropriate for this folder and account.
QString defaultJournalPath(AccountPtr account);
};
/**
@ -111,7 +122,7 @@ public:
/**
* wrapper for QDir::cleanPath("Z:\\"), which returns "Z:\\", but we need "Z:" instead
*/
QString cleanPath();
QString cleanPath() const;
/**
* remote folder path
@ -206,6 +217,12 @@ public:
*/
void scheduleThisFolderSoon();
/**
* Migration: When this flag is true, this folder will save to
* the backwards-compatible 'Folders' section in the config file.
*/
void setSaveBackwardsCompatible(bool save);
signals:
void syncStateChange();
void syncStarted();
@ -334,6 +351,16 @@ private:
QScopedPointer<SyncRunFileLog> _fileLog;
QTimer _scheduleSelfTimer;
/**
* When the same local path is synced to multiple accounts, only one
* of them can be stored in the settings in a way that's compatible
* with old clients that don't support it. This flag marks folders
* that shall be written in a backwards-compatible way, by being set
* on the *first* Folder instance that was configured for each local
* path.
*/
bool _saveBackwardsCompatible;
};
}

View file

@ -209,18 +209,16 @@ int FolderMan::setupFolders()
continue;
}
settings->beginGroup(id);
settings->beginGroup(QLatin1String("Folders"));
foreach (const auto& folderAlias, settings->childGroups()) {
FolderDefinition folderDefinition;
if (FolderDefinition::load(*settings, folderAlias, &folderDefinition)) {
Folder* f = addFolderInternal(std::move(folderDefinition), account.data());
if (f) {
scheduleFolder(f);
emit folderSyncStateChange(f);
}
}
}
settings->endGroup(); // Folders
setupFoldersHelper(*settings, account, true);
settings->endGroup();
// See Folder::saveToSettings for details about why this exists.
settings->beginGroup(QLatin1String("Multifolders"));
setupFoldersHelper(*settings, account, false);
settings->endGroup();
settings->endGroup(); // <account>
}
@ -229,6 +227,34 @@ int FolderMan::setupFolders()
return _folderMap.size();
}
void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account, bool backwardsCompatible)
{
foreach (const auto& folderAlias, settings.childGroups()) {
FolderDefinition folderDefinition;
if (FolderDefinition::load(settings, folderAlias, &folderDefinition)) {
// Migration: Old settings don't have journalPath
if (folderDefinition.journalPath.isEmpty()) {
folderDefinition.journalPath = folderDefinition.defaultJournalPath(account->account());
}
folderDefinition.defaultJournalPath(account->account());
// Migration: If an old db is found, move it to the new name.
if (backwardsCompatible) {
SyncJournalDb::maybeMigrateDb(folderDefinition.localPath, folderDefinition.absoluteJournalPath());
}
Folder* f = addFolderInternal(std::move(folderDefinition), account.data());
if (f) {
// Migration: Mark folders that shall be saved in a backwards-compatible way
if (backwardsCompatible) {
f->setSaveBackwardsCompatible(true);
}
scheduleFolder(f);
emit folderSyncStateChange(f);
}
}
}
}
int FolderMan::setupFoldersMigration()
{
ConfigFile cfg;
@ -259,18 +285,16 @@ int FolderMan::setupFoldersMigration()
return _folderMap.size();
}
bool FolderMan::ensureJournalGone(const QString &localPath)
bool FolderMan::ensureJournalGone( const QString& journalDbFile )
{
// FIXME move this to UI, not libowncloudsync
// remove old .csync_journal file
QString stateDbFile = localPath+QLatin1String("/.csync_journal.db");
while (QFile::exists(stateDbFile) && !QFile::remove(stateDbFile)) {
qDebug() << "Could not remove old db file at" << stateDbFile;
// remove the old journal file
while (QFile::exists(journalDbFile) && !QFile::remove(journalDbFile)) {
qDebug() << "Could not remove old db file at" << journalDbFile;
int ret = QMessageBox::warning(0, tr("Could not reset folder state"),
tr("An old sync journal '%1' was found, "
"but could not be removed. Please make sure "
"that no application is currently using it.")
.arg(QDir::fromNativeSeparators(QDir::cleanPath(stateDbFile))),
.arg(QDir::fromNativeSeparators(QDir::cleanPath(journalDbFile))),
QMessageBox::Retry|QMessageBox::Abort);
if (ret == QMessageBox::Abort) {
return false;
@ -853,11 +877,27 @@ void FolderMan::slotFolderSyncFinished( const SyncResult& )
Folder* FolderMan::addFolder(AccountState* accountState, const FolderDefinition& folderDefinition)
{
if (!ensureJournalGone(folderDefinition.localPath)) {
// Choose a db filename
auto definition = folderDefinition;
definition.journalPath = definition.defaultJournalPath(accountState->account());
if (!ensureJournalGone(definition.absoluteJournalPath())) {
return 0;
}
auto folder = addFolderInternal(folderDefinition, accountState);
auto folder = addFolderInternal(definition, accountState);
// Migration: The first account that's configured for a local folder shall
// be saved in a backwards-compatible way.
bool oneAccountOnly = true;
foreach (Folder* other, FolderMan::instance()->map()) {
if (other != folder && other->cleanPath() == folder->cleanPath()) {
oneAccountOnly = false;
break;
}
}
folder->setSaveBackwardsCompatible(oneAccountOnly);
if(folder) {
folder->saveToSettings();
emit folderSyncStateChange(folder);
@ -866,7 +906,8 @@ Folder* FolderMan::addFolder(AccountState* accountState, const FolderDefinition&
return folder;
}
Folder* FolderMan::addFolderInternal(FolderDefinition folderDefinition, AccountState* accountState)
Folder* FolderMan::addFolderInternal(FolderDefinition folderDefinition,
AccountState* accountState)
{
auto alias = folderDefinition.alias;
int count = 0;
@ -1229,17 +1270,16 @@ QString FolderMan::statusToString( SyncResult syncStatus, bool paused ) const
return folderMessage;
}
QString FolderMan::checkPathValidityForNewFolder(const QString& path, bool forNewDirectory)
QString FolderMan::checkPathValidityForNewFolder(const QString& path, const QUrl &serverUrl, bool forNewDirectory)
{
if (path.isEmpty()) {
return tr("No valid folder selected!");
}
QFileInfo selFile( path );
QString userInput = selFile.canonicalFilePath();
if (!selFile.exists()) {
return checkPathValidityForNewFolder(selFile.dir().path(), true);
return checkPathValidityForNewFolder(selFile.dir().path(), serverUrl, true);
}
if( !selFile.isDir() ) {
@ -1251,6 +1291,10 @@ QString FolderMan::checkPathValidityForNewFolder(const QString& path, bool forNe
}
// check if the local directory isn't used yet in another ownCloud sync
Qt::CaseSensitivity cs = Qt::CaseSensitive;
if( Utility::fsCasePreserving() ) {
cs = Qt::CaseInsensitive;
}
for (auto i = _folderMap.constBegin(); i != _folderMap.constEnd(); ++i ) {
Folder *f = static_cast<Folder*>(i.value());
@ -1258,39 +1302,60 @@ QString FolderMan::checkPathValidityForNewFolder(const QString& path, bool forNe
if( folderDir.isEmpty() ) {
continue;
}
if( ! folderDir.endsWith(QLatin1Char('/')) ) folderDir.append(QLatin1Char('/'));
if( ! folderDir.endsWith(QLatin1Char('/'), cs) ) folderDir.append(QLatin1Char('/'));
if (QDir::cleanPath(f->path()) == QDir::cleanPath(userInput)
&& QDir::cleanPath(QDir(f->path()).canonicalPath()) == QDir(userInput).canonicalPath()) {
return tr("The local folder %1 is already used in a folder sync connection. "
"Please pick another one!")
.arg(QDir::toNativeSeparators(userInput));
}
if (!forNewDirectory && QDir::cleanPath(folderDir).startsWith(QDir::cleanPath(userInput)+'/')) {
const QString folderDirClean = QDir::cleanPath(folderDir)+'/';
const QString userDirClean = QDir::cleanPath(path)+'/';
// folderDir follows sym links, path not.
bool differentPathes = !Utility::fileNamesEqual(QDir::cleanPath(folderDir), QDir::cleanPath(path));
if (!forNewDirectory && differentPathes && folderDirClean.startsWith(userDirClean,cs)) {
return tr("The local folder %1 already contains a folder used in a folder sync connection. "
"Please pick another one!")
.arg(QDir::toNativeSeparators(userInput));
.arg(QDir::toNativeSeparators(path));
}
QString absCleanUserFolder = QDir::cleanPath(QDir(userInput).canonicalPath())+'/';
if (!forNewDirectory && QDir::cleanPath(folderDir).startsWith(absCleanUserFolder) ) {
return tr("The local folder %1 is a symbolic link. "
"The link target already contains a folder used in a folder sync connection. "
"Please pick another one!")
.arg(QDir::toNativeSeparators(userInput));
}
// QDir::cleanPath keeps links
// canonicalPath() remove symlinks and uses the symlink targets.
QString absCleanUserFolder = QDir::cleanPath(QDir(path).canonicalPath())+'/';
if (QDir::cleanPath(QString(userInput)).startsWith( QDir::cleanPath(folderDir)+'/')) {
if ( (forNewDirectory || differentPathes) && userDirClean.startsWith( folderDirClean, cs )) {
return tr("The local folder %1 is already contained in a folder used in a folder sync connection. "
"Please pick another one!")
.arg(QDir::toNativeSeparators(userInput));
.arg(QDir::toNativeSeparators(path));
}
if (absCleanUserFolder.startsWith( QDir::cleanPath(folderDir)+'/')) {
// both follow symlinks.
bool cleanUserEqualsCleanFolder = Utility::fileNamesEqual(absCleanUserFolder, folderDirClean );
if (differentPathes && absCleanUserFolder.startsWith( folderDirClean, cs ) &&
! cleanUserEqualsCleanFolder ) {
return tr("The local folder %1 is a symbolic link. "
"The link target is already contained in a folder used in a folder sync connection. "
"Please pick another one!")
.arg(QDir::toNativeSeparators(userInput));
.arg(QDir::toNativeSeparators(path));
}
if (differentPathes && folderDirClean.startsWith(absCleanUserFolder, cs) &&
!cleanUserEqualsCleanFolder && !forNewDirectory ) {
return tr("The local folder %1 contains a symbolic link. "
"The link target contains an already synced folder "
"Please pick another one!")
.arg(QDir::toNativeSeparators(path));
}
// if both pathes are equal, the server url needs to be different
// otherwise it would mean that a new connection from the same local folder
// to the same account is added which is not wanted. The account must differ.
if( serverUrl.isValid() && Utility::fileNamesEqual(absCleanUserFolder,folderDir ) ) {
QUrl folderUrl = f->accountState()->account()->url();
QString user = f->accountState()->account()->credentials()->user();
folderUrl.setUserName(user);
if( serverUrl == folderUrl ) {
return tr("There is already a sync from the server to this local folder. "
"Please pick another local folder!");
}
}
}

View file

@ -97,11 +97,11 @@ public:
Folder* setupFolderFromOldConfigFile(const QString &, AccountState *account );
/**
* Ensures that a given directory does not contain a .csync_journal.
* Ensures that a given directory does not contain a sync journal file.
*
* @returns false if the journal could not be removed, true otherwise.
*/
static bool ensureJournalGone(const QString &path);
static bool ensureJournalGone(const QString& journalDbFile);
/** Creates a new and empty local directory. */
bool startFromScratch( const QString& );
@ -128,7 +128,7 @@ public:
*
* @returns an empty string if it is allowed, or an error if it is not allowed
*/
QString checkPathValidityForNewFolder(const QString &path, bool forNewDirectory = false);
QString checkPathValidityForNewFolder(const QString &path, const QUrl& serverUrl = QUrl(), bool forNewDirectory = false);
/**
* While ignoring hidden files can theoretically be switched per folder,
@ -261,7 +261,8 @@ private:
/** Adds a new folder, does not add it to the account settings and
* does not set an account on the new folder.
*/
Folder* addFolderInternal(FolderDefinition folderDefinition, AccountState* accountState);
Folder* addFolderInternal(FolderDefinition folderDefinition,
AccountState* accountState);
/* unloads a folder object, does not delete it */
void unloadFolder( Folder * );
@ -277,6 +278,8 @@ private:
// restarts the application (Linux only)
void restartApplication();
void setupFoldersHelper(QSettings& settings, AccountStatePtr account, bool backwardsCompatible);
QSet<Folder*> _disabledFolders;
Folder::Map _folderMap;
QString _folderConfigPath;

View file

@ -169,7 +169,8 @@ void FolderWatcherPrivate::slotReceivedNotification(int fd)
if (event->len > 0 && event->wd > -1) {
QByteArray fileName(event->name);
// qDebug() << Q_FUNC_INFO << event->name;
if (fileName.startsWith(".csync_journal.db") ||
if (fileName.startsWith("._sync_") ||
fileName.startsWith(".csync_journal.db") ||
fileName.startsWith(".owncloudsync.log")) {
// qDebug() << "ignore journal";
} else {

View file

@ -56,8 +56,9 @@ QString FormatWarningsWizardPage::formatWarnings(const QStringList &warnings) co
return ret;
}
FolderWizardLocalPath::FolderWizardLocalPath()
: FormatWarningsWizardPage()
FolderWizardLocalPath::FolderWizardLocalPath(const AccountPtr& account)
: FormatWarningsWizardPage(),
_account(account)
{
_ui.setupUi(this);
registerField(QLatin1String("sourceFolder*"), _ui.localFolderLineEdit);
@ -89,8 +90,13 @@ void FolderWizardLocalPath::cleanupPage()
bool FolderWizardLocalPath::isComplete() const
{
QUrl serverUrl = _account->url();
serverUrl.setUserName( _account->credentials()->user() );
QString errorStr = FolderMan::instance()->checkPathValidityForNewFolder(
QDir::fromNativeSeparators(_ui.localFolderLineEdit->text()));
QDir::fromNativeSeparators(_ui.localFolderLineEdit->text()), serverUrl);
bool isOk = errorStr.isEmpty();
QStringList warnStrings;
@ -133,7 +139,7 @@ void FolderWizardLocalPath::slotChooseLocalFolder()
}
// =================================================================================
FolderWizardRemotePath::FolderWizardRemotePath(AccountPtr account)
FolderWizardRemotePath::FolderWizardRemotePath(const AccountPtr& account)
: FormatWarningsWizardPage()
,_warnWasVisible(false)
,_account(account)
@ -473,7 +479,7 @@ void FolderWizardRemotePath::showWarn( const QString& msg ) const
// ====================================================================================
FolderWizardSelectiveSync::FolderWizardSelectiveSync(AccountPtr account)
FolderWizardSelectiveSync::FolderWizardSelectiveSync(const AccountPtr& account)
{
QVBoxLayout *layout = new QVBoxLayout(this);
_treeView = new SelectiveSyncTreeView(account, this);
@ -527,7 +533,7 @@ void FolderWizardSelectiveSync::cleanupPage()
FolderWizard::FolderWizard(AccountPtr account, QWidget *parent)
: QWizard(parent),
_folderWizardSourcePage(new FolderWizardLocalPath),
_folderWizardSourcePage(new FolderWizardLocalPath(account)),
_folderWizardTargetPage(0),
_folderWizardSelectiveSyncPage(new FolderWizardSelectiveSync(account))
{

View file

@ -49,7 +49,7 @@ class FolderWizardLocalPath : public FormatWarningsWizardPage
{
Q_OBJECT
public:
FolderWizardLocalPath();
explicit FolderWizardLocalPath(const AccountPtr& account);
~FolderWizardLocalPath();
virtual bool isComplete() const Q_DECL_OVERRIDE;
@ -63,6 +63,7 @@ protected slots:
private:
Ui_FolderWizardSourcePage _ui;
Folder::Map _folderMap;
AccountPtr _account;
};
@ -75,7 +76,7 @@ class FolderWizardRemotePath : public FormatWarningsWizardPage
{
Q_OBJECT
public:
explicit FolderWizardRemotePath(AccountPtr account);
explicit FolderWizardRemotePath(const AccountPtr& account);
~FolderWizardRemotePath();
virtual bool isComplete() const Q_DECL_OVERRIDE;
@ -117,7 +118,7 @@ class FolderWizardSelectiveSync : public QWizardPage
{
Q_OBJECT
public:
explicit FolderWizardSelectiveSync(AccountPtr account);
explicit FolderWizardSelectiveSync(const AccountPtr& account);
~FolderWizardSelectiveSync();
virtual bool validatePage() Q_DECL_OVERRIDE;

View file

@ -521,7 +521,7 @@ void OwncloudSetupWizard::slotAssistantFinished( int result )
qDebug() << "Adding folder definition for" << localFolder << _remoteFolder;
FolderDefinition folderDefinition;
folderDefinition.localPath = localFolder;
folderDefinition.targetPath = _remoteFolder;
folderDefinition.targetPath = FolderDefinition::prepareTargetPath(_remoteFolder);
folderDefinition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles();
auto f = folderMan->addFolder(account, folderDefinition);

View file

@ -125,8 +125,13 @@ void OwncloudAdvancedSetupPage::initializePage()
void OwncloudAdvancedSetupPage::updateStatus()
{
const QString locFolder = localFolder();
const QString url = static_cast<OwncloudWizard *>(wizard())->ocUrl();
const QString user = static_cast<OwncloudWizard *>(wizard())->getCredentials()->user();
QUrl serverUrl(url);
serverUrl.setUserName(user);
// check if the local folder exists. If so, and if its not empty, show a warning.
QString errorStr = FolderMan::instance()->checkPathValidityForNewFolder(locFolder);
QString errorStr = FolderMan::instance()->checkPathValidityForNewFolder(locFolder, serverUrl);
_localFolderValid = errorStr.isEmpty();
QString t;

View file

@ -177,7 +177,7 @@ void PropagateRemoteMove::finalize()
record._contentChecksum = oldRecord._contentChecksum;
record._contentChecksumType = oldRecord._contentChecksumType;
if (record._fileSize != oldRecord._fileSize) {
qDebug() << "Warning: file sizes differ on server vs csync_journal: " << record._fileSize << oldRecord._fileSize;
qDebug() << "Warning: file sizes differ on server vs sync journal: " << record._fileSize << oldRecord._fileSize;
record._fileSize = oldRecord._fileSize; // server might have claimed different size, we take the old one from the DB
}
}

View file

@ -83,7 +83,10 @@ SyncEngine::SyncEngine(AccountPtr account, const QString& localPath,
Q_ASSERT(localPath.endsWith(QLatin1Char('/')));
csync_create(&_csync_ctx, localPath.toUtf8().data());
csync_init(_csync_ctx);
const QString dbFile = _journal->databaseFilePath();
csync_init(_csync_ctx, dbFile.toUtf8().data());
_excludedFiles.reset(new ExcludedFiles(&_csync_ctx->excludes));
_syncFileStatusTracker.reset(new SyncFileStatusTracker(this));

View file

@ -164,6 +164,8 @@ private slots:
private:
void handleSyncError(CSYNC *ctx, const char *state);
QString journalDbFilePath() const;
static int treewalkLocal( TREE_WALK_FILE*, void *);
static int treewalkRemote( TREE_WALK_FILE*, void *);
int treewalkFile( TREE_WALK_FILE*, bool );

View file

@ -16,6 +16,8 @@
#include <QStringList>
#include <QDebug>
#include <QElapsedTimer>
#include <QUrl>
#include "ownsql.h"
#include <inttypes.h>
@ -30,17 +32,63 @@
namespace OCC {
SyncJournalDb::SyncJournalDb(const QString& path, QObject *parent) :
QObject(parent), _transaction(0)
SyncJournalDb::SyncJournalDb(const QString& dbFilePath, QObject *parent) :
QObject(parent),
_dbFile(dbFilePath),
_transaction(0)
{
_dbFile = path;
if( !_dbFile.endsWith('/') ) {
_dbFile.append('/');
}
QString SyncJournalDb::makeDbName(const QUrl& remoteUrl,
const QString& remotePath,
const QString& user)
{
QString journalPath = QLatin1String("._sync_");
QString key = QString::fromUtf8("%1@%2:%3").arg(
user,
remoteUrl.toString(),
remotePath);
QByteArray ba = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Md5);
journalPath.append( ba.left(6).toHex() );
journalPath.append(".db");
return journalPath;
}
bool SyncJournalDb::maybeMigrateDb(const QString& localPath, const QString& absoluteJournalPath)
{
const QString oldDbName = localPath + QLatin1String(".csync_journal.db");
if( !FileSystem::fileExists(oldDbName) ) {
return true;
}
_dbFile.append(".csync_journal.db");
const QString newDbName = absoluteJournalPath;
// Whenever there is an old db file, migrate it to the new db path.
// This is done to make switching from older versions to newer versions
// work correctly even if the user had previously used a new version
// and therefore already has an (outdated) new-style db file.
QString error;
if( FileSystem::fileExists( newDbName ) ) {
if( !FileSystem::remove(newDbName, &error) ) {
qDebug() << "Database migration: Could not remove db file" << newDbName
<< "due to" << error;
return false;
}
}
if( !FileSystem::rename(oldDbName, newDbName, &error) ) {
qDebug() << "Database migration: could not rename " << oldDbName
<< "to" << newDbName << ":" << error;
return false;
}
qDebug() << "Journal successfully migrated from" << oldDbName << "to" << newDbName;
return true;
}
bool SyncJournalDb::exists()
@ -49,7 +97,7 @@ bool SyncJournalDb::exists()
return (!_dbFile.isEmpty() && QFile::exists(_dbFile));
}
QString SyncJournalDb::databaseFilePath()
QString SyncJournalDb::databaseFilePath() const
{
return _dbFile;
}
@ -135,8 +183,6 @@ bool SyncJournalDb::checkConnect()
return false;
}
bool isNewDb = !QFile::exists(_dbFile);
// The database file is created by this call (SQLITE_OPEN_CREATE)
if( !_db.openOrCreateReadWrite(_dbFile) ) {
QString error = _db.error();
@ -310,10 +356,9 @@ bool SyncJournalDb::checkConnect()
SqlQuery versionQuery("SELECT major, minor, patch FROM version;", _db);
if (!versionQuery.next()) {
// If there was no entry in the table, it means we are likely upgrading from 1.5
if (!isNewDb) {
qDebug() << Q_FUNC_INFO << "possibleUpgradeFromMirall_1_5 detected!";
forceRemoteDiscovery = true;
}
qDebug() << Q_FUNC_INFO << "possibleUpgradeFromMirall_1_5 detected!";
forceRemoteDiscovery = true;
createQuery.prepare("INSERT INTO version VALUES (?1, ?2, ?3, ?4);");
createQuery.bindValue(1, MIRALL_VERSION_MAJOR);
createQuery.bindValue(2, MIRALL_VERSION_MINOR);

View file

@ -37,9 +37,17 @@ class OWNCLOUDSYNC_EXPORT SyncJournalDb : public QObject
{
Q_OBJECT
public:
explicit SyncJournalDb(const QString& path, QObject *parent = 0);
explicit SyncJournalDb(const QString& dbFilePath, QObject *parent = 0);
virtual ~SyncJournalDb();
/// Create a journal path for a specific configuration
static QString makeDbName(const QUrl& remoteUrl,
const QString& remotePath,
const QString& user);
/// Migrate a csync_journal to the new path, if necessary. Returns false on error
static bool maybeMigrateDb(const QString& localPath, const QString& absoluteJournalPath);
// to verify that the record could be queried successfully check
// with SyncJournalFileRecord::isValid()
SyncJournalFileRecord getFileRecord(const QString& filename);
@ -58,7 +66,8 @@ public:
bool exists();
void walCheckpoint();
QString databaseFilePath();
QString databaseFilePath() const;
static qint64 getPHash(const QString& );
void updateErrorBlacklistEntry( const SyncJournalErrorBlacklistRecord& item );

View file

@ -277,12 +277,26 @@ bool Utility::fsCasePreserving()
if( isWindows() || isMac() ) {
re = true;
} else {
static bool isTest = qgetenv("OWNCLOUD_TEST_CASE_PRESERVING").toInt();
bool isTest = qgetenv("OWNCLOUD_TEST_CASE_PRESERVING").toInt();
re = isTest;
}
return re;
}
bool Utility::fileNamesEqual( const QString& fn1, const QString& fn2)
{
const QDir fd1(fn1);
const QDir fd2(fn2);
// Attention: If the path does not exist, canonicalPath returns ""
// ONLY use this function with existing pathes.
const QString a = fd1.canonicalPath();
const QString b = fd2.canonicalPath();
bool re = !a.isEmpty() && QString::compare( a, b,
fsCasePreserving() ? Qt::CaseInsensitive : Qt::CaseSensitive) == 0;
return re;
}
QDateTime Utility::qDateTimeFromTime_t(qint64 t)
{
return QDateTime::fromMSecsSinceEpoch(t * 1000);

View file

@ -105,6 +105,11 @@ namespace Utility
// if false, the two cases are two different files.
OWNCLOUDSYNC_EXPORT bool fsCasePreserving();
// Check if two pathes that MUST exist are equal. This function
// uses QDir::canonicalPath() to judge and cares for the systems
// case sensitivity.
OWNCLOUDSYNC_EXPORT bool fileNamesEqual( const QString& fn1, const QString& fn2);
// Call the given command with the switch --version and rerun the first line
// of the output.
// If command is empty, the function calls the running application which, on

View file

@ -737,7 +737,7 @@ public:
_account->setUrl(QUrl(QStringLiteral("http://admin:admin@localhost/owncloud")));
_account->setCredentials(new FakeCredentials{_fakeQnam});
_journalDb.reset(new OCC::SyncJournalDb(localPath()));
_journalDb.reset(new OCC::SyncJournalDb(localPath() + "._sync_test.db"));
_syncEngine.reset(new OCC::SyncEngine(_account, localPath(), "", _journalDb.get()));
// A new folder will update the local file state database on first sync.

View file

@ -16,9 +16,20 @@
#include "account.h"
#include "accountstate.h"
#include "configfile.h"
#include "creds/httpcredentials.h"
using namespace OCC;
class HttpCredentialsTest : public HttpCredentials {
public:
HttpCredentialsTest(const QString& user, const QString& password)
: HttpCredentials(user, password, "", "")
{}
void askFromUser() Q_DECL_OVERRIDE {
}
};
static FolderDefinition folderDefinition(const QString &path) {
FolderDefinition d;
@ -53,7 +64,13 @@ private slots:
f.write("hello");
}
AccountStatePtr newAccountState(new AccountState(Account::create()));
AccountPtr account = Account::create();
QUrl url("http://example.de");
HttpCredentialsTest *cred = new HttpCredentialsTest("testuser", "secret");
account->setCredentials(cred);
account->setUrl( url );
AccountStatePtr newAccountState(new AccountState(account));
FolderMan *folderman = FolderMan::instance();
QCOMPARE(folderman, &_fm);
QVERIFY(folderman->addFolder(newAccountState.data(), folderDefinition(dir.path() + "/sub/ownCloud1")));
@ -61,6 +78,8 @@ private slots:
// those should be allowed
// QString FolderMan::checkPathValidityForNewFolder(const QString& path, const QUrl &serverUrl, bool forNewDirectory)
QCOMPARE(folderman->checkPathValidityForNewFolder(dir.path() + "/sub/free"), QString());
QCOMPARE(folderman->checkPathValidityForNewFolder(dir.path() + "/free2/"), QString());
// Not an existing directory -> Ok
@ -71,11 +90,21 @@ private slots:
// A file -> Error
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/sub/file.txt").isNull());
// There are folders configured in those folders: -> ERROR
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/sub/ownCloud1").isNull());
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/ownCloud2/").isNull());
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/sub").isNull());
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/sub/").isNull());
// There are folders configured in those folders, url needs to be taken into account: -> ERROR
QUrl url2(url);
const QString user = account->credentials()->user();
url2.setUserName(user);
// The following both fail because they refer to the same account (user and url)
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/sub/ownCloud1", url2).isNull());
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/ownCloud2/", url2).isNull());
// Now it will work because the account is different
QUrl url3("http://anotherexample.org");
url3.setUserName("dummy");
QCOMPARE(folderman->checkPathValidityForNewFolder(dir.path() + "/sub/ownCloud1", url3), QString());
QCOMPARE(folderman->checkPathValidityForNewFolder(dir.path() + "/ownCloud2/", url3), QString());
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path()).isNull());
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/sub/ownCloud1/folder").isNull());
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/sub/ownCloud1/folder/f").isNull());
@ -93,7 +122,12 @@ private slots:
// Not Ok
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/link2").isNull());
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/link3").isNull());
// link 3 points to an existing sync folder. To make it fail, the account must be the same
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/link3", url2).isNull());
// while with a different account, this is fine
QCOMPARE(folderman->checkPathValidityForNewFolder(dir.path() + "/link3", url3), QString());
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/link4").isNull());
QVERIFY(!folderman->checkPathValidityForNewFolder(dir.path() + "/link3/folder").isNull());

View file

@ -13,18 +13,13 @@
using namespace OCC;
namespace {
const char testdbC[] = "/tmp";
}
class TestSyncJournalDB : public QObject
{
Q_OBJECT
public:
TestSyncJournalDB()
: _db(testdbC)
: _db("/tmp/csync-test.db")
{
}
@ -41,6 +36,8 @@ private slots:
void cleanupTestCase()
{
const QString file = _db.databaseFilePath();
QFile::remove(file);
}
void testFileRecord()

View file

@ -5,6 +5,9 @@
*/
#include <QtTest>
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
#include <QTemporaryDir>
#endif
#include "utility.h"
@ -150,6 +153,52 @@ private slots:
s = timeAgoInWords(earlyTS, laterTS );
QCOMPARE(s, QLatin1String("Less than a minute ago"));
}
void testFsCasePreserving()
{
qputenv("OWNCLOUD_TEST_CASE_PRESERVING", "1");
QVERIFY(fsCasePreserving());
qputenv("OWNCLOUD_TEST_CASE_PRESERVING", "0");
QVERIFY(! fsCasePreserving());
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
qunsetenv("OWNCLOUD_TEST_CASE_PRESERVING");
QVERIFY(isMac() || isWindows() ? fsCasePreserving() : ! fsCasePreserving());
#endif
}
void testFileNamesEqual()
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
qDebug() << "*** checking fileNamesEqual function";
QTemporaryDir dir;
QVERIFY(dir.isValid());
QDir dir2(dir.path());
QVERIFY(dir2.mkpath("test"));
if( !fsCasePreserving() ) {
QVERIFY(dir2.mkpath("TEST"));
}
QVERIFY(dir2.mkpath("test/TESTI"));
QVERIFY(dir2.mkpath("TESTI"));
QString a = dir.path();
QString b = dir.path();
QVERIFY(fileNamesEqual(a, b));
QVERIFY(fileNamesEqual(a+"/test", b+"/test")); // both exist
QVERIFY(fileNamesEqual(a+"/test/TESTI", b+"/test/../test/TESTI")); // both exist
qputenv("OWNCLOUD_TEST_CASE_PRESERVING", "1");
QVERIFY(fileNamesEqual(a+"/test", b+"/TEST")); // both exist
QVERIFY(!fileNamesEqual(a+"/test", b+"/test/TESTI")); // both are different
dir.remove();
qunsetenv("OWNCLOUD_TEST_CASE_PRESERVING");
#endif
}
};
QTEST_APPLESS_MAIN(TestUtility)