Merge remote-tracking branch 'origin/1.7'

Conflicts:
	src/CMakeLists.txt
	src/cmd/cmd.cpp
	src/gui/socketapi.h
	src/libsync/syncengine.h
	test/CMakeLists.txt
This commit is contained in:
Christian Kamm 2014-10-22 10:20:38 +02:00
commit 7ae0338f5c
132 changed files with 168481 additions and 11619 deletions

2
.gitattributes vendored
View file

@ -2,4 +2,4 @@
.gitignore export-ignore .gitignore export-ignore
.gitattributes export-ignore .gitattributes export-ignore
.commit-template export-ignore .commit-template export-ignore
/binary export-ignore binary/ export-ignore

View file

@ -122,6 +122,12 @@ find_package(Sphinx)
find_package(PdfLatex) find_package(PdfLatex)
find_package(SQLite3 3.8.0 REQUIRED)
# On some OS, we want to use our own, not the system sqlite
if (USE_OUR_OWN_SQLITE3)
include_directories(BEFORE ${SQLITE3_INCLUDE_DIR})
endif()
configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)

View file

@ -4,7 +4,7 @@ set( MIRALL_VERSION_PATCH 0 )
set( MIRALL_SOVERSION 0 ) set( MIRALL_SOVERSION 0 )
if ( NOT DEFINED MIRALL_VERSION_SUFFIX ) if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
set( MIRALL_VERSION_SUFFIX "beta1") #e.g. beta1, beta2, rc1 set( MIRALL_VERSION_SUFFIX "beta4") #e.g. beta1, beta2, rc1
endif( NOT DEFINED MIRALL_VERSION_SUFFIX ) endif( NOT DEFINED MIRALL_VERSION_SUFFIX )
if( NOT DEFINED MIRALL_VERSION_BUILD ) if( NOT DEFINED MIRALL_VERSION_BUILD )

View file

@ -23,7 +23,7 @@ identity="$3"
prjfile=$build_path/admin/osx/macosx.pkgproj prjfile=$build_path/admin/osx/macosx.pkgproj
# The name of the installer package # The name of the installer package
installer="ownCloud-@MIRALL_VERSION_FULL@@MIRALL_VERSION_SUFFIX@" installer="@APPLICATION_NAME@-@MIRALL_VERSION_FULL@@MIRALL_VERSION_SUFFIX@"
installer_file="$installer.pkg" installer_file="$installer.pkg"
installer_file_tar="$installer.pkg.tar" installer_file_tar="$installer.pkg.tar"
installer_file_tar_bz2="$installer.pkg.tar.bz2" installer_file_tar_bz2="$installer.pkg.tar.bz2"
@ -62,9 +62,9 @@ fi
# Sparkle wants a tbz, it cannot install raw pkg # Sparkle wants a tbz, it cannot install raw pkg
cd $install_path cd $install_path
tar cf $installer_file_tar $installer_file tar cf "$installer_file_tar" "$installer_file"
bzip2 -9 $installer_file_tar bzip2 -9 "$installer_file_tar"
mv $installer_file_tar_bz2 $installer_file_tbz mv "$installer_file_tar_bz2" "$installer_file_tbz"
rc=$? rc=$?
if [ $rc == 0 ]; then if [ $rc == 0 ]; then
echo "Successfully created $installer_file" echo "Successfully created $installer_file"

334
admin/osx/macdeployqt.py Executable file
View file

@ -0,0 +1,334 @@
#!/usr/bin/python
# This file is part of ownCloud.
# It was inspired in large part by the macdeploy script in Clementine
# and Tomahawk
#
# ownCloud is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# ownCLoud is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ownCloud. If not, see <http://www.gnu.org/licenses/>.
import os
import re
import subprocess
import commands
import sys
from glob import glob
def QueryQMake(attrib):
return subprocess.check_output([qmake_path, '-query', attrib]).rstrip('\n')
FRAMEWORK_SEARCH_PATH=[
'/Library/Frameworks',
os.path.join(os.environ['HOME'], 'Library/Frameworks')
]
LIBRARY_SEARCH_PATH=['/usr/local/lib', '.']
QT_PLUGINS = [
'accessible/libqtaccessiblewidgets.dylib',
'sqldrivers/libqsqlite.dylib',
'platforms/libqcocoa.dylib',
'imageformats/libqgif.dylib',
'imageformats/libqico.dylib',
'imageformats/libqjpeg.dylib',
'imageformats/libqsvg.dylib',
'imageformats/libqmng.dylib',
]
QT_PLUGINS_SEARCH_PATH=[
# os.path.join(os.environ['QTDIR'], 'plugins'),
'/usr/local/Cellar/qt/5.2.1/plugins',
]
class Error(Exception):
pass
class CouldNotFindQtPluginErrorFindFrameworkError(Error):
pass
class InstallNameToolError(Error):
pass
class CouldNotFindQtPluginError(Error):
pass
class CouldNotFindScriptPluginError(Error):
pass
class CouldNotFindFrameworkError(Error):
pass
if len(sys.argv) < 3:
print 'Usage: %s <bundle.app> <path-to-qmake>' % sys.argv[0]
exit()
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
bundle_dir = sys.argv[1]
qmake_path = sys.argv[2]
bundle_name = os.path.basename(bundle_dir).split('.')[0]
commands = []
binary_dir = os.path.join(bundle_dir, 'Contents', 'MacOS')
frameworks_dir = os.path.join(bundle_dir, 'Contents', 'Frameworks')
commands.append(['mkdir', '-p', frameworks_dir])
resources_dir = os.path.join(bundle_dir, 'Contents', 'Resources')
commands.append(['mkdir', '-p', resources_dir])
plugins_dir = os.path.join(bundle_dir, 'Contents', 'PlugIns')
binaries = [i for i in glob(os.path.join(bundle_dir, 'Contents', 'MacOS', "*")) if is_exe(i)];
fixed_libraries = []
fixed_frameworks = []
def WriteQtConf():
print "Writing qt.conf..."
with open(os.path.join(resources_dir, 'qt.conf'), 'w') as f:
f.write("[Paths]\nPlugins = PlugIns\n");
f.close()
def GetBrokenLibraries(binary):
#print "Checking libs for binary: %s" % binary
output = subprocess.Popen(['otool', '-L', binary], stdout=subprocess.PIPE).communicate()[0]
broken_libs = {
'frameworks': [],
'libs': []}
for line in [x.split(' ')[0].lstrip() for x in output.split('\n')[1:]]:
#print "Checking line: %s" % line
if not line: # skip empty lines
continue
if os.path.basename(binary) == os.path.basename(line):
#print "mnope %s-%s" % (os.path.basename(binary), os.path.basename(line))
continue
if re.match(r'^\s*/System/', line):
continue # System framework
elif re.match(r'^\s*/usr/lib/', line):
#print "unix style system lib"
continue # unix style system library
elif re.match(r'Breakpad', line):
continue # Manually added by cmake.
elif re.match(r'^\s*@executable_path', line) or re.match(r'^\s*@loader_path', line):
# Potentially already fixed library
if '.framework' in line:
relative_path = os.path.join(*line.split('/')[3:])
if not os.path.exists(os.path.join(frameworks_dir, relative_path)):
broken_libs['frameworks'].append(relative_path)
else:
relative_path = os.path.join(*line.split('/')[1:])
#print "RELPATH %s %s" % (relative_path, os.path.join(binary_dir, relative_path))
if not os.path.exists(os.path.join(binary_dir, relative_path)):
broken_libs['libs'].append(relative_path)
elif re.search(r'\w+\.framework', line):
broken_libs['frameworks'].append(line)
else:
broken_libs['libs'].append(line)
return broken_libs
def FindFramework(path):
search_pathes = FRAMEWORK_SEARCH_PATH
search_pathes.insert(0, QueryQMake('QT_INSTALL_LIBS'))
for search_path in search_pathes:
abs_path = os.path.join(search_path, path)
if os.path.exists(abs_path):
return abs_path
raise CouldNotFindFrameworkError(path)
def FindLibrary(path):
if os.path.exists(path):
return path
search_pathes = LIBRARY_SEARCH_PATH
search_pathes.insert(0, QueryQMake('QT_INSTALL_LIBS'))
for search_path in search_pathes:
abs_path = os.path.join(search_path, path)
if os.path.exists(abs_path):
return abs_path
else: # try harder---look for lib name in library folders
newpath = os.path.join(search_path,os.path.basename(path))
if os.path.exists(newpath):
return newpath
return ""
#raise CouldNotFindFrameworkError(path)
def FixAllLibraries(broken_libs):
for framework in broken_libs['frameworks']:
FixFramework(framework)
for lib in broken_libs['libs']:
FixLibrary(lib)
def FixFramework(path):
if path in fixed_libraries:
return
else:
fixed_libraries.append(path)
abs_path = FindFramework(path)
broken_libs = GetBrokenLibraries(abs_path)
FixAllLibraries(broken_libs)
new_path = CopyFramework(abs_path)
id = os.sep.join(new_path.split(os.sep)[3:])
FixFrameworkId(new_path, id)
for framework in broken_libs['frameworks']:
FixFrameworkInstallPath(framework, new_path)
for library in broken_libs['libs']:
FixLibraryInstallPath(library, new_path)
def FixLibrary(path):
if path in fixed_libraries or FindSystemLibrary(os.path.basename(path)) is not None:
return
else:
fixed_libraries.append(path)
abs_path = FindLibrary(path)
if abs_path == "":
print "Could not resolve %s, not fixing!" % path
return
broken_libs = GetBrokenLibraries(abs_path)
FixAllLibraries(broken_libs)
new_path = CopyLibrary(abs_path)
FixLibraryId(new_path)
for framework in broken_libs['frameworks']:
FixFrameworkInstallPath(framework, new_path)
for library in broken_libs['libs']:
FixLibraryInstallPath(library, new_path)
def FixPlugin(abs_path, subdir):
broken_libs = GetBrokenLibraries(abs_path)
FixAllLibraries(broken_libs)
new_path = CopyPlugin(abs_path, subdir)
for framework in broken_libs['frameworks']:
FixFrameworkInstallPath(framework, new_path)
for library in broken_libs['libs']:
FixLibraryInstallPath(library, new_path)
def FixBinary(path):
broken_libs = GetBrokenLibraries(path)
FixAllLibraries(broken_libs)
for framework in broken_libs['frameworks']:
FixFrameworkInstallPath(framework, path)
for library in broken_libs['libs']:
FixLibraryInstallPath(library, path)
def CopyLibrary(path):
new_path = os.path.join(binary_dir, os.path.basename(path))
args = ['ditto', '--arch=x86_64', path, new_path]
commands.append(args)
args = ['chmod', 'u+w', new_path]
commands.append(args)
return new_path
def CopyPlugin(path, subdir):
new_path = os.path.join(plugins_dir, subdir, os.path.basename(path))
args = ['mkdir', '-p', os.path.dirname(new_path)]
commands.append(args)
args = ['ditto', '--arch=x86_64', path, new_path]
commands.append(args)
args = ['chmod', 'u+w', new_path]
commands.append(args)
return new_path
def CopyFramework(path):
parts = path.split(os.sep)
print "CopyFramework:", path
for i, part in enumerate(parts):
if re.match(r'\w+\.framework', part):
full_path = os.path.join(frameworks_dir, *parts[i:-1])
break
args = ['mkdir', '-p', full_path]
commands.append(args)
args = ['ditto', '--arch=x86_64', path, full_path]
commands.append(args)
args = ['chmod', 'u+w', os.path.join(full_path, parts[-1])]
commands.append(args)
info_plist = os.path.join(os.path.split(path)[0], '..', '..', 'Contents', 'Info.plist')
if os.path.exists(info_plist):
args = ['cp', '-r', info_plist, resources_dir]
commands.append(args)
return os.path.join(full_path, parts[-1])
def FixId(path, library_name):
id = '@executable_path/../Frameworks/%s' % library_name
args = ['install_name_tool', '-id', id, path]
commands.append(args)
def FixLibraryId(path):
library_name = os.path.basename(path)
FixId(path, library_name)
def FixFrameworkId(path, id):
FixId(path, id)
def FixInstallPath(library_path, library, new_path):
args = ['install_name_tool', '-change', library_path, new_path, library]
commands.append(args)
def FindSystemLibrary(library_name):
for path in ['/lib', '/usr/lib']:
full_path = os.path.join(path, library_name)
if os.path.exists(full_path):
return full_path
return None
def FixLibraryInstallPath(library_path, library):
system_library = FindSystemLibrary(os.path.basename(library_path))
if system_library is None:
new_path = '@executable_path/../MacOS/%s' % os.path.basename(library_path)
FixInstallPath(library_path, library, new_path)
else:
FixInstallPath(library_path, library, system_library)
def FixFrameworkInstallPath(library_path, library):
parts = library_path.split(os.sep)
for i, part in enumerate(parts):
if re.match(r'\w+\.framework', part):
full_path = os.path.join(*parts[i:])
break
new_path = '@executable_path/../Frameworks/%s' % full_path
FixInstallPath(library_path, library, new_path)
def FindQtPlugin(name):
search_path = QT_PLUGINS_SEARCH_PATH
search_path.insert(0, QueryQMake('QT_INSTALL_PLUGINS'))
for path in search_path:
if os.path.exists(path):
if os.path.exists(os.path.join(path, name)):
return os.path.join(path, name)
raise CouldNotFindQtPluginError(name)
for binary in binaries:
FixBinary(binary)
for plugin in QT_PLUGINS:
FixPlugin(FindQtPlugin(plugin), os.path.dirname(plugin))
if len(sys.argv) <= 2:
print 'Will run %d commands:' % len(commands)
for command in commands:
print ' '.join(command)
for command in commands:
p = subprocess.Popen(command)
os.waitpid(p.pid, 0)
WriteQtConf()

View file

@ -22,7 +22,7 @@
<key>GID</key> <key>GID</key>
<integer>80</integer> <integer>80</integer>
<key>PATH</key> <key>PATH</key>
<string>owncloud.app</string> <string>@APPLICATION_EXECUTABLE@.app</string>
<key>PATH_TYPE</key> <key>PATH_TYPE</key>
<integer>3</integer> <integer>3</integer>
<key>PERMISSIONS</key> <key>PERMISSIONS</key>
@ -1055,7 +1055,7 @@
<key>BACKGROUND_PATH</key> <key>BACKGROUND_PATH</key>
<dict> <dict>
<key>PATH</key> <key>PATH</key>
<string>./owncloud.app/Contents/Resources/owncloud_logo_blue.png</string> <string>./@APPLICATION_EXECUTABLE@.app/Contents/Resources/owncloud_logo_blue.png</string>
<key>PATH_TYPE</key> <key>PATH_TYPE</key>
<integer>3</integer> <integer>3</integer>
</dict> </dict>

View file

@ -5,7 +5,6 @@
src_app="$1" src_app="$1"
identity="$2" identity="$2"
QT_FMWKS=`basename ${TMP_APP}/Contents/Frameworks`/Qt*
QT_FMWK_VERSION="5" QT_FMWK_VERSION="5"
fix_frameworks() { fix_frameworks() {
@ -26,8 +25,8 @@ fix_frameworks() {
} }
fix_frameworks "$src_app" `qmake -query QT_INSTALL_LIBS` "$src_app"/Contents/Frameworks fix_frameworks "$src_app" `qmake -query QT_INSTALL_LIBS` "$src_app"/Contents/Frameworks
codesign -s "$identity" --force --verify --verbose --deep "$src_app" codesign -s "$identity" --force --verbose=4 --deep "$src_app"
# Just for our debug purposes: # Just for our debug purposes:
spctl -a -t exec -vv $src_app spctl -a -t exec -vv $src_app
codesign -dv $src_app codesign -dv $src_app

2
binary

@ -1 +1 @@
Subproject commit 82d72bc62d1f036a4d73a8e9375b52db6e0573b3 Subproject commit 18d9ac810b03866e1939d66723d01b544bbdde7e

View file

@ -14,7 +14,8 @@ if (${CMAKE_C_COMPILER_ID} MATCHES "(GNU|Clang)")
# add -Wconversion ? # add -Wconversion ?
# cannot be pedantic with sqlite3 directly linked # cannot be pedantic with sqlite3 directly linked
if (NOT CSYNC_STATIC_COMPILE_DIR) # FIXME Can we somehow not use those flags for sqlite3.* but use them for the rest of csync?
if (NOT USE_OUR_OWN_SQLITE3)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -pedantic -pedantic-errors") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -pedantic -pedantic-errors")
endif() endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wmissing-prototypes") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wmissing-prototypes")

View file

@ -50,8 +50,18 @@ if (SQLite3_FIND_VERSION AND _SQLITE3_VERSION)
set(SQLite3_VERSION _SQLITE3_VERSION) set(SQLite3_VERSION _SQLITE3_VERSION)
endif (SQLite3_FIND_VERSION AND _SQLITE3_VERSION) endif (SQLite3_FIND_VERSION AND _SQLITE3_VERSION)
include(FindPackageHandleStandardArgs) if (APPLE OR WIN32)
find_package_handle_standard_args(SQLite3 DEFAULT_MSG SQLITE3_LIBRARIES SQLITE3_INCLUDE_DIRS) set(USE_OUR_OWN_SQLITE3 TRUE)
set(SQLITE3_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/src/3rdparty/sqlite3)
set(SQLITE3_LIBRARIES "")
set(SQLITE3_SOURCE ${SQLITE3_INCLUDE_DIR}/sqlite3.c)
MESSAGE(STATUS "Using own sqlite3 from " ${SQLITE3_INCLUDE_DIR})
else()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(SQLite3 DEFAULT_MSG SQLITE3_LIBRARIES SQLITE3_INCLUDE_DIRS)
endif()
# show the SQLITE3_INCLUDE_DIRS and SQLITE3_LIBRARIES variables only in the advanced view # show the SQLITE3_INCLUDE_DIRS and SQLITE3_LIBRARIES variables only in the advanced view
mark_as_advanced(SQLITE3_INCLUDE_DIRS SQLITE3_LIBRARIES) mark_as_advanced(SQLITE3_INCLUDE_DIRS SQLITE3_LIBRARIES)

View file

@ -391,6 +391,7 @@ Section "${APPLICATION_NAME}" SEC_APPLICATION
; Make sure only to copy qt, not qt_help, etc ; Make sure only to copy qt, not qt_help, etc
File "${MING_SHARE}\qt5\translations\qt_??.qm" File "${MING_SHARE}\qt5\translations\qt_??.qm"
File "${MING_SHARE}\qt5\translations\qt_??_??.qm" File "${MING_SHARE}\qt5\translations\qt_??_??.qm"
File "${MING_SHARE}\qt5\translations\qtbase_*.qm"
File "${MING_SHARE}\qt5\translations\qtkeychain_*.qm" File "${MING_SHARE}\qt5\translations\qtkeychain_*.qm"
SetOutPath "$INSTDIR\platforms" SetOutPath "$INSTDIR\platforms"

View file

@ -153,7 +153,18 @@ if(NOT Qt5Core_FOUND)
include( ${QT_USE_FILE} ) include( ${QT_USE_FILE} )
endmacro() endmacro()
add_definitions("-DQ_DECL_OVERRIDE=override") if (CMAKE_COMPILER_IS_GNUCC)
execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion
OUTPUT_VARIABLE GCC_VERSION)
if (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7)
add_definitions("-DQ_DECL_OVERRIDE=override")
else()
add_definitions("-DQ_DECL_OVERRIDE=")
endif()
else() #clang or others
add_definitions("-DQ_DECL_OVERRIDE=override")
endif()
endif() endif()
if( Qt5Core_DIR ) if( Qt5Core_DIR )

View file

@ -4,13 +4,6 @@ add_subdirectory(std)
add_subdirectory(httpbf) add_subdirectory(httpbf)
# Statically include sqlite # Statically include sqlite
if (CSYNC_STATIC_COMPILE_DIR)
set(SQLITE3_INCLUDE_DIRS "")
set(SQLITE3_LIBRARIES "")
include_directories(${CSYNC_STATIC_COMPILE_DIR})
else (CSYNC_STATIC_COMPILE_DIR)
find_package(SQLite3 3.3.9 REQUIRED)
endif()
set(CSYNC_PUBLIC_INCLUDE_DIRS set(CSYNC_PUBLIC_INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
@ -86,8 +79,8 @@ set(csync_HDRS
) )
# Statically include sqlite # Statically include sqlite
if (CSYNC_STATIC_COMPILE_DIR) if (USE_OUR_OWN_SQLITE3)
list(APPEND csync_SRCS ${CSYNC_STATIC_COMPILE_DIR}/dictionary.c ${CSYNC_STATIC_COMPILE_DIR}/sqlite3.c) list(APPEND csync_SRCS ${SQLITE3_SOURCE})
endif() endif()
include_directories( include_directories(

View file

@ -254,6 +254,13 @@ int csync_update(CSYNC *ctx) {
csync_gettime(&finish); csync_gettime(&finish);
/* Finalize the sql precompiled statements after the update run since
* it runs in its own thread. Precompiled statements shoult not be shared
* across thread borders according to
* http://www.sqlite.org/cvstrac/wiki?p=MultiThreading
*/
csync_statedb_finalize_statements(ctx);
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG,
"Update detection for remote replica took %.2f seconds " "Update detection for remote replica took %.2f seconds "
"walking %zu files.", "walking %zu files.",

View file

@ -110,7 +110,7 @@ static int ssl_callback_by_neon(void *userdata, int failures,
} }
} }
DEBUG_WEBDAV("## VERIFY_SSL CERT: %d", ret ); DEBUG_WEBDAV("## VERIFY_SSL CERT: %d", ret );
return ret; return ret;
} }
/* /*
@ -761,6 +761,7 @@ int owncloud_commit(CSYNC* ctx) {
} }
ctx->owncloud_context->is_first_propfind = true; ctx->owncloud_context->is_first_propfind = true;
ctx->owncloud_context->dav_session.no_recursive_propfind = true;
/* DEBUG_WEBDAV( "********** vio_module_shutdown" ); */ /* DEBUG_WEBDAV( "********** vio_module_shutdown" ); */
ctx->owncloud_context->dav_session.ctx = 0; ctx->owncloud_context->dav_session.ctx = 0;

View file

@ -339,6 +339,8 @@ void fill_webdav_properties_into_resource(struct resource* newres, const ne_prop
file_id = ne_propset_value( set, &ls_props[4] ); file_id = ne_propset_value( set, &ls_props[4] );
directDownloadUrl = ne_propset_value( set, &ls_props[5] ); directDownloadUrl = ne_propset_value( set, &ls_props[5] );
directDownloadCookies = ne_propset_value( set, &ls_props[6] ); directDownloadCookies = ne_propset_value( set, &ls_props[6] );
// permission flags: Defined in https://github.com/owncloud/core/issues/8322
perm = ne_propset_value( set, &ls_props[7] ); perm = ne_propset_value( set, &ls_props[7] );
if( resourcetype && strncmp( resourcetype, "<DAV:collection>", 16 ) == 0) { if( resourcetype && strncmp( resourcetype, "<DAV:collection>", 16 ) == 0) {

View file

@ -100,6 +100,8 @@ struct csync_s {
sqlite3_stmt* by_hash_stmt; sqlite3_stmt* by_hash_stmt;
sqlite3_stmt* by_fileid_stmt; sqlite3_stmt* by_fileid_stmt;
sqlite3_stmt* by_inode_stmt; sqlite3_stmt* by_inode_stmt;
int lastReturnValue;
} statedb; } statedb;
struct { struct {
@ -146,8 +148,8 @@ struct csync_s {
struct csync_owncloud_ctx_s *owncloud_context; struct csync_owncloud_ctx_s *owncloud_context;
/* hooks for checking the white list */ /* hooks for checking the white list */
void *checkBlackListData; void *checkSelectiveSyncBlackListData;
int (*checkBlackListHook)(void*, const char*); int (*checkSelectiveSyncBlackListHook)(void*, const char*);
}; };

View file

@ -138,6 +138,11 @@ static int _csync_merge_algorithm_visitor(void *obj, void *data) {
/* Do not remove a directory that has ignored files */ /* Do not remove a directory that has ignored files */
break; break;
} }
if (cur->child_modified) {
/* re-create directory that has modified contents */
cur->instruction = CSYNC_INSTRUCTION_NEW;
break;
}
cur->instruction = CSYNC_INSTRUCTION_REMOVE; cur->instruction = CSYNC_INSTRUCTION_REMOVE;
break; break;
case CSYNC_INSTRUCTION_EVAL_RENAME: case CSYNC_INSTRUCTION_EVAL_RENAME:

View file

@ -50,6 +50,22 @@
#define BUF_SIZE 16 #define BUF_SIZE 16
#define sqlite_open(A, B) sqlite3_open_v2(A,B, SQLITE_OPEN_READONLY+SQLITE_OPEN_NOMUTEX, NULL)
#define SQLTM_TIME 150000
#define SQLTM_COUNT 10
#define SQLITE_BUSY_HANDLED(F) if(1) { \
int n = 0; \
do { rc = F ; \
if( (rc == SQLITE_BUSY) || (rc == SQLITE_LOCKED) ) { \
n++; \
usleep(SQLTM_TIME); \
} \
}while( (n < SQLTM_COUNT) && ((rc == SQLITE_BUSY) || (rc == SQLITE_LOCKED))); \
}
void csync_set_statedb_exists(CSYNC *ctx, int val) { void csync_set_statedb_exists(CSYNC *ctx, int val) {
ctx->statedb.exists = val; ctx->statedb.exists = val;
} }
@ -73,85 +89,12 @@ static int _csync_check_db_integrity(sqlite3 *db) {
c_strlist_destroy(result); c_strlist_destroy(result);
} }
return rc; if( sqlite3_threadsafe() == 0 ) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "* WARNING: SQLite module is not threadsafe!");
} rc = -1;
static int _csync_statedb_check(const char *statedb) {
int fd = -1, rc;
ssize_t r;
char buf[BUF_SIZE] = {0};
sqlite3 *db = NULL;
csync_stat_t sb;
mbchar_t *wstatedb = c_utf8_to_locale(statedb);
if (wstatedb == NULL) {
return -1;
}
/* check db version */
#ifdef _WIN32
_fmode = _O_BINARY;
#endif
fd = _topen(wstatedb, O_RDONLY);
if (fd >= 0) {
/* Check size. Size of zero is a valid database actually. */
rc = _tfstat(fd, &sb);
if (rc == 0) {
if (sb.st_size == 0) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Database size is zero byte!");
close(fd);
} else {
r = read(fd, (void *) buf, sizeof(buf) - 1);
close(fd);
if (r >= 0) {
buf[BUF_SIZE - 1] = '\0';
if (c_streq(buf, "SQLite format 3")) {
if (sqlite3_open(statedb, &db ) == SQLITE_OK) {
rc = _csync_check_db_integrity(db);
if( sqlite3_close(db) != 0 ) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "WARN: sqlite3_close error!");
}
if( rc >= 0 ) {
/* everything is fine */
c_free_locale_string(wstatedb);
return 0;
}
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Integrity check failed!");
} else {
/* resources need to be freed even when open failed */
sqlite3_close(db);
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "database corrupted, removing!");
}
} else {
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "sqlite version mismatch");
}
}
}
} else {
close(fd);
}
/* if it comes here, the database is broken and should be recreated. */
_tunlink(wstatedb);
} }
c_free_locale_string(wstatedb); return rc;
/* create database */
rc = sqlite3_open(statedb, &db);
if (rc == SQLITE_OK) {
sqlite3_close(db);
csync_win32_set_file_hidden(statedb, true);
return 1;
}
sqlite3_close(db);
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "sqlite3_open failed: %s %s", sqlite3_errmsg(db), statedb);
return -1;
} }
static int _csync_statedb_is_empty(sqlite3 *db) { static int _csync_statedb_is_empty(sqlite3 *db) {
@ -187,19 +130,10 @@ int csync_statedb_load(CSYNC *ctx, const char *statedb, sqlite3 **pdb) {
return -1; return -1;
} }
/* csync_statedb_check tries to open the statedb and creates it in case ctx->statedb.lastReturnValue = SQLITE_OK;
* its not there.
*/
check_rc = _csync_statedb_check(statedb);
if (check_rc < 0) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: checking csync database failed - bail out.");
rc = -1; /* Openthe database */
goto out; if (sqlite_open(statedb, &db) != SQLITE_OK) {
}
/* Open or create the temporary database */
if (sqlite3_open(statedb, &db) != SQLITE_OK) {
const char *errmsg= sqlite3_errmsg(ctx->statedb.db); const char *errmsg= sqlite3_errmsg(ctx->statedb.db);
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: Failed to sqlite3 open statedb - bail out: %s.", CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: Failed to sqlite3 open statedb - bail out: %s.",
errmsg ? errmsg : "<no sqlite3 errormsg>"); errmsg ? errmsg : "<no sqlite3 errormsg>");
@ -208,16 +142,31 @@ int csync_statedb_load(CSYNC *ctx, const char *statedb, sqlite3 **pdb) {
goto out; goto out;
} }
/* If check_rc == 1 the database is new and empty as a result. */ if (_csync_check_db_integrity(db) != 0) {
if ((check_rc == 1) || _csync_statedb_is_empty(db)) { const char *errmsg= sqlite3_errmsg(db);
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "statedb doesn't exist"); CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: sqlite3 integrity check failed - bail out: %s.",
errmsg ? errmsg : "<no sqlite3 errormsg>");
rc = -1;
goto out;
}
if (_csync_statedb_is_empty(db)) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "statedb contents doesn't exist");
csync_set_statedb_exists(ctx, 0); csync_set_statedb_exists(ctx, 0);
} else { } else {
csync_set_statedb_exists(ctx, 1); csync_set_statedb_exists(ctx, 1);
} }
/* Print out the version */
//
result = csync_statedb_query(db, "SELECT sqlite_version();");
if (result && result->count >= 1) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "sqlite3 version \"%s\"", *result->vector);
}
c_strlist_destroy(result);
/* optimization for speeding up SQLite */ /* optimization for speeding up SQLite */
result = csync_statedb_query(db, "PRAGMA synchronous = FULL;"); result = csync_statedb_query(db, "PRAGMA synchronous = NORMAL;");
c_strlist_destroy(result); c_strlist_destroy(result);
result = csync_statedb_query(db, "PRAGMA case_sensitive_like = ON;"); result = csync_statedb_query(db, "PRAGMA case_sensitive_like = ON;");
c_strlist_destroy(result); c_strlist_destroy(result);
@ -243,21 +192,7 @@ int csync_statedb_close(CSYNC *ctx) {
return -1; return -1;
} }
/* deallocate query resources */ csync_statedb_finalize_statements(ctx);
if( ctx->statedb.by_hash_stmt ) {
rc = sqlite3_finalize(ctx->statedb.by_hash_stmt);
ctx->statedb.by_hash_stmt = NULL;
}
if( ctx->statedb.by_fileid_stmt ) {
rc = sqlite3_finalize(ctx->statedb.by_fileid_stmt);
ctx->statedb.by_fileid_stmt = NULL;
}
if( ctx->statedb.by_inode_stmt ) {
rc = sqlite3_finalize(ctx->statedb.by_inode_stmt);
ctx->statedb.by_inode_stmt = NULL;
}
sqlite3_close(ctx->statedb.db); sqlite3_close(ctx->statedb.db);
@ -281,7 +216,7 @@ static int _csync_file_stat_from_metadata_table( csync_file_stat_t **st, sqlite3
column_count = sqlite3_column_count(stmt); column_count = sqlite3_column_count(stmt);
rc = sqlite3_step(stmt); SQLITE_BUSY_HANDLED( sqlite3_step(stmt) );
if( rc == SQLITE_ROW ) { if( rc == SQLITE_ROW ) {
if(column_count > 7) { if(column_count > 7) {
@ -344,7 +279,8 @@ csync_file_stat_t *csync_statedb_get_stat_by_hash(CSYNC *ctx,
if( ctx->statedb.by_hash_stmt == NULL ) { if( ctx->statedb.by_hash_stmt == NULL ) {
const char *hash_query = "SELECT * FROM metadata WHERE phash=?1"; const char *hash_query = "SELECT * FROM metadata WHERE phash=?1";
rc = sqlite3_prepare_v2(ctx->statedb.db, hash_query, strlen(hash_query), &ctx->statedb.by_hash_stmt, NULL); SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, hash_query, strlen(hash_query), &ctx->statedb.by_hash_stmt, NULL));
ctx->statedb.lastReturnValue = rc;
if( rc != SQLITE_OK ) { if( rc != SQLITE_OK ) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Unable to create stmt for hash query."); CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Unable to create stmt for hash query.");
return NULL; return NULL;
@ -358,6 +294,7 @@ csync_file_stat_t *csync_statedb_get_stat_by_hash(CSYNC *ctx,
sqlite3_bind_int64(ctx->statedb.by_hash_stmt, 1, (long long signed int)phash); sqlite3_bind_int64(ctx->statedb.by_hash_stmt, 1, (long long signed int)phash);
rc = _csync_file_stat_from_metadata_table(&st, ctx->statedb.by_hash_stmt); rc = _csync_file_stat_from_metadata_table(&st, ctx->statedb.by_hash_stmt);
ctx->statedb.lastReturnValue = rc;
if( !(rc == SQLITE_ROW || rc == SQLITE_DONE) ) { if( !(rc == SQLITE_ROW || rc == SQLITE_DONE) ) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Could not get line from metadata: %d!", rc); CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Could not get line from metadata: %d!", rc);
} }
@ -366,6 +303,28 @@ csync_file_stat_t *csync_statedb_get_stat_by_hash(CSYNC *ctx,
return st; return st;
} }
void csync_statedb_finalize_statements(CSYNC *ctx) {
if( !ctx ) {
return;
}
/* deallocate query resources */
if( ctx->statedb.by_fileid_stmt ) {
sqlite3_finalize(ctx->statedb.by_fileid_stmt);
ctx->statedb.by_fileid_stmt = NULL;
}
if( ctx->statedb.by_hash_stmt ) {
sqlite3_finalize(ctx->statedb.by_hash_stmt);
ctx->statedb.by_hash_stmt = NULL;
}
if( ctx->statedb.by_inode_stmt) {
sqlite3_finalize(ctx->statedb.by_inode_stmt);
ctx->statedb.by_inode_stmt = NULL;
}
ctx->statedb.lastReturnValue = SQLITE_OK;
}
csync_file_stat_t *csync_statedb_get_stat_by_file_id(CSYNC *ctx, csync_file_stat_t *csync_statedb_get_stat_by_file_id(CSYNC *ctx,
const char *file_id ) { const char *file_id ) {
csync_file_stat_t *st = NULL; csync_file_stat_t *st = NULL;
@ -385,7 +344,8 @@ csync_file_stat_t *csync_statedb_get_stat_by_file_id(CSYNC *ctx,
if( ctx->statedb.by_fileid_stmt == NULL ) { if( ctx->statedb.by_fileid_stmt == NULL ) {
const char *query = "SELECT * FROM metadata WHERE fileid=?1"; const char *query = "SELECT * FROM metadata WHERE fileid=?1";
rc = sqlite3_prepare_v2(ctx->statedb.db, query, strlen(query), &ctx->statedb.by_fileid_stmt, NULL); SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, query, strlen(query), &ctx->statedb.by_fileid_stmt, NULL));
ctx->statedb.lastReturnValue = rc;
if( rc != SQLITE_OK ) { if( rc != SQLITE_OK ) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Unable to create stmt for file id query."); CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Unable to create stmt for file id query.");
return NULL; return NULL;
@ -396,6 +356,7 @@ csync_file_stat_t *csync_statedb_get_stat_by_file_id(CSYNC *ctx,
sqlite3_bind_text(ctx->statedb.by_fileid_stmt, 1, file_id, -1, SQLITE_STATIC); sqlite3_bind_text(ctx->statedb.by_fileid_stmt, 1, file_id, -1, SQLITE_STATIC);
rc = _csync_file_stat_from_metadata_table(&st, ctx->statedb.by_fileid_stmt); rc = _csync_file_stat_from_metadata_table(&st, ctx->statedb.by_fileid_stmt);
ctx->statedb.lastReturnValue = rc;
if( !(rc == SQLITE_ROW || rc == SQLITE_DONE) ) { if( !(rc == SQLITE_ROW || rc == SQLITE_DONE) ) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Could not get line from metadata: %d!", rc); CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Could not get line from metadata: %d!", rc);
} }
@ -423,7 +384,8 @@ csync_file_stat_t *csync_statedb_get_stat_by_inode(CSYNC *ctx,
if( ctx->statedb.by_inode_stmt == NULL ) { if( ctx->statedb.by_inode_stmt == NULL ) {
const char *inode_query = "SELECT * FROM metadata WHERE inode=?1"; const char *inode_query = "SELECT * FROM metadata WHERE inode=?1";
rc = sqlite3_prepare_v2(ctx->statedb.db, inode_query, strlen(inode_query), &ctx->statedb.by_inode_stmt, NULL); SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, inode_query, strlen(inode_query), &ctx->statedb.by_inode_stmt, NULL));
ctx->statedb.lastReturnValue = rc;
if( rc != SQLITE_OK ) { if( rc != SQLITE_OK ) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Unable to create stmt for inode query."); CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Unable to create stmt for inode query.");
return NULL; return NULL;
@ -437,6 +399,7 @@ csync_file_stat_t *csync_statedb_get_stat_by_inode(CSYNC *ctx,
sqlite3_bind_int64(ctx->statedb.by_inode_stmt, 1, (long long signed int)inode); sqlite3_bind_int64(ctx->statedb.by_inode_stmt, 1, (long long signed int)inode);
rc = _csync_file_stat_from_metadata_table(&st, ctx->statedb.by_inode_stmt); rc = _csync_file_stat_from_metadata_table(&st, ctx->statedb.by_inode_stmt);
ctx->statedb.lastReturnValue = rc;
if( !(rc == SQLITE_ROW || rc == SQLITE_DONE) ) { if( !(rc == SQLITE_ROW || rc == SQLITE_DONE) ) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Could not get line from metadata by inode: %d!", rc); CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Could not get line from metadata by inode: %d!", rc);
} }
@ -485,7 +448,8 @@ int csync_statedb_get_below_path( CSYNC *ctx, const char *path ) {
return -1; return -1;
} }
rc = sqlite3_prepare_v2(ctx->statedb.db, BELOW_PATH_QUERY, -1, &stmt, NULL); SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, BELOW_PATH_QUERY, -1, &stmt, NULL));
ctx->statedb.lastReturnValue = rc;
if( rc != SQLITE_OK ) { if( rc != SQLITE_OK ) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Unable to create stmt for below path query."); CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Unable to create stmt for below path query.");
return -1; return -1;
@ -507,6 +471,7 @@ int csync_statedb_get_below_path( CSYNC *ctx, const char *path ) {
cnt = 0; cnt = 0;
ctx->statedb.lastReturnValue = rc;
do { do {
csync_file_stat_t *st = NULL; csync_file_stat_t *st = NULL;
@ -522,6 +487,7 @@ int csync_statedb_get_below_path( CSYNC *ctx, const char *path ) {
} }
} while( rc == SQLITE_ROW ); } while( rc == SQLITE_ROW );
ctx->statedb.lastReturnValue = rc;
if( rc != SQLITE_DONE ) { if( rc != SQLITE_DONE ) {
ctx->status_code = CSYNC_STATUS_TREE_ERROR; ctx->status_code = CSYNC_STATUS_TREE_ERROR;
} else { } else {

View file

@ -99,25 +99,10 @@ int csync_statedb_get_below_path(CSYNC *ctx, const char *path);
c_strlist_t *csync_statedb_query(sqlite3 *db, const char *statement); c_strlist_t *csync_statedb_query(sqlite3 *db, const char *statement);
/** /**
* @brief Insert function for the statedb. * @brief csync_statedb_finalize_statements - Clear prepared statements
* * @param ctx The csync context
* @param ctx The csync context.
* @param statement The SQL statement to insert into the statedb.
*
* @return The rowid of the most recent INSERT on success, 0 if the query
* wasn't successful.
*/ */
typedef struct csync_progressinfo_s { void csync_statedb_finalize_statements(CSYNC *ctx);
struct csync_progressinfo_s *next;
uint64_t phash;
uint64_t modtime;
char *md5;
int error;
int chunk;
int transferId;
char *tmpfile;
char *error_string;
} csync_progressinfo_t;
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -100,6 +100,10 @@ static bool _csync_sameextension(const char *p1, const char *p2) {
} }
#endif #endif
static bool _last_db_return_error(CSYNC* ctx) {
return ctx->statedb.lastReturnValue != SQLITE_OK && ctx->statedb.lastReturnValue != SQLITE_DONE && ctx->statedb.lastReturnValue != SQLITE_ROW;
}
static int _csync_detect_update(CSYNC *ctx, const char *file, static int _csync_detect_update(CSYNC *ctx, const char *file,
const csync_vio_file_stat_t *fs, const int type) { const csync_vio_file_stat_t *fs, const int type) {
uint64_t h = 0; uint64_t h = 0;
@ -146,10 +150,10 @@ static int _csync_detect_update(CSYNC *ctx, const char *file,
if (excluded != CSYNC_NOT_EXCLUDED) { if (excluded != CSYNC_NOT_EXCLUDED) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "%s excluded (%d)", path, excluded); CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "%s excluded (%d)", path, excluded);
if (excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE) { if (excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE) {
return 0; return 1;
} }
if (excluded == CSYNC_FILE_SILENTLY_EXCLUDED) { if (excluded == CSYNC_FILE_SILENTLY_EXCLUDED) {
return 0; return 1;
} }
if (ctx->current_fs) { if (ctx->current_fs) {
@ -157,9 +161,9 @@ static int _csync_detect_update(CSYNC *ctx, const char *file,
} }
} }
if (ctx->current == REMOTE_REPLICA && ctx->checkBlackListHook) { if (ctx->current == REMOTE_REPLICA && ctx->checkSelectiveSyncBlackListHook) {
if (ctx->checkBlackListHook(ctx->checkBlackListData, path)) { if (ctx->checkSelectiveSyncBlackListHook(ctx->checkSelectiveSyncBlackListData, path)) {
return 0; return 1;
} }
} }
@ -190,8 +194,15 @@ static int _csync_detect_update(CSYNC *ctx, const char *file,
} }
if (fs->mtime == 0) { if (fs->mtime == 0) {
tmp = csync_statedb_get_stat_by_hash(ctx, h);
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "file: %s - mtime is zero!", path); CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "file: %s - mtime is zero!", path);
tmp = csync_statedb_get_stat_by_hash(ctx, h);
if(_last_db_return_error(ctx)) {
SAFE_FREE(st);
ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL;
return -1;
}
if (tmp == NULL) { if (tmp == NULL) {
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "file: %s - not found in db, IGNORE!", path); CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "file: %s - not found in db, IGNORE!", path);
st->instruction = CSYNC_INSTRUCTION_IGNORE; st->instruction = CSYNC_INSTRUCTION_IGNORE;
@ -228,6 +239,12 @@ static int _csync_detect_update(CSYNC *ctx, const char *file,
if (csync_get_statedb_exists(ctx)) { if (csync_get_statedb_exists(ctx)) {
tmp = csync_statedb_get_stat_by_hash(ctx, h); tmp = csync_statedb_get_stat_by_hash(ctx, h);
if(_last_db_return_error(ctx)) {
SAFE_FREE(st);
ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL;
return -1;
}
if(tmp && tmp->phash == h ) { /* there is an entry in the database */ if(tmp && tmp->phash == h ) { /* there is an entry in the database */
/* we have an update! */ /* we have an update! */
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Database entry found, compare: %" PRId64 " <-> %" PRId64 ", etag: %s <-> %s, inode: %" PRId64 " <-> %" PRId64 ", size: %" PRId64 " <-> %" PRId64, CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Database entry found, compare: %" PRId64 " <-> %" PRId64 ", etag: %s <-> %s, inode: %" PRId64 " <-> %" PRId64 ", size: %" PRId64 " <-> %" PRId64,
@ -289,6 +306,12 @@ static int _csync_detect_update(CSYNC *ctx, const char *file,
tmp = csync_statedb_get_stat_by_inode(ctx, fs->inode); tmp = csync_statedb_get_stat_by_inode(ctx, fs->inode);
if(_last_db_return_error(ctx)) {
SAFE_FREE(st);
ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL;
return -1;
}
/* translate the file type between the two stat types csync has. */ /* translate the file type between the two stat types csync has. */
if( tmp && tmp->type == 0 ) { if( tmp && tmp->type == 0 ) {
tmp_vio_type = CSYNC_VIO_FILE_TYPE_REGULAR; tmp_vio_type = CSYNC_VIO_FILE_TYPE_REGULAR;
@ -319,6 +342,12 @@ static int _csync_detect_update(CSYNC *ctx, const char *file,
} else { } else {
/* Remote Replica Rename check */ /* Remote Replica Rename check */
tmp = csync_statedb_get_stat_by_file_id(ctx, fs->file_id); tmp = csync_statedb_get_stat_by_file_id(ctx, fs->file_id);
if(_last_db_return_error(ctx)) {
SAFE_FREE(st);
ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL;
return -1;
}
if(tmp ) { /* tmp existing at all */ if(tmp ) { /* tmp existing at all */
if ((tmp->type == CSYNC_FTW_TYPE_DIR && fs->type != CSYNC_VIO_FILE_TYPE_DIRECTORY) || if ((tmp->type == CSYNC_FTW_TYPE_DIR && fs->type != CSYNC_VIO_FILE_TYPE_DIRECTORY) ||
(tmp->type == CSYNC_FTW_TYPE_FILE && fs->type != CSYNC_VIO_FILE_TYPE_REGULAR)) { (tmp->type == CSYNC_FTW_TYPE_FILE && fs->type != CSYNC_VIO_FILE_TYPE_REGULAR)) {
@ -346,8 +375,9 @@ static int _csync_detect_update(CSYNC *ctx, const char *file,
} }
} }
} else { } else {
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Unable to open statedb, setting inst to NEW" ); CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Unable to open statedb" );
st->instruction = CSYNC_INSTRUCTION_NEW; ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL;
return -1;
} }
out: out:
@ -603,10 +633,13 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
path = filename + ulen; path = filename + ulen;
/* skip ".csync_journal.db" and ".csync_journal.db.ctmp" */ /* skip ".csync_journal.db" and ".csync_journal.db.ctmp" */
/* Isn't this done via csync_exclude already? */
if (c_streq(path, ".csync_journal.db") if (c_streq(path, ".csync_journal.db")
|| c_streq(path, ".csync_journal.db.ctmp") || c_streq(path, ".csync_journal.db.ctmp")
|| c_streq(path, ".csync_journal.db.ctmp-journal") || c_streq(path, ".csync_journal.db.ctmp-journal")
|| c_streq(path, ".csync-progressdatabase")) { || c_streq(path, ".csync-progressdatabase")
|| c_streq(path, ".csync_journal.db-shm")
|| c_streq(path, ".csync_journal.db-wal")) {
csync_vio_file_stat_destroy(dirent); csync_vio_file_stat_destroy(dirent);
dirent = NULL; dirent = NULL;
SAFE_FREE(filename); SAFE_FREE(filename);
@ -652,6 +685,11 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
uint64_t h = c_jhash64((uint8_t *) path, len, 0); uint64_t h = c_jhash64((uint8_t *) path, len, 0);
etag = csync_statedb_get_etag( ctx, h ); etag = csync_statedb_get_etag( ctx, h );
if(_last_db_return_error(ctx)) {
ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL;
goto error;
}
if( etag ) { if( etag ) {
SAFE_FREE(fs->etag); SAFE_FREE(fs->etag);
fs->etag = etag; fs->etag = etag;
@ -671,11 +709,6 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
rc = fn(ctx, filename, fs, flag); rc = fn(ctx, filename, fs, flag);
/* this function may update ctx->current and ctx->read_from_db */ /* this function may update ctx->current and ctx->read_from_db */
if (ctx->current_fs && previous_fs && ctx->current_fs->child_modified) {
/* If a directory has modified files, put the flag on the parent directory as well */
previous_fs->child_modified = ctx->current_fs->child_modified;
}
/* Only for the local replica we have to destroy stat(), for the remote one it is a pointer to dirent */ /* Only for the local replica we have to destroy stat(), for the remote one it is a pointer to dirent */
if (ctx->replica == LOCAL_REPLICA) { if (ctx->replica == LOCAL_REPLICA) {
csync_vio_file_stat_destroy(fs); csync_vio_file_stat_destroy(fs);
@ -690,7 +723,7 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
goto error; goto error;
} }
if (flag == CSYNC_FTW_FLAG_DIR && depth if (flag == CSYNC_FTW_FLAG_DIR && depth && rc == 0
&& (!ctx->current_fs || ctx->current_fs->instruction != CSYNC_INSTRUCTION_IGNORE)) { && (!ctx->current_fs || ctx->current_fs->instruction != CSYNC_INSTRUCTION_IGNORE)) {
rc = csync_ftw(ctx, filename, fn, depth - 1); rc = csync_ftw(ctx, filename, fn, depth - 1);
if (rc < 0) { if (rc < 0) {
@ -710,6 +743,11 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
} }
} }
if (ctx->current_fs && previous_fs && ctx->current_fs->child_modified) {
/* If a directory has modified files, put the flag on the parent directory as well */
previous_fs->child_modified = ctx->current_fs->child_modified;
}
if (flag == CSYNC_FTW_FLAG_DIR && ctx->current_fs if (flag == CSYNC_FTW_FLAG_DIR && ctx->current_fs
&& (ctx->current_fs->instruction == CSYNC_INSTRUCTION_EVAL || && (ctx->current_fs->instruction == CSYNC_INSTRUCTION_EVAL ||
ctx->current_fs->instruction == CSYNC_INSTRUCTION_NEW)) { ctx->current_fs->instruction == CSYNC_INSTRUCTION_NEW)) {

View file

@ -55,19 +55,11 @@ static void setup(void **state)
static void setup_db(void **state) static void setup_db(void **state)
{ {
CSYNC *csync; char *errmsg;
char *stmt = NULL;
int rc = 0; int rc = 0;
c_strlist_t *result = NULL; sqlite3 *db = NULL;
setup(state); const char *sql = "CREATE TABLE IF NOT EXISTS metadata ("
csync = *state;
// rc = csync_statedb_create_tables(csync->statedb.db);
assert_int_equal(rc, 0);
result = csync_statedb_query(csync->statedb.db,
"CREATE TABLE IF NOT EXISTS metadata ("
"phash INTEGER(8)," "phash INTEGER(8),"
"pathlen INTEGER," "pathlen INTEGER,"
"path VARCHAR(4096)," "path VARCHAR(4096),"
@ -79,29 +71,25 @@ static void setup_db(void **state)
"type INTEGER," "type INTEGER,"
"md5 VARCHAR(32)," "md5 VARCHAR(32),"
"PRIMARY KEY(phash)" "PRIMARY KEY(phash)"
");" ");";
);
assert_non_null(result); const char *sql2 = "INSERT INTO metadata"
c_strlist_destroy(result);
stmt = sqlite3_mprintf("INSERT INTO metadata"
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5) VALUES" "(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5) VALUES"
"(%lu, %d, '%q', %d, %d, %d, %d, %lu, %d, %lu);", "(42, 42, 'Its funny stuff', 23, 42, 43, 55, 66, 2, 54);";
42,
42,
"It's a rainy day", setup(state);
23, rc = sqlite3_open( TESTDB, &db);
42, assert_int_equal(rc, SQLITE_OK);
42,
42, rc = sqlite3_exec( db, sql, NULL, NULL, &errmsg );
42, assert_int_equal(rc, SQLITE_OK);
2,
43); rc = sqlite3_exec( db, sql2, NULL, NULL, &errmsg );
assert_int_equal(rc, SQLITE_OK);
sqlite3_close(db);
// rc = csync_statedb_insert(csync->statedb.db, stmt);
sqlite3_free(stmt);
} }
static void teardown(void **state) { static void teardown(void **state) {
@ -139,41 +127,6 @@ static void check_csync_statedb_query_statement(void **state)
} }
} }
static void check_csync_statedb_create_error(void **state)
{
CSYNC *csync = *state;
c_strlist_t *result;
result = csync_statedb_query(csync->statedb.db, "CREATE TABLE test(phash INTEGER, text VARCHAR(10));");
assert_non_null(result);
c_strlist_destroy(result);
result = csync_statedb_query(csync->statedb.db, "CREATE TABLE test(phash INTEGER, text VARCHAR(10));");
assert_null(result);
c_strlist_destroy(result);
}
static void check_csync_statedb_insert_statement(void **state)
{
CSYNC *csync = *state;
c_strlist_t *result;
int rc = 0;
result = csync_statedb_query(csync->statedb.db, "CREATE TABLE test(phash INTEGER, text VARCHAR(10));");
assert_non_null(result);
c_strlist_destroy(result);
// rc = csync_statedb_insert(csync->statedb.db, "INSERT;");
assert_int_equal(rc, 0);
// rc = csync_statedb_insert(csync->statedb.db, "INSERT");
assert_int_equal(rc, 0);
// rc = csync_statedb_insert(csync->statedb.db, "");
assert_int_equal(rc, 0);
}
static void check_csync_statedb_drop_tables(void **state) static void check_csync_statedb_drop_tables(void **state)
{ {
// CSYNC *csync = *state; // CSYNC *csync = *state;
@ -255,8 +208,6 @@ int torture_run_tests(void)
{ {
const UnitTest tests[] = { const UnitTest tests[] = {
unit_test_setup_teardown(check_csync_statedb_query_statement, setup, teardown), unit_test_setup_teardown(check_csync_statedb_query_statement, setup, teardown),
unit_test_setup_teardown(check_csync_statedb_create_error, setup, teardown),
unit_test_setup_teardown(check_csync_statedb_insert_statement, setup, teardown),
unit_test_setup_teardown(check_csync_statedb_drop_tables, 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_insert_metadata, setup, teardown),
unit_test_setup_teardown(check_csync_statedb_write, setup, teardown), unit_test_setup_teardown(check_csync_statedb_write, setup, teardown),

View file

@ -23,11 +23,64 @@
#define TESTDB "/tmp/check_csync/journal.db" #define TESTDB "/tmp/check_csync/journal.db"
static int firstrun = 1;
static void statedb_create_metadata_table(sqlite3 *db)
{
int rc = 0;
if( db ) {
const char *sql = "CREATE TABLE IF NOT EXISTS metadata("
"phash INTEGER(8),"
"pathlen INTEGER,"
"path VARCHAR(4096),"
"inode INTEGER,"
"uid INTEGER,"
"gid INTEGER,"
"mode INTEGER,"
"modtime INTEGER(8),"
"type INTEGER,"
"md5 VARCHAR(32),"
"PRIMARY KEY(phash));";
rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
const char *msg = sqlite3_errmsg(db);
assert_int_equal( rc, SQLITE_OK );
}
}
static void statedb_insert_metadata(sqlite3 *db)
{
int rc = 0;
if( db ) {
char *stmt = sqlite3_mprintf("INSERT INTO metadata"
"(phash, pathlen, path, inode, uid, gid, mode, modtime,type,md5) VALUES"
"(%lld, %d, '%q', %d, %d, %d, %d, %lld, %d, '%q');",
(long long signed int)42,
42,
"I_was_wurst_before_I_became_wurstsalat",
619070,
42,
42,
42,
(long long signed int)42,
0,
"4711");
char *errmsg;
rc = sqlite3_exec(db, stmt, NULL, NULL, &errmsg);
sqlite3_free(stmt);
assert_int_equal( rc, SQLITE_OK );
}
}
static void setup(void **state) static void setup(void **state)
{ {
CSYNC *csync; CSYNC *csync;
int rc; int rc;
unlink(TESTDB);
rc = system("mkdir -p /tmp/check_csync"); rc = system("mkdir -p /tmp/check_csync");
assert_int_equal(rc, 0); assert_int_equal(rc, 0);
rc = system("mkdir -p /tmp/check_csync1"); rc = system("mkdir -p /tmp/check_csync1");
@ -38,10 +91,21 @@ static void setup(void **state)
assert_int_equal(rc, 0); assert_int_equal(rc, 0);
rc = csync_init(csync); rc = csync_init(csync);
assert_int_equal(rc, 0); assert_int_equal(rc, 0);
/* Create a new db with metadata */
sqlite3 *db;
csync->statedb.file = c_strdup(TESTDB);
rc = sqlite3_open(csync->statedb.file, &db);
statedb_create_metadata_table(db);
if( firstrun ) {
statedb_insert_metadata(db);
firstrun = 0;
}
sqlite3_close(db);
rc = csync_statedb_load(csync, TESTDB, &csync->statedb.db); rc = csync_statedb_load(csync, TESTDB, &csync->statedb.db);
assert_int_equal(rc, 0); assert_int_equal(rc, 0);
*state = csync; *state = csync;
} }
@ -71,6 +135,7 @@ static void teardown(void **state)
CSYNC *csync = *state; CSYNC *csync = *state;
int rc; int rc;
unlink( csync->statedb.file);
rc = csync_destroy(csync); rc = csync_destroy(csync);
assert_int_equal(rc, 0); assert_int_equal(rc, 0);
@ -249,6 +314,7 @@ static void check_csync_detect_update_db_eval(void **state)
csync_vio_file_stat_destroy(fs); csync_vio_file_stat_destroy(fs);
} }
static void check_csync_detect_update_db_rename(void **state) static void check_csync_detect_update_db_rename(void **state)
{ {
CSYNC *csync = *state; CSYNC *csync = *state;
@ -256,27 +322,6 @@ static void check_csync_detect_update_db_rename(void **state)
csync_vio_file_stat_t *fs; csync_vio_file_stat_t *fs;
int rc = 0; int rc = 0;
char *stmt = NULL;
// rc = csync_statedb_create_tables(csync->statedb.db);
assert_int_equal(rc, 0);
stmt = sqlite3_mprintf("INSERT INTO metadata"
"(phash, pathlen, path, inode, uid, gid, mode, modtime,type,md5) VALUES"
"(%lld, %d, '%q', %d, %d, %d, %d, %lld, %d, '%q');",
(long long signed int)42,
42,
"I_was_wurst_before_I_became_wurstsalat",
619070,
42,
42,
42,
(long long signed int)42,
0,
"4711");
// rc = csync_statedb_insert(csync->statedb.db, stmt);
sqlite3_free(stmt);
fs = create_fstat("wurst.txt", 0, 1, 42); fs = create_fstat("wurst.txt", 0, 1, 42);
assert_non_null(fs); assert_non_null(fs);

1
csync/tests/ownCloud/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
t1.cfg

View file

@ -63,7 +63,7 @@ our %config;
@ISA = qw(Exporter); @ISA = qw(Exporter);
@EXPORT = qw( initTesting createRemoteDir removeRemoteDir createLocalDir cleanup csync @EXPORT = qw( initTesting createRemoteDir removeRemoteDir createLocalDir cleanup csync
assertLocalDirs assertLocalAndRemoteDir glob_put put_to_dir assertLocalDirs assertLocalAndRemoteDir glob_put put_to_dir
putToDirLWP localDir remoteDir localCleanup createLocalFile md5OfFile putToDirLWP localDir remoteDir localCleanup createLocalFile md5OfFile
remoteCleanup server initLocalDir initRemoteDir moveRemoteFile remoteCleanup server initLocalDir initRemoteDir moveRemoteFile
printInfo remoteFileId createShare removeShare assert printInfo remoteFileId createShare removeShare assert
@ -73,12 +73,12 @@ sub server
{ {
return $owncloud; return $owncloud;
} }
sub fromFileName($) sub fromFileName($)
{ {
my ($file) = @_; my ($file) = @_;
if ( $^O eq "darwin" ) { if ( $^O eq "darwin" ) {
my $fromFileName = NFC( Encode::decode('utf-8', $file) ); my $fromFileName = NFC( Encode::decode('utf-8', $file) );
return $fromFileName; return $fromFileName;
} else { } else {
return $file; return $file;
@ -89,7 +89,7 @@ sub fromFileName($)
sub initTesting(;$) sub initTesting(;$)
{ {
my ($prefix) = @_; my ($prefix) = @_;
my $cfgFile = "./t1.cfg"; my $cfgFile = "./t1.cfg";
$cfgFile = "/etc/ownCloud/t1.cfg" if( -r "/etc/ownCloud/t1.cfg" ); $cfgFile = "/etc/ownCloud/t1.cfg" if( -r "/etc/ownCloud/t1.cfg" );
@ -115,6 +115,8 @@ sub initTesting(;$)
$owncloud .= "/" unless( $owncloud =~ /\/$/ ); $owncloud .= "/" unless( $owncloud =~ /\/$/ );
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
print "Connecting to ownCloud at ". $owncloud ."\n"; print "Connecting to ownCloud at ". $owncloud ."\n";
# For SSL set the environment variable needed by the LWP module for SSL # For SSL set the environment variable needed by the LWP module for SSL
@ -129,15 +131,15 @@ sub initTesting(;$)
-pass=> $passwd ); -pass=> $passwd );
# $d->DebugLevel(3); # $d->DebugLevel(3);
$prefix = "t1" unless( defined $prefix ); $prefix = "t1" unless( defined $prefix );
my $dirId = sprintf("%02d", rand(100)); my $dirId = sprintf("%02d", rand(100));
my $dateTime = strftime('%Y%m%d%H%M%S',localtime); my $dateTime = strftime('%Y%m%d%H%M%S',localtime);
my $dir = sprintf( "%s-%s-%s/", $prefix, $dateTime, $dirId ); my $dir = sprintf( "%s-%s-%s/", $prefix, $dateTime, $dirId );
$localDir = $dir; $localDir = $dir;
$localDir .= "/" unless( $localDir =~ /\/$/ ); $localDir .= "/" unless( $localDir =~ /\/$/ );
$remoteDir = $dir; $remoteDir = $dir;
initRemoteDir(); initRemoteDir();
initLocalDir(); initLocalDir();
printf( "Test directory name is %s\n", $dir ); printf( "Test directory name is %s\n", $dir );
@ -166,7 +168,9 @@ sub testDirUrl()
# the global var $remoteDir; # the global var $remoteDir;
sub initRemoteDir sub initRemoteDir
{ {
$d->open( $owncloud ); $d->open( $owncloud )
or die("Couldn't open $owncloud: " .$d->message . "\n");
my $url = testDirUrl(); my $url = testDirUrl();
my $re = $d->mkcol( $url ); my $re = $d->mkcol( $url );
@ -204,7 +208,7 @@ sub removeRemoteDir($;$)
if( $re == 0 ) { if( $re == 0 ) {
print "Failed to remove directory <$url>:" . $d->message() ."\n"; print "Failed to remove directory <$url>:" . $d->message() ."\n";
} }
return $re; return $re;
} }
@ -336,7 +340,7 @@ sub localDir()
return $localDir; return $localDir;
} }
sub remoteDir() sub remoteDir()
{ {
return $remoteDir; return $remoteDir;
} }
@ -381,7 +385,7 @@ sub traverse( $$;$ )
{ {
my ($remote, $acceptConflicts, $aurl) = @_; my ($remote, $acceptConflicts, $aurl) = @_;
$remote .= '/' unless $remote =~ /(^|\/)$/; $remote .= '/' unless $remote =~ /(^|\/)$/;
my $url = testDirUrl() . $remote; my $url = testDirUrl() . $remote;
if( $aurl ) { if( $aurl ) {
$url = $aurl . $remote; $url = $aurl . $remote;
@ -489,13 +493,13 @@ sub glob_put( $$;$ )
print " *** Putting $lfile to $puturl\n"; print " *** Putting $lfile to $puturl\n";
# putToDirLWP( $lfile, $puturl ); # putToDirLWP( $lfile, $puturl );
put_to_dir($lfile, $puturl, $optionsRef); put_to_dir($lfile, $puturl, $optionsRef);
# if( ! $d->put( -local=>$lfile, -url=> $puturl ) ) { # if( ! $d->put( -local=>$lfile, -url=> $puturl ) ) {
#print " ### FAILED to put: ". $d->message . '\n'; #print " ### FAILED to put: ". $d->message . '\n';
# s} # s}
} }
} }
} }
} }
@ -525,7 +529,7 @@ sub put_to_dir( $$;$ )
} }
} }
# The HTTP DAV module often does a PROPFIND before it really PUTs. That # The HTTP DAV module often does a PROPFIND before it really PUTs. That
# is not neccessary if we know that the directory is really there. # is not neccessary if we know that the directory is really there.
# Use this function in this case: # Use this function in this case:
sub putToDirLWP($$) sub putToDirLWP($$)
@ -545,13 +549,13 @@ sub putToDirLWP($$)
my $string = <FILE>; my $string = <FILE>;
close FILE; close FILE;
my $ua = LWP::UserAgent->new(); my $ua = LWP::UserAgent->new( ssl_opts => { verify_hostname => 0 });
$ua->agent( "ownCloudTest_$localDir"); $ua->agent( "ownCloudTest_$localDir");
my $req = PUT $puturl, Content_Type => 'application/octet-stream', my $req = PUT $puturl, Content_Type => 'application/octet-stream',
Content => $string; Content => $string;
$req->authorization_basic($user, $passwd); $req->authorization_basic($user, $passwd);
my $response = $ua->request($req); my $response = $ua->request($req);
if ($response->is_success()) { if ($response->is_success()) {
# print "OK: ", $response->content; # print "OK: ", $response->content;
} else { } else {
@ -579,7 +583,7 @@ sub getToFileLWP( $$ )
my $geturl = testDirUrl() . $file; my $geturl = testDirUrl() . $file;
print "GETting $geturl to $localFile\n"; print "GETting $geturl to $localFile\n";
my $ua = LWP::UserAgent->new(); my $ua = LWP::UserAgent->new( ssl_opts => { verify_hostname => 0 });
$ua->agent( "ownCloudTest_$localDir"); $ua->agent( "ownCloudTest_$localDir");
$ua->credentials( server(), "foo", $user, $passwd); $ua->credentials( server(), "foo", $user, $passwd);
my $req = $ua->get($geturl, ":content_file" => $localFile); my $req = $ua->get($geturl, ":content_file" => $localFile);
@ -594,15 +598,15 @@ sub getToFileLWP( $$ )
} }
} }
sub createLocalFile( $$ ) sub createLocalFile( $$ )
{ {
my ($fname, $size) = @_; my ($fname, $size) = @_;
$size = 1024 unless( $size ); $size = 1024 unless( $size );
my $md5 = Digest::MD5->new; my $md5 = Digest::MD5->new;
open(FILE, ">", $fname) or die "Can't open $fname for writing ($!)"; open(FILE, ">", $fname) or die "Can't open $fname for writing ($!)";
my $minimum = 32; my $minimum = 32;
my $range = 96; my $range = 96;
@ -620,20 +624,20 @@ sub createLocalFile( $$ )
print FILE $s; print FILE $s;
$md5->add($s); $md5->add($s);
close FILE; close FILE;
return $md5->hexdigest; return $md5->hexdigest;
} }
sub md5OfFile( $ ) sub md5OfFile( $ )
{ {
my ($file) = @_; my ($file) = @_;
open FILE, "$file"; open FILE, "$file";
my $ctx = Digest::MD5->new; my $ctx = Digest::MD5->new;
$ctx->addfile (*FILE); $ctx->addfile (*FILE);
my $hash = $ctx->hexdigest; my $hash = $ctx->hexdigest;
close (FILE); close (FILE);
return $hash; return $hash;
} }
@ -647,27 +651,27 @@ sub moveRemoteFile($$;$)
my $fromUrl = testDirUrl(). $from; my $fromUrl = testDirUrl(). $from;
my $toUrl = testDirUrl() . $to; my $toUrl = testDirUrl() . $to;
if( $no_testdir ) { if( $no_testdir ) {
$fromUrl = $from; $fromUrl = $from;
$toUrl = $to; $toUrl = $to;
} }
$d->move($fromUrl, $toUrl); $d->move($fromUrl, $toUrl);
} }
sub printInfo($) sub printInfo($)
{ {
my ($info) = @_; my ($info) = @_;
my $tt = 6+length( $info ); my $tt = 6+length( $info );
print "#" x $tt; print "#" x $tt;
printf( "\n# %2d. %s", $infoCnt, $info ); printf( "\n# %2d. %s", $infoCnt, $info );
print "\n" unless $info =~ /\n$/; print "\n" unless $info =~ /\n$/;
print "#" x $tt; print "#" x $tt;
print "\n"; print "\n";
$infoCnt++; $infoCnt++;
} }
@ -718,16 +722,15 @@ sub createShare($$)
my $re = $dd->mkcol( $url ); my $re = $dd->mkcol( $url );
if( $re == 0 ) { if( $re == 0 ) {
print "Failed to create test dir $url\n"; print "Failed to create test dir $url\n";
} }
my $ua = LWP::UserAgent->new(); my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 } );
$ua->agent( "ownCloudTest_sharing"); $ua->agent( "ownCloudTest_sharing");
# http://localhost/ocm/ocs/v1.php/apps/files_sharing/api/v1/shares # http://localhost/ocm/ocs/v1.php/apps/files_sharing/api/v1/shares
my $puturl = $ocs_url . "apps/files_sharing/api/v1/shares"; my $puturl = $ocs_url . "ocs/v1.php/apps/files_sharing/api/v1/shares";
my $string = "path=$dir&shareType=0&shareWith=$user&publicUpload=false&permissions=$readWrite"; my $string = "path=$dir&shareType=0&shareWith=$user&publicUpload=false&permissions=$readWrite";
print ">>>>>>>>>> $string\n"; print ">>>>>>>>>> $puturl $string\n";
my $req = POST $puturl, Content => $string; my $req = POST $puturl, Content => $string;
$req->authorization_basic($share_user, $share_passwd); $req->authorization_basic($share_user, $share_passwd);
@ -757,17 +760,16 @@ sub removeShare($$)
-pass => $share_passwd ); -pass => $share_passwd );
$dd->open( $owncloud); $dd->open( $owncloud);
my $ua = LWP::UserAgent->new(); my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 });
$ua->agent( "ownCloudTest_sharing"); $ua->agent( "ownCloudTest_sharing");
# http://localhost/ocm/ocs/v1.php/apps/files_sharing/api/v1/shares
my $url = $ocs_url . "apps/files_sharing/api/v1/shares/" . $shareId; my $url = $ocs_url . "ocs/v1.php/apps/files_sharing/api/v1/shares/" . $shareId;
my $req = DELETE $url; my $req = DELETE $url;
$req->authorization_basic($share_user, $share_passwd); $req->authorization_basic($share_user, $share_passwd);
my $response = $ua->request($req); my $response = $ua->request($req);
if ($response->is_success()) { if ($response->is_success()) {
# print "OK: ", $response->content;
print $response->decoded_content; print $response->decoded_content;
if( $response->decoded_content =~ /<status_code>(\d+)<\/status_code>/m) { if( $response->decoded_content =~ /<status_code>(\d+)<\/status_code>/m) {
my $code = $1; my $code = $1;

View file

@ -169,6 +169,19 @@ assertLocalAndRemoteDir( '', 0);
assert( -e localDir().'remoteToLocal1/rtlX' ); assert( -e localDir().'remoteToLocal1/rtlX' );
assert( -e localDir().'remoteToLocal1/rtlX/rtl11/file.txt' ); assert( -e localDir().'remoteToLocal1/rtlX/rtl11/file.txt' );
printInfo( "Remove a directory on the server with new files on the client");
removeRemoteDir('remoteToLocal1/rtlX');
system("echo hello > " . localDir(). "remoteToLocal1/rtlX/rtl11/hello.txt");
csync();
assertLocalAndRemoteDir( '', 0);
# file.txt must be gone because the directory was removed on the server, but hello.txt must be there
# as it is a new file
assert( ! -e localDir().'remoteToLocal1/rtlX/rtl11/file.txt' );
assert( -e localDir().'remoteToLocal1/rtlX/rtl11/hello.txt' );
# ================================================================== # ==================================================================
cleanup(); cleanup();

View file

@ -91,7 +91,10 @@ assertLocalAndRemoteDir( 'newdir', 0);
assert( -e localDir().'newdir/rtl1/rtl11/newfile.dat' ); assert( -e localDir().'newdir/rtl1/rtl11/newfile.dat' );
assert( -e localDir().'newdir/rtl1/rtl11/myfile.txt' ); assert( -e localDir().'newdir/rtl1/rtl11/myfile.txt' );
assert( ! -e localDir().'newdir/rtl11/test.txt' ); assert( ! -e localDir().'newdir/rtl11/test.txt' );
assert( ! -e localDir().'remoteToLocal1' ); # BUG! remoteToLocal1 is not deleted because changes were detected
# (even if the changed fileswere moved)
# assert( ! -e localDir().'remoteToLocal1' );
assert( ! -e localDir().'remoteToLocal1/rtl1' );
printInfo("Move file and create another one with the same name."); printInfo("Move file and create another one with the same name.");
move( localDir() . 'newdir/myfile.txt', localDir() . 'newdir/oldfile.txt' ); move( localDir() . 'newdir/myfile.txt', localDir() . 'newdir/oldfile.txt' );

View file

@ -33,11 +33,13 @@ print "Hello, this is t6, a tester for csync with ownCloud.\n";
initTesting(); initTesting();
sub createPostUpdateScript() sub createPostUpdateScript($)
{ {
my $srcFile = localDir()."BIG.file"; my ($name) = @_;
my $srcFile = localDir().'BIG1.file';
my $cred = configValue("user") . ":" . configValue("passwd"); my $cred = configValue("user") . ":" . configValue("passwd");
my $cmd = "curl -T $srcFile -u $cred " . testDirUrl(); my $cmd = "curl -T $srcFile -u $cred " . testDirUrl().$name;
my $script = "/tmp/post_update_script.sh"; my $script = "/tmp/post_update_script.sh";
open SC, ">$script" || die("Can not create script file"); open SC, ">$script" || die("Can not create script file");
print SC "#!/bin/bash\n"; print SC "#!/bin/bash\n";
@ -48,11 +50,11 @@ sub createPostUpdateScript()
return $script; return $script;
} }
sub getETagFromJournal($) sub getETagFromJournal($$)
{ {
my ($num) = @_; my ($name,$num) = @_;
my $sql = "sqlite3 " . localDir() . ".csync_journal.db \"SELECT md5 FROM metadata WHERE path='BIG.file';\""; my $sql = "sqlite3 " . localDir() . ".csync_journal.db \"SELECT md5 FROM metadata WHERE path='$name';\"";
open(my $fh, '-|', $sql) or die $!; open(my $fh, '-|', $sql) or die $!;
my $etag = <$fh>; my $etag = <$fh>;
close $fh; close $fh;
@ -61,14 +63,14 @@ sub getETagFromJournal($)
return $etag; return $etag;
} }
sub chunkFileTest( $$ ) sub chunkFileTest( $$ )
{ {
my ($name, $size) = @_; my ($name, $size) = @_;
# Big file chunking # Big file chunking
createLocalFile( localDir().$name, $size ); createLocalFile( localDir().$name, $size );
assert( -e localDir().$name ); assert( -e localDir().$name );
my $bigMd5 = md5OfFile( localDir().$name ); my $bigMd5 = md5OfFile( localDir().$name );
csync(); csync();
@ -89,26 +91,39 @@ sub chunkFileTest( $$ )
} }
printInfo("Big file that needs chunking with default chunk size"); printInfo("Big file that needs chunking with default chunk size");
chunkFileTest( "BIG.file", 23251233 ); chunkFileTest( "BIG1.file", 23251233 );
printInfo("Update the existing file and trigger reupload"); printInfo("Update the existing file and trigger reupload");
# change the existing file again -> update # change the existing file again -> update
chunkFileTest( "BIG.file", 21762122 ); chunkFileTest( "BIG2.file", 21762122 );
printInfo("Cause a precondition failed error"); printInfo("Cause a precondition failed error");
# Now overwrite the existing file to change it # Now overwrite the existing file to change it
createLocalFile( localDir()."BIG.file", 21832199 ); createLocalFile( localDir()."BIG3.file", 21832 );
sleep(2);
csync();
createLocalFile( localDir().'BIG3.file', 34323 );
sleep(2);
# and create a post update script # and create a post update script
my $script = createPostUpdateScript(); my $script = createPostUpdateScript('BIG3.file');
$ENV{'OWNCLOUD_POST_UPDATE_SCRIPT'} = $script; $ENV{'OWNCLOUD_POST_UPDATE_SCRIPT'} = $script;
# Save the etag before the sync # Save the etag before the sync
my $firstETag = getETagFromJournal('First'); my $firstETag = getETagFromJournal('BIG3.file', 'First');
csync(); # Sync, which ends in a precondition failed error sleep(2);
csync(); # Sync, which ends in a precondition failed error
# get the etag again. It has to be unchanged because of the error. # get the etag again. It has to be unchanged because of the error.
my $secondETag = getETagFromJournal('Second'); my $secondETag = getETagFromJournal('BIG3.file', 'Second');
assert( $firstETag eq $secondETag, "Different ETags, no precondition error." );
# Now the result is that there is a conflict file because since 1.7
# the sync is stopped on preconditoin failed and done again.
my $seen = 0;
opendir(my $dh, localDir() );
while(readdir $dh) {
$seen = 1 if ( /BIG3_conflict.*\.file/ );
}
closedir $dh;
assert( $seen == 1, "No conflict file created on precondition failed!" );
unlink($script); unlink($script);
# Set a custom chunk size in environment. # Set a custom chunk size in environment.

View file

@ -48,6 +48,7 @@ mkdir($tmpdir);
createLocalFile( $tmpdir . "HELLO.dat", 100 ); createLocalFile( $tmpdir . "HELLO.dat", 100 );
createLocalFile( $tmpdir . "Hello.dat", 150 ); createLocalFile( $tmpdir . "Hello.dat", 150 );
createLocalFile( $tmpdir . "Normal.dat", 110 ); createLocalFile( $tmpdir . "Normal.dat", 110 );
createLocalFile( $tmpdir . "test.dat", 170 );
#put them in some directories #put them in some directories
createRemoteDir( "dir" ); createRemoteDir( "dir" );
@ -73,14 +74,20 @@ assertLocalAndRemoteDir( '', 0);
printInfo( "Renaming one file to the same name as another one with different casing" ); printInfo( "Renaming one file to the same name as another one with different casing" );
moveRemoteFile( 'dir/Hello.dat', 'dir/NORMAL.dat'); moveRemoteFile( 'dir/Hello.dat', 'dir/NORMAL.dat');
moveRemoteFile( 'dir/test.dat', 'dir/TEST.dat');
csync(); csync();
#It should not have do the move # Hello -> NORMAL should not have do the move since the case conflict
assert( -e localDir() . 'dir/Hello.dat' ); assert( -e localDir() . 'dir/Hello.dat' );
assert( !-e localDir() . 'dir/NORMAL.dat' ); assert( !-e localDir() . 'dir/NORMAL.dat' );
assert( -e localDir() . 'dir/Normal.dat' ); assert( -e localDir() . 'dir/Normal.dat' );
#test->TEST should have been worked.
assert( -e localDir() . 'dir/TEST.dat' );
assert( !-e localDir() . 'dir/test.dat' );
printInfo( "Another directory with the same name but different casing is created" ); printInfo( "Another directory with the same name but different casing is created" );
createRemoteDir( "DIR" ); createRemoteDir( "DIR" );

View file

@ -3,7 +3,7 @@ if(APPLE)
add_custom_target( mac_overlayplugin ALL add_custom_target( mac_overlayplugin ALL
xcodebuild -workspace ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/OwnCloud.xcworkspace xcodebuild -workspace ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/OwnCloud.xcworkspace
-scheme OwnCloudFinder.osax SYMROOT=${CMAKE_CURRENT_BINARY_DIR} archive -scheme OwnCloudFinder.osax SYMROOT=${CMAKE_CURRENT_BINARY_DIR} archive
COMMENT building Mac Overlay iccons) COMMENT building Mac Overlay icons)
INSTALL( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Release/OwnCloudFinder.osax/Contents INSTALL( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Release/OwnCloudFinder.osax/Contents
DESTINATION ${CMAKE_INSTALL_PREFIX}/Library/ScriptingAdditions/OwnCloudFinder.osax/ ) DESTINATION ${CMAKE_INSTALL_PREFIX}/Library/ScriptingAdditions/OwnCloudFinder.osax/ )

View file

@ -118,7 +118,7 @@ static ContentManager* sharedInstance = nil;
if (![_fileNamesCache objectForKey:normalizedPath] || ![[_fileNamesCache objectForKey:normalizedPath] isEqualTo:res]) { if (![_fileNamesCache objectForKey:normalizedPath] || ![[_fileNamesCache objectForKey:normalizedPath] isEqualTo:res]) {
[_fileNamesCache setObject:res forKey:normalizedPath]; [_fileNamesCache setObject:res forKey:normalizedPath];
// NSLog(@"SET value %d", [res intValue]); //NSLog(@"SET value %d %@", [res intValue], normalizedPath);
_hasChangedContent = YES; _hasChangedContent = YES;
[self performSelector:@selector(repaintAllWindowsIfNeeded) withObject:0 afterDelay:1.0]; // 1 sec [self performSelector:@selector(repaintAllWindowsIfNeeded) withObject:0 afterDelay:1.0]; // 1 sec
} }
@ -139,12 +139,12 @@ static ContentManager* sharedInstance = nil;
} }
NSString* normalizedPath = [path decomposedStringWithCanonicalMapping]; NSString* normalizedPath = [path decomposedStringWithCanonicalMapping];
if (![[RequestManager sharedInstance] isRegisteredPath:normalizedPath]) { if (![[RequestManager sharedInstance] isRegisteredPath:normalizedPath isDirectory:isDir]) {
return [NSNumber numberWithInt:0]; return [NSNumber numberWithInt:0];
} }
NSNumber* result = [_fileNamesCache objectForKey:normalizedPath]; NSNumber* result = [_fileNamesCache objectForKey:normalizedPath];
// NSLog(@"XXXXXXX Asking for icon for path %@ = %d",path, [result intValue]); // NSLog(@"XXXXXXX Asking for icon for path %@ = %d",normalizedPath, [result intValue]);
if( result == nil ) { if( result == nil ) {
// start the async call // start the async call
@ -185,22 +185,29 @@ static ContentManager* sharedInstance = nil;
} }
if( [keysToDelete count] > 0 ) { if( [keysToDelete count] > 0 ) {
NSLog( @"Entries to delete: %d", [keysToDelete count]); NSLog( @"Entries to delete: %lu", (unsigned long)[keysToDelete count]);
[_fileNamesCache removeObjectsForKeys:keysToDelete]; [_fileNamesCache removeObjectsForKeys:keysToDelete];
} }
} }
- (void)reFetchFileNameCacheForPath:(NSString*)path - (void)reFetchFileNameCacheForPath:(NSString*)path
{ {
NSLog(@"%@", NSStringFromSelector(_cmd)); NSLog(@"%@", NSStringFromSelector(_cmd));
for (id p in [_fileNamesCache keyEnumerator]) { for (id p in [_fileNamesCache keyEnumerator]) {
if ( path && [p hasPrefix:path] ) { if ( path && [p hasPrefix:path] ) {
NSNumber *askState = [[RequestManager sharedInstance] askForIcon:p isDirectory:false]; // FIXME [[RequestManager sharedInstance] askForIcon:p isDirectory:false]; // FIXME isDirectory parameter
//[_fileNamesCache setObject:askState forKey:p]; //[_fileNamesCache setObject:askState forKey:p]; We don't do this since we want to keep the old icon meanwhile
NSLog(@"%@ %@", NSStringFromSelector(_cmd), p); //NSLog(@"%@ %@", NSStringFromSelector(_cmd), p);
} }
} }
// Ask for directory itself
if ([path hasSuffix:@"/"]) {
path = [path substringToIndex:path.length - 1];
}
[[RequestManager sharedInstance] askForIcon:path isDirectory:true];
//NSLog(@"%@ %@", NSStringFromSelector(_cmd), path);
} }
@ -226,7 +233,7 @@ static ContentManager* sharedInstance = nil;
- (void)repaintAllWindowsIfNeeded - (void)repaintAllWindowsIfNeeded
{ {
if (!_hasChangedContent) { if (!_hasChangedContent) {
NSLog(@"%@ Repaint scheduled but not needed", NSStringFromSelector(_cmd)); //NSLog(@"%@ Repaint scheduled but not needed", NSStringFromSelector(_cmd));
return; return;
} }

View file

@ -0,0 +1,24 @@
//
// FinishedIconCache.h
// OwnCloudFinder
//
// Created by Markus Goetz on 01/10/14.
//
//
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
@interface FinishedIconCache : NSObject {
NSCache *_cache;
long long _hits;
long long _misses;
}
+ (FinishedIconCache*)sharedInstance;
- (NSImage*)getIcon:(NSString*)fileName overlayIconIndex:(int)idx width:(float)w height:(float)h;
- (void)registerIcon:(NSImage*)icon withFileName:(NSString*)fileName overlayIconIndex:(int)idx width:(float)w height:(float)h;
@end

View file

@ -0,0 +1,91 @@
//
// FinishedIconCache.m
// OwnCloudFinder
//
// Created by Markus Goetz on 01/10/14.
//
//
#import "FinishedIconCache.h"
@interface FinishedIconCacheItem : NSObject
@property (nonatomic, strong) NSImage *icon;
@property (nonatomic) NSTimeInterval maxAge;
@end
@implementation FinishedIconCacheItem
@synthesize icon;
@synthesize maxAge;
- (void)dealloc {
//NSLog(@"RELEASE %@ %@", self, self.icon);
if (self.icon) {
[self->icon release];
}
[super dealloc];
}
@end
@implementation FinishedIconCache
static FinishedIconCache* sharedInstance = nil;
- init
{
self = [super init];
if (self)
{
_cache = [[NSCache alloc] init];
_cache.totalCostLimit = (2880 * 1800); // mbp15 screen size
_hits = 0;
_misses = 0;
}
return self;
}
- (void)dealloc
{
[_cache dealloc];
[super dealloc];
}
+ (FinishedIconCache*)sharedInstance
{
@synchronized(self)
{
if (sharedInstance == nil)
{
sharedInstance = [[self alloc] init];
}
}
return sharedInstance;
}
- (NSImage*)getIcon:(NSString*)fileName overlayIconIndex:(int)idx width:(float)w height:(float)h
{
NSString *cacheKey = [NSString stringWithFormat:@"%@--%d--%f%f", fileName, idx, w,h];
FinishedIconCacheItem *item = [_cache objectForKey:cacheKey];
if (item) {
if (item.maxAge > [[NSDate date] timeIntervalSinceReferenceDate]) {
_hits++;
return item.icon;
}
}
_misses++;
return NULL;
}
- (void)registerIcon:(NSImage*)icon withFileName:(NSString*)fileName overlayIconIndex:(int)idx width:(float)w height:(float)h
{
NSString *cacheKey = [NSString stringWithFormat:@"%@--%d--%f%f", fileName, idx, w, h];
FinishedIconCacheItem *item = [[FinishedIconCacheItem alloc] init];
item.icon = icon;
// max age between 1 sec and 5 sec
item.maxAge = [[NSDate date] timeIntervalSinceReferenceDate] + 1.0 + 4.0*((double)arc4random() / 0x100000000);
[_cache setObject:item forKey:cacheKey cost:w*h];
[item release];
//NSLog(@"CACHE hit/miss ratio: %f", (float)_hits/(float)_misses);
}
@end

View file

@ -3,25 +3,77 @@
// //
// This class is in the public domain. // This class is in the public domain.
// Originally created by Robbie Hanson in Q3 2010. // Originally created by Robbie Hanson in Q3 2010.
// Updated and maintained by Deusty LLC and the Mac development community. // Updated and maintained by Deusty LLC and the Apple development community.
// //
// http://code.google.com/p/cocoaasyncsocket/ // https://github.com/robbiehanson/CocoaAsyncSocket
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <Security/Security.h> #import <Security/Security.h>
#import <Security/SecureTransport.h>
#import <dispatch/dispatch.h> #import <dispatch/dispatch.h>
@class GCDAsyncReadPacket; @class GCDAsyncReadPacket;
@class GCDAsyncWritePacket; @class GCDAsyncWritePacket;
@class GCDAsyncSocketPreBuffer;
#if TARGET_OS_IPHONE
// Compiling for iOS
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 // iOS 5.0 supported
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000 // iOS 5.0 supported and required
#define IS_SECURE_TRANSPORT_AVAILABLE YES
#define SECURE_TRANSPORT_MAYBE_AVAILABLE 1
#define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 0
#else // iOS 5.0 supported but not required
#ifndef NSFoundationVersionNumber_iPhoneOS_5_0
#define NSFoundationVersionNumber_iPhoneOS_5_0 881.00
#endif
#define IS_SECURE_TRANSPORT_AVAILABLE (NSFoundationVersionNumber >= NSFoundationVersionNumber_iPhoneOS_5_0)
#define SECURE_TRANSPORT_MAYBE_AVAILABLE 1
#define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 1
#endif
#else // iOS 5.0 not supported
#define IS_SECURE_TRANSPORT_AVAILABLE NO
#define SECURE_TRANSPORT_MAYBE_AVAILABLE 0
#define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 1
#endif
#else
// Compiling for Mac OS X
#define IS_SECURE_TRANSPORT_AVAILABLE YES
#define SECURE_TRANSPORT_MAYBE_AVAILABLE 1
#define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 0
#endif
extern NSString *const GCDAsyncSocketException; extern NSString *const GCDAsyncSocketException;
extern NSString *const GCDAsyncSocketErrorDomain; extern NSString *const GCDAsyncSocketErrorDomain;
#if !TARGET_OS_IPHONE extern NSString *const GCDAsyncSocketQueueName;
extern NSString *const GCDAsyncSocketThreadName;
#if SECURE_TRANSPORT_MAYBE_AVAILABLE
extern NSString *const GCDAsyncSocketSSLCipherSuites; extern NSString *const GCDAsyncSocketSSLCipherSuites;
#if TARGET_OS_IPHONE
extern NSString *const GCDAsyncSocketSSLProtocolVersionMin;
extern NSString *const GCDAsyncSocketSSLProtocolVersionMax;
#else
extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters; extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters;
#endif #endif
#endif
enum GCDAsyncSocketError enum GCDAsyncSocketError
{ {
@ -42,51 +94,6 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface GCDAsyncSocket : NSObject @interface GCDAsyncSocket : NSObject
{
uint32_t flags;
uint16_t config;
id delegate;
dispatch_queue_t delegateQueue;
int socket4FD;
int socket6FD;
int connectIndex;
NSData * connectInterface4;
NSData * connectInterface6;
dispatch_queue_t socketQueue;
dispatch_source_t accept4Source;
dispatch_source_t accept6Source;
dispatch_source_t connectTimer;
dispatch_source_t readSource;
dispatch_source_t writeSource;
dispatch_source_t readTimer;
dispatch_source_t writeTimer;
NSMutableArray *readQueue;
NSMutableArray *writeQueue;
GCDAsyncReadPacket *currentRead;
GCDAsyncWritePacket *currentWrite;
unsigned long socketFDBytesAvailable;
NSMutableData *partialReadBuffer;
#if TARGET_OS_IPHONE
CFStreamClientContext streamContext;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
#else
SSLContextRef sslContext;
NSMutableData *sslReadBuffer;
size_t sslWriteCachedLength;
#endif
id userData;
}
/** /**
* GCDAsyncSocket uses the standard delegate paradigm, * GCDAsyncSocket uses the standard delegate paradigm,
@ -99,6 +106,8 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
* The socket queue is optional. * The socket queue is optional.
* If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue. * If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue.
* If you choose to provide a socket queue, the socket queue must not be a concurrent queue. * If you choose to provide a socket queue, the socket queue must not be a concurrent queue.
* If you choose to provide a socket queue, and the socket queue has a configured target queue,
* then please see the discussion for the method markSocketQueueTargetQueue.
* *
* The delegate queue and socket queue can optionally be the same. * The delegate queue and socket queue can optionally be the same.
**/ **/
@ -121,39 +130,6 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; - (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; - (void)synchronouslySetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
/**
* Traditionally sockets are not closed until the conversation is over.
* However, it is technically possible for the remote enpoint to close its write stream.
* Our socket would then be notified that there is no more data to be read,
* but our socket would still be writeable and the remote endpoint could continue to receive our data.
*
* The argument for this confusing functionality stems from the idea that a client could shut down its
* write stream after sending a request to the server, thus notifying the server there are to be no further requests.
* In practice, however, this technique did little to help server developers.
*
* To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close
* and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell
* is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work.
* Otherwise an error will be occur shortly (when the remote end sends us a RST packet).
*
* In addition to the technical challenges and confusion, many high level socket/stream API's provide
* no support for dealing with the problem. If the read stream is closed, the API immediately declares the
* socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does.
* It might sound like poor design at first, but in fact it simplifies development.
*
* The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket.
* Thus it actually makes sense to close the socket at this point.
* And in fact this is what most networking developers want and expect to happen.
* However, if you are writing a server that interacts with a plethora of clients,
* you might encounter a client that uses the discouraged technique of shutting down its write stream.
* If this is the case, you can set this property to NO,
* and make use of the socketDidCloseReadStream delegate method.
*
* The default value is YES.
**/
- (BOOL)autoDisconnectOnClosedReadStream;
- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag;
/** /**
* By default, both IPv4 and IPv6 are enabled. * By default, both IPv4 and IPv6 are enabled.
* *
@ -211,6 +187,15 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
**/ **/
- (BOOL)acceptOnInterface:(NSString *)interface port:(uint16_t)port error:(NSError **)errPtr; - (BOOL)acceptOnInterface:(NSString *)interface port:(uint16_t)port error:(NSError **)errPtr;
/**
* Tells the socket to begin listening and accepting connections on the unix domain at the given url.
* When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it,
* and the socket:didAcceptNewSocket: delegate method will be invoked.
*
* The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
**/
- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr;
#pragma mark Connecting #pragma mark Connecting
/** /**
@ -326,6 +311,10 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
viaInterface:(NSString *)interface viaInterface:(NSString *)interface
withTimeout:(NSTimeInterval)timeout withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr; error:(NSError **)errPtr;
/**
* Connects to the unix domain socket at the given url, using the specified timeout.
*/
- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
#pragma mark Disconnecting #pragma mark Disconnecting
@ -389,6 +378,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
**/ **/
- (NSString *)connectedHost; - (NSString *)connectedHost;
- (uint16_t)connectedPort; - (uint16_t)connectedPort;
- (NSURL *)connectedUrl;
- (NSString *)localHost; - (NSString *)localHost;
- (uint16_t)localPort; - (uint16_t)localPort;
@ -646,6 +636,12 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
maxLength:(NSUInteger)length maxLength:(NSUInteger)length
tag:(long)tag; tag:(long)tag;
/**
* Returns progress of the current read, from 0.0 to 1.0, or NaN if no current read (use isnan() to check).
* The parameters "tag", "done" and "total" will be filled in if they aren't NULL.
**/
- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr;
#pragma mark Writing #pragma mark Writing
/** /**
@ -667,6 +663,12 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
**/ **/
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
/**
* Returns progress of the current write, from 0.0 to 1.0, or NaN if no current write (use isnan() to check).
* The parameters "tag", "done" and "total" will be filled in if they aren't NULL.
**/
- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr;
#pragma mark Security #pragma mark Security
/** /**
@ -678,7 +680,8 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
* Any reads or writes scheduled after this method is called will occur over the secured connection. * Any reads or writes scheduled after this method is called will occur over the secured connection.
* *
* The possible keys and values for the TLS settings are well documented. * The possible keys and values for the TLS settings are well documented.
* Some possible keys are: * Standard keys are:
*
* - kCFStreamSSLLevel * - kCFStreamSSLLevel
* - kCFStreamSSLAllowsExpiredCertificates * - kCFStreamSSLAllowsExpiredCertificates
* - kCFStreamSSLAllowsExpiredRoots * - kCFStreamSSLAllowsExpiredRoots
@ -688,6 +691,18 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
* - kCFStreamSSLCertificates * - kCFStreamSSLCertificates
* - kCFStreamSSLIsServer * - kCFStreamSSLIsServer
* *
* If SecureTransport is available on iOS:
*
* - GCDAsyncSocketSSLCipherSuites
* - GCDAsyncSocketSSLProtocolVersionMin
* - GCDAsyncSocketSSLProtocolVersionMax
*
* If SecureTransport is available on Mac OS X:
*
* - GCDAsyncSocketSSLCipherSuites
* - GCDAsyncSocketSSLDiffieHellmanParameters;
*
*
* Please refer to Apple's documentation for associated values, as well as other possible keys. * Please refer to Apple's documentation for associated values, as well as other possible keys.
* *
* If you pass in nil or an empty dictionary, the default settings will be used. * If you pass in nil or an empty dictionary, the default settings will be used.
@ -712,6 +727,114 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
#pragma mark Advanced #pragma mark Advanced
/**
* Traditionally sockets are not closed until the conversation is over.
* However, it is technically possible for the remote enpoint to close its write stream.
* Our socket would then be notified that there is no more data to be read,
* but our socket would still be writeable and the remote endpoint could continue to receive our data.
*
* The argument for this confusing functionality stems from the idea that a client could shut down its
* write stream after sending a request to the server, thus notifying the server there are to be no further requests.
* In practice, however, this technique did little to help server developers.
*
* To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close
* and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell
* is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work.
* Otherwise an error will be occur shortly (when the remote end sends us a RST packet).
*
* In addition to the technical challenges and confusion, many high level socket/stream API's provide
* no support for dealing with the problem. If the read stream is closed, the API immediately declares the
* socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does.
* It might sound like poor design at first, but in fact it simplifies development.
*
* The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket.
* Thus it actually makes sense to close the socket at this point.
* And in fact this is what most networking developers want and expect to happen.
* However, if you are writing a server that interacts with a plethora of clients,
* you might encounter a client that uses the discouraged technique of shutting down its write stream.
* If this is the case, you can set this property to NO,
* and make use of the socketDidCloseReadStream delegate method.
*
* The default value is YES.
**/
- (BOOL)autoDisconnectOnClosedReadStream;
- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag;
/**
* GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
* In most cases, the instance creates this queue itself.
* However, to allow for maximum flexibility, the internal queue may be passed in the init method.
* This allows for some advanced options such as controlling socket priority via target queues.
* However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
*
* For example, imagine there are 2 queues:
* dispatch_queue_t socketQueue;
* dispatch_queue_t socketTargetQueue;
*
* If you do this (pseudo-code):
* socketQueue.targetQueue = socketTargetQueue;
*
* Then all socketQueue operations will actually get run on the given socketTargetQueue.
* This is fine and works great in most situations.
* But if you run code directly from within the socketTargetQueue that accesses the socket,
* you could potentially get deadlock. Imagine the following code:
*
* - (BOOL)socketHasSomething
* {
* __block BOOL result = NO;
* dispatch_block_t block = ^{
* result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
* }
* if (is_executing_on_queue(socketQueue))
* block();
* else
* dispatch_sync(socketQueue, block);
*
* return result;
* }
*
* What happens if you call this method from the socketTargetQueue? The result is deadlock.
* This is because the GCD API offers no mechanism to discover a queue's targetQueue.
* Thus we have no idea if our socketQueue is configured with a targetQueue.
* If we had this information, we could easily avoid deadlock.
* But, since these API's are missing or unfeasible, you'll have to explicitly set it.
*
* IF you pass a socketQueue via the init method,
* AND you've configured the passed socketQueue with a targetQueue,
* THEN you should pass the end queue in the target hierarchy.
*
* For example, consider the following queue hierarchy:
* socketQueue -> ipQueue -> moduleQueue
*
* This example demonstrates priority shaping within some server.
* All incoming client connections from the same IP address are executed on the same target queue.
* And all connections for a particular module are executed on the same target queue.
* Thus, the priority of all networking for the entire module can be changed on the fly.
* Additionally, networking traffic from a single IP cannot monopolize the module.
*
* Here's how you would accomplish something like that:
* - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
* {
* dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
* dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
*
* dispatch_set_target_queue(socketQueue, ipQueue);
* dispatch_set_target_queue(iqQueue, moduleQueue);
*
* return socketQueue;
* }
* - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
* {
* [clientConnections addObject:newSocket];
* [newSocket markSocketQueueTargetQueue:moduleQueue];
* }
*
* Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
* This is often NOT the case, as such queues are used solely for execution shaping.
**/
- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;
/** /**
* It's not thread-safe to access certain variables from outside the socket's internal queue. * It's not thread-safe to access certain variables from outside the socket's internal queue.
* *
@ -805,7 +928,9 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
**/ **/
- (BOOL)enableBackgroundingOnSocket; - (BOOL)enableBackgroundingOnSocket;
#else #endif
#if SECURE_TRANSPORT_MAYBE_AVAILABLE
/** /**
* This method is only available from within the context of a performBlock: invocation. * This method is only available from within the context of a performBlock: invocation.
@ -881,6 +1006,12 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
**/ **/
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port; - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;
/**
* Called when a socket connects and is ready for reading and writing.
* The host parameter will be an IP address, not a DNS name.
**/
- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url;
/** /**
* Called when a socket has completed reading the requested data into memory. * Called when a socket has completed reading the requested data into memory.
* Not called if there is an error. * Not called if there is an error.

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,7 @@
#import <objc/runtime.h> #import <objc/runtime.h>
#import "ContentManager.h" #import "ContentManager.h"
#import "IconCache.h" #import "IconCache.h"
#import "FinishedIconCache.h"
#import "IconOverlayHandlers.h" #import "IconOverlayHandlers.h"
#import "Finder/Finder.h" #import "Finder/Finder.h"
@ -69,7 +70,6 @@
NSURL* url = [node previewItemURL]; NSURL* url = [node previewItemURL];
NSError *error;
BOOL isDir; BOOL isDir;
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDir] == NO) { if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDir] == NO) {
NSLog(@"ERROR: Could not determine file type of %@", [url path]); NSLog(@"ERROR: Could not determine file type of %@", [url path]);
@ -77,21 +77,32 @@
} }
NSNumber* imageIndex = [[ContentManager sharedInstance] iconByPath:[url path] isDirectory:isDir]; NSNumber* imageIndex = [[ContentManager sharedInstance] iconByPath:[url path] isDirectory:isDir];
// NSLog(@"2 The icon index is %d", [imageIndex intValue]); //NSLog(@"2 The icon index is %d %@ %@", [imageIndex intValue], [url path], isDir ? @"isDir" : @"");
if ([imageIndex intValue] > 0) if ([imageIndex intValue] > 0)
{ {
NSImage* icon = [arg1 _nsImage]; NSImage* icon = [arg1 _nsImage];
[icon lockFocus]; // Use the short term icon cache that possibly has the finished icon
FinishedIconCache *finishedIconCache = [FinishedIconCache sharedInstance];
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort]; NSImage *finishedImage = [finishedIconCache getIcon:[url path] overlayIconIndex:imageIndex width:[icon size].width height:[icon size].height];
if (finishedImage) {
//NSLog(@"X Got finished image from cache %@ %@", finishedImage, [url path]);
return [[[IKImageWrapper alloc] initWithNSImage:finishedImage] autorelease];;
} else {
//NSLog(@"X Need to redraw %@", [url path]);
}
NSImage* iconimage = [[IconCache sharedInstance] getIcon:[NSNumber numberWithInt:[imageIndex intValue]]]; NSImage* iconimage = [[IconCache sharedInstance] getIcon:[NSNumber numberWithInt:[imageIndex intValue]]];
if (iconimage != nil) if (iconimage != nil)
{ {
[icon lockFocus];
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
CGRect destRect = CGRectMake(0, 0, [icon size].width, [icon size].height); CGRect destRect = CGRectMake(0, 0, [icon size].width, [icon size].height);
CGImageRef cgImage = [iconimage CGImageForProposedRect:&destRect CGImageRef cgImage = [iconimage CGImageForProposedRect:&destRect
context:[NSGraphicsContext currentContext] context:[NSGraphicsContext currentContext]
hints:nil]; hints:nil];
@ -103,9 +114,11 @@
NSLog(@"No image given!!!!!11 %@", [url path]); NSLog(@"No image given!!!!!11 %@", [url path]);
} }
[icon unlockFocus];
} }
[icon unlockFocus]; // Insert into cache
[finishedIconCache registerIcon:icon withFileName:[url path] overlayIconIndex:imageIndex width:[icon size].width height:[icon size].height];
return [[[IKImageWrapper alloc] initWithNSImage:icon] autorelease]; return [[[IKImageWrapper alloc] initWithNSImage:icon] autorelease];
} }

View file

@ -1,251 +0,0 @@
//
// JSONKit.h
// http://github.com/johnezang/JSONKit
// Dual licensed under either the terms of the BSD License, or alternatively
// under the terms of the Apache License, Version 2.0, as specified below.
//
/*
Copyright (c) 2011, John Engelhart
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Zang Industries nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
Copyright 2011 John Engelhart
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <stddef.h>
#include <stdint.h>
#include <limits.h>
#include <TargetConditionals.h>
#include <AvailabilityMacros.h>
#ifdef __OBJC__
#import <Foundation/NSArray.h>
#import <Foundation/NSData.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSError.h>
#import <Foundation/NSObjCRuntime.h>
#import <Foundation/NSString.h>
#endif // __OBJC__
#ifdef __cplusplus
extern "C" {
#endif
// For Mac OS X < 10.5.
#ifndef NSINTEGER_DEFINED
#define NSINTEGER_DEFINED
#if defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
typedef long NSInteger;
typedef unsigned long NSUInteger;
#define NSIntegerMin LONG_MIN
#define NSIntegerMax LONG_MAX
#define NSUIntegerMax ULONG_MAX
#else // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
typedef int NSInteger;
typedef unsigned int NSUInteger;
#define NSIntegerMin INT_MIN
#define NSIntegerMax INT_MAX
#define NSUIntegerMax UINT_MAX
#endif // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
#endif // NSINTEGER_DEFINED
#ifndef _JSONKIT_H_
#define _JSONKIT_H_
#if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__APPLE_CC__) && (__APPLE_CC__ >= 5465)
#define JK_DEPRECATED_ATTRIBUTE __attribute__((deprecated))
#else
#define JK_DEPRECATED_ATTRIBUTE
#endif
#define JSONKIT_VERSION_MAJOR 1
#define JSONKIT_VERSION_MINOR 4
typedef NSUInteger JKFlags;
/*
JKParseOptionComments : Allow C style // and /_* ... *_/ (without a _, obviously) comments in JSON.
JKParseOptionUnicodeNewlines : Allow Unicode recommended (?:\r\n|[\n\v\f\r\x85\p{Zl}\p{Zp}]) newlines.
JKParseOptionLooseUnicode : Normally the decoder will stop with an error at any malformed Unicode.
This option allows JSON with malformed Unicode to be parsed without reporting an error.
Any malformed Unicode is replaced with \uFFFD, or "REPLACEMENT CHARACTER".
*/
enum {
JKParseOptionNone = 0,
JKParseOptionStrict = 0,
JKParseOptionComments = (1 << 0),
JKParseOptionUnicodeNewlines = (1 << 1),
JKParseOptionLooseUnicode = (1 << 2),
JKParseOptionPermitTextAfterValidJSON = (1 << 3),
JKParseOptionValidFlags = (JKParseOptionComments | JKParseOptionUnicodeNewlines | JKParseOptionLooseUnicode | JKParseOptionPermitTextAfterValidJSON),
};
typedef JKFlags JKParseOptionFlags;
enum {
JKSerializeOptionNone = 0,
JKSerializeOptionPretty = (1 << 0),
JKSerializeOptionEscapeUnicode = (1 << 1),
JKSerializeOptionEscapeForwardSlashes = (1 << 4),
JKSerializeOptionValidFlags = (JKSerializeOptionPretty | JKSerializeOptionEscapeUnicode | JKSerializeOptionEscapeForwardSlashes),
};
typedef JKFlags JKSerializeOptionFlags;
#ifdef __OBJC__
typedef struct JKParseState JKParseState; // Opaque internal, private type.
// As a general rule of thumb, if you use a method that doesn't accept a JKParseOptionFlags argument, it defaults to JKParseOptionStrict
@interface JSONDecoder : NSObject {
JKParseState *parseState;
}
+ (id)decoder;
+ (id)decoderWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)initWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (void)clearCache;
// The parse... methods were deprecated in v1.4 in favor of the v1.4 objectWith... methods.
- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length: instead.
- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithUTF8String:length:error: instead.
// The NSData MUST be UTF8 encoded JSON.
- (id)parseJSONData:(NSData *)jsonData JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData: instead.
- (id)parseJSONData:(NSData *)jsonData error:(NSError **)error JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4. Use objectWithData:error: instead.
// Methods that return immutable collection objects.
- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length;
- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error;
// The NSData MUST be UTF8 encoded JSON.
- (id)objectWithData:(NSData *)jsonData;
- (id)objectWithData:(NSData *)jsonData error:(NSError **)error;
// Methods that return mutable collection objects.
- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length;
- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error;
// The NSData MUST be UTF8 encoded JSON.
- (id)mutableObjectWithData:(NSData *)jsonData;
- (id)mutableObjectWithData:(NSData *)jsonData error:(NSError **)error;
@end
////////////
#pragma mark Deserializing methods
////////////
@interface NSString (JSONKitDeserializing)
- (id)objectFromJSONString;
- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
- (id)mutableObjectFromJSONString;
- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
@end
@interface NSData (JSONKitDeserializing)
// The NSData MUST be UTF8 encoded JSON.
- (id)objectFromJSONData;
- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
- (id)mutableObjectFromJSONData;
- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
@end
////////////
#pragma mark Serializing methods
////////////
@interface NSString (JSONKitSerializing)
// Convenience methods for those that need to serialize the receiving NSString (i.e., instead of having to serialize a NSArray with a single NSString, you can "serialize to JSON" just the NSString).
// Normally, a string that is serialized to JSON has quotation marks surrounding it, which you may or may not want when serializing a single string, and can be controlled with includeQuotes:
// includeQuotes:YES `a "test"...` -> `"a \"test\"..."`
// includeQuotes:NO `a "test"...` -> `a \"test\"...`
- (NSData *)JSONData; // Invokes JSONDataWithOptions:JKSerializeOptionNone includeQuotes:YES
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error;
- (NSString *)JSONString; // Invokes JSONStringWithOptions:JKSerializeOptionNone includeQuotes:YES
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error;
@end
@interface NSArray (JSONKitSerializing)
- (NSData *)JSONData;
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
- (NSString *)JSONString;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
@end
@interface NSDictionary (JSONKitSerializing)
- (NSData *)JSONData;
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
- (NSString *)JSONString;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
@end
#ifdef __BLOCKS__
@interface NSArray (JSONKitSerializingBlockAdditions)
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
@end
@interface NSDictionary (JSONKitSerializingBlockAdditions)
- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
@end
#endif
#endif // __OBJC__
#endif // _JSONKIT_H_
#ifdef __cplusplus
} // extern "C"
#endif

File diff suppressed because it is too large Load diff

View file

@ -7,9 +7,9 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
0B13ECAE173C687400548DA1 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */; }; 0B13ECAE173C687400548DA1 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
0B13ECAF173C687900548DA1 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BFC9ACB173C57E400CDD329 /* Security.framework */; }; 0B13ECAF173C687900548DA1 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BFC9ACB173C57E400CDD329 /* Security.framework */; };
0BFAF21C16F8E6C10017EA7E /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BFAF21B16F8E6C10017EA7E /* JSONKit.m */; }; 5BB74A8719DBF9BB001BAAAC /* FinishedIconCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BB74A8619DBF9BB001BAAAC /* FinishedIconCache.m */; };
692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */; }; 692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */; };
692C18A9166617F500BF6A53 /* IconOverlayHandlers.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18A8166617F500BF6A53 /* IconOverlayHandlers.m */; }; 692C18A9166617F500BF6A53 /* IconOverlayHandlers.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18A8166617F500BF6A53 /* IconOverlayHandlers.m */; };
692C18AC1666392700BF6A53 /* MenuManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18AB1666392700BF6A53 /* MenuManager.m */; }; 692C18AC1666392700BF6A53 /* MenuManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18AB1666392700BF6A53 /* MenuManager.m */; };
@ -29,9 +29,9 @@
0B13ECAC173C686900548DA1 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = "<group>"; }; 0B13ECAC173C686900548DA1 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = "<group>"; };
0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = "<group>"; }; 0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = "<group>"; };
0B2BF60B176A43DB001246CD /* Finder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Finder.h; sourceTree = "<group>"; }; 0B2BF60B176A43DB001246CD /* Finder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Finder.h; sourceTree = "<group>"; };
0BFAF21A16F8E6C10017EA7E /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONKit.h; sourceTree = "<group>"; };
0BFAF21B16F8E6C10017EA7E /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONKit.m; sourceTree = "<group>"; };
0BFC9ACB173C57E400CDD329 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 0BFC9ACB173C57E400CDD329 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
5BB74A8519DBF9BB001BAAAC /* FinishedIconCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FinishedIconCache.h; sourceTree = "<group>"; };
5BB74A8619DBF9BB001BAAAC /* FinishedIconCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FinishedIconCache.m; sourceTree = "<group>"; };
692C18A316660C4600BF6A53 /* ContextMenuHandlers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContextMenuHandlers.h; sourceTree = "<group>"; }; 692C18A316660C4600BF6A53 /* ContextMenuHandlers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContextMenuHandlers.h; sourceTree = "<group>"; };
692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContextMenuHandlers.m; sourceTree = "<group>"; }; 692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContextMenuHandlers.m; sourceTree = "<group>"; };
692C18A7166617F500BF6A53 /* IconOverlayHandlers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IconOverlayHandlers.h; sourceTree = "<group>"; }; 692C18A7166617F500BF6A53 /* IconOverlayHandlers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IconOverlayHandlers.h; sourceTree = "<group>"; };
@ -104,7 +104,6 @@
children = ( children = (
0B2BF60A176A43DB001246CD /* Finder */, 0B2BF60A176A43DB001246CD /* Finder */,
0B08BAC21759627700C8351E /* GCDAsyncSocket */, 0B08BAC21759627700C8351E /* GCDAsyncSocket */,
0BFAF21916F8E6910017EA7E /* JSONKit */,
8C37DD99161593BD00016A95 /* FinderHook.h */, 8C37DD99161593BD00016A95 /* FinderHook.h */,
8C37DD9A161593BD00016A95 /* FinderHook.m */, 8C37DD9A161593BD00016A95 /* FinderHook.m */,
692C18A316660C4600BF6A53 /* ContextMenuHandlers.h */, 692C18A316660C4600BF6A53 /* ContextMenuHandlers.h */,
@ -117,6 +116,8 @@
69948B351636D50E0093B6CE /* ContentManager.m */, 69948B351636D50E0093B6CE /* ContentManager.m */,
8C99F6921622D145002D2135 /* IconCache.h */, 8C99F6921622D145002D2135 /* IconCache.h */,
8C99F6931622D145002D2135 /* IconCache.m */, 8C99F6931622D145002D2135 /* IconCache.m */,
5BB74A8519DBF9BB001BAAAC /* FinishedIconCache.h */,
5BB74A8619DBF9BB001BAAAC /* FinishedIconCache.m */,
692C18AA1666392700BF6A53 /* MenuManager.h */, 692C18AA1666392700BF6A53 /* MenuManager.h */,
692C18AB1666392700BF6A53 /* MenuManager.m */, 692C18AB1666392700BF6A53 /* MenuManager.m */,
); );
@ -140,15 +141,6 @@
path = Finder; path = Finder;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0BFAF21916F8E6910017EA7E /* JSONKit */ = {
isa = PBXGroup;
children = (
0BFAF21A16F8E6C10017EA7E /* JSONKit.h */,
0BFAF21B16F8E6C10017EA7E /* JSONKit.m */,
);
name = JSONKit;
sourceTree = "<group>";
};
19C28FB6FE9D52B211CA2CBB /* Products */ = { 19C28FB6FE9D52B211CA2CBB /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -226,10 +218,10 @@
8C99F6941622D145002D2135 /* IconCache.m in Sources */, 8C99F6941622D145002D2135 /* IconCache.m in Sources */,
69948B361636D50E0093B6CE /* ContentManager.m in Sources */, 69948B361636D50E0093B6CE /* ContentManager.m in Sources */,
6993878616494C000044E4DF /* RequestManager.m in Sources */, 6993878616494C000044E4DF /* RequestManager.m in Sources */,
5BB74A8719DBF9BB001BAAAC /* FinishedIconCache.m in Sources */,
692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */, 692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */,
692C18A9166617F500BF6A53 /* IconOverlayHandlers.m in Sources */, 692C18A9166617F500BF6A53 /* IconOverlayHandlers.m in Sources */,
692C18AC1666392700BF6A53 /* MenuManager.m in Sources */, 692C18AC1666392700BF6A53 /* MenuManager.m in Sources */,
0BFAF21C16F8E6C10017EA7E /* JSONKit.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -302,8 +294,9 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
COPY_PHASE_STRIP = YES; COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.7; MACOSX_DEPLOYMENT_TARGET = 10.7;

View file

@ -31,7 +31,7 @@
+ (RequestManager*)sharedInstance; + (RequestManager*)sharedInstance;
- (BOOL)isRegisteredPath:(NSString*)path; - (BOOL)isRegisteredPath:(NSString*)path isDirectory:(BOOL)isDir;
- (void)askOnSocket:(NSString*)path query:(NSString*)verb; - (void)askOnSocket:(NSString*)path query:(NSString*)verb;
- (NSNumber*)askForIcon:(NSString*)path isDirectory:(BOOL)isDir; - (NSNumber*)askForIcon:(NSString*)path isDirectory:(BOOL)isDir;
- (void)menuItemClicked:(NSDictionary*)actionDictionary; - (void)menuItemClicked:(NSDictionary*)actionDictionary;

View file

@ -77,14 +77,20 @@ static RequestManager* sharedInstance = nil;
} }
- (BOOL)isRegisteredPath:(NSString*)path - (BOOL)isRegisteredPath:(NSString*)path isDirectory:(BOOL)isDir
{ {
// check if the file in question is underneath a registered directory // check if the file in question is underneath a registered directory
NSArray *regPathes = [_registeredPathes allKeys]; NSArray *regPathes = [_registeredPathes allKeys];
BOOL registered = NO; BOOL registered = NO;
NSString* checkPath = [NSString stringWithString:path];
if (isDir) {
// append a slash
checkPath = [path stringByAppendingString:@"/"];
}
for( NSString *regPath in regPathes ) { for( NSString *regPath in regPathes ) {
if( [path hasPrefix:regPath]) { if( [checkPath hasPrefix:regPath]) {
// the path was registered // the path was registered
registered = YES; registered = YES;
break; break;
@ -99,7 +105,7 @@ static RequestManager* sharedInstance = nil;
NSString *verb = @"RETRIEVE_FILE_STATUS"; NSString *verb = @"RETRIEVE_FILE_STATUS";
NSNumber *res = [NSNumber numberWithInt:0]; NSNumber *res = [NSNumber numberWithInt:0];
if( [self isRegisteredPath:path] ) { if( [self isRegisteredPath:path isDirectory:isDir] ) {
if( _isConnected ) { if( _isConnected ) {
if(isDir) { if(isDir) {
verb = @"RETRIEVE_FOLDER_STATUS"; verb = @"RETRIEVE_FOLDER_STATUS";
@ -121,30 +127,37 @@ static RequestManager* sharedInstance = nil;
- (void)socket:(GCDAsyncSocket*)socket didReadData:(NSData*)data withTag:(long)tag - (void)socket:(GCDAsyncSocket*)socket didReadData:(NSData*)data withTag:(long)tag
{ {
NSArray *chunks;
NSString *answer = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSString *answer = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray *chunks = nil;
if (answer != nil && [answer length] > 0) { if (answer != nil && [answer length] > 0) {
// cut a trailing newline // cut a trailing newline
answer = [answer substringToIndex:[answer length] - 1]; answer = [answer substringToIndex:[answer length] - 1];
chunks = [answer componentsSeparatedByString: @":"]; chunks = [answer componentsSeparatedByString: @":"];
} }
NSLog(@"READ from socket (%ld): <%@>", tag, answer);
ContentManager *contentman = [ContentManager sharedInstance]; ContentManager *contentman = [ContentManager sharedInstance];
if( [chunks count] > 0 && tag == READ_TAG ) { if( chunks && [chunks count] > 0 && tag == READ_TAG ) {
NSLog(@"READ from socket (%ld): <%@>", tag, answer);
if( [[chunks objectAtIndex:0] isEqualToString:@"STATUS"] ) { if( [[chunks objectAtIndex:0] isEqualToString:@"STATUS"] ) {
[contentman setResultForPath:[chunks objectAtIndex:2] result:[chunks objectAtIndex:1]]; NSString *path = [chunks objectAtIndex:2];
if( [chunks count] > 3 ) {
for( int i = 2; i < [chunks count]-1; i++ ) {
path = [NSString stringWithFormat:@"%@:%@",
path, [chunks objectAtIndex:i+1] ];
}
}
[contentman setResultForPath:path result:[chunks objectAtIndex:1]];
} else if( [[chunks objectAtIndex:0] isEqualToString:@"UPDATE_VIEW"] ) { } else if( [[chunks objectAtIndex:0] isEqualToString:@"UPDATE_VIEW"] ) {
NSString *path = [chunks objectAtIndex:1]; NSString *path = [chunks objectAtIndex:1];
[contentman reFetchFileNameCacheForPath:path]; [contentman reFetchFileNameCacheForPath:path];
} else if( [[chunks objectAtIndex:0 ] isEqualToString:@"REGISTER_PATH"] ) { } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"REGISTER_PATH"] ) {
NSNumber *one = [NSNumber numberWithInt:1]; NSNumber *one = [NSNumber numberWithInt:1];
NSString *path = [chunks objectAtIndex:1]; NSString *path = [chunks objectAtIndex:1];
NSLog(@"Registering path: %@", path);
[_registeredPathes setObject:one forKey:path]; [_registeredPathes setObject:one forKey:path];
[contentman repaintAllWindows]; [contentman repaintAllWindows];
} else if( [[chunks objectAtIndex:0 ] isEqualToString:@"UNREGISTER_PATH"] ) { } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"UNREGISTER_PATH"] ) {
NSNumber *one = [NSNumber numberWithInt:1];
NSString *path = [chunks objectAtIndex:1]; NSString *path = [chunks objectAtIndex:1];
[_registeredPathes removeObjectForKey:path]; [_registeredPathes removeObjectForKey:path];
@ -155,8 +168,8 @@ static RequestManager* sharedInstance = nil;
} else { } else {
NSLog(@"Unknown command %@", [chunks objectAtIndex:0]); NSLog(@"Unknown command %@", [chunks objectAtIndex:0]);
} }
} else { } else if (tag != READ_TAG) {
NSLog(@"Received unknown tag %ld", tag); NSLog(@"Received unknown tag %ld <%@>", tag, answer);
} }
// Read on and on // Read on and on
NSData* stop = [@"\n" dataUsingEncoding:NSUTF8StringEncoding]; NSData* stop = [@"\n" dataUsingEncoding:NSUTF8StringEncoding];
@ -170,16 +183,26 @@ static RequestManager* sharedInstance = nil;
return 0.0; return 0.0;
} }
-(void)socket:(GCDAsyncSocket*)socket didConnectToUrl:(NSURL *)url {
NSLog(@"didConnectToUrl %@", url);
[self socketDidConnect:socket];
}
- (void)socket:(GCDAsyncSocket*)socket didConnectToHost:(NSString*)host port:(UInt16)port - (void)socket:(GCDAsyncSocket*)socket didConnectToHost:(NSString*)host port:(UInt16)port
{ {
NSLog( @"Connected to host successfully!"); [self socketDidConnect:socket];
}
// Our impl
- (void)socketDidConnect:(GCDAsyncSocket*)socket {
NSLog( @"Connected to sync client successfully!");
_isConnected = YES; _isConnected = YES;
_isRunning = NO; _isRunning = NO;
if( [_requestQueue count] > 0 ) { if( [_requestQueue count] > 0 ) {
NSLog( @"We have to empty the queue"); NSLog( @"We have to empty the queue");
for( NSString *path in _requestQueue ) { for( NSString *path in _requestQueue ) {
[self askOnSocket:path]; [self askOnSocket:path query:@"RETRIEVE_FILE_STATUS"];
} }
} }
@ -221,12 +244,27 @@ static RequestManager* sharedInstance = nil;
{ {
if (!_isRunning) if (!_isRunning)
{ {
NSLog(@"Connect Socket!");
NSError *err = nil; NSError *err = nil;
if (![_socket connectToHost:@"localhost" onPort:34001 withTimeout:5 error:&err]) // Asynchronous! BOOL useTcp = NO;
{ if (useTcp) {
// If there was an error, it's likely something like "already connected" or "no delegate set" NSLog(@"Connect Socket");
NSLog(@"I goofed: %@", err); if (![_socket connectToHost:@"localhost" onPort:34001 withTimeout:5 error:&err]) {
// If there was an error, it's likely something like "already connected" or "no delegate set"
NSLog(@"I goofed: %@", err);
}
} else if (!useTcp) {
NSURL *url = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
if ([paths count])
{
// file:///Users/guruz/Library/Caches/SyncStateHelper/ownCloud.socket
// FIXME Generify this and support all sockets there since multiple sync clients might be running
url =[NSURL fileURLWithPath:[[[paths objectAtIndex:0] stringByAppendingPathComponent:@"SyncStateHelper"] stringByAppendingPathComponent:@"ownCloud.socket"]];
}
if (url) {
NSLog(@"Connect Socket to %@", url);
[_socket connectToUrl:url withTimeout:5 error:&err];
}
} }
_isRunning = YES; _isRunning = YES;

View file

@ -14,7 +14,9 @@ if( UNIX AND NOT APPLE )
FOREACH(size 128x128 16x16 256x256 32x32 48x48 64x64 72x72) FOREACH(size 128x128 16x16 256x256 32x32 48x48 64x64 72x72)
file(GLOB files "${size}/*.png") file(GLOB files "${size}/*.png")
FOREACH( file ${files} ) FOREACH( file ${files} )
STRING(REPLACE "oC" ${APPLICATION_NAME} brandedName ${file}) # the GLOB returns a absolute path. Make it relative by replacing the current src dir by nothing
STRING(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/${size}/" "" shortFile ${file})
STRING(REPLACE "oC" ${APPLICATION_NAME} brandedName ${shortFile})
install(FILES ${file} DESTINATION ${ICON_DIR}/${size}/apps RENAME ${brandedName}) install(FILES ${file} DESTINATION ${ICON_DIR}/${size}/apps RENAME ${brandedName})
ENDFOREACH(file) ENDFOREACH(file)
ENDFOREACH(size) ENDFOREACH(size)

View file

@ -1,3 +1,2 @@
configure_file(fixbranding.sh.in ${CMAKE_CURRENT_BINARY_DIR}/fixbranding.sh)
install(FILES ${APPLICATION_EXECUTABLE}.py DESTINATION ${DATADIR}/nautilus-python/extensions) install(FILES syncstate.py DESTINATION ${DATADIR}/nautilus-python/extensions)

View file

@ -1,4 +0,0 @@
#!/bin/sh
sed 's/oC_/@APPLICATION_EXECUTABLE@_/' ownCloud.py
mv ownCloud.py @APPLICATION_EXECUTABLE@.py

View file

@ -1,4 +1,16 @@
#!/usr/bin/python3 #!/usr/bin/python3
#
# Copyright (C) by Klaas Freitag <freitag@owncloud.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
import os import os
import urllib import urllib
@ -6,34 +18,45 @@ import socket
from gi.repository import GObject, Nautilus from gi.repository import GObject, Nautilus
class ownCloudExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider): class syncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider):
nautilusVFSFile_table = {} nautilusVFSFile_table = {}
registered_paths = {} registered_paths = {}
remainder = '' remainder = ''
connected = False connected = False
watch_id = 0 watch_id = 0
appname = 'ownCloud'
def __init__(self): def __init__(self):
self.connectToOwnCloud self.connectToSocketServer
if not self.connected: if not self.connected:
# try again in 5 seconds - attention, logic inverted! # try again in 5 seconds - attention, logic inverted!
GObject.timeout_add(5000, self.connectToOwnCloud) GObject.timeout_add(5000, self.connectToSocketServer)
def port(self): def connectToSocketServer(self):
return 34001 # Fixme, read from config file. do_reconnect = True
def connectToOwnCloud(self):
try: try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(("localhost", self.port())) postfix = "/"+self.appname+"/socket"
self.sock.settimeout(5) sock_file = os.environ["XDG_RUNTIME_DIR"]+postfix
self.connected = True print ("XXXX " + sock_file + " <=> " + postfix)
self.watch_id = GObject.io_add_watch(self.sock, GObject.IO_IN, self.handle_notify) if sock_file != postfix:
except: try:
print("Connect could not be established, try again later!") print("Socket File: "+sock_file)
self.sock.connect(sock_file)
self.connected = True
print("Setting connected to %r" % self.connected )
self.watch_id = GObject.io_add_watch(self.sock, GObject.IO_IN, self.handle_notify)
do_reconnect = False
except Exception, e:
print("Could not connect to unix socket." + str(e))
else:
print("Sock-File not valid: "+sock_file)
except Exception, e:
print("Connect could not be established, try again later " + str(e))
self.sock.close() self.sock.close()
return not self.connected # print("Returning %r" % do_reconnect)
return do_reconnect
def sendCommand(self, cmd): def sendCommand(self, cmd):
if self.connected: if self.connected:
@ -43,7 +66,7 @@ class ownCloudExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoP
print("Sending failed.") print("Sending failed.")
GObject.source_remove(self.watch_id) GObject.source_remove(self.watch_id)
self.connected = False self.connected = False
GObject.timeout_add(5000, self.connectToOwnCloud) GObject.timeout_add(5000, self.connectToSocketServer)
def find_item_for_file(self, path): def find_item_for_file(self, path):
if path in self.nautilusVFSFile_table: if path in self.nautilusVFSFile_table:
@ -52,6 +75,7 @@ class ownCloudExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoP
return None return None
def askForOverlay(self, file): def askForOverlay(self, file):
# print("Asking for overlay for "+file)
if os.path.isdir(file): if os.path.isdir(file):
folderStatus = self.sendCommand("RETRIEVE_FOLDER_STATUS:"+file+"\n"); folderStatus = self.sendCommand("RETRIEVE_FOLDER_STATUS:"+file+"\n");
@ -60,28 +84,30 @@ class ownCloudExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoP
def invalidate_items_underneath(self, path): def invalidate_items_underneath(self, path):
update_items = [] update_items = []
for p in self.nautilusVFSFile_table: if not self.nautilusVFSFile_table:
if p == path or p.startswith(path): self.askForOverlay(path)
item = self.nautilusVFSFile_table[p] else:
update_items.append(item) for p in self.nautilusVFSFile_table:
if p == path or p.startswith(path):
item = self.nautilusVFSFile_table[p]['item']
update_items.append(item)
for item in update_items: for item in update_items:
item.invalidate_extension_info() item.invalidate_extension_info()
# self.update_file_info(item)
# Handles a single line of server respoonse and sets the emblem # Handles a single line of server respoonse and sets the emblem
def handle_server_response(self, l): def handle_server_response(self, l):
Emblems = { 'OK' : 'oC_ok', Emblems = { 'OK' : self.appname +'_ok',
'SYNC' : 'oC_sync', 'SYNC' : self.appname +'_sync',
'NEW' : 'oC_sync', 'NEW' : self.appname +'_sync',
'IGNORE' : 'oC_warn', 'IGNORE' : self.appname +'_warn',
'ERROR' : 'oC_error', 'ERROR' : self.appname +'_error',
'OK+SWM' : 'oC_ok_shared', 'OK+SWM' : self.appname +'_ok_shared',
'SYNC+SWM' : 'oC_sync_shared', 'SYNC+SWM' : self.appname +'_sync_shared',
'NEW+SWM' : 'oC_sync_shared', 'NEW+SWM' : self.appname +'_sync_shared',
'IGNORE+SWM': 'oC_warn_shared', 'IGNORE+SWM': self.appname +'_warn_shared',
'ERROR+SWM' : 'oC_error_shared', 'ERROR+SWM' : self.appname +'_error_shared',
'NOP' : 'oC_error' 'NOP' : self.appname +'_error'
} }
print("Server response: "+l) print("Server response: "+l)
@ -92,11 +118,16 @@ class ownCloudExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoP
# file = parts[1] # file = parts[1]
# print "Action for " + file + ": "+parts[0] # print "Action for " + file + ": "+parts[0]
if action == 'STATUS': if action == 'STATUS':
emblem = Emblems[parts[1]] newState = parts[1]
emblem = Emblems[newState]
if emblem: if emblem:
item = self.find_item_for_file(parts[2]) itemStore = self.find_item_for_file(parts[2])
if item: if itemStore:
item.add_emblem(emblem) if( not itemStore['state'] or newState != itemStore['state'] ):
item = itemStore['item']
item.add_emblem(emblem)
# print "Setting emblem on " + parts[2]+ "<>"+emblem+"<>"
self.nautilusVFSFile_table[parts[2]] = {'item': item, 'state':newState}
elif action == 'UPDATE_VIEW': elif action == 'UPDATE_VIEW':
# Search all items underneath this path and invalidate them # Search all items underneath this path and invalidate them
@ -116,7 +147,7 @@ class ownCloudExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoP
self.sock.close() self.sock.close()
self.connected = False self.connected = False
GObject.source_remove(self.watch_id) GObject.source_remove(self.watch_id)
GObject.timeout_add(5000, self.connectToOwnCloud) GObject.timeout_add(5000, self.connectToSocketServer)
else: else:
# print "We got unknown action " + action # print "We got unknown action " + action
@ -158,7 +189,7 @@ class ownCloudExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoP
for reg_path in self.registered_paths: for reg_path in self.registered_paths:
if filename.startswith(reg_path): if filename.startswith(reg_path):
self.nautilusVFSFile_table[filename] = item self.nautilusVFSFile_table[filename] = {'item': item, 'state':''}
# item.add_string_attribute('share_state', "share state") # item.add_string_attribute('share_state', "share state")
self.askForOverlay(filename) self.askForOverlay(filename)

View file

@ -36,17 +36,13 @@ extern HINSTANCE instanceHandle;
#define IDM_DISPLAY 0 #define IDM_DISPLAY 0
#define IDB_OK 101 #define IDB_OK 101
namespace {
static std::vector<std::wstring> s_watchedDirectories;
}
OCOverlay::OCOverlay(int state) OCOverlay::OCOverlay(int state)
: _communicationSocket(0) : _referenceCount(1)
, _referenceCount(1)
, _checker(new RemotePathChecker(PORT))
, _state(state) , _state(state)
{ {
static RemotePathChecker s_remotePathChecker;
_checker = &s_remotePathChecker;
} }
OCOverlay::~OCOverlay(void) OCOverlay::~OCOverlay(void)
@ -121,23 +117,13 @@ IFACEMETHODIMP OCOverlay::GetPriority(int *pPriority)
IFACEMETHODIMP OCOverlay::IsMemberOf(PCWSTR pwszPath, DWORD dwAttrib) IFACEMETHODIMP OCOverlay::IsMemberOf(PCWSTR pwszPath, DWORD dwAttrib)
{ {
auto watchedDirectories = _checker->WatchedDirectories();
//if(!_IsOverlaysEnabled())
//{
// return MAKE_HRESULT(S_FALSE, 0, 0);
//}
// FIXME: Use Registry instead, this will only trigger once
// and now follow any user changes in the client
if (s_watchedDirectories.empty()) {
s_watchedDirectories = _checker->WatchedDirectories();
}
wstring wpath(pwszPath); wstring wpath(pwszPath);
wpath.append(L"\\"); //wpath.append(L"\\");
vector<wstring>::iterator it; vector<wstring>::iterator it;
bool watched = false; bool watched = false;
for (it = s_watchedDirectories.begin(); it != s_watchedDirectories.end(); ++it) { for (it = watchedDirectories.begin(); it != watchedDirectories.end(); ++it) {
if (StringUtil::begins_with(wpath, *it)) { if (StringUtil::begins_with(wpath, *it)) {
watched = true; watched = true;
} }

View file

@ -35,14 +35,13 @@ public:
IFACEMETHODIMP_(ULONG) Release(); IFACEMETHODIMP_(ULONG) Release();
protected: protected:
~OCOverlay(void); ~OCOverlay();
private: private:
//bool _GenerateMessage(const wchar_t*, std::wstring*); //bool _GenerateMessage(const wchar_t*, std::wstring*);
bool _IsOverlaysEnabled(); bool _IsOverlaysEnabled();
long _referenceCount; long _referenceCount;
CommunicationSocket* _communicationSocket;
RemotePathChecker* _checker; RemotePathChecker* _checker;
int _state; int _state;
}; };

View file

@ -21,6 +21,7 @@
#include <windows.h> #include <windows.h>
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <array>
#include <fstream> #include <fstream>
@ -30,8 +31,8 @@ using namespace std;
#define DEFAULT_BUFLEN 4096 #define DEFAULT_BUFLEN 4096
CommunicationSocket::CommunicationSocket(int port) CommunicationSocket::CommunicationSocket()
: _port(port), _clientSocket(INVALID_SOCKET) : _pipe(INVALID_HANDLE_VALUE)
{ {
} }
@ -43,64 +44,39 @@ CommunicationSocket::~CommunicationSocket()
bool CommunicationSocket::Close() bool CommunicationSocket::Close()
{ {
WSACleanup(); WSACleanup();
bool closed = (closesocket(_clientSocket) == 0); if (_pipe == INVALID_HANDLE_VALUE) {
shutdown(_clientSocket, SD_BOTH); return false;
_clientSocket = INVALID_SOCKET; }
return closed; CloseHandle(_pipe);
_pipe = INVALID_HANDLE_VALUE;
return true;
} }
bool CommunicationSocket::Connect() bool CommunicationSocket::Connect(const std::wstring &pipename)
{ {
WSADATA wsaData; _pipe = CreateFile(pipename.data(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
HRESULT iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (_pipe == INVALID_HANDLE_VALUE) {
return false;
if (iResult != NO_ERROR) { }
int error = WSAGetLastError(); return true;
}
_clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (_clientSocket == INVALID_SOCKET) {
//int error = WSAGetLastError();
Close();
return false;
}
struct sockaddr_in clientService;
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr(PLUG_IN_SOCKET_ADDRESS);
clientService.sin_port = htons(_port);
iResult = connect(_clientSocket, (SOCKADDR*)&clientService, sizeof(clientService));
DWORD timeout = 500; // ms
setsockopt(_clientSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*) &timeout, sizeof(DWORD));
if (iResult == SOCKET_ERROR) {
//int error = WSAGetLastError();
Close();
return false;
}
return true;
} }
bool CommunicationSocket::SendMsg(const wchar_t* message) bool CommunicationSocket::SendMsg(const wchar_t* message)
{ {
const char* utf8_msg = StringUtil::toUtf8(message); auto utf8_msg = StringUtil::toUtf8(message);
size_t result = send(_clientSocket, utf8_msg, (int)strlen(utf8_msg), 0);
delete[] utf8_msg;
if (result == SOCKET_ERROR) { DWORD numBytesWritten = 0;
//int error = WSAGetLastError(); auto result = WriteFile( _pipe, utf8_msg.c_str(), DWORD(utf8_msg.size()), &numBytesWritten, NULL);
closesocket(_clientSocket);
return false;
}
return true; if (result) {
return true;
} else {
Close();
return false;
}
} }
bool CommunicationSocket::ReadLine(wstring* response) bool CommunicationSocket::ReadLine(wstring* response)
@ -109,21 +85,43 @@ bool CommunicationSocket::ReadLine(wstring* response)
return false; return false;
} }
vector<char> resp_utf8; response->clear();
char buffer;
if (_pipe == INVALID_HANDLE_VALUE) {
return false;
}
while (true) { while (true) {
int bytesRead = recv(_clientSocket, &buffer, 1, 0); int lbPos = 0;
if (bytesRead <= 0) { auto it = std::find(_buffer.begin() + lbPos, _buffer.end(), '\n');
response = 0; if (it != _buffer.end()) {
*response = StringUtil::toUtf16(_buffer.data(), DWORD(it - _buffer.begin()));
_buffer.erase(_buffer.begin(), it + 1);
return true;
}
std::array<char, 128> resp_utf8;
DWORD numBytesRead = 0;
DWORD totalBytesAvailable = 0;
auto result = PeekNamedPipe(_pipe, NULL, 0, 0, &totalBytesAvailable, 0);
if (!result) {
Close();
return false;
}
if (totalBytesAvailable == 0) {
return false; return false;
} }
if (buffer == '\n') { result = ReadFile(_pipe, resp_utf8.data(), DWORD(resp_utf8.size()), &numBytesRead, NULL);
resp_utf8.push_back(0); if (!result) {
*response = StringUtil::toUtf16(&resp_utf8[0], resp_utf8.size()); Close();
return true; return false;
} else { }
resp_utf8.push_back(buffer); if (numBytesRead <= 0) {
} return false;
}
_buffer.insert(_buffer.end(), resp_utf8.begin(), resp_utf8.begin()+numBytesRead);
continue;
} }
} }

View file

@ -20,23 +20,27 @@
#pragma warning (disable : 4251) #pragma warning (disable : 4251)
#include <string> #include <string>
#include <vector>
#include <WinSock2.h> #include <WinSock2.h>
class __declspec(dllexport) CommunicationSocket class __declspec(dllexport) CommunicationSocket
{ {
public: public:
CommunicationSocket(int port); CommunicationSocket();
~CommunicationSocket(); ~CommunicationSocket();
bool Connect(); bool Connect(const std::wstring& pipename);
bool Close(); bool Close();
bool SendMsg(const wchar_t*); bool SendMsg(const wchar_t*);
bool ReadLine(std::wstring*); bool ReadLine(std::wstring*);
HANDLE Event() { return _pipe; }
private: private:
int _port; HANDLE _pipe;
SOCKET _clientSocket; std::vector<char> _buffer;
bool _connected;
}; };
#endif #endif

View file

@ -20,88 +20,173 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <iterator> #include <iterator>
#include <unordered_set>
#include <cassert>
#include <shlobj.h>
using namespace std; using namespace std;
RemotePathChecker::RemotePathChecker(int port)
: _port(port) // This code is run in a thread
void RemotePathChecker::workerThreadLoop()
{ {
auto pipename = std::wstring(L"\\\\.\\pipe\\");
pipename += L"ownCloud";
bool connected = false;
CommunicationSocket socket;
std::unordered_set<std::wstring> asked;
while(!_stop) {
Sleep(50);
if (!connected) {
asked.clear();
if (!WaitNamedPipe(pipename.data(), 5 * 1000)) {
continue;
}
if (!socket.Connect(pipename)) {
continue;
}
connected = true;
std::unique_lock<std::mutex> lock(_mutex);
_connected = true;
}
{
std::unique_lock<std::mutex> lock(_mutex);
while (!_pending.empty() && !_stop) {
auto filePath = _pending.front();
_pending.pop();
lock.unlock();
if (!asked.count(filePath)) {
asked.insert(filePath);
socket.SendMsg(wstring(L"RETRIEVE_FILE_STATUS:" + filePath + L'\n').data());
}
lock.lock();
}
}
std::wstring response;
while (!_stop && socket.ReadLine(&response)) {
if (StringUtil::begins_with(response, wstring(L"REGISTER_PATH:"))) {
wstring responsePath = response.substr(14); // length of REGISTER_PATH:
{ std::unique_lock<std::mutex> lock(_mutex);
_watchedDirectories.push_back(responsePath);
}
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, responsePath.data(), NULL);
} else if (StringUtil::begins_with(response, wstring(L"UNREGISTER_PATH:"))) {
wstring responsePath = response.substr(16); // length of UNREGISTER_PATH:
{ std::unique_lock<std::mutex> lock(_mutex);
_watchedDirectories.erase(
std::remove(_watchedDirectories.begin(), _watchedDirectories.end(), responsePath),
_watchedDirectories.end());
// Remove any item from the cache
for (auto it = _cache.begin(); it != _cache.end() ; ) {
if (StringUtil::begins_with(it->first, responsePath)) {
it = _cache.erase(it);
} else {
++it;
}
}
}
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, responsePath.data(), NULL);
} else if (StringUtil::begins_with(response, wstring(L"STATUS:")) ||
StringUtil::begins_with(response, wstring(L"BROADCAST:"))) {
auto statusBegin = response.find(L':', 0);
assert(statusBegin != std::wstring::npos);
auto statusEnd = response.find(L':', statusBegin + 1);
if (statusEnd == std::wstring::npos) {
// the command do not contains two colon?
continue;
}
auto responseStatus = response.substr(statusBegin+1, statusEnd - statusBegin-1);
auto responsePath = response.substr(statusEnd+1);
auto state = _StrToFileState(responseStatus);
auto erased = asked.erase(responsePath);
bool changed = false;
{ std::unique_lock<std::mutex> lock(_mutex);
auto &it = _cache[responsePath];
changed = it == state;
it = state;
}
if (changed) {
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, responsePath.data(), NULL);
}
}
}
if (socket.Event() == INVALID_HANDLE_VALUE) {
std::unique_lock<std::mutex> lock(_mutex);
_cache.clear();
_watchedDirectories.clear();
_connected = connected = false;
}
if (_stop) return;
HANDLE handles[2] = { _newQueries, socket.Event() };
WaitForMultipleObjects(2, handles, false, 0);
}
}
RemotePathChecker::RemotePathChecker()
: _connected(false)
, _newQueries(CreateEvent(NULL, true, true, NULL))
, _thread([this]{ this->workerThreadLoop(); })
{
}
RemotePathChecker::~RemotePathChecker()
{
_stop = true;
//_newQueries.notify_all();
SetEvent(_newQueries);
_thread.join();
CloseHandle(_newQueries);
} }
vector<wstring> RemotePathChecker::WatchedDirectories() vector<wstring> RemotePathChecker::WatchedDirectories()
{ {
vector<wstring> watchedDirectories; std::unique_lock<std::mutex> lock(_mutex);
wstring response; return _watchedDirectories;
bool needed = false;
CommunicationSocket socket(_port);
socket.Connect();
while (socket.ReadLine(&response)) {
if (StringUtil::begins_with(response, wstring(L"REGISTER_PATH:"))) {
size_t pathBegin = response.find(L':', 0);
if (pathBegin == -1) {
continue;
}
// chop trailing '\n'
wstring responsePath = response.substr(pathBegin + 1, response.length()-1);
watchedDirectories.push_back(responsePath);
}
}
return watchedDirectories;
} }
bool RemotePathChecker::IsMonitoredPath(const wchar_t* filePath, int* state) bool RemotePathChecker::IsMonitoredPath(const wchar_t* filePath, int* state)
{ {
wstring request; assert(state); assert(filePath);
wstring response;
bool needed = false;
CommunicationSocket socket(_port); std::unique_lock<std::mutex> lock(_mutex);
socket.Connect(); if (!_connected) {
request = L"RETRIEVE_FILE_STATUS:"; return false;
request += filePath; }
request += L'\n';
if (!socket.SendMsg(request.c_str())) { auto path = std::wstring(filePath);
return false;
}
while (socket.ReadLine(&response)) { auto it = _cache.find(path);
// discard broadcast messages if (it != _cache.end()) {
if (StringUtil::begins_with(response, wstring(L"STATUS:"))) { *state = it->second;
break; return true;
} }
}
size_t statusBegin = response.find(L':', 0); _pending.push(filePath);
if (statusBegin == -1) SetEvent(_newQueries);
return false; return false;
size_t statusEnd = response.find(L':', statusBegin + 1);
if (statusEnd == -1)
return false;
wstring responseStatus = response.substr(statusBegin+1, statusEnd - statusBegin-1);
wstring responsePath = response.substr(statusEnd+1);
if (responsePath == filePath) {
if (!state) {
return false;
}
*state = _StrToFileState(responseStatus);
if (*state == StateNone) {
return false;
}
needed = true;
}
return needed;
} }
int RemotePathChecker::_StrToFileState(const std::wstring &str) RemotePathChecker::FileState RemotePathChecker::_StrToFileState(const std::wstring &str)
{ {
if (str == L"NOP" || str == L"NONE") { if (str == L"NOP" || str == L"NONE") {
return StateNone; return StateNone;

View file

@ -16,6 +16,12 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <unordered_map>
#include <queue>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#pragma once #pragma once
@ -29,14 +35,33 @@ public:
StateWarning, StateWarningSWM, StateWarning, StateWarningSWM,
StateNone StateNone
}; };
RemotePathChecker(int port); RemotePathChecker();
~RemotePathChecker();
std::vector<std::wstring> WatchedDirectories(); std::vector<std::wstring> WatchedDirectories();
bool IsMonitoredPath(const wchar_t* filePath, int* state); bool IsMonitoredPath(const wchar_t* filePath, int* state);
private: private:
int _StrToFileState(const std::wstring &str); FileState _StrToFileState(const std::wstring &str);
int _port; std::mutex _mutex;
std::atomic<bool> _stop;
// Everything here is protected by the _mutex
/** The list of paths we need to query. The main thread fill this, and the worker thread
* send that to the socket. */
std::queue<std::wstring> _pending;
std::unordered_map<std::wstring, FileState> _cache;
std::vector<std::wstring> _watchedDirectories;
bool _connected;
// The main thread notifies when there are new items in _pending
//std::condition_variable _newQueries;
HANDLE _newQueries;
std::thread _thread;
void workerThreadLoop();
}; };
#endif #endif

View file

@ -11,22 +11,26 @@
* details. * details.
*/ */
#include <Windows.h> #include <locale>
#include <string>
#include <codecvt>
#include "StringUtil.h" #include "StringUtil.h"
char* StringUtil::toUtf8(const wchar_t *utf16, int len) std::string StringUtil::toUtf8(const wchar_t *utf16, int len)
{ {
int newlen = WideCharToMultiByte(CP_UTF8, 0, utf16, len, NULL, 0, NULL, NULL); if (len < 0) {
char* str = new char[newlen]; len = wcslen(utf16);
WideCharToMultiByte(CP_UTF8, 0, utf16, -1, str, newlen, NULL, NULL); }
return str; std::wstring_convert<std::codecvt_utf8_utf16<wchar_t> > converter;
return converter.to_bytes(utf16, utf16+len);
} }
wchar_t* StringUtil::toUtf16(const char *utf8, int len) std::wstring StringUtil::toUtf16(const char *utf8, int len)
{ {
int newlen = MultiByteToWideChar(CP_UTF8, 0, utf8, len, NULL, 0); if (len < 0) {
wchar_t* wstr = new wchar_t[newlen]; len = strlen(utf8);
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, newlen); }
return wstr; std::wstring_convert<std::codecvt_utf8_utf16<wchar_t> > converter;
return converter.from_bytes(utf8, utf8+len);
} }

View file

@ -20,15 +20,14 @@
class __declspec(dllexport) StringUtil { class __declspec(dllexport) StringUtil {
public: public:
static char* toUtf8(const wchar_t* utf16, int len = -1); static std::string toUtf8(const wchar_t* utf16, int len = -1);
static wchar_t* toUtf16(const char* utf8, int len = -1); static std::wstring toUtf16(const char* utf8, int len = -1);
template<class T> template<class T>
static bool begins_with(const T& input, const T& match) static bool begins_with(const T& input, const T& match)
{ {
return input.size() >= match.size() return input.size() >= match.size()
&& equal(match.begin(), match.end(), input.begin()); && std::equal(match.begin(), match.end(), input.begin());
} }
}; };

148882
src/3rdparty/sqlite3/sqlite3.c vendored Normal file

File diff suppressed because it is too large Load diff

7494
src/3rdparty/sqlite3/sqlite3.h vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -24,8 +24,8 @@
#include "account.h" #include "account.h"
#include "clientproxy.h" #include "clientproxy.h"
#include "mirallconfigfile.h" // ONLY ACCESS THE STATIC FUNCTIONS!
#include "creds/httpcredentials.h" #include "creds/httpcredentials.h"
#include "csync.h"
#include "simplesslerrorhandler.h" #include "simplesslerrorhandler.h"
#include "syncengine.h" #include "syncengine.h"
#include "syncjournaldb.h" #include "syncjournaldb.h"
@ -33,8 +33,12 @@
#include "cmd.h" #include "cmd.h"
#include "theme.h"
#include "netrcparser.h" #include "netrcparser.h"
#include "version.h"
#include "config.h"
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
#include <windows.h> #include <windows.h>
#else #else
@ -67,7 +71,7 @@ public:
EchoDisabler() EchoDisabler()
{ {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); hStdin = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(hStdin, &mode); GetConsoleMode(hStdin, &mode);
SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT)); SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT));
#else #else
@ -89,6 +93,7 @@ public:
private: private:
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
DWORD mode = 0; DWORD mode = 0;
HANDLE hStdin;
#else #else
termios tios; termios tios;
#endif #endif
@ -105,21 +110,37 @@ QString queryPassword(const QString &user)
class HttpCredentialsText : public HttpCredentials { class HttpCredentialsText : public HttpCredentials {
public: public:
HttpCredentialsText(const QString& user, const QString& password) : HttpCredentials(user, password) {} HttpCredentialsText(const QString& user, const QString& password)
: HttpCredentials(user, password),
_sslTrusted(false)
{}
QString queryPassword(bool *ok) { QString queryPassword(bool *ok) {
if (ok) { if (ok) {
*ok = true; *ok = true;
} }
return ::queryPassword(user()); return ::queryPassword(user());
} }
void setSSLTrusted( bool isTrusted ) {
_sslTrusted = isTrusted;
}
bool sslIsTrusted() {
return _sslTrusted;
}
private:
bool _sslTrusted;
}; };
void help() void help()
{ {
const char* appName = APPLICATION_EXECUTABLE "cmd"; const char *binaryName = APPLICATION_EXECUTABLE "cmd";
std::cout << appName << " - command line " APPLICATION_NAME " client tool." << std::endl;
std::cout << binaryName << " - command line " APPLICATION_NAME " client tool" << std::endl;
std::cout << "" << std::endl; std::cout << "" << std::endl;
std::cout << "Usage: " << appName << " <source_dir> <server_url>" << std::endl; std::cout << "Usage: " << binaryName << " <source_dir> <server_url>" << std::endl;
std::cout << "" << std::endl; std::cout << "" << std::endl;
std::cout << "A proxy can either be set manually using --httpproxy." << std::endl; std::cout << "A proxy can either be set manually using --httpproxy." << std::endl;
std::cout << "Otherwise, the setting from a configured sync client will be used." << std::endl; std::cout << "Otherwise, the setting from a configured sync client will be used." << std::endl;
@ -129,37 +150,52 @@ void help()
std::cout << " --httpproxy [proxy] Specify a http proxy to use." << std::endl; std::cout << " --httpproxy [proxy] Specify a http proxy to use." << std::endl;
std::cout << " Proxy is http://server:port" << std::endl; std::cout << " Proxy is http://server:port" << std::endl;
std::cout << " --trust Trust the SSL certification." << std::endl; std::cout << " --trust Trust the SSL certification." << std::endl;
std::cout << " --exclude [file] exclude list file" << std::endl; std::cout << " --exclude [file] Exclude list file" << std::endl;
std::cout << " --user, -u [name] Use [name] as the login name" << std::endl; std::cout << " --user, -u [name] Use [name] as the login name" << std::endl;
std::cout << " --password, -p [pass] Use [pass] as password" << std::endl; std::cout << " --password, -p [pass] Use [pass] as password" << std::endl;
std::cout << " -n Use netrc (5) for login" << std::endl; std::cout << " -n Use netrc (5) for login" << std::endl;
std::cout << " --non-interactive Do not block execution with interaction" << std::endl; std::cout << " --non-interactive Do not block execution with interaction" << std::endl;
std::cout << " --version, -v Display version and exit" << std::endl;
std::cout << "" << std::endl; std::cout << "" << std::endl;
exit(1); exit(1);
} }
void showVersion() {
const char *binaryName = APPLICATION_EXECUTABLE "cmd";
std::cout << binaryName << " version " << qPrintable(Theme::instance()->version()) << std::endl;
exit(1);
}
void parseOptions( const QStringList& app_args, CmdOptions *options ) void parseOptions( const QStringList& app_args, CmdOptions *options )
{ {
QStringList args(app_args); QStringList args(app_args);
if( args.count() < 3 ) { int argCount = args.count();
if( argCount < 3 ) {
if (argCount >= 2) {
const QString option = args.at(1);
if (option == "-v" || option == "--version") {
showVersion();
}
}
help(); help();
} }
options->target_url = args.takeLast(); options->target_url = args.takeLast();
// check if the remote.php/webdav tail was added and append if not. // check if the remote.php/webdav tail was added and append if not.
if( !options->target_url.contains("remote.php/webdav")) { if(!options->target_url.endsWith("/")) {
if(!options->target_url.endsWith("/")) { options->target_url.append("/");
options->target_url.append("/"); }
} if( !options->target_url.contains("remote.php/webdav/")) {
options->target_url.append("remote.php/webdav"); options->target_url.append("remote.php/webdav/");
} }
if (options->target_url.startsWith("http")) if (options->target_url.startsWith("http"))
options->target_url.replace(0, 4, "owncloud"); options->target_url.replace(0, 4, "owncloud");
options->source_dir = args.takeLast(); options->source_dir = args.takeLast();
if( !QFile::exists( options->source_dir )) { if( !QFile::exists( options->source_dir )) {
std::cerr << "Source dir does not exists." << std::endl; std::cerr << "Source dir '" << qPrintable(options->source_dir) << "' does not exist." << std::endl;
exit(1); exit(1);
} }
@ -183,7 +219,7 @@ void parseOptions( const QStringList& app_args, CmdOptions *options )
} else if( (option == "-u" || option == "--user") && !it.peekNext().startsWith("-") ) { } else if( (option == "-u" || option == "--user") && !it.peekNext().startsWith("-") ) {
options->user = it.next(); options->user = it.next();
} else if( (option == "-p" || option == "--password") && !it.peekNext().startsWith("-") ) { } else if( (option == "-p" || option == "--password") && !it.peekNext().startsWith("-") ) {
options->user = it.next(); options->password = it.next();
} else if( option == "--exclude" && !it.peekNext().startsWith("-") ) { } else if( option == "--exclude" && !it.peekNext().startsWith("-") ) {
options->exclude = it.next(); options->exclude = it.next();
} else { } else {
@ -208,43 +244,45 @@ int main(int argc, char **argv) {
parseOptions( app.arguments(), &options ); parseOptions( app.arguments(), &options );
QUrl url = QUrl::fromUserInput(options.target_url);
QUrl url = QUrl::fromUserInput(options.target_url); // Order of retrieval attempt (later attempts override earlier ones):
// 1. From URL
// 2. From options
// 3. From netrc (if enabled)
// 4. From prompt (if interactive)
// Fetch username and password. If empty, try to retrieve QString user = url.userName();
// from URL and strip URL QString password = url.password();
QString user;
QString password;
if (options.useNetrc) { if (!options.user.isEmpty()) {
NetrcParser parser; user = options.user;
if (parser.parse()) { }
NetrcParser::LoginPair pair = parser.find(url.host());
user = pair.first;
password = pair.second;
}
} else {
user = options.user;
if (user.isEmpty()) {
user = url.userName();
}
password = options.password;
if (password.isEmpty()) {
password = url.password();
}
if (options.interactive) { if (!options.password.isEmpty()) {
if (user.isEmpty()) { password = options.password;
std::cout << "Please enter user name: "; }
std::string s;
std::getline(std::cin, s); if (options.useNetrc) {
user = QString::fromStdString(s); NetrcParser parser;
} if (parser.parse()) {
if (password.isEmpty()) { NetrcParser::LoginPair pair = parser.find(url.host());
password = queryPassword(user); user = pair.first;
} password = pair.second;
} }
} }
if (options.interactive) {
if (user.isEmpty()) {
std::cout << "Please enter user name: ";
std::string s;
std::getline(std::cin, s);
user = QString::fromStdString(s);
}
if (password.isEmpty()) {
password = queryPassword(user);
}
}
// ### ensure URL is free of credentials // ### ensure URL is free of credentials
if (url.userName().isEmpty()) { if (url.userName().isEmpty()) {
@ -254,18 +292,25 @@ int main(int argc, char **argv) {
url.setPassword(password); url.setPassword(password);
} }
// take the unmodified url to pass to csync_create()
QByteArray remUrl = options.target_url.toUtf8();
Account account; Account account;
// Find the folder and the original owncloud url // 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.setPath(splitted.value(0));
url.setScheme(url.scheme().replace("owncloud", "http")); url.setScheme(url.scheme().replace("owncloud", "http"));
QString folder = splitted.value(1); QString folder = splitted.value(1);
SimpleSslErrorHandler *sslErrorHandler = new SimpleSslErrorHandler; SimpleSslErrorHandler *sslErrorHandler = new SimpleSslErrorHandler;
HttpCredentials *cred = new HttpCredentialsText(user, password); HttpCredentialsText *cred = new HttpCredentialsText(user, password);
if( options.trustSSL ) {
cred->setSSLTrusted(true);
}
account.setUrl(url); account.setUrl(url);
account.setCredentials(cred); account.setCredentials(cred);
account.setSslErrorHandler(sslErrorHandler); account.setSslErrorHandler(sslErrorHandler);
@ -275,8 +320,9 @@ int main(int argc, char **argv) {
restart_sync: restart_sync:
CSYNC *_csync_ctx; CSYNC *_csync_ctx;
if( csync_create( &_csync_ctx, options.source_dir.toUtf8(), if( csync_create( &_csync_ctx, options.source_dir.toUtf8(),
url.toEncoded().constData()) < 0 ) { remUrl.constData()) < 0 ) {
qFatal("Unable to create csync-context!"); qFatal("Unable to create csync-context!");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -331,14 +377,29 @@ restart_sync:
clientProxy.setCSyncProxy(QUrl(url), _csync_ctx); clientProxy.setCSyncProxy(QUrl(url), _csync_ctx);
} }
// Exclude lists
QString systemExcludeListFn = MirallConfigFile::excludeFileFromSystem();
int loadedSystemExcludeList = false;
if (!systemExcludeListFn.isEmpty()) {
loadedSystemExcludeList = csync_add_exclude_list(_csync_ctx, systemExcludeListFn.toLocal8Bit());
}
int loadedUserExcludeList = false;
if (!options.exclude.isEmpty()) { if (!options.exclude.isEmpty()) {
csync_add_exclude_list(_csync_ctx, options.exclude.toLocal8Bit()); loadedUserExcludeList = csync_add_exclude_list(_csync_ctx, options.exclude.toLocal8Bit());
}
if (loadedSystemExcludeList != 0 && loadedUserExcludeList != 0) {
// Always make sure at least one list had been loaded
qFatal("Cannot load system exclude list or list supplied via --exclude");
return EXIT_FAILURE;
} }
cred->syncContextPreStart(_csync_ctx); cred->syncContextPreStart(_csync_ctx);
Cmd cmd; Cmd cmd;
SyncJournalDb db(options.source_dir); SyncJournalDb db(options.source_dir);
SyncEngine engine(_csync_ctx, options.source_dir, QUrl(options.target_url).path(), folder, &db); SyncEngine engine(_csync_ctx, options.source_dir, QUrl(options.target_url).path(), folder, &db);
QObject::connect(&engine, SIGNAL(finished()), &app, SLOT(quit())); QObject::connect(&engine, SIGNAL(finished()), &app, SLOT(quit()));
QObject::connect(&engine, SIGNAL(transmissionProgress(Progress::Info)), &cmd, SLOT(transmissionProgressSlot())); QObject::connect(&engine, SIGNAL(transmissionProgress(Progress::Info)), &cmd, SLOT(transmissionProgressSlot()));

View file

@ -193,13 +193,7 @@ if(NOT BUILD_OWNCLOUD_OSX_BUNDLE)
add_executable( ${APPLICATION_EXECUTABLE} WIN32 main.cpp ${final_src}) add_executable( ${APPLICATION_EXECUTABLE} WIN32 main.cpp ${final_src})
qt5_use_modules(${APPLICATION_EXECUTABLE} Widgets Network Xml WebKitWidgets Sql ${ADDITIONAL_APP_MODULES}) qt5_use_modules(${APPLICATION_EXECUTABLE} Widgets Network Xml WebKitWidgets Sql ${ADDITIONAL_APP_MODULES})
else() else()
if (Qt5Core_FOUND) # set(CMAKE_INSTALL_PREFIX ".") # Examples use /Applications. hurmpf.
include(DeployQt5)
else(Qt5Core_FOUND)
include(DeployQt4)
endif(Qt5Core_FOUND)
set(CMAKE_INSTALL_PREFIX ".") # Examples use /Applications. hurmpf.
set(MACOSX_BUNDLE_ICON_FILE "ownCloud.icns") set(MACOSX_BUNDLE_ICON_FILE "ownCloud.icns")
# we must add MACOSX_BUNDLE only if building a bundle # we must add MACOSX_BUNDLE only if building a bundle
@ -208,8 +202,15 @@ else()
set (QM_DIR ${OWNCLOUD_OSX_BUNDLE}/Contents/Resources/Translations) set (QM_DIR ${OWNCLOUD_OSX_BUNDLE}/Contents/Resources/Translations)
install(FILES ${mirall_I18N} DESTINATION ${QM_DIR}) install(FILES ${mirall_I18N} DESTINATION ${QM_DIR})
get_target_property(_qmake Qt5::qmake LOCATION)
execute_process(COMMAND ${_qmake} -query QT_INSTALL_TRANSLATIONS
OUTPUT_VARIABLE QT_TRANSLATIONS_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
file(GLOB qt_I18N ${QT_TRANSLATIONS_DIR}/qt_??.qm ${QT_TRANSLATIONS_DIR}/qt_??_??.qm) file(GLOB qt_I18N ${QT_TRANSLATIONS_DIR}/qt_??.qm ${QT_TRANSLATIONS_DIR}/qt_??_??.qm)
install(FILES ${qt_I18N} DESTINATION ${QM_DIR}) install(FILES ${qt_I18N} DESTINATION ${QM_DIR})
file(GLOB qtbase_I18N ${QT_TRANSLATIONS_DIR}/qtbase_??.qm ${QT_TRANSLATIONS_DIR}/qt_??_??.qm)
install(FILES ${qtbase_I18N} DESTINATION ${QM_DIR})
file(GLOB qtkeychain_I18N ${QT_TRANSLATIONS_DIR}/qtkeychain*.qm) file(GLOB qtkeychain_I18N ${QT_TRANSLATIONS_DIR}/qtkeychain*.qm)
install(FILES ${qtkeychain_I18N} DESTINATION ${QM_DIR}) install(FILES ${qtkeychain_I18N} DESTINATION ${QM_DIR})
endif() endif()
@ -221,6 +222,7 @@ qt5_use_modules(updater Widgets Network Xml)
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY}
) )
# Only relevant for Linux? On OS X it by default properly checks in the bundle directory next to the exe
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE}" ) INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE}" )
@ -233,19 +235,19 @@ install(TARGETS ${APPLICATION_EXECUTABLE}
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
LIBRARY DESTINATION lib LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib ARCHIVE DESTINATION lib
BUNDLE DESTINATION bin BUNDLE DESTINATION "."
) )
#FIXME: find a nice solution to make the second if(BUILD_OWNCLOUD_OSX_BUNDLE) unnecessary #FIXME: find a nice solution to make the second if(BUILD_OWNCLOUD_OSX_BUNDLE) unnecessary
# currently it needs to be done because the code right above needs to be executed no matter # currently it needs to be done because the code right above needs to be executed no matter
# if building a bundle or not and the install_qt4_executable needs to be called afterwards # if building a bundle or not and the install_qt4_executable needs to be called afterwards
if(BUILD_OWNCLOUD_OSX_BUNDLE) if(BUILD_OWNCLOUD_OSX_BUNDLE AND NOT BUILD_LIBRARIES_ONLY)
if(Qt5Core_FOUND) get_target_property (QT_QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION)
install_qt5_executable(${OWNCLOUD_OSX_BUNDLE} "qtaccessiblewidgets;qsqlite;qcocoa") install(CODE "
else(Qt5Core_FOUND) message(STATUS \"Deploying (Qt) dependencies and fixing library pathes...\")
install_qt4_executable(${OWNCLOUD_OSX_BUNDLE} "qtaccessiblewidgets;qsqlite") execute_process(COMMAND \"${CMAKE_SOURCE_DIR}/admin/osx/macdeployqt.py\" ${CMAKE_INSTALL_PREFIX}/${OWNCLOUD_OSX_BUNDLE} ${QT_QMAKE_EXECUTABLE})
endif(Qt5Core_FOUND) " COMPONENT RUNTIME)
endif() endif()
if(NOT BUILD_OWNCLOUD_OSX_BUNDLE AND NOT WIN32) if(NOT BUILD_OWNCLOUD_OSX_BUNDLE AND NOT WIN32)

View file

@ -383,7 +383,7 @@ void AccountSettings::slotResetCurrentFolder()
if( ret == QMessageBox::Yes ) { if( ret == QMessageBox::Yes ) {
FolderMan *folderMan = FolderMan::instance(); FolderMan *folderMan = FolderMan::instance();
Folder *f = folderMan->folder(alias); Folder *f = folderMan->folder(alias);
f->slotTerminateAndPauseSync(); f->slotTerminateSync();
f->wipe(); f->wipe();
folderMan->slotScheduleAllFolders(); folderMan->slotScheduleAllFolders();
} }
@ -499,7 +499,7 @@ void AccountSettings::slotEnableCurrentFolder()
// message box can return at any time while the thread keeps running, // message box can return at any time while the thread keeps running,
// so better check again after the user has responded. // so better check again after the user has responded.
if ( f->isBusy() && terminate ) { if ( f->isBusy() && terminate ) {
f->slotTerminateAndPauseSync(); f->slotTerminateSync();
} }
f->setSyncPaused(!currentlyPaused); // toggle the pause setting f->setSyncPaused(!currentlyPaused); // toggle the pause setting
folderMan->slotSetFolderPaused( alias, !currentlyPaused ); folderMan->slotSetFolderPaused( alias, !currentlyPaused );
@ -605,7 +605,7 @@ void AccountSettings::slotSetProgress(const QString& folder, const Progress::Inf
item->setData( QVariant(true), FolderStatusDelegate::AddProgressSpace ); item->setData( QVariant(true), FolderStatusDelegate::AddProgressSpace );
if (!progress._currentDiscoveredFolder.isEmpty()) { if (!progress._currentDiscoveredFolder.isEmpty()) {
item->setData( tr("Discovering %1").arg(progress._currentDiscoveredFolder) , FolderStatusDelegate::SyncProgressItemString ); item->setData( tr("Discovering '%1'").arg(progress._currentDiscoveredFolder) , FolderStatusDelegate::SyncProgressItemString );
return; return;
} }

View file

@ -534,12 +534,15 @@ void Application::setupTranslations()
setProperty("ui_lang", lang); setProperty("ui_lang", lang);
const QString qtTrPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); const QString qtTrPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
const QString qtTrFile = QLatin1String("qt_") + lang; const QString qtTrFile = QLatin1String("qt_") + lang;
if (qtTranslator->load(qtTrFile, qtTrPath)) { const QString qtBaseTrFile = QLatin1String("qtbase_") + lang;
qtTranslator->load(qtTrFile, trPath); if (!qtTranslator->load(qtTrFile, qtTrPath)) {
if (!qtTranslator->load(qtTrFile, trPath)) {
qtTranslator->load(qtBaseTrFile, trPath);
}
} }
const QString qtkeychainFile = QLatin1String("qt_") + lang; const QString qtkeychainTrFile = QLatin1String("qtkeychain_") + lang;
if (!qtkeychainTranslator->load(qtkeychainFile, qtTrPath)) { if (!qtkeychainTranslator->load(qtkeychainTrFile, qtTrPath)) {
qtkeychainTranslator->load(qtkeychainFile, trPath); qtkeychainTranslator->load(qtkeychainTrFile, trPath);
} }
if (!translator->isEmpty()) if (!translator->isEmpty())
installTranslator(translator); installTranslator(translator);

View file

@ -61,6 +61,7 @@ Folder::Folder(const QString &alias, const QString &path, const QString& secondP
, _csyncUnavail(false) , _csyncUnavail(false)
, _wipeDb(false) , _wipeDb(false)
, _proxyDirty(true) , _proxyDirty(true)
, _forceSyncOnPollTimeout(false)
, _journal(path) , _journal(path)
, _csync_ctx(0) , _csync_ctx(0)
{ {
@ -270,10 +271,25 @@ void Folder::slotPollTimerTimeout()
return; return;
} }
if (quint64(_timeSinceLastSync.elapsed()) > MirallConfigFile().forceSyncInterval() || bool forceSyncIntervalExpired =
_lastEtag.isNull() || quint64(_timeSinceLastSync.elapsed()) > MirallConfigFile().forceSyncInterval();
!(_syncResult.status() == SyncResult::Success ||_syncResult.status() == SyncResult::Problem)) { bool okSyncResult =
qDebug() << "** Force Sync now, state is " << _syncResult.statusString(); _syncResult.status() == SyncResult::Success ||
_syncResult.status() == SyncResult::Problem;
if (forceSyncIntervalExpired ||
_forceSyncOnPollTimeout ||
!okSyncResult) {
if (forceSyncIntervalExpired) {
qDebug() << "** Force Sync, because it has been " << _timeSinceLastSync.elapsed() << "ms "
<< "since the last sync";
}
if (_forceSyncOnPollTimeout) {
qDebug() << "** Force Sync, because it was requested";
}
if (!okSyncResult) {
qDebug() << "** Force Sync, because the last sync had status: " << _syncResult.statusString();
}
_forceSyncOnPollTimeout = false;
emit scheduleToSync(alias()); emit scheduleToSync(alias());
} else { } else {
// do the ordinary etag check for the root folder. // do the ordinary etag check for the root folder.
@ -315,12 +331,13 @@ void Folder::bubbleUpSyncResult()
int updatedItems = 0; int updatedItems = 0;
int ignoredItems = 0; int ignoredItems = 0;
int renamedItems = 0; int renamedItems = 0;
int errorItems = 0;
SyncFileItem firstItemNew; SyncFileItem firstItemNew;
SyncFileItem firstItemDeleted; SyncFileItem firstItemDeleted;
SyncFileItem firstItemUpdated; SyncFileItem firstItemUpdated;
SyncFileItem firstItemRenamed; SyncFileItem firstItemRenamed;
Logger *logger = Logger::instance(); SyncFileItem firstItemError;
SyncRunFileLog syncFileLog; SyncRunFileLog syncFileLog;
@ -336,7 +353,10 @@ void Folder::bubbleUpSyncResult()
// and process the item to the gui // and process the item to the gui
if( item._status == SyncFileItem::FatalError || item._status == SyncFileItem::NormalError ) { if( item._status == SyncFileItem::FatalError || item._status == SyncFileItem::NormalError ) {
slotSyncError( tr("%1: %2").arg(item._file, item._errorString) ); slotSyncError( tr("%1: %2").arg(item._file, item._errorString) );
logger->postOptionalGuiLog(item._file, item._errorString); errorItems++;
if (firstItemError.isEmpty()) {
firstItemError = item;
}
} else { } else {
// add new directories or remove gone away dirs to the watcher // add new directories or remove gone away dirs to the watcher
if (item._isDirectory && item._instruction == CSYNC_INSTRUCTION_NEW ) { if (item._isDirectory && item._instruction == CSYNC_INSTRUCTION_NEW ) {
@ -389,9 +409,9 @@ void Folder::bubbleUpSyncResult()
qDebug() << "Processing result list and logging took " << timer.elapsed() << " Milliseconds."; qDebug() << "Processing result list and logging took " << timer.elapsed() << " Milliseconds.";
_syncResult.setWarnCount(ignoredItems); _syncResult.setWarnCount(ignoredItems);
createGuiLog( firstItemNew._file, SyncFileStatus(SyncFileStatus::STATUS_NEW), newItems ); createGuiLog( firstItemNew._file, SyncFileStatus::STATUS_NEW, newItems );
createGuiLog( firstItemDeleted._file, SyncFileStatus(SyncFileStatus::STATUS_REMOVE), removedItems ); createGuiLog( firstItemDeleted._file, SyncFileStatus::STATUS_REMOVE, removedItems );
createGuiLog( firstItemUpdated._file, SyncFileStatus(SyncFileStatus::STATUS_UPDATED), updatedItems ); createGuiLog( firstItemUpdated._file, SyncFileStatus::STATUS_UPDATED, updatedItems );
if( !firstItemRenamed.isEmpty() ) { if( !firstItemRenamed.isEmpty() ) {
SyncFileStatus status(SyncFileStatus::STATUS_RENAME); SyncFileStatus status(SyncFileStatus::STATUS_RENAME);
@ -404,6 +424,8 @@ void Folder::bubbleUpSyncResult()
createGuiLog( firstItemRenamed._file, status, renamedItems, firstItemRenamed._renameTarget ); createGuiLog( firstItemRenamed._file, status, renamedItems, firstItemRenamed._renameTarget );
} }
createGuiLog( firstItemError._file, SyncFileStatus::STATUS_ERROR, errorItems );
qDebug() << "OO folder slotSyncFinished: result: " << int(_syncResult.status()); qDebug() << "OO folder slotSyncFinished: result: " << int(_syncResult.status());
} }
@ -454,6 +476,13 @@ void Folder::createGuiLog( const QString& filename, SyncFileStatus status, int c
text = tr("%1 has been moved to %2.").arg(file).arg(renameTarget); text = tr("%1 has been moved to %2.").arg(file).arg(renameTarget);
} }
break; break;
case SyncFileStatus::STATUS_ERROR:
if( count > 1 ) {
text = tr("%1 and %2 other files could not be synced due to errors. See the log for details.", "%1 names a file.").arg(file).arg(count-1);
} else {
text = tr("%1 could not be synced due to an error. See the log for details.").arg(file);
}
break;
default: default:
break; break;
} }
@ -484,11 +513,90 @@ QString Folder::configFile()
return _configFile; return _configFile;
} }
static void addErroredSyncItemPathsToList(const SyncFileItemVector& items, QSet<QString>* set) {
Q_FOREACH(const SyncFileItem &item, items) {
if (item.hasErrorStatus()) {
set->insert(item._file);
}
}
}
void Folder::slotThreadTreeWalkResult(const SyncFileItemVector& items) void Folder::slotThreadTreeWalkResult(const SyncFileItemVector& items)
{ {
addErroredSyncItemPathsToList(items, &this->_stateLastSyncItemsWithError);
_syncResult.setSyncFileItemVector(items); _syncResult.setSyncFileItemVector(items);
} }
void Folder::slotAboutToPropagate(const SyncFileItemVector& items)
{
// empty the tainted list since the status generation code will use the _syncedItems
// (which imply the folder) to generate the syncing state icon now.
_stateTaintedFolders.clear();
addErroredSyncItemPathsToList(items, &this->_stateLastSyncItemsWithError);
}
bool Folder::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s)
{
if (t == CSYNC_FTW_TYPE_DIR) {
qDebug() << Q_FUNC_INFO << "ASKING ERROR FOLDERS" << fn;
if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) {
s->set(SyncFileStatus::STATUS_ERROR);
return true;
}
// If sync is running, check _syncedItems, possibly give it STATUS_EVAL (=syncing down)
if (!_engine.isNull()) {
qDebug() << Q_FUNC_INFO << "SYNC IS RUNNING, asking SyncEngine" << fn;
if (_engine->estimateState(fn, t, s)) {
return true;
}
}
qDebug() << Q_FUNC_INFO << "ASKING TAINTED FOLDERS" << fn;
if (Utility::doesSetContainPrefix(_stateTaintedFolders, fn)) {
qDebug() << Q_FUNC_INFO << "Folder is tainted, EVAL!" << fn;
s->set(SyncFileStatus::STATUS_EVAL);
return true;
}
return false;
} else if ( t== CSYNC_FTW_TYPE_FILE) {
// check if errorList has the directory/file
if (Utility::doesSetContainPrefix(_stateLastSyncItemsWithError, fn)) {
s->set(SyncFileStatus::STATUS_ERROR);
return true;
}
// If sync running: _syncedItems -> SyncingState
if (!_engine.isNull()) {
if (_engine->estimateState(fn, t, s)) {
return true;
}
}
}
return false;
}
void Folder::watcherSlot(QString fn)
{
// FIXME: On OS X we could not do this "if" since on OS X the file watcher ignores events for ourselves
// however to have the same behaviour atm on all platforms, we don't do it
if (!_engine.isNull()) {
qDebug() << Q_FUNC_INFO << "Sync running, IGNORE event for " << fn;
return;
}
QFileInfo fi(fn);
if (fi.isFile()) {
fn = fi.path(); // depending on OS, file watcher might be for dir or file
}
// Make it a relative path depending on the folder
QString relativePath = fn.remove(0, path().length());
qDebug() << Q_FUNC_INFO << fi.canonicalFilePath() << fn << relativePath;
_stateTaintedFolders.insert(relativePath);
// Notify the SocketAPI?
}
void Folder::slotTerminateSync() void Folder::slotTerminateSync()
{ {
qDebug() << "folder " << alias() << " Terminating!"; qDebug() << "folder " << alias() << " Terminating!";
@ -503,12 +611,6 @@ void Folder::slotTerminateSync()
} }
} }
void Folder::slotTerminateAndPauseSync()
{
slotTerminateSync();
FolderMan::instance()->slotSetFolderPaused(alias(), true);
}
// This removes the csync File database // This removes the csync File database
// This is needed to provide a clean startup again in case another // This is needed to provide a clean startup again in case another
// local folder is synced to the same ownCloud. // local folder is synced to the same ownCloud.
@ -617,6 +719,8 @@ void Folder::startSync(const QStringList &pathList)
connect( _engine.data(), SIGNAL(treeWalkResult(const SyncFileItemVector&)), connect( _engine.data(), SIGNAL(treeWalkResult(const SyncFileItemVector&)),
this, SLOT(slotThreadTreeWalkResult(const SyncFileItemVector&)), Qt::QueuedConnection); this, SLOT(slotThreadTreeWalkResult(const SyncFileItemVector&)), Qt::QueuedConnection);
connect( _engine.data(), SIGNAL(aboutToPropagate(const SyncFileItemVector&)),
this, SLOT(slotAboutToPropagate(const SyncFileItemVector&)), Qt::QueuedConnection);
connect(_engine.data(), SIGNAL(started()), SLOT(slotSyncStarted()), Qt::QueuedConnection); connect(_engine.data(), SIGNAL(started()), SLOT(slotSyncStarted()), Qt::QueuedConnection);
connect(_engine.data(), SIGNAL(finished()), SLOT(slotSyncFinished()), Qt::QueuedConnection); connect(_engine.data(), SIGNAL(finished()), SLOT(slotSyncFinished()), Qt::QueuedConnection);
@ -683,8 +787,11 @@ void Folder::slotCsyncUnavailable()
void Folder::slotSyncFinished() void Folder::slotSyncFinished()
{ {
qDebug() << "-> CSync Finished slot with error " << _csyncError << "warn count" << _syncResult.warnCount(); if( _csyncError ) {
qDebug() << "-> SyncEngine finished with ERROR, warn count is" << _syncResult.warnCount();
} else {
qDebug() << "-> SyncEngine finished without problem.";
}
bubbleUpSyncResult(); bubbleUpSyncResult();
bool anotherSyncNeeded = false; bool anotherSyncNeeded = false;
@ -695,6 +802,15 @@ void Folder::slotSyncFinished()
// _watcher->setEventsEnabledDelayed(2000); // _watcher->setEventsEnabledDelayed(2000);
// This is for sync state calculation
_stateLastSyncItemsWithError = _stateLastSyncItemsWithErrorNew;
_stateLastSyncItemsWithErrorNew.clear();
_stateTaintedFolders.clear(); // heuristic: assume the sync had been done, new file watches needed to taint dirs
if (_csyncError || _csyncUnavail) {
// Taint the whole sync dir, we cannot give reliable state information
_stateTaintedFolders.insert(QLatin1String("/"));
}
if (_csyncError) { if (_csyncError) {
_syncResult.setStatus(SyncResult::Error); _syncResult.setStatus(SyncResult::Error);
@ -725,9 +841,9 @@ void Folder::slotSyncFinished()
_pollTimer.start(); _pollTimer.start();
_timeSinceLastSync.restart(); _timeSinceLastSync.restart();
} else { } else {
// Another sync is required. We will make sure that the poll timer occurs soon enough // Another sync is required. We will make sure that the poll timer occurs soon enough.
// and we clear the etag to force a sync qDebug() << "another sync was requested by the finished sync";
_lastEtag.clear(); _forceSyncOnPollTimeout = true;
QTimer::singleShot(1000, this, SLOT(slotPollTimerTimeout() )); QTimer::singleShot(1000, this, SLOT(slotPollTimerTimeout() ));
} }
@ -761,6 +877,10 @@ void Folder::slotTransmissionProgress(const Progress::Info &pi)
// a job is completed: count the errors and forward to the ProgressDispatcher // a job is completed: count the errors and forward to the ProgressDispatcher
void Folder::slotJobCompleted(const SyncFileItem &item) void Folder::slotJobCompleted(const SyncFileItem &item)
{ {
if (item.hasErrorStatus()) {
_stateLastSyncItemsWithError.insert(item._file);
}
if (Progress::isWarningKind(item._status)) { if (Progress::isWarningKind(item._status)) {
// Count all error conditions. // Count all error conditions.
_syncResult.setWarnCount(_syncResult.warnCount()+1); _syncResult.setWarnCount(_syncResult.warnCount()+1);
@ -793,7 +913,8 @@ void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool *cancel)
if (*cancel) { if (*cancel) {
wipe(); wipe();
// speed up next sync // speed up next sync
_lastEtag = QString(); _lastEtag.clear();
_forceSyncOnPollTimeout = true;
QTimer::singleShot(50, this, SLOT(slotPollTimerTimeout())); QTimer::singleShot(50, this, SLOT(slotPollTimerTimeout()));
} }
} }

View file

@ -27,6 +27,7 @@
#include <QDir> #include <QDir>
#include <QHash> #include <QHash>
#include <QSet>
#include <QObject> #include <QObject>
#include <QStringList> #include <QStringList>
@ -34,15 +35,12 @@
#include <QTimer> #include <QTimer>
#include <qelapsedtimer.h> #include <qelapsedtimer.h>
class QFileSystemWatcher;
class QThread; class QThread;
namespace Mirall { namespace Mirall {
class SyncEngine; class SyncEngine;
class FolderWatcher;
class Folder : public QObject class Folder : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -119,12 +117,12 @@ public:
// Used by the Socket API // Used by the Socket API
SyncJournalDb *journalDb() { return &_journal; } SyncJournalDb *journalDb() { return &_journal; }
CSYNC *csyncContext() { return _csync_ctx; }
QStringList selectiveSyncBlackList() { return _selectiveSyncBlackList; } QStringList selectiveSyncBlackList() { return _selectiveSyncBlackList; }
void setSelectiveSyncBlackList(const QStringList &blackList) void setSelectiveSyncBlackList(const QStringList &blackList)
{ _selectiveSyncBlackList = blackList; } { _selectiveSyncBlackList = blackList; }
bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s);
signals: signals:
void syncStateChange(); void syncStateChange();
@ -138,7 +136,6 @@ public slots:
* terminate the current sync run * terminate the current sync run
*/ */
void slotTerminateSync(); void slotTerminateSync();
void slotTerminateAndPauseSync();
void slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool*); void slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool*);
@ -171,10 +168,13 @@ private slots:
void etagRetreived(const QString &); void etagRetreived(const QString &);
void slotNetworkUnavailable(); void slotNetworkUnavailable();
void slotThreadTreeWalkResult(const SyncFileItemVector& ); void slotAboutToPropagate(const SyncFileItemVector& );
void slotThreadTreeWalkResult(const SyncFileItemVector& ); // after sync is done
void slotEmitFinishedDelayed(); void slotEmitFinishedDelayed();
void watcherSlot(QString);
private: private:
bool init(); bool init();
@ -203,6 +203,12 @@ private:
QTimer _pollTimer; QTimer _pollTimer;
QString _lastEtag; QString _lastEtag;
QElapsedTimer _timeSinceLastSync; QElapsedTimer _timeSinceLastSync;
bool _forceSyncOnPollTimeout;
// For the SocketAPI folder states
QSet<QString> _stateLastSyncItemsWithErrorNew; // gets moved to _stateLastSyncItemsWithError at end of sync
QSet<QString> _stateLastSyncItemsWithError;
QSet<QString> _stateTaintedFolders;
SyncJournalDb _journal; SyncJournalDb _journal;

View file

@ -146,6 +146,9 @@ void FolderMan::registerFolderMonitor( Folder *folder )
connect(fw, SIGNAL(folderChanged(QString)), _folderWatcherSignalMapper, SLOT(map())); connect(fw, SIGNAL(folderChanged(QString)), _folderWatcherSignalMapper, SLOT(map()));
_folderWatcherSignalMapper->setMapping(fw, folder->alias()); _folderWatcherSignalMapper->setMapping(fw, folder->alias());
_folderWatchers.insert(folder->alias(), fw); _folderWatchers.insert(folder->alias(), fw);
// This is at the moment only for the behaviour of the SocketApi.
connect(fw, SIGNAL(folderChanged(QString)), folder, SLOT(watcherSlot(QString)));
} }
// register the folder with the socket API // register the folder with the socket API
@ -441,6 +444,24 @@ void FolderMan::slotScheduleSync( const QString& alias )
return; return;
} }
// The folder watcher fires a lot of bogus notifications during
// a sync operation, both for actual user files and the database
// and log. Never enqueue a folder for sync while it is syncing.
// We lose some genuine sync requests that way, but that can't be
// helped.
// ^^ FIXME: Note that this is not the case on OS X
if( _currentSyncFolder == alias ) {
qDebug() << "folder " << alias << " is currently syncing. NOT scheduling.";
return;
}
if( _socketApi ) {
// We want the SocketAPI to already now update so that it can show the EVAL icon
// for files/folders. Only do this when not syncing, else we might get a lot
// of those notifications.
_socketApi->slotUpdateFolderView(alias);
}
qDebug() << "Schedule folder " << alias << " to sync!"; qDebug() << "Schedule folder " << alias << " to sync!";
if( ! _scheduleQueue.contains(alias) ) { if( ! _scheduleQueue.contains(alias) ) {
@ -451,7 +472,9 @@ void FolderMan::slotScheduleSync( const QString& alias )
f->prepareToSync(); f->prepareToSync();
} else { } else {
qDebug() << "Folder is not enabled, not scheduled!"; qDebug() << "Folder is not enabled, not scheduled!";
_socketApi->slotUpdateFolderView(f->alias()); if( _socketApi ) {
_socketApi->slotUpdateFolderView(f->alias());
}
return; return;
} }
_scheduleQueue.enqueue(alias); _scheduleQueue.enqueue(alias);
@ -513,8 +536,10 @@ void FolderMan::slotStartScheduledFolderSync()
// reread the excludes of the socket api // reread the excludes of the socket api
// FIXME: the excludes need rework. // FIXME: the excludes need rework.
_socketApi->slotClearExcludesList(); if( _socketApi ) {
_socketApi->slotReadExcludes(); _socketApi->slotClearExcludesList();
_socketApi->slotReadExcludes();
}
} }
} }
} }
@ -527,6 +552,8 @@ void FolderMan::slotFolderSyncStarted( )
/* /*
* a folder indicates that its syncing is finished. * a folder indicates that its syncing is finished.
* Start the next sync after the system had some milliseconds to breath. * Start the next sync after the system had some milliseconds to breath.
* This delay is particularly useful to avoid late file change notifications
* (that we caused ourselves by syncing) from triggering another spurious sync.
*/ */
void FolderMan::slotFolderSyncFinished( const SyncResult& ) void FolderMan::slotFolderSyncFinished( const SyncResult& )
{ {
@ -561,11 +588,11 @@ Folder *FolderMan::folderForPath(const QString &path)
const QString folderPath = QDir::cleanPath(folder->path())+QLatin1Char('/'); const QString folderPath = QDir::cleanPath(folder->path())+QLatin1Char('/');
if(absolutePath.startsWith(folderPath)) { if(absolutePath.startsWith(folderPath)) {
qDebug() << "found folder: " << folder->path() << " for " << absolutePath; //qDebug() << "found folder: " << folder->path() << " for " << absolutePath;
return folder; return folder;
} }
} }
qDebug() << "ERROR: could not find folder for " << absolutePath;
return 0; return 0;
} }

View file

@ -81,7 +81,7 @@ bool FolderWatcher::pathIsIgnored( const QString& path )
QFileInfo fInfo(path); QFileInfo fInfo(path);
if( fInfo.isHidden() ) { if( fInfo.isHidden() ) {
qDebug() << "* Discarded as is hidden!"; qDebug() << "* Discarded as is hidden!" << fInfo.filePath();
return true; return true;
} }

View file

@ -232,6 +232,9 @@ FolderWizardRemotePath::FolderWizardRemotePath()
_ui.setupUi(this); _ui.setupUi(this);
_ui.warnFrame->hide(); _ui.warnFrame->hide();
_ui.folderTreeWidget->setSortingEnabled(true);
_ui.folderTreeWidget->sortByColumn(0, Qt::AscendingOrder);
connect(_ui.addFolderButton, SIGNAL(clicked()), SLOT(slotAddRemoteFolder())); connect(_ui.addFolderButton, SIGNAL(clicked()), SLOT(slotAddRemoteFolder()));
connect(_ui.refreshButton, SIGNAL(clicked()), SLOT(slotRefreshFolders())); connect(_ui.refreshButton, SIGNAL(clicked()), SLOT(slotRefreshFolders()));
connect(_ui.folderTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), SIGNAL(completeChanged())); connect(_ui.folderTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), SIGNAL(completeChanged()));

View file

@ -82,6 +82,7 @@ int main(int argc, char **argv)
// if the application is already running, notify it. // if the application is already running, notify it.
if( app.isRunning() ) { if( app.isRunning() ) {
qDebug() << Q_FUNC_INFO << "Already running, exiting...";
QStringList args = app.arguments(); QStringList args = app.arguments();
if ( args.size() > 1 && ! app.giveHelp() ) { if ( args.size() > 1 && ! app.giveHelp() ) {
QString msg = args.join( QLatin1String("|") ); QString msg = args.join( QLatin1String("|") );

View file

@ -50,7 +50,6 @@ ownCloudGui::ownCloudGui(Application *parent) :
_settingsDialog(new SettingsDialog(this)), _settingsDialog(new SettingsDialog(this)),
#endif #endif
_logBrowser(0), _logBrowser(0),
_contextMenu(0),
_recentActionsMenu(0), _recentActionsMenu(0),
_folderOpenActionMapper(new QSignalMapper(this)), _folderOpenActionMapper(new QSignalMapper(this)),
_recentItemsMapper(new QSignalMapper(this)), _recentItemsMapper(new QSignalMapper(this)),
@ -303,11 +302,11 @@ void ownCloudGui::setupContextMenu()
_recentActionsMenu->addAction(tr("None.")); _recentActionsMenu->addAction(tr("None."));
_recentActionsMenu->addAction(_actionRecent); _recentActionsMenu->addAction(_actionRecent);
} else { } else {
_contextMenu = new QMenu(_contextMenu); _contextMenu.reset(new QMenu());
_recentActionsMenu = new QMenu(tr("Recent Changes")); _recentActionsMenu = new QMenu(tr("Recent Changes"), _contextMenu.data());
// this must be called only once after creating the context menu, or // this must be called only once after creating the context menu, or
// it will trigger a bug in Ubuntu's SNI bridge patch (11.10, 12.04). // it will trigger a bug in Ubuntu's SNI bridge patch (11.10, 12.04).
_tray->setContextMenu(_contextMenu); _tray->setContextMenu(_contextMenu.data());
} }
_contextMenu->setTitle(Theme::instance()->appNameGUI() ); _contextMenu->setTitle(Theme::instance()->appNameGUI() );
_contextMenu->addAction(_actionOpenoC); _contextMenu->addAction(_actionOpenoC);
@ -464,7 +463,7 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const Progress::Info
Q_UNUSED(folder); Q_UNUSED(folder);
if (!progress._currentDiscoveredFolder.isEmpty()) { if (!progress._currentDiscoveredFolder.isEmpty()) {
_actionStatus->setText( tr("Discovering %1") _actionStatus->setText( tr("Discovering '%1'")
.arg( progress._currentDiscoveredFolder )); .arg( progress._currentDiscoveredFolder ));
} else if (progress._totalSize == 0 ) { } else if (progress._totalSize == 0 ) {
quint64 currentFile = progress._completedFileCount + progress._currentItems.count(); quint64 currentFile = progress._completedFileCount + progress._currentItems.count();

View file

@ -85,7 +85,7 @@ private:
#endif #endif
QPointer<LogBrowser>_logBrowser; QPointer<LogBrowser>_logBrowser;
// tray's menu // tray's menu
QMenu *_contextMenu; QScopedPointer<QMenu> _contextMenu;
QMenu *_recentActionsMenu; QMenu *_recentActionsMenu;
QAction *_actionLogin; QAction *_actionLogin;

View file

@ -29,6 +29,8 @@
#include "ui_protocolwidget.h" #include "ui_protocolwidget.h"
#include <climits>
namespace Mirall { namespace Mirall {
ProtocolWidget::ProtocolWidget(QWidget *parent) : ProtocolWidget::ProtocolWidget(QWidget *parent) :
@ -183,6 +185,15 @@ void ProtocolWidget::slotOpenFile( QTreeWidgetItem *item, int )
} }
} }
QString ProtocolWidget::fixupFilename( const QString& name )
{
if( Utility::isMac() ) {
QString n(name);
return n.replace(QChar(':'), QChar('/'));
}
return name;
}
QTreeWidgetItem* ProtocolWidget::createCompletedTreewidgetItem(const QString& folder, const SyncFileItem& item) QTreeWidgetItem* ProtocolWidget::createCompletedTreewidgetItem(const QString& folder, const SyncFileItem& item)
{ {
QStringList columns; QStringList columns;
@ -193,7 +204,7 @@ QTreeWidgetItem* ProtocolWidget::createCompletedTreewidgetItem(const QString& fo
QString message; QString message;
columns << timeStr; columns << timeStr;
columns << item._file; columns << fixupFilename(item._file);
columns << folder; columns << folder;
if (Progress::isWarningKind(item._status)) { if (Progress::isWarningKind(item._status)) {
message= item._errorString; message= item._errorString;
@ -205,7 +216,13 @@ QTreeWidgetItem* ProtocolWidget::createCompletedTreewidgetItem(const QString& fo
} }
} else { } else {
message = Progress::asResultString(item); // if the error string is set, it's prefered because it is a usefull user message.
// at least should be...
if(item._errorString.isEmpty()) {
message = Progress::asResultString(item);
} else {
message = item._errorString;
}
columns << message; columns << message;
if (Progress::isSizeDependent(item._instruction)) { if (Progress::isSizeDependent(item._instruction)) {
columns << Utility::octetsToString( item._size ); columns << Utility::octetsToString( item._size );
@ -247,7 +264,7 @@ void ProtocolWidget::computeResyncButtonEnabled()
void ProtocolWidget::slotProgressInfo( const QString& folder, const Progress::Info& progress ) void ProtocolWidget::slotProgressInfo( const QString& folder, const Progress::Info& progress )
{ {
if( progress._completedFileCount == 0 ) { if( progress._completedFileCount == ULLONG_MAX ) {
// The sync is restarting, clean the old items // The sync is restarting, clean the old items
cleanIgnoreItems(folder); cleanIgnoreItems(folder);
computeResyncButtonEnabled(); computeResyncButtonEnabled();

View file

@ -56,6 +56,8 @@ private:
void setSyncResultStatus(const SyncResult& result ); void setSyncResultStatus(const SyncResult& result );
void cleanIgnoreItems( const QString& folder ); void cleanIgnoreItems( const QString& folder );
void computeResyncButtonEnabled(); void computeResyncButtonEnabled();
QString fixupFilename( const QString& name );
QTreeWidgetItem* createCompletedTreewidgetItem(const QString &folder, const SyncFileItem &item ); QTreeWidgetItem* createCompletedTreewidgetItem(const QString &folder, const SyncFileItem &item );

View file

@ -31,11 +31,13 @@
namespace Mirall { namespace Mirall {
SelectiveSyncTreeView::SelectiveSyncTreeView(Account *account, QWidget* parent) SelectiveSyncTreeView::SelectiveSyncTreeView(Account *account, QWidget* parent)
: QTreeWidget(parent), _account(account) : QTreeWidget(parent), _inserting(false), _account(account)
{ {
connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(slotItemExpanded(QTreeWidgetItem*))); connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(slotItemExpanded(QTreeWidgetItem*)));
connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotItemChanged(QTreeWidgetItem*,int))); connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotItemChanged(QTreeWidgetItem*,int)));
header()->hide(); header()->hide();
setSortingEnabled(true);
sortByColumn(0, Qt::AscendingOrder);
} }
void SelectiveSyncTreeView::refreshFolders() void SelectiveSyncTreeView::refreshFolders()
@ -260,7 +262,7 @@ void SelectiveSyncDialog::init(Account *account)
{ {
QVBoxLayout *layout = new QVBoxLayout(this); QVBoxLayout *layout = new QVBoxLayout(this);
_treeView = new SelectiveSyncTreeView(account, this); _treeView = new SelectiveSyncTreeView(account, this);
layout->addWidget(new QLabel(tr("Only checked folders will sync to this computer"))); layout->addWidget(new QLabel(tr("Unchecked folders will not be sync to this computer")));
layout->addWidget(_treeView); layout->addWidget(_treeView);
QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal); QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal);
QPushButton *button; QPushButton *button;
@ -274,6 +276,7 @@ void SelectiveSyncDialog::init(Account *account)
void SelectiveSyncDialog::accept() void SelectiveSyncDialog::accept()
{ {
if (_folder) { if (_folder) {
auto oldBlackListSet = _folder->selectiveSyncBlackList().toSet();
QStringList blackList = _treeView->createBlackList(); QStringList blackList = _treeView->createBlackList();
_folder->setSelectiveSyncBlackList(blackList); _folder->setSelectiveSyncBlackList(blackList);
@ -281,11 +284,19 @@ void SelectiveSyncDialog::accept()
QSettings settings(_folder->configFile(), QSettings::IniFormat); QSettings settings(_folder->configFile(), QSettings::IniFormat);
settings.beginGroup(FolderMan::escapeAlias(_folder->alias())); settings.beginGroup(FolderMan::escapeAlias(_folder->alias()));
settings.setValue("blackList", blackList); settings.setValue("blackList", blackList);
FolderMan *folderMan = FolderMan::instance(); FolderMan *folderMan = FolderMan::instance();
if (_folder->isBusy()) { if (_folder->isBusy()) {
_folder->slotTerminateAndPauseSync(); _folder->slotTerminateSync();
} }
//The part that changed should not be read from the DB on next sync because there might be new folders
// (the ones that are no longer in the blacklist)
auto blackListSet = blackList.toSet();
auto changes = (oldBlackListSet - blackListSet) + (blackListSet - oldBlackListSet);
foreach(const auto &it, changes) {
_folder->journalDb()->avoidReadFromDbOnNextSync(it);
}
folderMan->slotScheduleSync(_folder->alias()); folderMan->slotScheduleSync(_folder->alias());
} }
QDialog::accept(); QDialog::accept();

View file

@ -46,7 +46,7 @@ private:
QString _folderPath; QString _folderPath;
QString _rootName; QString _rootName;
QStringList _oldBlackList; QStringList _oldBlackList;
bool _inserting = false; // set to true when we are inserting new items on the list bool _inserting; // set to true when we are inserting new items on the list
Account *_account; Account *_account;
}; };

View file

@ -39,6 +39,11 @@ SettingsDialogMac::SettingsDialogMac(ownCloudGui *gui, QWidget *parent)
closeWindowAction->setShortcut(QKeySequence("Ctrl+W")); closeWindowAction->setShortcut(QKeySequence("Ctrl+W"));
connect(closeWindowAction, SIGNAL(triggered()), SLOT(close())); connect(closeWindowAction, SIGNAL(triggered()), SLOT(close()));
addAction(closeWindowAction); addAction(closeWindowAction);
// People perceive this as a Window, so also make Ctrl+H work
QAction *hideWindowAction = new QAction(this);
hideWindowAction->setShortcut(QKeySequence("Ctrl+H"));
connect(hideWindowAction, SIGNAL(triggered()), SLOT(hide()));
addAction(hideWindowAction);
setObjectName("SettingsMac"); // required as group for saveGeometry call setObjectName("SettingsMac"); // required as group for saveGeometry call

View file

@ -33,6 +33,15 @@
#include <QFile> #include <QFile>
#include <QDir> #include <QDir>
#include <QApplication> #include <QApplication>
#include <QLocalSocket>
#include <sqlite3.h>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QStandardPaths>
#endif
// This is the version that is returned when the client asks for the VERSION. // This is the version that is returned when the client asks for the VERSION.
// The first number should be changed if there is an incompatible change that breaks old clients. // The first number should be changed if there is an incompatible change that breaks old clients.
@ -54,147 +63,56 @@ CSYNC_EXCLUDE_TYPE csync_excluded_no_ctx(c_strlist_t *excludes, const char *path
int csync_exclude_load(const char *fname, c_strlist_t **list); int csync_exclude_load(const char *fname, c_strlist_t **list);
} }
namespace {
const int PORT = 34001;
}
namespace Mirall { namespace Mirall {
#define DEBUG qDebug() << "SocketApi: " #define DEBUG qDebug() << "SocketApi: "
namespace SocketApiHelper {
SyncFileStatus fileStatus(Folder *folder, const QString& systemFileName, c_strlist_t *excludes );
/**
* @brief recursiveFolderStatus
* @param fileName - the relative file name to examine
* @return the resulting status
*
* The resulting status can only be either SYNC which means all files
* are in sync, ERROR if an error occured, or EVAL if something needs
* to be synced underneath this dir.
*/
// compute the file status of a directory recursively. It returns either
// "all in sync" or "needs update" or "error", no more details.
SyncFileStatus recursiveFolderStatus(Folder *folder, const QString& fileName, c_strlist_t *excludes )
{
QDir dir(folder->path() + fileName);
const QStringList dirEntries = dir.entryList( QDir::AllEntries | QDir::NoDotAndDotDot );
SyncFileStatus result(SyncFileStatus::STATUS_SYNC);
foreach( const QString entry, dirEntries ) {
QString normalizedFile = QString(fileName + QLatin1Char('/') + entry).normalized(QString::NormalizationForm_C);
QFileInfo fi(entry);
SyncFileStatus sfs;
if( fi.isDir() ) {
sfs = recursiveFolderStatus(folder, normalizedFile, excludes );
} else {
QString fs( normalizedFile );
if( fileName.isEmpty() ) {
// toplevel, no slash etc. needed.
fs = entry.normalized(QString::NormalizationForm_C);
}
sfs = fileStatus(folder, fs, excludes);
}
if( sfs.tag() == SyncFileStatus::STATUS_STAT_ERROR || sfs.tag() == SyncFileStatus::STATUS_ERROR ) {
return SyncFileStatus::STATUS_ERROR;
} else if( sfs.tag() == SyncFileStatus::STATUS_EVAL || sfs.tag() == SyncFileStatus::STATUS_NEW) {
result.set(SyncFileStatus::STATUS_EVAL);
}
}
return result;
}
/**
* Get status about a single file.
*/
SyncFileStatus fileStatus(Folder *folder, const QString& systemFileName, c_strlist_t *excludes )
{
// FIXME: Find a way for STATUS_ERROR
QString file = folder->path();
QString fileName = systemFileName.normalized(QString::NormalizationForm_C);
bool isSyncRootFolder = true;
if( fileName != QLatin1String("/") && !fileName.isEmpty() ) {
file = folder->path() + fileName;
isSyncRootFolder = false;
}
if( fileName.endsWith(QLatin1Char('/')) ) {
fileName.truncate(fileName.length()-1);
qDebug() << "Removed trailing slash: " << fileName;
}
QFileInfo fi(file);
if( !fi.exists() ) {
qDebug() << "OO File " << file << " is not existing";
return SyncFileStatus(SyncFileStatus::STATUS_STAT_ERROR);
}
// file is ignored?
if( fi.isSymLink() ) {
return SyncFileStatus(SyncFileStatus::STATUS_IGNORE);
}
int type = CSYNC_FTW_TYPE_FILE;
if( fi.isDir() ) {
type = CSYNC_FTW_TYPE_DIR;
}
// '\' is ignored, so convert to unix path before passing the path in.
QString unixFileName = QDir::fromNativeSeparators(fileName);
CSYNC_EXCLUDE_TYPE excl = csync_excluded_no_ctx(excludes, unixFileName.toUtf8(), type);
if( excl != CSYNC_NOT_EXCLUDED ) {
return SyncFileStatus(SyncFileStatus::STATUS_IGNORE);
}
// Problem: for the sync dir itself we do not have a record in the sync journal
// so the next check must not be used for the sync root folder.
SyncJournalFileRecord rec = folder->journalDb()->getFileRecord(unixFileName);
if( !isSyncRootFolder && !rec.isValid() ) {
return SyncFileStatus(SyncFileStatus::STATUS_NEW);
}
SyncFileStatus status(SyncFileStatus::STATUS_NONE);
if( type == CSYNC_FTW_TYPE_DIR ) {
// compute recursive status of the directory
status = recursiveFolderStatus( folder, fileName, excludes );
} else if( FileSystem::getModTime(fi.absoluteFilePath()) != Utility::qDateTimeToTime_t(rec._modtime) ) {
// file was locally modified.
status.set(SyncFileStatus::STATUS_EVAL);
} else {
status.set(SyncFileStatus::STATUS_SYNC);
}
if (rec._remotePerm.contains("S")) {
// FIXME! that should be an additional flag
status.setSharedWithMe(true);
}
return status;
}
}
SocketApi::SocketApi(QObject* parent) SocketApi::SocketApi(QObject* parent)
: QObject(parent) : QObject(parent)
, _localServer(new QTcpServer(this))
, _excludes(0) , _excludes(0)
{ {
// setup socket QString socketPath;
DEBUG << "Establishing SocketAPI server at" << PORT;
if (!_localServer->listen(QHostAddress::LocalHost, PORT)) { if (Utility::isWindows()) {
DEBUG << "Failed to bind to port" << PORT; socketPath = QLatin1String("\\\\.\\pipe\\")
+ Theme::instance()->appName();
} else if (Utility::isMac()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
// Always using Qt5 on OS X
QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
socketPath = runtimeDir + "/SyncStateHelper/" + Theme::instance()->appName() + ".socket";
// We use the generic SyncStateHelper name on OS X since the different branded clients
// should unfortunately not mention that they are ownCloud :-)
#endif
} else if( Utility::isLinux() ) {
QString runtimeDir;
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
#else
runtimeDir = QFile::decodeName(qgetenv("XDG_RUNTIME_DIR"));
#endif
socketPath = runtimeDir + "/" + Theme::instance()->appName() + "/socket";
} else {
DEBUG << "An unexpected system detected";
} }
connect(_localServer, SIGNAL(newConnection()), this, SLOT(slotNewConnection()));
QLocalServer::removeServer(socketPath);
QFileInfo info(socketPath);
if (!info.dir().exists()) {
bool result = info.dir().mkpath(".");
DEBUG << "creating" << info.dir().path() << result;
if( result ) {
QFile::setPermissions(socketPath,
QFile::Permissions(QFile::ReadOwner+QFile::WriteOwner+QFile::ExeOwner));
}
}
if(!_localServer.listen(socketPath)) {
DEBUG << "can't start server" << socketPath;
} else {
DEBUG << "server started, listening at " << socketPath;
}
connect(&_localServer, SIGNAL(newConnection()), this, SLOT(slotNewConnection()));
// folder watcher // folder watcher
connect(FolderMan::instance(), SIGNAL(folderSyncStateChange(QString)), this, SLOT(slotUpdateFolderView(QString))); connect(FolderMan::instance(), SIGNAL(folderSyncStateChange(QString)), this, SLOT(slotUpdateFolderView(QString)));
@ -207,7 +125,7 @@ SocketApi::SocketApi(QObject* parent)
SocketApi::~SocketApi() SocketApi::~SocketApi()
{ {
DEBUG << "dtor"; DEBUG << "dtor";
_localServer->close(); _localServer.close();
slotClearExcludesList(); slotClearExcludesList();
} }
@ -234,7 +152,7 @@ void SocketApi::slotReadExcludes()
void SocketApi::slotNewConnection() void SocketApi::slotNewConnection()
{ {
QTcpSocket* socket = _localServer->nextPendingConnection(); SocketType* socket = _localServer.nextPendingConnection();
if( ! socket ) { if( ! socket ) {
return; return;
@ -266,14 +184,14 @@ void SocketApi::onLostConnection()
{ {
DEBUG << "Lost connection " << sender(); DEBUG << "Lost connection " << sender();
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender()); SocketType* socket = qobject_cast<SocketType*>(sender());
_listeners.removeAll(socket); _listeners.removeAll(socket);
} }
void SocketApi::slotReadSocket() void SocketApi::slotReadSocket()
{ {
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender()); SocketType* socket = qobject_cast<SocketType*>(sender());
Q_ASSERT(socket); Q_ASSERT(socket);
while(socket->canReadLine()) { while(socket->canReadLine()) {
@ -281,12 +199,12 @@ void SocketApi::slotReadSocket()
QString command = line.split(":").first(); QString command = line.split(":").first();
QString function = QString(QLatin1String("command_")).append(command); QString function = QString(QLatin1String("command_")).append(command);
QString functionWithArguments = function + QLatin1String("(QString,QTcpSocket*)"); QString functionWithArguments = function + QLatin1String("(QString,SocketType*)");
int indexOfMethod = this->metaObject()->indexOfMethod(functionWithArguments.toAscii()); int indexOfMethod = this->metaObject()->indexOfMethod(functionWithArguments.toAscii());
QString argument = line.remove(0, command.length()+1).trimmed(); QString argument = line.remove(0, command.length()+1).trimmed();
if(indexOfMethod != -1) { if(indexOfMethod != -1) {
QMetaObject::invokeMethod(this, function.toAscii(), Q_ARG(QString, argument), Q_ARG(QTcpSocket*, socket)); QMetaObject::invokeMethod(this, function.toAscii(), Q_ARG(QString, argument), Q_ARG(SocketType*, socket));
} else { } else {
DEBUG << "The command is not supported by this version of the client:" << command << "with argument:" << argument; DEBUG << "The command is not supported by this version of the client:" << command << "with argument:" << argument;
} }
@ -306,11 +224,30 @@ void SocketApi::slotUnregisterPath( const QString& alias )
Folder *f = FolderMan::instance()->folder(alias); Folder *f = FolderMan::instance()->folder(alias);
if (f) { if (f) {
broadcastMessage(QLatin1String("UNREGISTER_PATH"), f->path(), QString::null, true ); broadcastMessage(QLatin1String("UNREGISTER_PATH"), f->path(), QString::null, true );
if( _dbQueries.contains(f)) {
SqlQuery *h = _dbQueries[f];
if( h ) {
h->finish();
}
_dbQueries.remove(f);
}
if( _openDbs.contains(f) ) {
SqlDatabase *db = _openDbs[f];
if( db ) {
db->close();
}
_openDbs.remove(f);
}
} }
} }
void SocketApi::slotUpdateFolderView(const QString& alias) void SocketApi::slotUpdateFolderView(const QString& alias)
{ {
if (_listeners.isEmpty()) {
return;
}
Folder *f = FolderMan::instance()->folder(alias); Folder *f = FolderMan::instance()->folder(alias);
if (f) { if (f) {
// do only send UPDATE_VIEW for a couple of status // do only send UPDATE_VIEW for a couple of status
@ -320,17 +257,23 @@ void SocketApi::slotUpdateFolderView(const QString& alias)
f->syncResult().status() == SyncResult::Problem || f->syncResult().status() == SyncResult::Problem ||
f->syncResult().status() == SyncResult::Error || f->syncResult().status() == SyncResult::Error ||
f->syncResult().status() == SyncResult::SetupError ) { f->syncResult().status() == SyncResult::SetupError ) {
if( Utility::isWindows() ) {
Utility::winShellChangeNotify( f->path() ); broadcastMessage(QLatin1String("STATUS"), f->path() ,
} else { this->fileStatus(f, "", _excludes).toSocketAPIString());
broadcastMessage(QLatin1String("UPDATE_VIEW"), f->path() );
} broadcastMessage(QLatin1String("UPDATE_VIEW"), f->path() );
} else {
qDebug() << "Not sending UPDATE_VIEW for" << alias << "because status() is" << f->syncResult().status();
} }
} }
} }
void SocketApi::slotJobCompleted(const QString &folder, const SyncFileItem &item) void SocketApi::slotJobCompleted(const QString &folder, const SyncFileItem &item)
{ {
if (_listeners.isEmpty()) {
return;
}
Folder *f = FolderMan::instance()->folder(folder); Folder *f = FolderMan::instance()->folder(folder);
if (!f) { if (!f) {
return; return;
@ -347,6 +290,10 @@ void SocketApi::slotJobCompleted(const QString &folder, const SyncFileItem &item
void SocketApi::slotSyncItemDiscovered(const QString &folder, const SyncFileItem &item) void SocketApi::slotSyncItemDiscovered(const QString &folder, const SyncFileItem &item)
{ {
if (_listeners.isEmpty()) {
return;
}
if (item._instruction == CSYNC_INSTRUCTION_NONE) { if (item._instruction == CSYNC_INSTRUCTION_NONE) {
return; return;
} }
@ -364,18 +311,20 @@ void SocketApi::slotSyncItemDiscovered(const QString &folder, const SyncFileItem
void SocketApi::sendMessage(QTcpSocket *socket, const QString& message, bool doWait) void SocketApi::sendMessage(SocketType *socket, const QString& message, bool doWait)
{ {
DEBUG << "Sending message: " << message; DEBUG << "Sending message: " << message;
QString localMessage = message; QString localMessage = message;
if( ! localMessage.endsWith(QLatin1Char('\n'))) { if( ! localMessage.endsWith(QLatin1Char('\n'))) {
localMessage.append(QLatin1Char('\n')); localMessage.append(QLatin1Char('\n'));
} }
qint64 sent = socket->write(localMessage.toUtf8());
QByteArray bytesToSend = localMessage.toUtf8();
qint64 sent = socket->write(bytesToSend);
if( doWait ) { if( doWait ) {
socket->waitForBytesWritten(1000); socket->waitForBytesWritten(1000);
} }
if( sent != localMessage.toUtf8().length() ) { if( sent != bytesToSend.length() ) {
qDebug() << "WARN: Could not send all data on socket for " << localMessage; qDebug() << "WARN: Could not send all data on socket for " << localMessage;
} }
@ -394,13 +343,14 @@ void SocketApi::broadcastMessage( const QString& verb, const QString& path, cons
msg.append(QDir::toNativeSeparators(path)); msg.append(QDir::toNativeSeparators(path));
} }
DEBUG << "Broadcasting to" << _listeners.count() << "listeners: " << msg; // sendMessage already has a debug output
foreach(QTcpSocket *socket, _listeners) { //DEBUG << "Broadcasting to" << _listeners.count() << "listeners: " << msg;
foreach(SocketType *socket, _listeners) {
sendMessage(socket, msg, doWait); sendMessage(socket, msg, doWait);
} }
} }
void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString& argument, QTcpSocket* socket) void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString& argument, SocketType* socket)
{ {
// This command is the same as RETRIEVE_FILE_STATUS // This command is the same as RETRIEVE_FILE_STATUS
@ -408,7 +358,7 @@ void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString& argument, QTcpSock
command_RETRIEVE_FILE_STATUS(argument, socket); command_RETRIEVE_FILE_STATUS(argument, socket);
} }
void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QTcpSocket* socket) void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, SocketType* socket)
{ {
if( !socket ) { if( !socket ) {
qDebug() << "No valid socket object."; qDebug() << "No valid socket object.";
@ -426,7 +376,7 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QTcpSocket
statusString = QLatin1String("NOP"); statusString = QLatin1String("NOP");
} else { } else {
const QString file = argument.mid(syncFolder->path().length()); const QString file = argument.mid(syncFolder->path().length());
SyncFileStatus fileStatus = SocketApiHelper::fileStatus(syncFolder, file, _excludes); SyncFileStatus fileStatus = this->fileStatus(syncFolder, file, _excludes);
statusString = fileStatus.toSocketAPIString(); statusString = fileStatus.toSocketAPIString();
} }
@ -436,10 +386,186 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QTcpSocket
sendMessage(socket, message); sendMessage(socket, message);
} }
void SocketApi::command_VERSION(const QString&, QTcpSocket* socket) void SocketApi::command_VERSION(const QString&, SocketType* socket)
{ {
sendMessage(socket, QLatin1String("VERSION:" MIRALL_VERSION_STRING ":" MIRALL_SOCKET_API_VERSION)); sendMessage(socket, QLatin1String("VERSION:" MIRALL_VERSION_STRING ":" MIRALL_SOCKET_API_VERSION));
} }
SqlQuery* SocketApi::getSqlQuery( Folder *folder )
{
if( !folder ) {
return 0;
}
if( _dbQueries.contains(folder) ) {
return _dbQueries[folder];
}
/* No valid sql query object yet for this folder */
int rc;
const QString sql("SELECT inode, mode, modtime, type, md5, fileid, remotePerm FROM "
"metadata WHERE phash=?1");
QString dbFileName = folder->journalDb()->databaseFilePath();
QFileInfo fi(dbFileName);
if( fi.exists() ) {
SqlDatabase *db = new SqlDatabase;
if( db && db->open(dbFileName) ) {
_openDbs.insert(folder, db);
SqlQuery *query = new SqlQuery(*db);
rc = query->prepare(sql);
if( rc != SQLITE_OK ) {
delete query;
qDebug() << "Unable to prepare the query statement:" << rc;
return 0; // do not insert into hash
}
_dbQueries.insert( folder, query);
return query;
}
} else {
qDebug() << Q_FUNC_INFO << "Journal to query does not yet exist.";
}
return 0;
}
SyncJournalFileRecord SocketApi::dbFileRecord_capi( Folder *folder, QString fileName )
{
if( !(folder && folder->journalDb()) ) {
return SyncJournalFileRecord();
}
if( fileName.startsWith( folder->path() )) {
fileName.remove(0, folder->path().length());
}
SqlQuery *query = getSqlQuery(folder);
SyncJournalFileRecord rec;
if( query ) {
qlonglong phash = SyncJournalDb::getPHash( fileName );
query->bindValue(1, phash);
// int column_count = sqlite3_column_count(stmt);
if (query->next()) {
rec._path = fileName;
rec._inode = query->int64Value(0);
rec._mode = query->intValue(1);
rec._modtime = Utility::qDateTimeFromTime_t( query->int64Value(2));
rec._type = query->intValue(3);
rec._etag = query->baValue(4);
rec._fileId = query->baValue(5);
rec._remotePerm = query->baValue(6);
}
query->reset();
}
return rec;
}
/**
* Get status about a single file.
*/
SyncFileStatus SocketApi::fileStatus(Folder *folder, const QString& systemFileName, c_strlist_t *excludes )
{
QString file = folder->path();
QString fileName = systemFileName.normalized(QString::NormalizationForm_C);
if( fileName != QLatin1String("/") && !fileName.isEmpty() ) {
file = folder->path() + fileName;
}
if( fileName.endsWith(QLatin1Char('/')) ) {
fileName.truncate(fileName.length()-1);
qDebug() << "Removed trailing slash: " << fileName;
}
QFileInfo fi(file);
if( !fi.exists() ) {
qDebug() << "OO File " << file << " is not existing";
return SyncFileStatus(SyncFileStatus::STATUS_STAT_ERROR);
}
// file is ignored?
if( fi.isSymLink() ) {
return SyncFileStatus(SyncFileStatus::STATUS_IGNORE);
}
csync_ftw_type_e type = CSYNC_FTW_TYPE_FILE;
if( fi.isDir() ) {
type = CSYNC_FTW_TYPE_DIR;
}
// '\' is ignored, so convert to unix path before passing the path in.
QString unixFileName = QDir::fromNativeSeparators(fileName);
CSYNC_EXCLUDE_TYPE excl = csync_excluded_no_ctx(excludes, unixFileName.toUtf8(), type);
if( excl != CSYNC_NOT_EXCLUDED ) {
return SyncFileStatus(SyncFileStatus::STATUS_IGNORE);
}
SyncFileStatus status(SyncFileStatus::STATUS_NONE);
if (type == CSYNC_FTW_TYPE_DIR) {
if (folder->estimateState(fileName, type, &status)) {
qDebug() << Q_FUNC_INFO << "Folder estimated status for" << fileName << "to" << status.toSocketAPIString();
return status;
}
if (fileName == "") {
// sync folder itself
if (folder->syncResult().status() == SyncResult::Undefined
|| folder->syncResult().status() == SyncResult::NotYetStarted
|| folder->syncResult().status() == SyncResult::SyncPrepare
|| folder->syncResult().status() == SyncResult::SyncRunning
|| folder->syncResult().status() == SyncResult::Paused) {
status.set(SyncFileStatus::STATUS_EVAL);
return status;
} else if (folder->syncResult().status() == SyncResult::Success
|| folder->syncResult().status() == SyncResult::Problem) {
status.set(SyncFileStatus::STATUS_SYNC);
return status;
} else if (folder->syncResult().status() == SyncResult::Error
|| folder->syncResult().status() == SyncResult::SetupError
|| folder->syncResult().status() == SyncResult::SyncAbortRequested) {
status.set(SyncFileStatus::STATUS_ERROR);
return status;
}
}
SyncJournalFileRecord rec = dbFileRecord_capi(folder, unixFileName );
if (rec.isValid()) {
status.set(SyncFileStatus::STATUS_SYNC);
if (rec._remotePerm.contains("S")) {
status.setSharedWithMe(true);
}
} else {
status.set(SyncFileStatus::STATUS_EVAL);
}
} else if (type == CSYNC_FTW_TYPE_FILE) {
if (folder->estimateState(fileName, type, &status)) {
return status;
}
SyncJournalFileRecord rec = dbFileRecord_capi(folder, unixFileName );
if (rec.isValid()) {
if (rec._remotePerm.contains("S")) {
status.setSharedWithMe(true);
}
if( FileSystem::getModTime(fi.absoluteFilePath()) == Utility::qDateTimeToTime_t(rec._modtime) ) {
status.set(SyncFileStatus::STATUS_SYNC);
return status;
} else {
status.set(SyncFileStatus::STATUS_EVAL);
return status;
}
}
status.set(SyncFileStatus::STATUS_NEW);
return status;
}
return status;
}
} // namespace Mirall } // namespace Mirall

View file

@ -20,11 +20,16 @@ extern "C" {
#include <std/c_string.h> #include <std/c_string.h>
} }
#include <sqlite3.h>
#include <QWeakPointer> #include <QWeakPointer>
#include <QTcpSocket> #include <QTcpSocket>
#include <QTcpServer> #include <QTcpServer>
#include <QLocalServer>
#include "syncfileitem.h" #include "syncfileitem.h"
#include "syncjournalfilerecord.h"
#include "ownsql.h"
class QUrl; class QUrl;
class QLocalSocket; class QLocalSocket;
@ -32,6 +37,11 @@ class QStringList;
namespace Mirall { namespace Mirall {
typedef QLocalSocket SocketType;
class SyncFileStatus;
class Folder;
class SocketApi : public QObject class SocketApi : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -54,18 +64,29 @@ private slots:
void slotSyncItemDiscovered(const QString &, const SyncFileItem &); void slotSyncItemDiscovered(const QString &, const SyncFileItem &);
private: private:
void sendMessage(QTcpSocket* socket, const QString& message, bool doWait = false); SyncFileStatus fileStatus(Folder *folder, const QString& systemFileName, c_strlist_t *excludes );
SyncJournalFileRecord dbFileRecord( Folder *folder, QString fileName );
SyncJournalFileRecord dbFileRecord_capi( Folder *folder, QString fileName );
SyncFileStatus recursiveFolderStatus(Folder *folder, const QString& fileName, c_strlist_t *excludes );
SqlQuery *getSqlQuery( Folder *folder );
void sendMessage(SocketType* socket, const QString& message, bool doWait = false);
void broadcastMessage(const QString& verb, const QString &path, const QString &status = QString::null, bool doWait = false); void broadcastMessage(const QString& verb, const QString &path, const QString &status = QString::null, bool doWait = false);
Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString& argument, QTcpSocket* socket); Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString& argument, SocketType* socket);
Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString& argument, QTcpSocket* socket); Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString& argument, SocketType* socket);
Q_INVOKABLE void command_VERSION(const QString& argument, QTcpSocket* socket); Q_INVOKABLE void command_VERSION(const QString& argument, SocketType* socket);
private: #ifdef SOCKETAPI_TCP
QTcpServer *_localServer; QTcpServer _localServer;
QList<QTcpSocket*> _listeners; #else
QLocalServer _localServer;
#endif
QList<SocketType*> _listeners;
c_strlist_t *_excludes; c_strlist_t *_excludes;
QHash<Folder*, SqlQuery*> _dbQueries;
QHash<Folder*, SqlDatabase*> _openDbs;
}; };
} }

View file

@ -149,11 +149,10 @@ bool SslErrorDialog::checkFailingCertsKnown( const QList<QSslError> &errors )
} }
msg += QL("</div></body></html>"); msg += QL("</div></body></html>");
qDebug() << "# # # # # # "; //qDebug() << "# # # # # # ";
qDebug() << msg; //qDebug() << msg;
QTextDocument *doc = new QTextDocument(0); QTextDocument *doc = new QTextDocument(0);
QString style = styleSheet(); QString style = styleSheet();
qDebug() << "Style: " << style;
doc->addResource( QTextDocument::StyleSheetResource, QUrl( QL("format.css") ), style); doc->addResource( QTextDocument::StyleSheetResource, QUrl( QL("format.css") ), style);
doc->setHtml( msg ); doc->setHtml( msg );

View file

@ -80,7 +80,11 @@ QString Updater::getSystemInfo()
// To test, cmake with -DAPPLICATION_UPDATE_URL="http://127.0.0.1:8080/test.rss" // To test, cmake with -DAPPLICATION_UPDATE_URL="http://127.0.0.1:8080/test.rss"
Updater *Updater::create() Updater *Updater::create()
{ {
QUrl updateBaseUrl = addQueryParams(QUrl(QLatin1String(APPLICATION_UPDATE_URL))); QUrl updateBaseUrl(QString::fromLocal8Bit(qgetenv("OCC_UPDATE_URL")));
if (updateBaseUrl.isEmpty()) {
updateBaseUrl = QUrl(QLatin1String(APPLICATION_UPDATE_URL));
}
updateBaseUrl = addQueryParams(updateBaseUrl);
#if defined(Q_OS_MAC) && defined(HAVE_SPARKLE) #if defined(Q_OS_MAC) && defined(HAVE_SPARKLE)
updateBaseUrl.addQueryItem( QLatin1String("sparkle"), QLatin1String("true")); updateBaseUrl.addQueryItem( QLatin1String("sparkle"), QLatin1String("true"));
return new SparkleUpdater(updateBaseUrl.toString()); return new SparkleUpdater(updateBaseUrl.toString());

View file

@ -57,6 +57,7 @@ set(libsync_SRCS
syncresult.cpp syncresult.cpp
theme.cpp theme.cpp
utility.cpp utility.cpp
ownsql.cpp
creds/dummycredentials.cpp creds/dummycredentials.cpp
creds/abstractcredentials.cpp creds/abstractcredentials.cpp
creds/credentialsfactory.cpp creds/credentialsfactory.cpp

View file

@ -154,8 +154,10 @@ public:
QNetworkAccessManager* networkAccessManager(); QNetworkAccessManager* networkAccessManager();
QuotaInfo *quotaInfo(); QuotaInfo *quotaInfo();
signals: signals:
void stateChanged(int state); void stateChanged(int state);
void propagatorNetworkActivity();
protected Q_SLOTS: protected Q_SLOTS:
void slotHandleErrors(QNetworkReply*,QList<QSslError>); void slotHandleErrors(QNetworkReply*,QList<QSslError>);

View file

@ -66,7 +66,11 @@ int getauth(const char *prompt,
// qDebug() << "OOO Password requested!"; // qDebug() << "OOO Password requested!";
qstrncpy( buf, pwd.toUtf8().constData(), len ); qstrncpy( buf, pwd.toUtf8().constData(), len );
} else { } else {
re = handleNeonSSLProblems(prompt, buf, len, echo, verify, userdata); if( http_credentials->sslIsTrusted() ) {
qstrcpy( buf, "yes" ); // Certificate is fine!
} else {
re = handleNeonSSLProblems(prompt, buf, len, echo, verify, userdata);
}
} }
return re; return re;
} }

View file

@ -52,6 +52,7 @@ public:
virtual QString queryPassword(bool *ok) = 0; virtual QString queryPassword(bool *ok) = 0;
void invalidateToken(Account *account) Q_DECL_OVERRIDE; void invalidateToken(Account *account) Q_DECL_OVERRIDE;
QString fetchUser(Account *account); QString fetchUser(Account *account);
virtual bool sslIsTrusted() { return false; }
private Q_SLOTS: private Q_SLOTS:
void slotAuthentication(QNetworkReply*, QAuthenticator*); void slotAuthentication(QNetworkReply*, QAuthenticator*);

View file

@ -16,9 +16,11 @@
#include <csync_private.h> #include <csync_private.h>
#include <qdebug.h> #include <qdebug.h>
#include <QUrl>
namespace Mirall { namespace Mirall {
bool DiscoveryJob::isInBlackList(const QString& path) const bool DiscoveryJob::isInSelectiveSyncBlackList(const QString& path) const
{ {
if (_selectiveSyncBlackList.isEmpty()) { if (_selectiveSyncBlackList.isEmpty()) {
// If there is no black list, everything is allowed // If there is no black list, everything is allowed
@ -35,19 +37,24 @@ bool DiscoveryJob::isInBlackList(const QString& path) const
auto it = std::lower_bound(_selectiveSyncBlackList.begin(), _selectiveSyncBlackList.end(), pathSlash); auto it = std::lower_bound(_selectiveSyncBlackList.begin(), _selectiveSyncBlackList.end(), pathSlash);
if (it != _selectiveSyncBlackList.end() && *it == pathSlash) {
return true;
}
if (it == _selectiveSyncBlackList.begin()) { if (it == _selectiveSyncBlackList.begin()) {
return false; return false;
} }
--it; --it;
if (pathSlash.startsWith(*it + QLatin1Char('/'))) { Q_ASSERT(it->endsWith(QLatin1Char('/'))); // SyncEngine::setSelectiveSyncBlackList makes sure of that
if (pathSlash.startsWith(*it)) {
return true; return true;
} }
return false; return false;
} }
int DiscoveryJob::isInWhiteListCallBack(void *data, const char *path) int DiscoveryJob::isInSelectiveSyncBlackListCallBack(void *data, const char *path)
{ {
return static_cast<DiscoveryJob*>(data)->isInBlackList(QString::fromUtf8(path)); return static_cast<DiscoveryJob*>(data)->isInSelectiveSyncBlackList(QString::fromUtf8(path));
} }
void DiscoveryJob::update_job_update_callback (bool local, void DiscoveryJob::update_job_update_callback (bool local,
@ -65,15 +72,15 @@ void DiscoveryJob::update_job_update_callback (bool local,
updateJob->lastUpdateProgressCallbackCall.restart(); updateJob->lastUpdateProgressCallbackCall.restart();
} }
QString path = QString::fromUtf8(dirUrl).section('/', -1); QString path(QUrl::fromPercentEncoding(QByteArray(dirUrl)).section('/', -1));
emit updateJob->folderDiscovered(local, path); emit updateJob->folderDiscovered(local, path);
} }
} }
void DiscoveryJob::start() { void DiscoveryJob::start() {
_selectiveSyncBlackList.sort(); _selectiveSyncBlackList.sort();
_csync_ctx->checkBlackListHook = isInWhiteListCallBack; _csync_ctx->checkSelectiveSyncBlackListHook = isInSelectiveSyncBlackListCallBack;
_csync_ctx->checkBlackListData = this; _csync_ctx->checkSelectiveSyncBlackListData = this;
_csync_ctx->callbacks.update_callback = update_job_update_callback; _csync_ctx->callbacks.update_callback = update_job_update_callback;
_csync_ctx->callbacks.update_callback_userdata = this; _csync_ctx->callbacks.update_callback_userdata = this;
@ -85,8 +92,8 @@ void DiscoveryJob::start() {
lastUpdateProgressCallbackCall.invalidate(); lastUpdateProgressCallbackCall.invalidate();
int ret = csync_update(_csync_ctx); int ret = csync_update(_csync_ctx);
_csync_ctx->checkBlackListHook = 0; _csync_ctx->checkSelectiveSyncBlackListHook = 0;
_csync_ctx->checkBlackListData = 0; _csync_ctx->checkSelectiveSyncBlackListData = 0;
_csync_ctx->callbacks.update_callback = 0; _csync_ctx->callbacks.update_callback = 0;
_csync_ctx->callbacks.update_callback_userdata = 0; _csync_ctx->callbacks.update_callback_userdata = 0;

View file

@ -40,8 +40,8 @@ class DiscoveryJob : public QObject {
* return true if the given path should be synced, * return true if the given path should be synced,
* false if the path should be ignored * false if the path should be ignored
*/ */
bool isInBlackList(const QString &path) const; bool isInSelectiveSyncBlackList(const QString &path) const;
static int isInWhiteListCallBack(void *, const char *); static int isInSelectiveSyncBlackListCallBack(void *, const char *);
static void update_job_update_callback (bool local, static void update_job_update_callback (bool local,
const char *dirname, const char *dirname,

View file

@ -16,6 +16,7 @@
#include <QDir> #include <QDir>
#include <QStringList> #include <QStringList>
#include <QThread>
namespace Mirall { namespace Mirall {
@ -85,6 +86,7 @@ void Logger::log(Log log)
} else { } else {
// msg += "ownCloud - "; // msg += "ownCloud - ";
} }
msg += QString().sprintf("%p ", (void*)QThread::currentThread());
msg += log.message; msg += log.message;
// _logs.append(log); // _logs.append(log);
// std::cout << qPrintable(log.message) << std::endl; // std::cout << qPrintable(log.message) << std::endl;

View file

@ -205,15 +205,16 @@ QString MirallConfigFile::configPathWithAppName() const
return QFileInfo( configFile() ).dir().absolutePath().append("/"); return QFileInfo( configFile() ).dir().absolutePath().append("/");
} }
static const QLatin1String exclFile("sync-exclude.lst");
QString MirallConfigFile::excludeFile(Scope scope) const QString MirallConfigFile::excludeFile(Scope scope) const
{ {
// prefer sync-exclude.lst, but if it does not exist, check for // prefer sync-exclude.lst, but if it does not exist, check for
// exclude.lst for compatibility reasons in the user writeable // exclude.lst for compatibility reasons in the user writeable
// directories. // directories.
const QString exclFile("sync-exclude.lst");
QFileInfo fi;
if (scope != SystemScope) { if (scope != SystemScope) {
QFileInfo fi;
fi.setFile( configPath(), exclFile ); fi.setFile( configPath(), exclFile );
if( ! fi.isReadable() ) { if( ! fi.isReadable() ) {
@ -222,32 +223,37 @@ QString MirallConfigFile::excludeFile(Scope scope) const
if( ! fi.isReadable() ) { if( ! fi.isReadable() ) {
fi.setFile( configPath(), exclFile ); fi.setFile( configPath(), exclFile );
} }
return fi.absoluteFilePath();
} else if (scope != UserScope) {
return MirallConfigFile::excludeFileFromSystem();
} else {
Q_ASSERT(false);
return QString(); // unreachable
} }
}
if (scope != UserScope) { QString MirallConfigFile::excludeFileFromSystem()
// Check alternative places... {
if( ! fi.isReadable() ) { QFileInfo fi;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
fi.setFile( QCoreApplication::applicationDirPath(), exclFile ); fi.setFile( QCoreApplication::applicationDirPath(), exclFile );
#endif #endif
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
fi.setFile( QString( SYSCONFDIR "/%1").arg(Theme::instance()->appName()), exclFile ); fi.setFile( QString( SYSCONFDIR "/%1").arg(Theme::instance()->appName()), exclFile );
if ( ! fi.exists() ) { if ( ! fi.exists() ) {
// Prefer to return the preferred path! Only use the fallback location // Prefer to return the preferred path! Only use the fallback location
// if the other path does not exist and the fallback is valid. // if the other path does not exist and the fallback is valid.
QFileInfo nextToBinary( QCoreApplication::applicationDirPath(), exclFile ); QFileInfo nextToBinary( QCoreApplication::applicationDirPath(), exclFile );
if (nextToBinary.exists()) { if (nextToBinary.exists()) {
fi = nextToBinary; fi = nextToBinary;
}
}
#endif
#ifdef Q_OS_MAC
// exec path is inside the bundle
fi.setFile( QCoreApplication::applicationDirPath(),
QLatin1String("../Resources/") + exclFile );
#endif
} }
} }
#endif
#ifdef Q_OS_MAC
// exec path is inside the bundle
fi.setFile( QCoreApplication::applicationDirPath(),
QLatin1String("../Resources/") + exclFile );
#endif
return fi.absoluteFilePath(); return fi.absoluteFilePath();
} }

View file

@ -38,6 +38,7 @@ public:
QString configPathWithAppName() const; QString configPathWithAppName() const;
QString configFile() const; QString configFile() const;
QString excludeFile(Scope scope) const; QString excludeFile(Scope scope) const;
static QString excludeFileFromSystem(); // doesn't access config dir
bool exists(); bool exists();

View file

@ -39,6 +39,8 @@ Q_DECLARE_METATYPE(QTimer*)
namespace Mirall { namespace Mirall {
bool AbstractNetworkJob::preOc7WasDetected = false;
AbstractNetworkJob::AbstractNetworkJob(Account *account, const QString &path, QObject *parent) AbstractNetworkJob::AbstractNetworkJob(Account *account, const QString &path, QObject *parent)
: QObject(parent) : QObject(parent)
, _duration(0) , _duration(0)
@ -48,8 +50,22 @@ AbstractNetworkJob::AbstractNetworkJob(Account *account, const QString &path, QO
, _path(path) , _path(path)
{ {
_timer.setSingleShot(true); _timer.setSingleShot(true);
_timer.setInterval(10*1000); // default to 10 seconds. if (!AbstractNetworkJob::preOc7WasDetected) {
_timer.setInterval(10*1000); // default to 10 seconds.
} else {
qDebug() << "Pre-oc7 server detected, adjusting timeout values";
_timer.setInterval(60*1000); // long PROPFINDs in oc6 might take too long
}
connect(&_timer, SIGNAL(timeout()), this, SLOT(slotTimeout())); connect(&_timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
connect(this, SIGNAL(networkActivity()), SLOT(resetTimeout()));
// Network activity on the propagator jobs (GET/PUT) keeps all requests alive.
// This is a workaround for OC instances which only support one
// parallel up and download
if (_account) {
connect(_account, SIGNAL(propagatorNetworkActivity()), SLOT(resetTimeout()));
}
} }
void AbstractNetworkJob::setReply(QNetworkReply *reply) void AbstractNetworkJob::setReply(QNetworkReply *reply)
@ -80,11 +96,6 @@ void AbstractNetworkJob::setIgnoreCredentialFailure(bool ignore)
_ignoreCredentialFailure = ignore; _ignoreCredentialFailure = ignore;
} }
void AbstractNetworkJob::setAccount(Account *account)
{
_account = account;
}
void AbstractNetworkJob::setPath(const QString &path) void AbstractNetworkJob::setPath(const QString &path)
{ {
_path = path; _path = path;
@ -93,6 +104,8 @@ void AbstractNetworkJob::setPath(const QString &path)
void AbstractNetworkJob::setupConnections(QNetworkReply *reply) void AbstractNetworkJob::setupConnections(QNetworkReply *reply)
{ {
connect(reply, SIGNAL(finished()), SLOT(slotFinished())); connect(reply, SIGNAL(finished()), SLOT(slotFinished()));
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), SIGNAL(networkActivity()));
connect(reply, SIGNAL(uploadProgress(qint64,qint64)), SIGNAL(networkActivity()));
} }
QNetworkReply* AbstractNetworkJob::addTimer(QNetworkReply *reply) QNetworkReply* AbstractNetworkJob::addTimer(QNetworkReply *reply)
@ -179,9 +192,11 @@ QString AbstractNetworkJob::responseTimestamp()
return _responseTimestamp; return _responseTimestamp;
} }
AbstractNetworkJob::~AbstractNetworkJob() { AbstractNetworkJob::~AbstractNetworkJob()
if (_reply) {
if (_reply) {
_reply->deleteLater(); _reply->deleteLater();
}
} }
void AbstractNetworkJob::start() void AbstractNetworkJob::start()
@ -195,11 +210,14 @@ void AbstractNetworkJob::start()
void AbstractNetworkJob::slotTimeout() void AbstractNetworkJob::slotTimeout()
{ {
qDebug() << this << "Timeout" ; qDebug() << this << "Timeout";
reply()->abort(); if (reply()) {
reply()->abort();
} else {
qDebug() << "reply was NULL";
}
} }
/*********************************************************************************************/ /*********************************************************************************************/
RequestEtagJob::RequestEtagJob(Account *account, const QString &path, QObject *parent) RequestEtagJob::RequestEtagJob(Account *account, const QString &path, QObject *parent)
@ -419,7 +437,9 @@ bool CheckServerJob::finished()
bool success = false; bool success = false;
QByteArray body = reply()->readAll(); QByteArray body = reply()->readAll();
if( body.isEmpty() ) { int httpStatus = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if( body.isEmpty() || httpStatus != 200) {
qDebug() << "error: status.php replied " << httpStatus << body;
emit instanceNotFound(reply()); emit instanceNotFound(reply());
} else { } else {
QVariantMap status = QtJson::parse(QString::fromUtf8(body), success).toMap(); QVariantMap status = QtJson::parse(QString::fromUtf8(body), success).toMap();
@ -432,6 +452,12 @@ bool CheckServerJob::finished()
if( status.contains("installed") if( status.contains("installed")
&& status.contains("version") && status.contains("version")
&& status.contains("versionstring") ) { && status.contains("versionstring") ) {
QString versionString = status.value("version").toString();
if (versionString.contains('.') && versionString.split('.')[0].toInt() < 7) {
AbstractNetworkJob::preOc7WasDetected = true;
}
emit instanceFound(reply()->url(), status); emit instanceFound(reply()->url(), status);
} else { } else {
qDebug() << "No proper answer on " << requestedUrl; qDebug() << "No proper answer on " << requestedUrl;

View file

@ -55,15 +55,14 @@ public:
virtual void start(); virtual void start();
void setAccount(Account *account);
Account* account() const { return _account; } Account* account() const { return _account; }
void setPath(const QString &path); void setPath(const QString &path);
QString path() const { return _path; } QString path() const { return _path; }
void setReply(QNetworkReply *reply); void setReply(QNetworkReply *reply);
QNetworkReply* reply() const { return _reply; } QNetworkReply* reply() const { return _reply; }
void setIgnoreCredentialFailure(bool ignore); void setIgnoreCredentialFailure(bool ignore);
bool ignoreCredentialFailure() const { return _ignoreCredentialFailure; } bool ignoreCredentialFailure() const { return _ignoreCredentialFailure; }
@ -75,6 +74,7 @@ public slots:
void resetTimeout(); void resetTimeout();
signals: signals:
void networkError(QNetworkReply *reply); void networkError(QNetworkReply *reply);
void networkActivity();
protected: protected:
void setupConnections(QNetworkReply *reply); void setupConnections(QNetworkReply *reply);
QNetworkReply* davRequest(const QByteArray& verb, const QString &relPath, QNetworkReply* davRequest(const QByteArray& verb, const QString &relPath,
@ -94,6 +94,9 @@ protected:
QElapsedTimer _durationTimer; QElapsedTimer _durationTimer;
quint64 _duration; quint64 _duration;
// Timeout workarounds (Because of PHP session locking)
static bool preOc7WasDetected;
private slots: private slots:
void slotFinished(); void slotFinished();
virtual void slotTimeout(); virtual void slotTimeout();

View file

@ -51,14 +51,15 @@ void PropagateItemJob::done(SyncFileItem::Status status, const QString &errorStr
_item._errorString += tr("; Restoration Failed: ") + errorString; _item._errorString += tr("; Restoration Failed: ") + errorString;
} }
} else { } else {
_item._errorString = errorString; if( _item._errorString.isEmpty() ) {
_item._errorString = errorString;
}
} }
if( _propagator->_abortRequested.fetchAndAddRelaxed(0) ) { if( _propagator->_abortRequested.fetchAndAddRelaxed(0) &&
(status == SyncFileItem::NormalError || status == SyncFileItem::FatalError)) {
// an abort request is ongoing. Change the status to Soft-Error // an abort request is ongoing. Change the status to Soft-Error
status = SyncFileItem::SoftError; status = SyncFileItem::SoftError;
_item._errorString = tr("Operation was canceled by user interaction.");
} }
_item._status = status; _item._status = status;
@ -76,7 +77,7 @@ void PropagateItemJob::done(SyncFileItem::Status status, const QString &errorStr
} }
retries = defaultRetriesCount.fetchAndAddAcquire(0); retries = defaultRetriesCount.fetchAndAddAcquire(0);
} }
SyncJournalBlacklistRecord record(_item, retries);; SyncJournalBlacklistRecord record(_item, retries);
switch( status ) { switch( status ) {
case SyncFileItem::SoftError: case SyncFileItem::SoftError:
@ -96,9 +97,13 @@ void PropagateItemJob::done(SyncFileItem::Status status, const QString &errorStr
break; break;
case SyncFileItem::Success: case SyncFileItem::Success:
case SyncFileItem::Restoration: case SyncFileItem::Restoration:
if( _item._blacklistedInDb ) { if( _item._hasBlacklistEntry ) {
// wipe blacklist entry. // wipe blacklist entry.
_propagator->_journal->wipeBlacklistEntry(_item._file); _propagator->_journal->wipeBlacklistEntry(_item._file);
// remove a blacklist entry in case the file was moved.
if( _item._originalFile != _item._file ) {
_propagator->_journal->wipeBlacklistEntry(_item._originalFile);
}
} }
break; break;
case SyncFileItem::Conflict: case SyncFileItem::Conflict:
@ -380,10 +385,10 @@ bool OwncloudPropagator::localFileNameClash( const QString& relFile )
// returns false. // returns false.
} else { } else {
QString realFileName = QString::fromWCharArray( FindFileData.cFileName ); QString realFileName = QString::fromWCharArray( FindFileData.cFileName );
qDebug() << Q_FUNC_INFO << "Real file name is " << realFileName;
FindClose(hFind); FindClose(hFind);
if( ! file.endsWith(realFileName, Qt::CaseSensitive) ) { if( ! file.endsWith(realFileName, Qt::CaseSensitive) ) {
qDebug() << Q_FUNC_INFO << "Detected case clash between" << file << "and" << realFileName;
re = true; re = true;
} }
} }

View file

@ -52,7 +52,7 @@ QString ownCloudTheme::about() const
"<p>Copyright ownCloud, Inc.</p>" "<p>Copyright ownCloud, Inc.</p>"
"<p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>" "<p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>"
"ownCloud and the ownCloud Logo are registered trademarks of ownCloud, " "ownCloud and the ownCloud Logo are registered trademarks of ownCloud, "
"Inc. in the United States, other countries, or both</p>" "Inc. in the United States, other countries, or both.</p>"
) )
.arg(MIRALL_VERSION_STRING) .arg(MIRALL_VERSION_STRING)
.arg("http://" MIRALL_STRINGIFY(APPLICATION_DOMAIN)) .arg("http://" MIRALL_STRINGIFY(APPLICATION_DOMAIN))

292
src/libsync/ownsql.cpp Normal file
View file

@ -0,0 +1,292 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <QDateTime>
#include <QString>
#include <QDebug>
#include "ownsql.h"
#include "utility.h"
#define SQLITE_SLEEP_TIME_USEC 100000
#define SQLITE_REPEAT_COUNT 20
#define SQLITE_DO(A) if(1) { \
_errId = (A); if(_errId != SQLITE_OK) { _error= QString::fromUtf8(sqlite3_errmsg(_db)); \
} }
namespace Mirall {
SqlDatabase::SqlDatabase()
:_db(0),
_errId(0)
{
}
bool SqlDatabase::isOpen()
{
return _db != 0;
}
bool SqlDatabase::open( const QString& filename )
{
if(isOpen()) {
return true;
}
int flag = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|SQLITE_OPEN_NOMUTEX;
SQLITE_DO( sqlite3_open_v2(filename.toUtf8().constData(), &_db, flag, 0) );
if( _errId != SQLITE_OK ) {
close(); // FIXME: Correct?
_db = 0;
}
sqlite3_busy_timeout(_db, 5000);
return isOpen();
}
QString SqlDatabase::error() const
{
const QString err(_error);
// _error.clear();
return err;
}
void SqlDatabase::close()
{
if( _db ) {
SQLITE_DO(sqlite3_close(_db) );
_db = 0;
}
}
bool SqlDatabase::transaction()
{
if( ! _db ) {
return false;
}
SQLITE_DO(sqlite3_exec(_db, "BEGIN", 0, 0, 0));
return _errId == SQLITE_OK;
}
bool SqlDatabase::commit()
{
if( ! _db ) {
return false;
}
SQLITE_DO(sqlite3_exec(_db, "COMMIT", 0, 0, 0));
return _errId == SQLITE_OK;
}
sqlite3* SqlDatabase::sqliteDb()
{
return _db;
}
/* =========================================================================================== */
SqlQuery::SqlQuery( SqlDatabase db )
:_db(db.sqliteDb()),
_stmt(0)
{
}
SqlQuery::~SqlQuery()
{
if( _stmt ) {
finish();
}
}
SqlQuery::SqlQuery(const QString& sql, SqlDatabase db)
:_db(db.sqliteDb()),
_stmt(0)
{
prepare(sql);
}
int SqlQuery::prepare( const QString& sql)
{
QString s(sql);
_sql = s.trimmed();
if(_stmt ) {
finish();
}
if(!_sql.isEmpty() ) {
int n = 0;
int rc;
do {
rc = sqlite3_prepare_v2(_db, _sql.toUtf8().constData(), -1, &_stmt, 0);
if( (rc == SQLITE_BUSY) || (rc == SQLITE_LOCKED) ) {
n++;
Mirall::Utility::usleep(SQLITE_SLEEP_TIME_USEC);
}
} while( (n < SQLITE_REPEAT_COUNT) && ((rc == SQLITE_BUSY) || (rc == SQLITE_LOCKED)));
_errId = rc;
if( _errId != SQLITE_OK ) {
qDebug() << "Sqlite prepare statement error:" << _error << "in"<<_sql;
}
// Q_ASSERT(_errId == SQLITE_OK);
}
return _errId;
}
bool SqlQuery::isSelect()
{
return (!_sql.isEmpty() && _sql.startsWith("SELECT", Qt::CaseInsensitive));
}
bool SqlQuery::isPragma()
{
return (!_sql.isEmpty() && _sql.startsWith("PRAGMA", Qt::CaseInsensitive));
}
bool SqlQuery::exec()
{
// Don't do anything for selects, that is how we use the lib :-|
if(_stmt && !isSelect() && !isPragma() ) {
int rc, n = 0;
do {
rc = sqlite3_step(_stmt);
if( rc == SQLITE_LOCKED ) {
rc = sqlite3_reset(_stmt); /* This will also return SQLITE_LOCKED */
n++;
Mirall::Utility::usleep(SQLITE_SLEEP_TIME_USEC);
} else if( rc == SQLITE_BUSY ) {
Mirall::Utility::usleep(SQLITE_SLEEP_TIME_USEC);
n++;
}
} while( (n < SQLITE_REPEAT_COUNT) && ((rc == SQLITE_BUSY) || (rc == SQLITE_LOCKED)));
_errId = rc;
return (_errId == SQLITE_DONE); // either SQLITE_ROW or SQLITE_DONE
}
return true;
}
bool SqlQuery::next()
{
SQLITE_DO(sqlite3_step(_stmt));
return _errId == SQLITE_ROW;
}
void SqlQuery::bindValue(int pos, const QVariant& value)
{
int res = -1;
if( _stmt ) {
switch (value.type()) {
case QVariant::Int:
case QVariant::Bool:
res = sqlite3_bind_int(_stmt, pos, value.toInt());
break;
case QVariant::Double:
res = sqlite3_bind_double(_stmt, pos, value.toDouble());
break;
case QVariant::UInt:
case QVariant::LongLong:
res = sqlite3_bind_int64(_stmt, pos, value.toLongLong());
break;
case QVariant::DateTime: {
const QDateTime dateTime = value.toDateTime();
const QString str = dateTime.toString(QLatin1String("yyyy-MM-ddThh:mm:ss.zzz"));
res = sqlite3_bind_text16(_stmt, pos, str.utf16(),
str.size() * sizeof(ushort), SQLITE_TRANSIENT);
break;
}
case QVariant::Time: {
const QTime time = value.toTime();
const QString str = time.toString(QLatin1String("hh:mm:ss.zzz"));
res = sqlite3_bind_text16(_stmt, pos, str.utf16(),
str.size() * sizeof(ushort), SQLITE_TRANSIENT);
break;
}
case QVariant::String: {
if( !value.toString().isNull() ) {
// lifetime of string == lifetime of its qvariant
const QString *str = static_cast<const QString*>(value.constData());
res = sqlite3_bind_text16(_stmt, pos, str->utf16(),
(str->size()) * sizeof(QChar), SQLITE_TRANSIENT);
} else {
// unbound value create a null entry.
res = SQLITE_OK;
}
break; }
default: {
QString str = value.toString();
// SQLITE_TRANSIENT makes sure that sqlite buffers the data
res = sqlite3_bind_text16(_stmt, pos, str.utf16(),
(str.size()) * sizeof(QChar), SQLITE_TRANSIENT);
break; }
}
}
if (res != SQLITE_OK) {
qDebug() << Q_FUNC_INFO << "ERROR" << value.toString() << res;
}
Q_ASSERT( res == SQLITE_OK );
}
QString SqlQuery::stringValue(int index)
{
return QString::fromUtf16(static_cast<const ushort*>(sqlite3_column_text16(_stmt, index)));
}
int SqlQuery::intValue(int index)
{
return sqlite3_column_int(_stmt, index);
}
quint64 SqlQuery::int64Value(int index)
{
return sqlite3_column_int64(_stmt, index);
}
QByteArray SqlQuery::baValue(int index)
{
return QByteArray( static_cast<const char*>(sqlite3_column_blob(_stmt, index)),
sqlite3_column_bytes(_stmt, index));
}
QString SqlQuery::error() const
{
return _error;
}
QString SqlQuery::lastQuery() const
{
return _sql;
}
int SqlQuery::numRowsAffected()
{
return 1;
}
void SqlQuery::finish()
{
SQLITE_DO(sqlite3_finalize(_stmt));
_stmt = 0;
}
void SqlQuery::reset()
{
SQLITE_DO(sqlite3_reset(_stmt));
}
} // namespace Mirall

83
src/libsync/ownsql.h Normal file
View file

@ -0,0 +1,83 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#ifndef OWNSQL_H
#define OWNSQL_H
#include <sqlite3.h>
#include <QObject>
#include <QVariant>
#include "owncloudlib.h"
namespace Mirall {
class OWNCLOUDSYNC_EXPORT SqlDatabase
{
public:
explicit SqlDatabase();
bool isOpen();
bool open( const QString& filename );
bool transaction();
bool commit();
void close();
QString error() const;
sqlite3* sqliteDb();
private:
sqlite3 *_db;
QString _error; // last error string
int _errId;
};
class OWNCLOUDSYNC_EXPORT SqlQuery
{
Q_DISABLE_COPY(SqlQuery)
public:
explicit SqlQuery();
explicit SqlQuery(SqlDatabase db);
explicit SqlQuery(const QString& sql, SqlDatabase db);
~SqlQuery();
QString error() const;
QString stringValue(int index);
int intValue(int index);
quint64 int64Value(int index);
QByteArray baValue(int index);
bool isSelect();
bool isPragma();
bool exec();
int prepare( const QString& sql );
bool next();
void bindValue(int pos, const QVariant& value);
QString lastQuery() const;
int numRowsAffected();
void reset();
void finish();
private:
sqlite3 *_db;
sqlite3_stmt *_stmt;
QString _error;
int _errId;
QString _sql;
};
} // namespace Mirall
#endif // OWNSQL_H

View file

@ -94,7 +94,7 @@ void PUTFileJob::start() {
} }
connect(reply(), SIGNAL(uploadProgress(qint64,qint64)), this, SIGNAL(uploadProgress(qint64,qint64))); connect(reply(), SIGNAL(uploadProgress(qint64,qint64)), this, SIGNAL(uploadProgress(qint64,qint64)));
connect(reply(), SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(resetTimeout())); connect(this, SIGNAL(networkActivity()), account(), SIGNAL(propagatorNetworkActivity()));
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }
@ -310,6 +310,7 @@ void PropagateUploadFileQNAM::slotPutFinished()
// Precondition Failed: Maybe the bad etag is in the database, we need to clear the // Precondition Failed: Maybe the bad etag is in the database, we need to clear the
// parent folder etag so we won't read from DB next sync. // parent folder etag so we won't read from DB next sync.
_propagator->_journal->avoidReadFromDbOnNextSync(_item._file); _propagator->_journal->avoidReadFromDbOnNextSync(_item._file);
_propagator->_anotherSyncNeeded = true;
} }
done(classifyError(err, _item._httpErrorCode), errorString); done(classifyError(err, _item._httpErrorCode), errorString);
@ -448,6 +449,12 @@ GETFileJob::GETFileJob(Account* account, const QUrl& url, QFile *device,
void GETFileJob::start() { void GETFileJob::start() {
if (_resumeStart > 0) {
_headers["Range"] = "bytes=" + QByteArray::number(_resumeStart) +'-';
_headers["Accept-Ranges"] = "bytes";
qDebug() << "Retry with range " << _headers["Range"];
}
QNetworkRequest req; QNetworkRequest req;
for(QMap<QByteArray, QByteArray>::const_iterator it = _headers.begin(); it != _headers.end(); ++it) { for(QMap<QByteArray, QByteArray>::const_iterator it = _headers.begin(); it != _headers.end(); ++it) {
req.setRawHeader(it.key(), it.value()); req.setRawHeader(it.key(), it.value());
@ -469,15 +476,22 @@ void GETFileJob::start() {
connect(reply(), SIGNAL(metaDataChanged()), this, SLOT(slotMetaDataChanged())); connect(reply(), SIGNAL(metaDataChanged()), this, SLOT(slotMetaDataChanged()));
connect(reply(), SIGNAL(readyRead()), this, SLOT(slotReadyRead())); connect(reply(), SIGNAL(readyRead()), this, SLOT(slotReadyRead()));
connect(reply(), SIGNAL(downloadProgress(qint64,qint64)), this, SIGNAL(downloadProgress(qint64,qint64))); connect(reply(), SIGNAL(downloadProgress(qint64,qint64)), this, SIGNAL(downloadProgress(qint64,qint64)));
connect(this, SIGNAL(networkActivity()), account(), SIGNAL(propagatorNetworkActivity()));
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }
void GETFileJob::slotMetaDataChanged() void GETFileJob::slotMetaDataChanged()
{ {
if (reply()->error() != QNetworkReply::NoError int httpStatus = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|| reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() / 100 != 2) {
// We will handle the error when the job is finished. // If the status code isn't 2xx, don't write the reply body to the file.
// For any error: handle it when the job is finished, not here.
if (httpStatus / 100 != 2) {
_device->close();
return;
}
if (reply()->error() != QNetworkReply::NoError) {
return; return;
} }
_etag = get_etag_from_reply(reply()); _etag = get_etag_from_reply(reply());
@ -547,16 +561,17 @@ void GETFileJob::slotReadyRead()
return; return;
} }
qint64 w = _device->write(buffer.constData(), r); if (_device->isOpen()) {
if (w != r) { qint64 w = _device->write(buffer.constData(), r);
_errorString = _device->errorString(); if (w != r) {
_errorStatus = SyncFileItem::NormalError; _errorString = _device->errorString();
qDebug() << "Error while writing to file" << w << r << _errorString; _errorStatus = SyncFileItem::NormalError;
reply()->abort(); qDebug() << "Error while writing to file" << w << r << _errorString;
return; reply()->abort();
return;
}
} }
} }
resetTimeout();
} }
void GETFileJob::slotTimeout() void GETFileJob::slotTimeout()
@ -626,18 +641,13 @@ void PropagateDownloadFileQNAM::start()
QMap<QByteArray, QByteArray> headers; QMap<QByteArray, QByteArray> headers;
quint64 startSize = 0; quint64 startSize = _tmpFile.size();
if (_tmpFile.size() > 0) { if (startSize > 0) {
quint64 done = _tmpFile.size(); if (startSize == _item._size) {
if (done == _item._size) {
qDebug() << "File is already complete, no need to download"; qDebug() << "File is already complete, no need to download";
downloadFinished(); downloadFinished();
return; return;
} }
headers["Range"] = "bytes=" + QByteArray::number(done) +'-';
headers["Accept-Ranges"] = "bytes";
qDebug() << "Retry with range " << headers["Range"];
startSize = done;
} }
if (_item._directDownloadUrl.isEmpty()) { if (_item._directDownloadUrl.isEmpty()) {
@ -647,14 +657,27 @@ void PropagateDownloadFileQNAM::start()
&_tmpFile, headers, expectedEtagForResume, startSize); &_tmpFile, headers, expectedEtagForResume, startSize);
} else { } else {
// We were provided a direct URL, use that one // We were provided a direct URL, use that one
qDebug() << Q_FUNC_INFO << "directDownloadUrl given for " << _item._file << _item._directDownloadUrl;
// Direct URLs don't support resuming, so clear an existing tmp file
if (startSize > 0) {
qDebug() << Q_FUNC_INFO << "resuming not supported for directDownloadUrl, deleting temporary";
_tmpFile.close();
if (!_tmpFile.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) {
done(SyncFileItem::NormalError, _tmpFile.errorString());
return;
}
startSize = 0;
}
if (!_item._directDownloadCookies.isEmpty()) { if (!_item._directDownloadCookies.isEmpty()) {
headers["Cookie"] = _item._directDownloadCookies.toUtf8(); headers["Cookie"] = _item._directDownloadCookies.toUtf8();
} }
QUrl url = QUrl::fromUserInput(_item._directDownloadUrl); QUrl url = QUrl::fromUserInput(_item._directDownloadUrl);
_job = new GETFileJob(AccountManager::instance()->account(), _job = new GETFileJob(AccountManager::instance()->account(),
url, url,
&_tmpFile, headers); &_tmpFile, headers);
qDebug() << Q_FUNC_INFO << "directDownloadUrl given for " << _item._file << _item._directDownloadUrl;
} }
_job->setTimeout(_propagator->httpTimeout() * 1000); _job->setTimeout(_propagator->httpTimeout() * 1000);
connect(_job, SIGNAL(finishedSignal()), this, SLOT(slotGetFinished())); connect(_job, SIGNAL(finishedSignal()), this, SLOT(slotGetFinished()));
@ -677,18 +700,34 @@ void PropagateDownloadFileQNAM::slotGetFinished()
QNetworkReply::NetworkError err = job->reply()->error(); QNetworkReply::NetworkError err = job->reply()->error();
if (err != QNetworkReply::NoError) { if (err != QNetworkReply::NoError) {
if (_tmpFile.size() == 0) { _item._httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// don't keep the temporary file if it is empty.
// If we sent a 'Range' header and get 416 back, we want to retry
// without the header.
bool badRangeHeader = job->resumeStart() > 0 && _item._httpErrorCode == 416;
if (badRangeHeader) {
qDebug() << Q_FUNC_INFO << "server replied 416 to our range request, trying again without";
_propagator->_anotherSyncNeeded = true;
}
// Don't keep the temporary file if it is empty or we
// used a bad range header.
if (_tmpFile.size() == 0 || badRangeHeader) {
_tmpFile.close(); _tmpFile.close();
_tmpFile.remove(); _tmpFile.remove();
_propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo()); _propagator->_journal->setDownloadInfo(_item._file, SyncJournalDb::DownloadInfo());
} }
_item._httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
_propagator->_activeJobs--; _propagator->_activeJobs--;
SyncFileItem::Status status = job->errorStatus(); SyncFileItem::Status status = job->errorStatus();
if (status == SyncFileItem::NoStatus) { if (status == SyncFileItem::NoStatus) {
status = classifyError(err, _item._httpErrorCode); status = classifyError(err, _item._httpErrorCode);
} }
if (badRangeHeader) {
// Can't do this in classifyError() because 416 without a
// Range header should result in NormalError.
status = SyncFileItem::SoftError;
}
done(status, job->errorString()); done(status, job->errorString());
return; return;
} }
@ -759,6 +798,7 @@ void PropagateDownloadFileQNAM::downloadFinished()
return; return;
} }
existingFile.refresh();
// Maybe we downloaded a newer version of the file than we thought we would... // Maybe we downloaded a newer version of the file than we thought we would...
// Get up to date information for the journal. // Get up to date information for the journal.
FileSystem::setModTime(fn, _item._modtime); FileSystem::setModTime(fn, _item._modtime);

View file

@ -247,7 +247,11 @@ void PropagateLocalRename::start()
qDebug() << "MOVE " << _propagator->_localDir + _item._file << " => " << _propagator->_localDir + _item._renameTarget; qDebug() << "MOVE " << _propagator->_localDir + _item._file << " => " << _propagator->_localDir + _item._renameTarget;
QFile file(_propagator->_localDir + _item._file); QFile file(_propagator->_localDir + _item._file);
if (_propagator->localFileNameClash(_item._renameTarget)) { if (QString::compare(_item._file, _item._renameTarget, Qt::CaseInsensitive) != 0
&& _propagator->localFileNameClash(_item._renameTarget)) {
// Only use localFileNameClash for the destination if we know that the source was not
// the one conflicting (renaming A.txt -> a.txt is OK)
// Fixme: the file that is the reason for the clash could be named here, // Fixme: the file that is the reason for the clash could be named here,
// it would have to come out the localFileNameClash function // it would have to come out the localFileNameClash function
done(SyncFileItem::NormalError, tr( "File %1 can not be renamed to %2 because of a local file name clash") done(SyncFileItem::NormalError, tr( "File %1 can not be renamed to %2 because of a local file name clash")
@ -311,9 +315,6 @@ void PropagateRemoteRename::start()
if (updateErrorFromSession(rc)) { if (updateErrorFromSession(rc)) {
return; return;
} }
if (!updateMTimeAndETag(uri2.data(), _item._modtime))
return;
} }
// Wed, 15 Nov 1995 06:25:24 GMT // Wed, 15 Nov 1995 06:25:24 GMT
QDateTime dt = QDateTime::currentDateTimeUtc(); QDateTime dt = QDateTime::currentDateTimeUtc();

View file

@ -22,6 +22,7 @@
#include "discoveryphase.h" #include "discoveryphase.h"
#include "creds/abstractcredentials.h" #include "creds/abstractcredentials.h"
#include "csync_util.h" #include "csync_util.h"
#include "syncfilestatus.h"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <windows.h> #include <windows.h>
@ -29,6 +30,7 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#include <climits>
#include <assert.h> #include <assert.h>
#include <QDebug> #include <QDebug>
@ -196,12 +198,12 @@ bool SyncEngine::checkBlacklisting( SyncFileItem *item )
} }
SyncJournalBlacklistRecord entry = _journal->blacklistEntry(item->_file); SyncJournalBlacklistRecord entry = _journal->blacklistEntry(item->_file);
item->_blacklistedInDb = false; item->_hasBlacklistEntry = false;
// if there is a valid entry in the blacklist table and the retry count is // if there is a valid entry in the blacklist table and the retry count is
// already null or smaller than 0, the file is blacklisted. // already null or smaller than 0, the file is blacklisted.
if( entry.isValid() ) { if( entry.isValid() ) {
item->_blacklistedInDb = true; item->_hasBlacklistEntry = true;
if( entry._retryCount <= 0 ) { if( entry._retryCount <= 0 ) {
re = true; re = true;
@ -288,7 +290,7 @@ void SyncEngine::deleteStaleBlacklistEntries()
// Find all blacklisted paths that we want to preserve. // Find all blacklisted paths that we want to preserve.
QSet<QString> blacklist_file_paths; QSet<QString> blacklist_file_paths;
foreach(const SyncFileItem& it, _syncedItems) { foreach(const SyncFileItem& it, _syncedItems) {
if (it._status == SyncFileItem::FileIgnored) if (it._hasBlacklistEntry)
blacklist_file_paths.insert(it._file); blacklist_file_paths.insert(it._file);
} }
@ -401,7 +403,7 @@ int SyncEngine::treewalkFile( TREE_WALK_FILE *file, bool remote )
int re = 0; int re = 0;
switch(file->instruction) { switch(file->instruction) {
case CSYNC_INSTRUCTION_NONE: case CSYNC_INSTRUCTION_NONE:
if (file->should_update_etag && !item._isDirectory) { if (remote && item._should_update_etag && !item._isDirectory && item._instruction == CSYNC_INSTRUCTION_NONE) {
// Update the database now already (new fileid or etag or remotePerm) // Update the database now already (new fileid or etag or remotePerm)
// Those are files that were detected as "resolved conflict". // Those are files that were detected as "resolved conflict".
// They should have been a conflict because they both were new, or both // They should have been a conflict because they both were new, or both
@ -523,37 +525,41 @@ void SyncEngine::startSync()
csync_resume(_csync_ctx); csync_resume(_csync_ctx);
int fileRecordCount = -1;
if (!_journal->exists()) { if (!_journal->exists()) {
qDebug() << "=====sync looks new (no DB exists), activating recursive PROPFIND if csync supports it"; qDebug() << "=====sync looks new (no DB exists)";
} else {
qDebug() << "=====sync with existing DB";
}
fileRecordCount = _journal->getFileRecordCount(); // this creates the DB if it does not exist yet
bool isUpdateFrom_1_5 = _journal->isUpdateFrom_1_5();
if( fileRecordCount == -1 ) {
qDebug() << "No way to create a sync journal!";
emit csyncError(tr("Unable to initialize a sync journal."));
finalize();
return;
// database creation error!
}
if (fileRecordCount >= 1 && isUpdateFrom_1_5) {
qDebug() << "detected update from 1.5" << fileRecordCount << isUpdateFrom_1_5;
// Disable the read from DB to be sure to re-read all the fileid and etags.
csync_set_read_from_db(_csync_ctx, false);
} else {
csync_set_read_from_db(_csync_ctx, true);
}
bool usingSelectiveSync = (!_selectiveSyncBlackList.isEmpty());
qDebug() << (usingSelectiveSync ? "====Using Selective Sync" : "====NOT Using Selective Sync");
if (fileRecordCount >= 0 && fileRecordCount < 50 && !usingSelectiveSync) {
qDebug() << "===== Activating recursive PROPFIND (currently" << fileRecordCount << "file records)";
bool no_recursive_propfind = false; bool no_recursive_propfind = false;
csync_set_module_property(_csync_ctx, "no_recursive_propfind", &no_recursive_propfind); csync_set_module_property(_csync_ctx, "no_recursive_propfind", &no_recursive_propfind);
} else { } else {
// retrieve the file count from the db and close it afterwards because bool no_recursive_propfind = true;
// csync_update also opens the database. csync_set_module_property(_csync_ctx, "no_recursive_propfind", &no_recursive_propfind);
int fileRecordCount = 0;
fileRecordCount = _journal->getFileRecordCount();
bool isUpdateFrom_1_5 = _journal->isUpdateFrom_1_5();
_journal->close();
if( fileRecordCount == -1 ) {
qDebug() << "No way to create a sync journal!";
emit csyncError(tr("Unable to initialize a sync journal."));
finalize();
return;
// database creation error!
} else if ( fileRecordCount < 50 ) {
qDebug() << "=====sync DB has only" << fileRecordCount << "items, enable recursive PROPFIND if csync supports it";
bool no_recursive_propfind = false;
csync_set_module_property(_csync_ctx, "no_recursive_propfind", &no_recursive_propfind);
} else {
qDebug() << "=====sync with existing DB";
}
if (fileRecordCount > 1 && isUpdateFrom_1_5) {
qDebug() << "detected update from 1.5";
// Disable the read from DB to be sure to re-read all the fileid and etags.
csync_set_read_from_db(_csync_ctx, false);
}
} }
csync_set_userdata(_csync_ctx, this); csync_set_userdata(_csync_ctx, this);
@ -609,6 +615,17 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
} }
qDebug() << "<<#### Discovery end #################################################### " << _stopWatch.addLapTime(QLatin1String("Discovery Finished")); qDebug() << "<<#### Discovery end #################################################### " << _stopWatch.addLapTime(QLatin1String("Discovery Finished"));
// Sanity check
if (!_journal->isConnected()) {
qDebug() << "Bailing out, DB failure";
emit csyncError(tr("Cannot open the sync journal"));
finalize();
return;
} else {
// Commits a possibly existing (should not though) transaction and starts a new one for the propagate phase
_journal->commitIfNeededAndStartNewTransaction("Post discovery");
}
if( csync_reconcile(_csync_ctx) < 0 ) { if( csync_reconcile(_csync_ctx) < 0 ) {
handleSyncError(_csync_ctx, "csync_reconcile"); handleSyncError(_csync_ctx, "csync_reconcile");
return; return;
@ -646,17 +663,11 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
// make sure everything is allowed // make sure everything is allowed
checkForPermission(); checkForPermission();
// Sanity check
if (!_journal->isConnected()) {
qDebug() << "Bailing out, DB failure";
emit csyncError(tr("Cannot open the sync journal"));
finalize();
return;
}
// To announce the beginning of the sync // To announce the beginning of the sync
emit aboutToPropagate(_syncedItems); emit aboutToPropagate(_syncedItems);
_progressInfo._completedFileCount = ULLONG_MAX; // indicate the start with max
emit transmissionProgress(_progressInfo); emit transmissionProgress(_progressInfo);
_progressInfo._completedFileCount = 0;
if (!_hasNoneFiles && _hasRemoveFile) { if (!_hasNoneFiles && _hasRemoveFile) {
qDebug() << Q_FUNC_INFO << "All the files are going to be changed, asking the user"; qDebug() << Q_FUNC_INFO << "All the files are going to be changed, asking the user";
@ -678,14 +689,16 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
Q_ASSERT(session); Q_ASSERT(session);
// post update phase script: allow to tweak stuff by a custom script in debug mode. // post update phase script: allow to tweak stuff by a custom script in debug mode.
#ifndef NDEBUG
if( !qgetenv("OWNCLOUD_POST_UPDATE_SCRIPT").isEmpty() ) { if( !qgetenv("OWNCLOUD_POST_UPDATE_SCRIPT").isEmpty() ) {
#ifndef NDEBUG
QString script = qgetenv("OWNCLOUD_POST_UPDATE_SCRIPT"); QString script = qgetenv("OWNCLOUD_POST_UPDATE_SCRIPT");
qDebug() << "OOO => Post Update Script: " << script; qDebug() << "OOO => Post Update Script: " << script;
QProcess::execute(script.toUtf8()); QProcess::execute(script.toUtf8());
} #else
qDebug() << "**** Attention: POST_UPDATE_SCRIPT installed, but not executed because compiled with NDEBUG";
#endif #endif
}
// do a database commit // do a database commit
_journal->commit("post treewalk"); _journal->commit("post treewalk");
@ -817,6 +830,11 @@ QString SyncEngine::adjustRenamedPath(const QString& original)
return original; return original;
} }
/**
*
* Make sure that we are allowed to do what we do by checking the permissions and the selective sync list
*
*/
void SyncEngine::checkForPermission() void SyncEngine::checkForPermission()
{ {
for (SyncFileItemVector::iterator it = _syncedItems.begin(); it != _syncedItems.end(); ++it) { for (SyncFileItemVector::iterator it = _syncedItems.begin(); it != _syncedItems.end(); ++it) {
@ -826,6 +844,25 @@ void SyncEngine::checkForPermission()
continue; continue;
} }
// Do not propagate anything in the server if it is in the selective sync blacklist
const QString path = it->destination() + QLatin1Char('/');
if (std::binary_search(_selectiveSyncBlackList.constBegin(), _selectiveSyncBlackList.constEnd(),
path)) {
it->_instruction = CSYNC_INSTRUCTION_IGNORE;
it->_status = SyncFileItem::FileIgnored;
it->_errorString = tr("Ignored because of the \"choose what to sync\" blacklist");
if (it->_isDirectory) {
for (SyncFileItemVector::iterator it_next = it + 1; it_next != _syncedItems.end() && it_next->_file.startsWith(path); ++it_next) {
it = it_next;
it->_instruction = CSYNC_INSTRUCTION_IGNORE;
it->_status = SyncFileItem::FileIgnored;
it->_errorString = tr("Ignored because of the \"choose what to sync\" blacklist");
}
}
continue;
}
switch(it->_instruction) { switch(it->_instruction) {
case CSYNC_INSTRUCTION_NEW: { case CSYNC_INSTRUCTION_NEW: {
int slashPos = it->_file.lastIndexOf('/'); int slashPos = it->_file.lastIndexOf('/');
@ -840,7 +877,6 @@ void SyncEngine::checkForPermission()
it->_status = SyncFileItem::NormalError; it->_status = SyncFileItem::NormalError;
it->_errorString = tr("Not allowed because you don't have permission to add sub-directories in that directory"); it->_errorString = tr("Not allowed because you don't have permission to add sub-directories in that directory");
const QString path = it->_file + QLatin1Char('/');
for (SyncFileItemVector::iterator it_next = it + 1; it_next != _syncedItems.end() && it_next->_file.startsWith(path); ++it_next) { for (SyncFileItemVector::iterator it_next = it + 1; it_next != _syncedItems.end() && it_next->_file.startsWith(path); ++it_next) {
it = it_next; it = it_next;
it->_instruction = CSYNC_INSTRUCTION_ERROR; it->_instruction = CSYNC_INSTRUCTION_ERROR;
@ -882,7 +918,8 @@ void SyncEngine::checkForPermission()
if (perms.isNull()) { if (perms.isNull()) {
// No permissions set // No permissions set
break; break;
} if (!perms.contains("D")) { }
if (!perms.contains("D")) {
qDebug() << "checkForPermission: RESTORING" << it->_file; qDebug() << "checkForPermission: RESTORING" << it->_file;
it->_should_update_etag = true; it->_should_update_etag = true;
it->_instruction = CSYNC_INSTRUCTION_NEW; it->_instruction = CSYNC_INSTRUCTION_NEW;
@ -892,7 +929,6 @@ void SyncEngine::checkForPermission()
if (it->_isDirectory) { if (it->_isDirectory) {
// restore all sub items // restore all sub items
const QString path = it->_file + QLatin1Char('/');
for (SyncFileItemVector::iterator it_next = it + 1; for (SyncFileItemVector::iterator it_next = it + 1;
it_next != _syncedItems.end() && it_next->_file.startsWith(path); ++it_next) { it_next != _syncedItems.end() && it_next->_file.startsWith(path); ++it_next) {
it = it_next; it = it_next;
@ -912,6 +948,26 @@ void SyncEngine::checkForPermission()
it->_errorString = tr("Not allowed to remove, restoring"); it->_errorString = tr("Not allowed to remove, restoring");
} }
} }
} else if(perms.contains("S") && perms.contains("D")) {
// this is a top level shared dir which can be removed to unshare it,
// regardless if it is a read only share or not.
// To avoid that we try to restore files underneath this dir which have
// not delete permission we fast forward the iterator and leave the
// delete jobs intact. It is not physically tried to remove this files
// underneath, propagator sees that.
if( it->_isDirectory ) {
SyncFileItemVector::iterator it_prev = it - 1;
// put a more descriptive message if really a top level share dir is removed.
if( it_prev != _syncedItems.begin() && !(path.startsWith(it_prev->_file)) ) {
it->_errorString = tr("Local files and share folder removed.");
}
for (SyncFileItemVector::iterator it_next = it + 1;
it_next != _syncedItems.end() && it_next->_file.startsWith(path); ++it_next) {
it = it_next;
}
}
} }
break; break;
} }
@ -983,7 +1039,6 @@ void SyncEngine::checkForPermission()
if (it->_isDirectory) { if (it->_isDirectory) {
const QString path = it->_renameTarget + QLatin1Char('/');
for (SyncFileItemVector::iterator it_next = it + 1; for (SyncFileItemVector::iterator it_next = it + 1;
it_next != _syncedItems.end() && it_next->destination().startsWith(path); ++it_next) { it_next != _syncedItems.end() && it_next->destination().startsWith(path); ++it_next) {
it = it_next; it = it_next;
@ -1014,6 +1069,29 @@ QByteArray SyncEngine::getPermissions(const QString& file) const
return _remotePerms.value(file); return _remotePerms.value(file);
} }
void SyncEngine::setSelectiveSyncBlackList(const QStringList& list)
{
_selectiveSyncBlackList = list;
for (int i = 0; i < _selectiveSyncBlackList.count(); ++i) {
if (!_selectiveSyncBlackList.at(i).endsWith(QLatin1Char('/'))) {
_selectiveSyncBlackList[i].append(QLatin1Char('/'));
}
}
}
bool SyncEngine::estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s)
{
Q_UNUSED(t);
Q_FOREACH(const SyncFileItem &item, _syncedItems) {
//qDebug() << Q_FUNC_INFO << fn << item._file << fn.startsWith(item._file) << item._file.startsWith(fn);
if (item._file.startsWith(fn)) {
qDebug() << Q_FUNC_INFO << "Setting" << fn << " to STATUS_EVAL";
s->set(SyncFileStatus::STATUS_EVAL);
return true;
}
}
return false;
}
void SyncEngine::abort() void SyncEngine::abort()
{ {

View file

@ -33,6 +33,7 @@
#include "syncfileitem.h" #include "syncfileitem.h"
#include "progressdispatcher.h" #include "progressdispatcher.h"
#include "utility.h" #include "utility.h"
#include "syncfilestatus.h"
class QProcess; class QProcess;
@ -61,12 +62,13 @@ public:
Utility::StopWatch &stopWatch() { return _stopWatch; } Utility::StopWatch &stopWatch() { return _stopWatch; }
void setSelectiveSyncBlackList(const QStringList &list) void setSelectiveSyncBlackList(const QStringList &list);
{ _selectiveSyncBlackList = list; }
/* Return true if we detected that another sync is needed to complete the sync */ /* Return true if we detected that another sync is needed to complete the sync */
bool isAnotherSyncNeeded() { return _anotherSyncNeeded; } bool isAnotherSyncNeeded() { return _anotherSyncNeeded; }
bool estimateState(QString fn, csync_ftw_type_e t, SyncFileStatus* s);
signals: signals:
void csyncError( const QString& ); void csyncError( const QString& );
void csyncUnavailable(); void csyncUnavailable();
@ -124,7 +126,11 @@ private:
void finalize(); void finalize();
static bool _syncRunning; //true when one sync is running somewhere (for debugging) static bool _syncRunning; //true when one sync is running somewhere (for debugging)
QMap<QString, SyncFileItem> _syncItemMap; QMap<QString, SyncFileItem> _syncItemMap;
// should be called _syncItems (present tense). It's the items from the _syncItemMap but
// sorted and re-adjusted based on permissions.
SyncFileItemVector _syncedItems; SyncFileItemVector _syncedItems;
CSYNC *_csync_ctx; CSYNC *_csync_ctx;

View file

@ -46,13 +46,13 @@ public:
Success, ///< The file was properly synced Success, ///< The file was properly synced
Conflict, ///< The file was properly synced, but a conflict was created Conflict, ///< The file was properly synced, but a conflict was created
FileIgnored, ///< The file is in the ignored list FileIgnored, ///< The file is in the ignored list (or blacklisted with no retries left)
Restoration ///< The file was restored because what should have been done was not allowed Restoration ///< The file was restored because what should have been done was not allowed
}; };
SyncFileItem() : _type(UnknownType), _direction(None), _isDirectory(false), SyncFileItem() : _type(UnknownType), _direction(None), _isDirectory(false),
_instruction(CSYNC_INSTRUCTION_NONE), _modtime(0), _instruction(CSYNC_INSTRUCTION_NONE), _modtime(0),
_size(0), _inode(0), _should_update_etag(false), _blacklistedInDb(false), _size(0), _inode(0), _should_update_etag(false), _hasBlacklistEntry(false),
_status(NoStatus), _httpErrorCode(0), _requestDuration(0), _isRestoration(false) {} _status(NoStatus), _httpErrorCode(0), _requestDuration(0), _isRestoration(false) {}
friend bool operator==(const SyncFileItem& item1, const SyncFileItem& item2) { friend bool operator==(const SyncFileItem& item1, const SyncFileItem& item2) {
@ -72,6 +72,13 @@ public:
return _file.isEmpty(); return _file.isEmpty();
} }
bool hasErrorStatus() const {
return _status == SyncFileItem::SoftError
|| _status == SyncFileItem::NormalError
|| _status == SyncFileItem::FatalError
|| !_errorString.isEmpty();
}
// Variables usefull for everybody // Variables usefull for everybody
QString _file; QString _file;
QString _renameTarget; QString _renameTarget;
@ -91,7 +98,11 @@ public:
QByteArray _remotePerm; QByteArray _remotePerm;
QString _directDownloadUrl; QString _directDownloadUrl;
QString _directDownloadCookies; QString _directDownloadCookies;
bool _blacklistedInDb;
/// Whether there's an entry in the blacklist table.
/// Note: that entry may have retries left, so this can be true
/// without the status being FileIgnored.
bool _hasBlacklistEntry;
// Variables usefull to report to the user // Variables usefull to report to the user
Status _status; Status _status;

File diff suppressed because it is too large Load diff

View file

@ -17,11 +17,10 @@
#include <QObject> #include <QObject>
#include <qmutex.h> #include <qmutex.h>
#include <QDateTime> #include <QDateTime>
#include <QSqlDatabase>
#include <QHash> #include <QHash>
#include <QSqlQuery>
#include "utility.h" #include "utility.h"
#include "ownsql.h"
namespace Mirall { namespace Mirall {
class SyncJournalFileRecord; class SyncJournalFileRecord;
@ -43,6 +42,10 @@ public:
bool deleteFileRecord( const QString& filename, bool recursively = false ); bool deleteFileRecord( const QString& filename, bool recursively = false );
int getFileRecordCount(); int getFileRecordCount();
bool exists(); bool exists();
void walCheckpoint();
QString databaseFilePath();
static qint64 getPHash(const QString& );
void updateBlacklistEntry( const SyncJournalBlacklistRecord& item ); void updateBlacklistEntry( const SyncJournalBlacklistRecord& item );
void wipeBlacklistEntry(const QString& file); void wipeBlacklistEntry(const QString& file);
@ -91,6 +94,7 @@ public:
* Commit will actually commit the transaction and create a new one. * Commit will actually commit the transaction and create a new one.
*/ */
void commit(const QString &context, bool startTrans = true); void commit(const QString &context, bool startTrans = true);
void commitIfNeededAndStartNewTransaction(const QString &context);
void close(); void close();
@ -106,31 +110,30 @@ public:
bool isUpdateFrom_1_5(); bool isUpdateFrom_1_5();
private: private:
qint64 getPHash(const QString& ) const;
bool updateDatabaseStructure(); bool updateDatabaseStructure();
bool sqlFail(const QString& log, const QSqlQuery &query ); bool sqlFail(const QString& log, const SqlQuery &query );
void commitInternal(const QString &context, bool startTrans = true); void commitInternal(const QString &context, bool startTrans = true);
void startTransaction(); void startTransaction();
void commitTransaction(); void commitTransaction();
QStringList tableColumns( const QString& table ); QStringList tableColumns( const QString& table );
bool checkConnect(); bool checkConnect();
QSqlDatabase _db; SqlDatabase _db;
QString _dbFile; QString _dbFile;
QMutex _mutex; // Public functions are protected with the mutex. QMutex _mutex; // Public functions are protected with the mutex.
int _transaction; int _transaction;
bool _possibleUpgradeFromMirall_1_5; bool _possibleUpgradeFromMirall_1_5;
QScopedPointer<QSqlQuery> _getFileRecordQuery; QScopedPointer<SqlQuery> _getFileRecordQuery;
QScopedPointer<QSqlQuery> _setFileRecordQuery; QScopedPointer<SqlQuery> _setFileRecordQuery;
QScopedPointer<QSqlQuery> _getDownloadInfoQuery; QScopedPointer<SqlQuery> _getDownloadInfoQuery;
QScopedPointer<QSqlQuery> _setDownloadInfoQuery; QScopedPointer<SqlQuery> _setDownloadInfoQuery;
QScopedPointer<QSqlQuery> _deleteDownloadInfoQuery; QScopedPointer<SqlQuery> _deleteDownloadInfoQuery;
QScopedPointer<QSqlQuery> _getUploadInfoQuery; QScopedPointer<SqlQuery> _getUploadInfoQuery;
QScopedPointer<QSqlQuery> _setUploadInfoQuery; QScopedPointer<SqlQuery> _setUploadInfoQuery;
QScopedPointer<QSqlQuery> _deleteUploadInfoQuery; QScopedPointer<SqlQuery> _deleteUploadInfoQuery;
QScopedPointer<QSqlQuery> _deleteFileRecordPhash; QScopedPointer<SqlQuery> _deleteFileRecordPhash;
QScopedPointer<QSqlQuery> _deleteFileRecordRecursively; QScopedPointer<SqlQuery> _deleteFileRecordRecursively;
QScopedPointer<QSqlQuery> _blacklistQuery; QScopedPointer<SqlQuery> _blacklistQuery;
/* This is the list of paths we called avoidReadFromDbOnNextSync on. /* 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 * It means that they should not be written to the DB in any case since doing

View file

@ -17,11 +17,13 @@
#include <QString> #include <QString>
#include <QDateTime> #include <QDateTime>
#include "owncloudlib.h"
namespace Mirall { namespace Mirall {
class SyncFileItem; class SyncFileItem;
class SyncJournalFileRecord class OWNCLOUDSYNC_EXPORT SyncJournalFileRecord
{ {
public: public:
SyncJournalFileRecord(); SyncJournalFileRecord();

View file

@ -224,6 +224,18 @@ QString Utility::toCSyncScheme(const QString &urlStr)
return url.toString(); return url.toString();
} }
bool Utility::doesSetContainPrefix(QSet<QString> &l, QString &p) {
Q_FOREACH (const QString &setPath, l) {
//qDebug() << Q_FUNC_INFO << p << setPath << setPath.startsWith(p);
if (setPath.startsWith(p)) {
return true;
}
}
//qDebug() << "-> NOOOOO!!!" << p << l.count();
return false;
}
QString Utility::escape(const QString &in) QString Utility::escape(const QString &in)
{ {
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
@ -360,17 +372,6 @@ bool Utility::isLinux()
#endif #endif
} }
void Utility::winShellChangeNotify( const QString& path )
{
#ifdef Q_OS_WIN
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT,
reinterpret_cast<const wchar_t *>(QDir::toNativeSeparators(path).utf16()), NULL );
#else
Q_UNUSED(path);
qDebug() << Q_FUNC_INFO << " is not implemented on non Windows systems.";
#endif
}
static const char STOPWATCH_END_TAG[] = "_STOPWATCH_END"; static const char STOPWATCH_END_TAG[] = "_STOPWATCH_END";

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