Merge remote-tracking branch 'upstream/master'

Conflicts:
	src/cmd/cmd.cpp
This commit is contained in:
Niels van Adrichem 2015-11-03 11:58:45 +01:00
commit 1ab44655e0
46 changed files with 1670 additions and 852 deletions

View file

@ -6,50 +6,50 @@ version 2.1 (release 2015-yy-zz)
Linux when building with Qt < 5.4 and having bandwidth limiting enabled. Linux when building with Qt < 5.4 and having bandwidth limiting enabled.
So now you need Qt 5.4 on Linux if you want bandwidth limiting. So now you need Qt 5.4 on Linux if you want bandwidth limiting.
version 2.0.2 (release 2015-10-22) version 2.0.2 (release 2015-10-21)
* csync_file_stat_s: Save a bit of memory * csync_file_stat_s: Save a bit of memory
* Shibboleth: Add our base user agent to WebKit * Shibboleth: Add our base user agent to WebKit
* SelectiveSync: Increase folder list timeout to 60 * SelectiveSync: Increase folder list timeout to 60
* Propagation: Try another sync on 423 Locked #3387 * Propagation: Try another sync on 423 Locked (#3387)
* Propagation: Make 423 Locked a soft error #3387 * Propagation: Make 423 Locked a soft error (#3387)
* Propagation: Reset upload blacklist if a chunk suceeds * Propagation: Reset upload blacklist if a chunk succeeds
* Application: Fix crash on early shutdown #3898 * Application: Fix crash on early shutdown (#3898)
* Linux: Don't show settings dialog always when launched twice #3273 #3771 #3485 * Linux: Don't show settings dialog always when launched twice (#3273, #3771, #3485)
* win32 vio: Add the OPEN_REPARSE_POINTS flag to the CreateFileW call. #3813 * win32 vio: Add the OPEN_REPARSE_POINTS flag to the CreateFileW call. (#3813)
* AccountSettings: only expand root elements on single click. * AccountSettings: only expand root elements on single click.
* AccountSettings: Do not allow to expand the folder list when disconnected. * AccountSettings: Do not allow to expand the folder list when disconnected.
* Use application SHORT name for the name of the MacOSX pkg file (ownBrander). * Use application SHORT name for the name of the MacOSX pkg file (ownBrander).
* FolderMan: Fix for removing a syncing folder #3843 * FolderMan: Fix for removing a syncing folder (#3843)
* ConnectionMethodDialog: Don't be insecure on close #3863 * ConnectionMethodDialog: Don't be insecure on close (#3863)
* Updater: Ensure folders are not removed #3747 * Updater: Ensure folders are not removed (#3747)
* Folder settings: Ensure path is cleaned #3811 * Folder settings: Ensure path is cleaned (#3811)
* Propagator: Simplify sub job finished counting #3844 * Propagator: Simplify sub job finished counting (#3844)
* Share dialog: Hide settings dialog before showing #3783 * Share dialog: Hide settings dialog before showing (#3783)
* UI: Only expand 1 level in folder list #3585 * UI: Only expand 1 level in folder list (#3585)
* UI: Allow folder expanding from button click #3585 * UI: Allow folder expanding from button click (#3585)
* UI: Expand folder treeview on single click #3585 * UI: Expand folder treeview on single click (#3585)
* GUI: Change tray menu order #3657 * GUI: Change tray menu order (#3657)
* GUI: Replace term "sign in" with "Log in" and friends. * GUI: Replace term "sign in" with "Log in" and friends.
* SetupPage: Fix crash caused by uninitialized Account object. * SetupPage: Fix crash caused by uninitialized Account object.
* Use a themable WebDAV path all over. * Use a themable WebDAV path all over.
* Units: Back to the "usual" mix units (JEDEC standard). * Units: Back to the "usual" mix units (JEDEC standard).
* csync io: Full UNC path support on Win #3748 * csync io: Full UNC path support on Win (#3748)
* Tray: Don't use the tray workaround with the KDE theme #3706, #3765 * Tray: Don't use the tray workaround with the KDE theme (#3706, #3765)
* ShareDialog: Fix folder display #3659 * ShareDialog: Fix folder display (#3659)
* AccountSettings: Restore from legacy only once #3565 * AccountSettings: Restore from legacy only once (#3565)
* SSL Certificate Error Dialog: show account name #3729 * SSL Certificate Error Dialog: show account name (#3729)
* Tray notification: Don't show a message about modified folder #3613 * Tray notification: Don't show a message about modified folder (#3613)
* PropagateLocalRemove: remove entries from the DB even if there was an error. * PropagateLocalRemove: remove entries from the DB even if there was an error.
* Settings UI improvements (eg. #3713, #3721, #3619 and others) * Settings UI improvements (eg. #3713, #3721, #3619 and others)
* Folder: Do not create the sync folder if it does not exist #3692 * Folder: Do not create the sync folder if it does not exist (#3692)
* Shell integratioon: don't show share menu item for top level folders * Shell integration: don't show share menu item for top level folders
* Tray: Hide while modifying menus #3656 #3672 * Tray: Hide while modifying menus (#3656, #3672)
* AddFolder: Improve remote path selection error handling #3573 * AddFolder: Improve remote path selection error handling (#3573)
* csync_update: Use excluded_traversal() to improve performance #3638 * csync_update: Use excluded_traversal() to improve performance (#3638)
* csync_excluded: Add fast _traversal() function #3638 * csync_excluded: Add fast _traversal() function (#3638)
* csync_exclude: Speed up siginificantly #3638 * csync_exclude: Speed up significantly (#3638)
* AccountSettings: Adjust quota info design #3644 #3651 * AccountSettings: Adjust quota info design (#3644, #3651)
* Adjust buttons on remove folder/account questions #3654 * Adjust buttons on remove folder/account questions (#3654)
version 2.0.1 (release 2015-09-01) version 2.0.1 (release 2015-09-01)
* AccountWizard: fix when the theme specify a override URL (#3699) * AccountWizard: fix when the theme specify a override URL (#3699)

View file

@ -741,15 +741,13 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
res = 0; res = 0;
} }
/* for non windows platforms, detect if the filename starts with a . /* if the filename starts with a . we consider it a hidden file
* and if so, it's a hidden file. For windows, the hidden state is * For windows, the hidden state is also discovered within the vio
* discovered within the vio local stat function. * local stat function.
*/ */
#ifndef _WIN32
if( d_name[0] == '.' ) { if( d_name[0] == '.' ) {
dirent->flags |= CSYNC_VIO_FILE_FLAGS_HIDDEN; dirent->flags |= CSYNC_VIO_FILE_FLAGS_HIDDEN;
} }
#endif
if( res == 0) { if( res == 0) {
switch (dirent->type) { switch (dirent->type) {

View file

@ -2,6 +2,10 @@
# #
# Copyright (C) by Klaas Freitag <freitag@owncloud.com> # Copyright (C) by Klaas Freitag <freitag@owncloud.com>
# #
# This program is the core of OwnCloud integration to Nautilus
# It will be installed on /usr/share/nautilus-python/extensions/ with the paquet owncloud-client-nautilus
# (https://github.com/owncloud/client/edit/master/shell_integration/nautilus/syncstate.py)
#
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or # the Free Software Foundation; either version 2 of the License, or
@ -18,7 +22,9 @@ import socket
from gi.repository import GObject, Nautilus from gi.repository import GObject, Nautilus
# do not touch the following line. print("Initializing owncloud-client-nautilus extension")
# Do not touch the following line.
appname = 'ownCloud' appname = 'ownCloud'
def get_local_path(url): def get_local_path(url):
@ -38,7 +44,6 @@ def get_runtime_dir():
return fallback return fallback
class SocketConnect(GObject.GObject): class SocketConnect(GObject.GObject):
def __init__(self): def __init__(self):
GObject.GObject.__init__(self) GObject.GObject.__init__(self)
@ -77,38 +82,38 @@ class SocketConnect(GObject.GObject):
def _connectToSocketServer(self): def _connectToSocketServer(self):
try: try:
self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
postfix = "/"+appname+"/socket" postfix = "/" + appname + "/socket" # Should use os.path.join instead
sock_file = get_runtime_dir()+postfix sock_file = get_runtime_dir() + postfix
print ("Socket: " + sock_file + " <=> " + postfix) print ("Socket: " + sock_file + " <=> " + postfix)
if sock_file != postfix: if sock_file != postfix:
try: try:
print("Socket File: "+sock_file) print("Socket File: " + sock_file)
self._sock.connect(sock_file) self._sock.connect(sock_file)
self.connected = True self.connected = True
print("Setting connected to %r" % self.connected ) print("Setting connected to %r." % self.connected )
self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify) self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify)
print("Socket watch id: "+str(self._watch_id)) print("Socket watch id: " + str(self._watch_id))
return False # don't run again return False # Don't run again
except Exception as e: except Exception as e:
print("Could not connect to unix socket." + str(e)) print("Could not connect to unix socket. " + str(e))
else: else:
print("Sock-File not valid: "+sock_file) print("Sock-File not valid: " + sock_file)
except Exception as e: except Exception as e: # Bad habbit
print("Connect could not be established, try again later ") print("Connect could not be established, try again later.")
self._sock.close() self._sock.close()
return True # run again, if enabled via timeout_add() return True # Run again, if enabled via timeout_add()
# notify is the raw answer from the socket # Notify is the raw answer from the socket
def _handle_notify(self, source, condition): def _handle_notify(self, source, condition):
data = source.recv(1024) data = source.recv(1024)
# prepend the remaining data from last call # Prepend the remaining data from last call
if len(self._remainder) > 0: if len(self._remainder) > 0:
data = self._remainder+data data = self._remainder + data
self._remainder = '' self._remainder = ''
if len(data) > 0: if len(data) > 0:
# remember the remainder for next round # Remember the remainder for next round
lastNL = data.rfind('\n'); lastNL = data.rfind('\n');
if lastNL > -1 and lastNL < len(data): if lastNL > -1 and lastNL < len(data):
self._remainder = data[lastNL+1:] self._remainder = data[lastNL+1:]
@ -119,10 +124,10 @@ class SocketConnect(GObject.GObject):
else: else:
return False return False
return True # run again return True # Run again
def _handle_server_response(self, line): def _handle_server_response(self, line):
print("Server response: "+line) print("Server response: " + line)
parts = line.split(':') parts = line.split(':')
action = parts[0] action = parts[0]
args = parts[1:] args = parts[1:]
@ -151,32 +156,33 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
def get_file_items(self, window, files): def get_file_items(self, window, files):
if len(files) != 1: if len(files) != 1:
return return
file=files[0] file = files[0]
items=[] items = []
# internal or external file?! # Internal or external file?!
syncedFile = False syncedFile = False
for reg_path in socketConnect.registered_paths: for reg_path in socketConnect.registered_paths:
topLevelFolder=False topLevelFolder = False
filename = get_local_path(file.get_uri()) filename = get_local_path(file.get_uri())
#check if its a folder (ends with an /), if yes add a "/" otherwise it will not find the entry in the table # Check if its a folder (ends with an /), if yes add a "/"
if os.path.isdir(filename+"/"): # otherwise it will not find the entry in the table
filename=filename+"/" if os.path.isdir(filename + "/"):
#check if toplevel folder, we need to ignore those as they cannot be shared filename += "/"
# Check if toplevel folder, we need to ignore those as they cannot be shared
if filename.count("/") < (reg_path.count("/")+2): if filename.count("/") < (reg_path.count("/")+2):
topLevelFolder=True topLevelFolder=True
# only show the menu extension if the file is synced and the sync # Only show the menu extension if the file is synced and the sync
# status is ok. Not for ignored files etc. # status is ok. Not for ignored files etc.
# ignore top level folders # ignore top level folders
if filename.startswith(reg_path) and topLevelFolder == False and socketConnect.nautilusVFSFile_table[filename]['state'] == 'OK': if filename.startswith(reg_path) and topLevelFolder == False and socketConnect.nautilusVFSFile_table[filename]['state'] == 'OK':
syncedFile = True syncedFile = True
# if it is neither in a synced folder or is a directory # If it is neither in a synced folder or is a directory
if (not syncedFile): if not syncedFile:
return items return items
# create an menu item # Create an menu item
labelStr = "Share with "+appname+"..." labelStr = "Share with " + appname + "..."
item = Nautilus.MenuItem(name='NautilusPython::ShareItem', label=labelStr, item = Nautilus.MenuItem(name='NautilusPython::ShareItem', label=labelStr,
tip='Share file %s through ownCloud' % file.get_name()) tip='Share file %s through ownCloud' % file.get_name())
item.connect("activate", self.menu_share, file) item.connect("activate", self.menu_share, file)
@ -187,8 +193,8 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
def menu_share(self, menu, file): def menu_share(self, menu, file):
filename = get_local_path(file.get_uri()) filename = get_local_path(file.get_uri())
print("Share file "+filename) print("Share file " + filename)
socketConnect.sendCommand("SHARE:"+filename+"\n") socketConnect.sendCommand("SHARE:" + filename + "\n")
class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider): class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider):
@ -205,7 +211,7 @@ class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.Info
return None return None
def askForOverlay(self, file): def askForOverlay(self, file):
# print("Asking for overlay for "+file) # print("Asking for overlay for "+file) # For debug only
if os.path.isdir(file): if os.path.isdir(file):
folderStatus = socketConnect.sendCommand("RETRIEVE_FOLDER_STATUS:"+file+"\n"); folderStatus = socketConnect.sendCommand("RETRIEVE_FOLDER_STATUS:"+file+"\n");
@ -240,8 +246,8 @@ class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.Info
'NOP' : appname +'_error' 'NOP' : appname +'_error'
} }
# file = args[0] # file = args[0] # For debug only
# print "Action for " + file + ": "+args[0] # print("Action for " + file + ": " + args[0]) # For debug only
if action == 'STATUS': if action == 'STATUS':
newState = args[0] newState = args[0]
emblem = Emblems[newState] emblem = Emblems[newState]
@ -253,7 +259,7 @@ class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.Info
if( not itemStore['state'] or newState != itemStore['state'] ): if( not itemStore['state'] or newState != itemStore['state'] ):
item = itemStore['item'] item = itemStore['item']
item.add_emblem(emblem) item.add_emblem(emblem)
# print "Setting emblem on " + args[1]+ "<>"+emblem+"<>" # print("Setting emblem on " + args[1] + "<>" + emblem + "<>") # For debug only
socketConnect.nautilusVFSFile_table[args[1]] = {'item': item, 'state':newState} socketConnect.nautilusVFSFile_table[args[1]] = {'item': item, 'state':newState}
elif action == 'UPDATE_VIEW': elif action == 'UPDATE_VIEW':
@ -278,9 +284,9 @@ class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.Info
if filename.startswith(reg_path): if filename.startswith(reg_path):
socketConnect.nautilusVFSFile_table[filename] = {'item': item, 'state':''} socketConnect.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)
break break
else: else:
# print("Not in scope:"+filename) # print("Not in scope:" + filename) # For debug only
pass pass

View file

@ -60,6 +60,7 @@ struct CmdOptions {
QString exclude; QString exclude;
QString unsyncedfolders; QString unsyncedfolders;
QString davPath; QString davPath;
int restartTimes;
}; };
// we can't use csync_set_userdata because the SyncEngine sets it already. // we can't use csync_set_userdata because the SyncEngine sets it already.
@ -157,19 +158,20 @@ void help()
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 << " --nonshib, -ns Use Non Shibboleth WebDAV authentication" << std::endl; std::cout << " --nonshib Use Non Shibboleth WebDAV authentication" << std::endl;
std::cout << " --davpath, -dp [path] Custom themed dav path, overrides --nonshib" << std::endl; std::cout << " --davpath [path] Custom themed dav path, overrides --nonshib" << std::endl;
std::cout << " --max-sync-retries [n] Retries maximum n times (default to 3)" << std::endl;
std::cout << " -h Sync hidden files,do not ignore them" << std::endl; std::cout << " -h Sync hidden files,do not ignore them" << std::endl;
std::cout << " --version, -v Display version and exit" << std::endl; std::cout << " --version, -v Display version and exit" << std::endl;
std::cout << "" << std::endl; std::cout << "" << std::endl;
exit(1); exit(0);
} }
void showVersion() { void showVersion() {
const char *binaryName = APPLICATION_EXECUTABLE "cmd"; const char *binaryName = APPLICATION_EXECUTABLE "cmd";
std::cout << binaryName << " version " << qPrintable(Theme::instance()->version()) << std::endl; std::cout << binaryName << " version " << qPrintable(Theme::instance()->version()) << std::endl;
exit(1); exit(0);
} }
void parseOptions( const QStringList& app_args, CmdOptions *options ) void parseOptions( const QStringList& app_args, CmdOptions *options )
@ -226,10 +228,12 @@ void parseOptions( const QStringList& app_args, CmdOptions *options )
options->exclude = it.next(); options->exclude = it.next();
} else if( option == "--unsyncedfolders" && !it.peekNext().startsWith("-") ) { } else if( option == "--unsyncedfolders" && !it.peekNext().startsWith("-") ) {
options->unsyncedfolders = it.next(); options->unsyncedfolders = it.next();
} else if( option == "--nonshib" || option == "-ns") { } else if( option == "--nonshib" ) {
options->nonShib = true; options->nonShib = true;
} else if( (option == "--davpath" || option == "-dp") && !it.peekNext().startsWith("-") ) { } else if( option == "--davpath" && !it.peekNext().startsWith("-") ) {
options->davPath = it.next(); options->davPath = it.next();
} else if( option == "--max-sync-retries" && !it.peekNext().startsWith("-") ) {
options->restartTimes = it.next().toInt();
} else { } else {
help(); help();
} }
@ -277,6 +281,7 @@ int main(int argc, char **argv) {
options.interactive = true; options.interactive = true;
options.ignoreHiddenFiles = true; options.ignoreHiddenFiles = true;
options.nonShib = false; options.nonShib = false;
options.restartTimes = 3;
ClientProxy clientProxy; ClientProxy clientProxy;
parseOptions( app.arguments(), &options ); parseOptions( app.arguments(), &options );
@ -374,6 +379,7 @@ int main(int argc, char **argv) {
account->setCredentials(cred); account->setCredentials(cred);
account->setSslErrorHandler(sslErrorHandler); account->setSslErrorHandler(sslErrorHandler);
int restartCount = 0;
restart_sync: restart_sync:
CSYNC *_csync_ctx; CSYNC *_csync_ctx;
@ -465,7 +471,7 @@ restart_sync:
} }
SyncEngine engine(account, _csync_ctx, options.source_dir, QUrl(options.target_url).path(), folder, &db); SyncEngine engine(account, _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(bool)), &app, SLOT(quit()));
QObject::connect(&engine, SIGNAL(transmissionProgress(ProgressInfo)), &cmd, SLOT(transmissionProgressSlot())); QObject::connect(&engine, SIGNAL(transmissionProgress(ProgressInfo)), &cmd, SLOT(transmissionProgressSlot()));
// Have to be done async, else, an error before exec() does not terminate the event loop. // Have to be done async, else, an error before exec() does not terminate the event loop.
@ -476,9 +482,13 @@ restart_sync:
csync_destroy(_csync_ctx); csync_destroy(_csync_ctx);
if (engine.isAnotherSyncNeeded()) { if (engine.isAnotherSyncNeeded()) {
qDebug() << "Restarting Sync, because another sync is needed"; if (restartCount < options.restartTimes) {
restartCount++;
qDebug() << "Restarting Sync, because another sync is needed" << restartCount;
goto restart_sync; goto restart_sync;
} }
qWarning() << "Another sync is needed, but not done because restart count is exceeded" << restartCount;
}
return 0; return 0;
} }

View file

@ -58,6 +58,7 @@ set(client_SRCS
protocolwidget.cpp protocolwidget.cpp
selectivesyncdialog.cpp selectivesyncdialog.cpp
settingsdialog.cpp settingsdialog.cpp
share.cpp
sharedialog.cpp sharedialog.cpp
socketapi.cpp socketapi.cpp
sslbutton.cpp sslbutton.cpp

View file

@ -455,7 +455,15 @@ void AccountSettings::slotUpdateQuota(qint64 total, qint64 used)
ui->quotaProgressBar->setToolTip(toolTip); ui->quotaProgressBar->setToolTip(toolTip);
} else { } else {
ui->quotaProgressBar->setVisible(false); ui->quotaProgressBar->setVisible(false);
ui->quotaInfoLabel->setToolTip(QString());
/* -1 means not computed; -2 means unknown; -3 means unlimited (#3940)*/
if (total == 0 || total == -1) {
ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available.")); ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available."));
} else {
QString usedStr = Utility::octetsToString(used);
ui->quotaInfoLabel->setText(tr("%1 in use").arg(usedStr));
}
} }
} }

View file

@ -427,6 +427,7 @@ void Application::showVersion()
stream << _theme->appName().toLatin1().constData() stream << _theme->appName().toLatin1().constData()
<< QLatin1String(" version ") << QLatin1String(" version ")
<< _theme->version().toLatin1().constData() << endl; << _theme->version().toLatin1().constData() << endl;
stream << "Using Qt " << qVersion() << endl;
displayHelpText(helpText); displayHelpText(helpText);
} }

View file

@ -887,7 +887,7 @@ void Folder::startSync(const QStringList &pathList)
this, SLOT(slotAboutToPropagate(SyncFileItemVector&))); this, SLOT(slotAboutToPropagate(SyncFileItemVector&)));
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(bool)), SLOT(slotSyncFinished(bool)), Qt::QueuedConnection);
connect(_engine.data(), SIGNAL(csyncError(QString)), SLOT(slotSyncError(QString)), Qt::QueuedConnection); connect(_engine.data(), SIGNAL(csyncError(QString)), SLOT(slotSyncError(QString)), Qt::QueuedConnection);
connect(_engine.data(), SIGNAL(csyncUnavailable()), SLOT(slotCsyncUnavailable()), Qt::QueuedConnection); connect(_engine.data(), SIGNAL(csyncUnavailable()), SLOT(slotCsyncUnavailable()), Qt::QueuedConnection);
@ -959,7 +959,7 @@ void Folder::slotCsyncUnavailable()
_csyncUnavail = true; _csyncUnavail = true;
} }
void Folder::slotSyncFinished() void Folder::slotSyncFinished(bool success)
{ {
qDebug() << " - client version" << qPrintable(Theme::instance()->version()) qDebug() << " - client version" << qPrintable(Theme::instance()->version())
<< " Qt" << qVersion() << " Qt" << qVersion()
@ -1017,7 +1017,7 @@ void Folder::slotSyncFinished()
qDebug() << "the last" << _consecutiveFailingSyncs << "syncs failed"; qDebug() << "the last" << _consecutiveFailingSyncs << "syncs failed";
} }
if (_syncResult.status() == SyncResult::Success) { if (_syncResult.status() == SyncResult::Success && success) {
// Clear the white list as all the folders that should be on that list are sync-ed // Clear the white list as all the folders that should be on that list are sync-ed
journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList()); journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList());
} }

View file

@ -251,7 +251,7 @@ private slots:
void slotSyncStarted(); void slotSyncStarted();
void slotSyncError(const QString& ); void slotSyncError(const QString& );
void slotCsyncUnavailable(); void slotCsyncUnavailable();
void slotSyncFinished(); void slotSyncFinished(bool);
void slotFolderDiscovered(bool local, QString folderName); void slotFolderDiscovered(bool local, QString folderName);
void slotTransmissionProgress(const ProgressInfo& pi); void slotTransmissionProgress(const ProgressInfo& pi);

View file

@ -22,6 +22,7 @@
#include <QFileIconProvider> #include <QFileIconProvider>
#include <QVarLengthArray> #include <QVarLengthArray>
#include <set>
Q_DECLARE_METATYPE(QPersistentModelIndex) Q_DECLARE_METATYPE(QPersistentModelIndex)
@ -546,10 +547,16 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
selectiveSyncBlackList = parentInfo->_folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList); selectiveSyncBlackList = parentInfo->_folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList);
} }
auto selectiveSyncUndecidedList = parentInfo->_folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList); auto selectiveSyncUndecidedList = parentInfo->_folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList);
QVarLengthArray<int, 10> undecidedIndexes; QVarLengthArray<int, 10> undecidedIndexes;
QVector<SubFolderInfo> newSubs; QVector<SubFolderInfo> newSubs;
std::set<QString> selectiveSyncUndecidedSet; // not QSet because it's not sorted
foreach (const QString &str, selectiveSyncUndecidedList) {
if (str.startsWith(parentInfo->_path) || parentInfo->_path == QLatin1String("/")) {
selectiveSyncUndecidedSet.insert(str);
}
}
newSubs.reserve(list.size() - 1); newSubs.reserve(list.size() - 1);
for (int i = 1; // skip the parent item (first in the list) for (int i = 1; // skip the parent item (first in the list)
i < list.size(); ++i) { i < list.size(); ++i) {
@ -586,11 +593,19 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
} }
} }
foreach(const QString &str , selectiveSyncUndecidedList) { auto it = selectiveSyncUndecidedSet.lower_bound(relativePath);
if (str == relativePath) { if (it != selectiveSyncUndecidedSet.end()) {
if (*it == relativePath) {
newInfo._isUndecided = true; newInfo._isUndecided = true;
} else if (str.startsWith(relativePath)) { selectiveSyncUndecidedSet.erase(it);
} else if ((*it).startsWith(relativePath)) {
undecidedIndexes.append(newInfo._pathIdx.last()); undecidedIndexes.append(newInfo._pathIdx.last());
// Remove all the items from the selectiveSyncUndecidedSet that starts with this path
QString relativePathNext = relativePath;
relativePathNext[relativePathNext.length()-1].unicode()++;
auto it2 = selectiveSyncUndecidedSet.lower_bound(relativePathNext);
selectiveSyncUndecidedSet.erase(it, it2);
} }
} }
newSubs.append(newInfo); newSubs.append(newInfo);
@ -603,6 +618,16 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
for (auto it = undecidedIndexes.begin(); it != undecidedIndexes.end(); ++it) { for (auto it = undecidedIndexes.begin(); it != undecidedIndexes.end(); ++it) {
suggestExpand(idx.child(*it, 0)); suggestExpand(idx.child(*it, 0));
} }
/* Try to remove the the undecided lists the items that are not on the server. */
auto it = std::remove_if(selectiveSyncUndecidedList.begin(), selectiveSyncUndecidedList.end(),
[&](const QString &s) { return selectiveSyncUndecidedSet.count(s); } );
if (it != selectiveSyncUndecidedList.end()) {
selectiveSyncUndecidedList.erase(it, selectiveSyncUndecidedList.end());
parentInfo->_folder->journalDb()->setSelectiveSyncList(
SyncJournalDb::SelectiveSyncUndecidedList, selectiveSyncUndecidedList);
emit dirtyChanged();
}
} }
void FolderStatusModel::slotLscolFinishedWithError(QNetworkReply* r) void FolderStatusModel::slotLscolFinishedWithError(QNetworkReply* r)

View file

@ -42,9 +42,9 @@ void OcsJob::addPassStatusCode(int code)
_passStatusCodes.append(code); _passStatusCodes.append(code);
} }
void OcsJob::appendPath(int id) void OcsJob::appendPath(const QString &id)
{ {
setPath(path() + QString("/%1").arg(id)); setPath(path() + QLatin1Char('/') + id);
} }
void OcsJob::start() void OcsJob::start()
@ -105,9 +105,11 @@ bool OcsJob::finished()
<< Account::concatUrlPath(account()->url(), path()) << Account::concatUrlPath(account()->url(), path())
<< _params << _params
<< "has unexpected status code:" << statusCode << replyData; << "has unexpected status code:" << statusCode << replyData;
} emit ocsError(statusCode, message);
} else {
emit jobFinished(json); emit jobFinished(json);
}
deleteLater();
return true; return true;
} }

View file

@ -79,7 +79,7 @@ protected:
* *
* This function appends the common id. so <PATH>/<ID> * This function appends the common id. so <PATH>/<ID>
*/ */
void appendPath(int id); void appendPath(const QString &id);
public: public:
/** /**
@ -108,6 +108,15 @@ signals:
*/ */
void jobFinished(QVariantMap reply); void jobFinished(QVariantMap reply);
/**
* The status code was not one of the expected (passing)
* status code for this command
*
* @param statusCode The actual status code
* @param message The message provided by the server
*/
void ocsError(int statusCode, const QString &message);
private slots: private slots:
virtual bool finished() Q_DECL_OVERRIDE; virtual bool finished() Q_DECL_OVERRIDE;

View file

@ -24,6 +24,7 @@ OcsShareJob::OcsShareJob(AccountPtr account, QObject* parent)
: OcsJob(account, parent) : OcsJob(account, parent)
{ {
setPath("ocs/v1.php/apps/files_sharing/api/v1/shares"); setPath("ocs/v1.php/apps/files_sharing/api/v1/shares");
connect(this, SIGNAL(jobFinished(QVariantMap)), this, SLOT(jobDone(QVariantMap)));
} }
void OcsShareJob::getShares(const QString &path) void OcsShareJob::getShares(const QString &path)
@ -36,7 +37,7 @@ void OcsShareJob::getShares(const QString &path)
start(); start();
} }
void OcsShareJob::deleteShare(int shareId) void OcsShareJob::deleteShare(const QString &shareId)
{ {
appendPath(shareId); appendPath(shareId);
setVerb("DELETE"); setVerb("DELETE");
@ -44,7 +45,7 @@ void OcsShareJob::deleteShare(int shareId)
start(); start();
} }
void OcsShareJob::setExpireDate(int shareId, const QDate &date) void OcsShareJob::setExpireDate(const QString &shareId, const QDate &date)
{ {
appendPath(shareId); appendPath(shareId);
setVerb("PUT"); setVerb("PUT");
@ -54,32 +55,35 @@ void OcsShareJob::setExpireDate(int shareId, const QDate &date)
} else { } else {
addParam(QString::fromLatin1("expireDate"), QString()); addParam(QString::fromLatin1("expireDate"), QString());
} }
_value = date;
start(); start();
} }
void OcsShareJob::setPassword(int shareId, const QString &password) void OcsShareJob::setPassword(const QString &shareId, const QString &password)
{ {
appendPath(shareId); appendPath(shareId);
setVerb("PUT"); setVerb("PUT");
addParam(QString::fromLatin1("password"), password); addParam(QString::fromLatin1("password"), password);
_value = password;
start(); start();
} }
void OcsShareJob::setPublicUpload(int shareId, bool publicUpload) void OcsShareJob::setPublicUpload(const QString &shareId, bool publicUpload)
{ {
appendPath(shareId); appendPath(shareId);
setVerb("PUT"); setVerb("PUT");
const QString value = QString::fromLatin1(publicUpload ? "true" : "false"); const QString value = QString::fromLatin1(publicUpload ? "true" : "false");
addParam(QString::fromLatin1("publicUpload"), value); addParam(QString::fromLatin1("publicUpload"), value);
_value = publicUpload;
start(); start();
} }
void OcsShareJob::createShare(const QString &path, ShareType shareType, const QString &password, const QDate &date) void OcsShareJob::createShare(const QString &path, Share::ShareType shareType, const QString &password, const QDate &date)
{ {
setVerb("POST"); setVerb("POST");
@ -87,7 +91,7 @@ void OcsShareJob::createShare(const QString &path, ShareType shareType, const QS
addParam(QString::fromLatin1("shareType"), QString::number(static_cast<int>(shareType))); addParam(QString::fromLatin1("shareType"), QString::number(static_cast<int>(shareType)));
if (!password.isEmpty()) { if (!password.isEmpty()) {
addParam(QString::fromLatin1("shareType"), password); addParam(QString::fromLatin1("password"), password);
} }
if (date.isValid()) { if (date.isValid()) {
@ -99,4 +103,9 @@ void OcsShareJob::createShare(const QString &path, ShareType shareType, const QS
start(); start();
} }
void OcsShareJob::jobDone(QVariantMap reply)
{
emit shareJobFinished(reply, _value);
}
} }

View file

@ -15,6 +15,7 @@
#define OCSSHAREJOB_H #define OCSSHAREJOB_H
#include "ocsjob.h" #include "ocsjob.h"
#include "share.h"
#include <QVector> #include <QVector>
#include <QList> #include <QList>
#include <QPair> #include <QPair>
@ -32,25 +33,6 @@ class OcsShareJob : public OcsJob {
Q_OBJECT Q_OBJECT
public: public:
/**
* Support sharetypes
*/
enum class ShareType : int {
Link = 3
};
/**
* Possible permissions
*/
enum class Permission : int {
Read = 1,
Update = 2,
Create = 4,
Delete = 8,
Share = 16,
All = 31
};
/** /**
* Constructor for new shares or listing of shares * Constructor for new shares or listing of shares
*/ */
@ -66,7 +48,7 @@ public:
/** /**
* Delete the current Share * Delete the current Share
*/ */
void deleteShare(int shareId); void deleteShare(const QString &shareId);
/** /**
* Set the expiration date of a share * Set the expiration date of a share
@ -74,7 +56,7 @@ public:
* @param date The expire date, if this date is invalid the expire date * @param date The expire date, if this date is invalid the expire date
* will be removed * will be removed
*/ */
void setExpireDate(int shareId, const QDate& date); void setExpireDate(const QString &shareId, const QDate& date);
/** /**
* Set the password of a share * Set the password of a share
@ -82,14 +64,14 @@ public:
* @param password The password of the share, if the password is empty the * @param password The password of the share, if the password is empty the
* share will be removed * share will be removed
*/ */
void setPassword(int shareId, const QString& password); void setPassword(const QString &shareId, const QString& password);
/** /**
* Void set the share to be public upload * Void set the share to be public upload
* *
* @param publicUpload Set or remove public upload * @param publicUpload Set or remove public upload
*/ */
void setPublicUpload(int shareId, bool publicUpload); void setPublicUpload(const QString &shareId, bool publicUpload);
/** /**
* Create a new share * Create a new share
@ -99,7 +81,25 @@ public:
* @param password Optionally a password for the share * @param password Optionally a password for the share
* @param date Optionally an expire date for the share * @param date Optionally an expire date for the share
*/ */
void createShare(const QString& path, ShareType shareType, const QString& password = "", const QDate& date = QDate()); void createShare(const QString& path, Share::ShareType shareType, const QString& password = "", const QDate& date = QDate());
signals:
/**
* Result of the OCS request
* The value parameter is only set if this was a put request.
* e.g. if we set the password to 'foo' the QVariant will hold a QString with 'foo'.
* This is needed so we can update the share objects properly
*
* @param reply The reply
* @param value To what did we set a variable (if we set any).
*/
void shareJobFinished(QVariantMap reply, QVariant value);
private slots:
void jobDone(QVariantMap reply);
private:
QVariant _value;
}; };
} }

View file

@ -102,9 +102,10 @@ void QuotaInfo::slotUpdateLastQuota(const QVariantMap &result)
{ {
// The server can return fractional bytes (#1374) // The server can return fractional bytes (#1374)
// <d:quota-available-bytes>1374532061.2</d:quota-available-bytes> // <d:quota-available-bytes>1374532061.2</d:quota-available-bytes>
quint64 avail = result["quota-available-bytes"].toDouble(); qint64 avail = result["quota-available-bytes"].toDouble();
_lastQuotaUsedBytes = result["quota-used-bytes"].toDouble(); _lastQuotaUsedBytes = result["quota-used-bytes"].toDouble();
_lastQuotaTotalBytes = _lastQuotaUsedBytes + avail; // negative value of the available quota have special meaning (#3940)
_lastQuotaTotalBytes = avail >= 0 ? _lastQuotaUsedBytes + avail : avail;
emit quotaUpdated(_lastQuotaTotalBytes, _lastQuotaUsedBytes); emit quotaUpdated(_lastQuotaTotalBytes, _lastQuotaUsedBytes);
_jobRestartTimer.start(defaultIntervalT); _jobRestartTimer.start(defaultIntervalT);
_lastQuotaRecieved = QDateTime::currentDateTime(); _lastQuotaRecieved = QDateTime::currentDateTime();

265
src/gui/share.cpp Normal file
View file

@ -0,0 +1,265 @@
/*
* Copyright (C) by Roeland Jago Douma <rullzer@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 "share.h"
#include "ocssharejob.h"
#include "account.h"
#include <QUrl>
namespace OCC {
Share::Share(AccountPtr account,
const QString& id,
const QString& path,
ShareType shareType,
Permissions permissions)
: _account(account),
_id(id),
_path(path),
_shareType(shareType),
_permissions(permissions)
{
}
QString Share::getId() const
{
return _id;
}
Share::ShareType Share::getShareType() const
{
return _shareType;
}
Share::Permissions Share::getPermissions() const
{
return _permissions;
}
void Share::deleteShare()
{
OcsShareJob *job = new OcsShareJob(_account, this);
connect(job, SIGNAL(shareJobFinished(QVariantMap, QVariant)), SLOT(slotDeleted()));
connect(job, SIGNAL(ocsError(int, const QString &)), SLOT(slotOcsError(int, const QString &)));
job->deleteShare(getId());
}
void Share::slotDeleted()
{
emit shareDeleted();
}
void Share::slotOcsError(int statusCode, const QString &message)
{
emit serverError(statusCode, message);
}
QUrl LinkShare::getLink() const
{
return _url;
}
QDate LinkShare::getExpireDate() const
{
return _expireDate;
}
bool LinkShare::isPasswordSet() const
{
return _passwordSet;
}
LinkShare::LinkShare(AccountPtr account,
const QString& id,
const QString& path,
Permissions permissions,
bool passwordSet,
const QUrl& url,
const QDate& expireDate)
: Share(account, id, path, Share::TypeLink, permissions),
_passwordSet(passwordSet),
_expireDate(expireDate),
_url(url)
{
}
bool LinkShare::getPublicUpload()
{
return ((_permissions & PermissionUpdate) &&
(_permissions & PermissionCreate));
}
void LinkShare::setPublicUpload(bool publicUpload)
{
OcsShareJob *job = new OcsShareJob(_account, this);
connect(job, SIGNAL(shareJobFinished(QVariantMap, QVariant)), SLOT(slotPublicUploadSet(QVariantMap, QVariant)));
connect(job, SIGNAL(ocsError(int, QString)), SLOT(slotOcsError(int, QString)));
job->setPublicUpload(getId(), publicUpload);
}
void LinkShare::slotPublicUploadSet(const QVariantMap&, const QVariant &value)
{
//TODO FIX permission with names
if (value.toBool()) {
_permissions = PermissionRead | PermissionUpdate | PermissionCreate;
} else {
_permissions = PermissionRead;
}
emit publicUploadSet();
}
void LinkShare::setPassword(const QString &password)
{
OcsShareJob *job = new OcsShareJob(_account, this);
connect(job, SIGNAL(shareJobFinished(QVariantMap, QVariant)), SLOT(slotPasswordSet(QVariantMap, QVariant)));
connect(job, SIGNAL(ocsError(int, QString)), SLOT(slotOcsError(int, QString)));
job->setPassword(getId(), password);
}
void LinkShare::slotPasswordSet(const QVariantMap&, const QVariant &value)
{
_passwordSet = value.toString() == "";
emit passwordSet();
}
void LinkShare::setExpireDate(const QDate &date)
{
OcsShareJob *job = new OcsShareJob(_account, this);
connect(job, SIGNAL(shareJobFinished(QVariantMap, QVariant)), SLOT(slotExpireDateSet(QVariantMap, QVariant)));
connect(job, SIGNAL(ocsError(int, QString)), SLOT(slotOcsError(int, QString)));
job->setExpireDate(getId(), date);
}
void LinkShare::slotExpireDateSet(const QVariantMap&, const QVariant &value)
{
_expireDate = value.toDate();
emit expireDateSet();
}
ShareManager::ShareManager(AccountPtr account, QObject *parent)
: QObject(parent),
_account(account)
{
}
void ShareManager::createLinkShare(const QString &path,
const QString &password)
{
OcsShareJob *job = new OcsShareJob(_account, this);
connect(job, SIGNAL(shareJobFinished(QVariantMap, QVariant)), SLOT(slotLinkShareCreated(QVariantMap)));
connect(job, SIGNAL(ocsError(int, QString)), SLOT(slotOcsError(int, QString)));
job->createShare(path, Share::TypeLink, password);
}
void ShareManager::slotLinkShareCreated(const QVariantMap &reply)
{
QString message;
int code = OcsShareJob::getJsonReturnCode(reply, message);
/*
* Before we had decent sharing capabilities on the server a 403 "generally"
* meant that a share was password protected
*/
if (code == 403) {
emit linkShareRequiresPassword();
return;
}
//Parse share
auto data = reply.value("ocs").toMap().value("data").toMap();
QSharedPointer<LinkShare> share(parseLinkShare(data));
emit linkShareCreated(share);
}
void ShareManager::fetchShares(const QString &path)
{
OcsShareJob *job = new OcsShareJob(_account, this);
connect(job, SIGNAL(shareJobFinished(QVariantMap, QVariant)), SLOT(slotSharesFetched(QVariantMap)));
connect(job, SIGNAL(ocsError(int, QString)), SLOT(slotOcsError(int, QString)));
job->getShares(path);
}
void ShareManager::slotSharesFetched(const QVariantMap &reply)
{
auto tmpShares = reply.value("ocs").toMap().value("data").toList();
const QString versionString = _account->serverVersion();
qDebug() << Q_FUNC_INFO << versionString << "Fetched" << tmpShares.count() << "shares";
QList<QSharedPointer<Share>> shares;
foreach(const auto &share, tmpShares) {
auto data = share.toMap();
auto shareType = data.value("share_type").toInt();
QSharedPointer<Share> newShare;
if (shareType == Share::TypeLink) {
newShare = parseLinkShare(data);
} else {
newShare = QSharedPointer<Share>(new Share(_account,
data.value("id").toString(),
data.value("path").toString(),
(Share::ShareType)shareType,
(Share::Permissions)data.value("permissions").toInt()));
}
shares.append(QSharedPointer<Share>(newShare));
}
qDebug() << Q_FUNC_INFO << "Sending " << shares.count() << "shares";
emit sharesFetched(shares);
}
QSharedPointer<LinkShare> ShareManager::parseLinkShare(const QVariantMap &data) {
QUrl url;
// From ownCloud server 8.2 the url field is always set for public shares
if (data.contains("url")) {
url = QUrl(data.value("url").toString());
} else if (_account->serverVersionInt() >= (8 << 16)) {
// From ownCloud server version 8 on, a different share link scheme is used.
url = QUrl(Account::concatUrlPath(_account->url(), QString("index.php/s/%1").arg(data.value("token").toString())).toString());
} else {
QList<QPair<QString, QString>> queryArgs;
queryArgs.append(qMakePair(QString("service"), QString("files")));
queryArgs.append(qMakePair(QString("t"), data.value("token").toString()));
url = QUrl(Account::concatUrlPath(_account->url(), QLatin1String("public.php"), queryArgs).toString());
}
QDate expireDate;
if (data.value("expiration").isValid()) {
expireDate = QDate::fromString(data.value("expiration").toString(), "yyyy-MM-dd 00:00:00");
}
return QSharedPointer<LinkShare>(new LinkShare(_account,
data.value("id").toString(),
data.value("path").toString(),
(Share::Permissions)data.value("permissions").toInt(),
data.value("share_with").isValid(),
url,
expireDate));
}
void ShareManager::slotOcsError(int statusCode, const QString &message)
{
emit serverError(statusCode, message);
}
}

248
src/gui/share.h Normal file
View file

@ -0,0 +1,248 @@
/*
* Copyright (C) by Roeland Jago Douma <rullzer@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 SHARE_H
#define SHARE_H
#include "accountfwd.h"
#include <QObject>
#include <QDate>
#include <QString>
#include <QList>
#include <QSharedPointer>
#include <QUrl>
namespace OCC {
class Share : public QObject {
Q_OBJECT
public:
/**
* Possible share types
*/
enum ShareType {
TypeUser = 0,
TypeGroup = 1,
TypeLink = 3,
TypeRemote = 6,
};
Q_DECLARE_FLAGS(ShareTypes, ShareType)
/**
* Possible permissions
*/
enum Permission {
PermissionRead = 1,
PermissionUpdate = 2,
PermissionCreate = 4,
PermissionDelete = 8,
PermissionShare = 16
};
Q_DECLARE_FLAGS(Permissions, Permission)
/*
* Constructor for shares
*/
explicit Share(AccountPtr account,
const QString& id,
const QString& path,
ShareType shareType,
Permissions permissions);
/*
* Get the id
*/
QString getId() const;
/*
* Get the shareType
*/
ShareType getShareType() const;
/*
* Get permissions
*/
Permissions getPermissions() const;
/*
* Set the permissions of a share
*
* On success the permissionsSet signal is emitted
* In case of a server error the serverError signal is emitted.
*/
void setPermissions(int permissions);
/**
* Deletes a share
*
* On success the shareDeleted signal is emitted
* In case of a server error the serverError signal is emitted.
*/
void deleteShare();
signals:
void permissionsSet();
void shareDeleted();
void serverError(int code, const QString &message);
protected:
AccountPtr _account;
QString _id;
QString _path;
ShareType _shareType;
Permissions _permissions;
protected slots:
void slotOcsError(int statusCode, const QString &message);
private slots:
void slotDeleted();
};
/**
* A Link share is just like a regular share but then slightly different.
* There are several methods in the API that either work differently for
* link shares or are only available to link shares.
*/
class LinkShare : public Share {
Q_OBJECT
public:
explicit LinkShare(AccountPtr account,
const QString& id,
const QString& path,
Permissions permissions,
bool passwordSet,
const QUrl& url,
const QDate& expireDate);
/*
* Get the share link
*/
QUrl getLink() const;
/*
* Get the publicUpload status of this share
*/
bool getPublicUpload();
/*
* Set a share to be public upload
* This function can only be called on link shares
*
* On success the publicUploadSet signal is emitted
* In case of a server error the serverError signal is emitted.
*/
void setPublicUpload(bool publicUpload);
/*
* Set the password
*
* On success the passwordSet signal is emitted
* In case of a server error the serverError signal is emitted.
*/
void setPassword(const QString& password);
/*
* Is the password set?
*/
bool isPasswordSet() const;
/*
* Get the expiration date
*/
QDate getExpireDate() const;
/*
* Set the expiration date
*
* On success the expireDateSet signal is emitted
* In case of a server error the serverError signal is emitted.
*/
void setExpireDate(const QDate& expireDate);
signals:
void expireDateSet();
void publicUploadSet();
void passwordSet();
private slots:
void slotPasswordSet(const QVariantMap&, const QVariant &value);
void slotPublicUploadSet(const QVariantMap&, const QVariant &value);
void slotExpireDateSet(const QVariantMap&, const QVariant &value);
private:
bool _passwordSet;
QDate _expireDate;
QUrl _url;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Share::Permissions)
/**
* The share manager allows for creating, retrieving and deletion
* of shares. It abstracts away from the OCS Share API, all the usages
* shares should talk to this manager and not use OCS Share Job directly
*/
class ShareManager : public QObject {
Q_OBJECT
public:
explicit ShareManager(AccountPtr _account, QObject *parent = NULL);
/**
* Tell the manager to create a link share
*
* @param path The path of the linkshare relative to the user folder on the server
* @param password The password of the share
*
* On success the signal linkShareCreated is emitted
* For older server the linkShareRequiresPassword signal is emitted when it seems appropiate
* In case of a server error the serverError signal is emitted
*/
void createLinkShare(const QString& path,
const QString& password="");
/**
* Fetch all the shares for path
*
* @param path The path to get the shares for relative to the users folder on the server
*
* On success the sharesFetched signal is emitted
* In case of a server error the serverError signal is emitted
*/
void fetchShares(const QString& path);
signals:
void linkShareCreated(const QSharedPointer<LinkShare> &share);
void linkShareRequiresPassword();
void sharesFetched(const QList<QSharedPointer<Share>> &shares);
void serverError(int code, const QString &message);
private slots:
void slotSharesFetched(const QVariantMap &reply);
void slotLinkShareCreated(const QVariantMap &reply);
void slotOcsError(int statusCode, const QString &message);
private:
QSharedPointer<LinkShare> parseLinkShare(const QVariantMap &data);
AccountPtr _account;
};
}
#endif // SHARE_H

View file

@ -23,8 +23,8 @@
#include "configfile.h" #include "configfile.h"
#include "capabilities.h" #include "capabilities.h"
#include "ocssharejob.h"
#include "thumbnailjob.h" #include "thumbnailjob.h"
#include "share.h"
#include "QProgressIndicator.h" #include "QProgressIndicator.h"
#include <QBuffer> #include <QBuffer>
@ -41,7 +41,8 @@ ShareDialog::ShareDialog(AccountPtr account, const QString &sharePath, const QSt
_sharePath(sharePath), _sharePath(sharePath),
_localPath(localPath), _localPath(localPath),
_passwordJobRunning(false), _passwordJobRunning(false),
_public_share_id(0), _manager(NULL),
_share(NULL),
_resharingAllowed(resharingAllowed) _resharingAllowed(resharingAllowed)
{ {
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
@ -90,7 +91,6 @@ ShareDialog::ShareDialog(AccountPtr account, const QString &sharePath, const QSt
_ui->lineEdit_password->hide(); _ui->lineEdit_password->hide();
_ui->pushButton_setPassword->hide(); _ui->pushButton_setPassword->hide();
_ui->calendar->setDate(QDate::currentDate().addDays(1));
_ui->calendar->setEnabled(false); _ui->calendar->setEnabled(false);
QFileInfo f_info(_localPath); QFileInfo f_info(_localPath);
@ -168,6 +168,16 @@ ShareDialog::ShareDialog(AccountPtr account, const QString &sharePath, const QSt
_ui->checkBox_editing->setEnabled(false); _ui->checkBox_editing->setEnabled(false);
} }
} }
/*
* Create the share manager and connect it properly
*/
_manager = new ShareManager(_account, this);
connect(_manager, SIGNAL(sharesFetched(QList<QSharedPointer<Share>>)), SLOT(slotSharesFetched(QList<QSharedPointer<Share>>)));
connect(_manager, SIGNAL(linkShareCreated(QSharedPointer<LinkShare>)), SLOT(slotCreateShareFetched(const QSharedPointer<LinkShare>)));
connect(_manager, SIGNAL(linkShareRequiresPassword()), SLOT(slotCreateShareRequiresPassword()));
connect(_manager, SIGNAL(serverError(int, QString)), SLOT(displayError(int, QString)));
} }
void ShareDialog::done( int r ) { void ShareDialog::done( int r ) {
@ -178,25 +188,12 @@ void ShareDialog::done( int r ) {
void ShareDialog::setExpireDate(const QDate &date) void ShareDialog::setExpireDate(const QDate &date)
{ {
if( _public_share_id == 0 ) {
// no public share so far.
return;
}
_pi_date->startAnimation(); _pi_date->startAnimation();
_share->setExpireDate(date);
OcsShareJob *job = new OcsShareJob(_account, this);
connect(job, SIGNAL(jobFinished(QVariantMap)), this, SLOT(slotExpireSet(QVariantMap)));
job->setExpireDate(_public_share_id, date);
} }
void ShareDialog::slotExpireSet(const QVariantMap &reply) void ShareDialog::slotExpireSet()
{ {
QString message;
int code = OcsShareJob::getJsonReturnCode(reply, message);
if (code != 100) {
displayError(code);
}
_pi_date->stopAnimation(); _pi_date->stopAnimation();
} }
@ -226,40 +223,22 @@ void ShareDialog::slotPasswordChanged(const QString& newText)
void ShareDialog::setPassword(const QString &password) void ShareDialog::setPassword(const QString &password)
{ {
if( _passwordJobRunning ) {
// This happens because the entry field and the button both trigger this slot.
return;
}
_pi_link->startAnimation(); _pi_link->startAnimation();
_pi_password->startAnimation(); _pi_password->startAnimation();
QString path;
if( _public_share_id > 0 ) { _ui->checkBox_password->setEnabled(false);
OcsShareJob *job = new OcsShareJob(_account, this); _ui->lineEdit_password->setEnabled(false);
connect(job, SIGNAL(jobFinished(QVariantMap)), this, SLOT(slotPasswordSet(QVariantMap)));
job->setPassword(_public_share_id, password); if( !_share.isNull() ) {
_share->setPassword(password);
} else { } else {
OcsShareJob *job = new OcsShareJob(_account, this); _ui->checkBox_shareLink->setEnabled(false);
connect(job, SIGNAL(jobFinished(QVariantMap)), this, SLOT(slotPasswordSet(QVariantMap))); _manager->createLinkShare(_sharePath, password);
connect(job, SIGNAL(jobFinished(QVariantMap)), this, SLOT(slotCreateShareFetched(QVariantMap)));
QDate date;
if( _ui->checkBox_expire->isChecked() ) {
date = _ui->calendar->date();
} }
job->createShare(_sharePath, OcsShareJob::ShareType::Link, password, date);
}
_passwordJobRunning = true;
} }
void ShareDialog::slotPasswordSet(const QVariantMap &reply) void ShareDialog::slotPasswordSet()
{ {
QString message;
int code = OcsShareJob::getJsonReturnCode(reply, message);
if (code != 100) {
displayError(code);
}
/* /*
* When setting/deleting a password from a share the old share is * When setting/deleting a password from a share the old share is
* deleted and a new one is created. So we need to refetch the shares * deleted and a new one is created. So we need to refetch the shares
@ -267,15 +246,12 @@ void ShareDialog::slotPasswordSet(const QVariantMap &reply)
*/ */
getShares(); getShares();
_passwordJobRunning = false;
_pi_password->stopAnimation(); _pi_password->stopAnimation();
} }
void ShareDialog::getShares() void ShareDialog::getShares()
{ {
OcsShareJob *job = new OcsShareJob(_account, this); _manager->fetchShares(_sharePath);
connect(job, SIGNAL(jobFinished(QVariantMap)), this, SLOT(slotSharesFetched(QVariantMap)));
job->getShares(_sharePath);
if (QFileInfo(_localPath).isFile()) { if (QFileInfo(_localPath).isFile()) {
ThumbnailJob *job2 = new ThumbnailJob(_sharePath, _account, this); ThumbnailJob *job2 = new ThumbnailJob(_sharePath, _account, this);
@ -284,34 +260,27 @@ void ShareDialog::getShares()
} }
} }
void ShareDialog::slotSharesFetched(const QVariantMap &reply) void ShareDialog::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
{ {
QString message;
int code = OcsShareJob::getJsonReturnCode(reply, message);
if (code != 100 && code != 404) {
displayError(code);
}
ShareDialog::_shares = reply.value("ocs").toMap().value("data").toList();
const QString versionString = _account->serverVersion(); const QString versionString = _account->serverVersion();
qDebug() << Q_FUNC_INFO << versionString << "Fetched" << shares.count() << "shares";
qDebug() << Q_FUNC_INFO << versionString << "Fetched" << ShareDialog::_shares.count() << "shares";
//Show link checkbox now //Show link checkbox now
_ui->checkBox_shareLink->setEnabled(true); _ui->checkBox_shareLink->setEnabled(true);
_pi_link->stopAnimation(); _pi_link->stopAnimation();
Q_FOREACH(auto share, ShareDialog::_shares) { Q_FOREACH(auto share, shares) {
QVariantMap data = share.toMap();
if (data.value("share_type").toInt() == static_cast<int>(OcsShareJob::ShareType::Link)) { if (share->getShareType() == Share::TypeLink) {
_public_share_id = data.value("id").toULongLong(); _share = qSharedPointerDynamicCast<LinkShare>(share);
_ui->pushButton_copy->show(); _ui->pushButton_copy->show();
_ui->widget_shareLink->show(); _ui->widget_shareLink->show();
_ui->checkBox_shareLink->setChecked(true); _ui->checkBox_shareLink->setChecked(true);
if (data.value("share_with").isValid()) { _ui->checkBox_password->setEnabled(true);
if (_share->isPasswordSet()) {
_ui->lineEdit_password->setEnabled(true);
_ui->checkBox_password->setChecked(true); _ui->checkBox_password->setChecked(true);
_ui->lineEdit_password->setPlaceholderText("********"); _ui->lineEdit_password->setPlaceholderText("********");
_ui->lineEdit_password->show(); _ui->lineEdit_password->show();
@ -323,8 +292,9 @@ void ShareDialog::slotSharesFetched(const QVariantMap &reply)
_ui->pushButton_setPassword->hide(); _ui->pushButton_setPassword->hide();
} }
if (data.value("expiration").isValid()) { _ui->checkBox_expire->setEnabled(true);
_ui->calendar->setDate(QDate::fromString(data.value("expiration").toString(), "yyyy-MM-dd 00:00:00")); if (_share->getExpireDate().isValid()) {
_ui->calendar->setDate(_share->getExpireDate());
_ui->calendar->setMinimumDate(QDate::currentDate().addDays(1)); _ui->calendar->setMinimumDate(QDate::currentDate().addDays(1));
_ui->calendar->setEnabled(true); _ui->calendar->setEnabled(true);
_ui->checkBox_expire->setChecked(true); _ui->checkBox_expire->setChecked(true);
@ -333,38 +303,33 @@ void ShareDialog::slotSharesFetched(const QVariantMap &reply)
_ui->checkBox_expire->setChecked(false); _ui->checkBox_expire->setChecked(false);
} }
if (data.value("permissions").isValid()) {
int permissions = data.value("permissions").toInt();
/* /*
* Only directories can have public upload set * Only directories can have public upload set
* For public links the server sets CREATE and UPDATE permissions. * For public links the server sets CREATE and UPDATE permissions.
*/ */
if (!_isFile && if (!_isFile) {
(permissions & static_cast<int>(OcsShareJob::Permission::Update)) && _ui->checkBox_editing->setEnabled(true);
(permissions & static_cast<int>(OcsShareJob::Permission::Create))) { if (_share->getPublicUpload()) {
_ui->checkBox_editing->setChecked(true); _ui->checkBox_editing->setChecked(true);
}
}
QString url;
// From ownCloud server 8.2 the url field is always set for public shares
if (data.contains("url")) {
url = data.value("url").toString();
} else if (versionString.contains('.') && versionString.split('.')[0].toInt() >= 8) {
// From ownCloud server version 8 on, a different share link scheme is used.
url = Account::concatUrlPath(_account->url(), QString("index.php/s/%1").arg(data.value("token").toString())).toString();
} else { } else {
QList<QPair<QString, QString>> queryArgs; _ui->checkBox_editing->setChecked(false);
queryArgs.append(qMakePair(QString("service"), QString("files"))); }
queryArgs.append(qMakePair(QString("t"), data.value("token").toString()));
url = Account::concatUrlPath(_account->url(), QLatin1String("public.php"), queryArgs).toString();
} }
setShareLink(url);
setShareLink(_share->getLink().toString());
_ui->pushButton_copy->setEnabled(true); _ui->pushButton_copy->setEnabled(true);
// Connect all shares signals to gui slots
connect(_share.data(), SIGNAL(expireDateSet()), SLOT(slotExpireSet()));
connect(_share.data(), SIGNAL(publicUploadSet()), SLOT(slotPublicUploadSet()));
connect(_share.data(), SIGNAL(passwordSet()), SLOT(slotPasswordSet()));
connect(_share.data(), SIGNAL(shareDeleted()), SLOT(slotDeleteShareFetched()));
connect(_share.data(), SIGNAL(serverError(int, QString)), SLOT(displayError(int, QString)));
break;
} }
} }
if( _shares.count()>0 ) { if( !_share.isNull() ) {
setShareCheckBoxTitle(true); setShareCheckBoxTitle(true);
} else { } else {
// If there are no shares yet, check the checkbox to create a link automatically. // If there are no shares yet, check the checkbox to create a link automatically.
@ -416,15 +381,9 @@ void ShareDialog::setShareLink( const QString& url )
} }
void ShareDialog::slotDeleteShareFetched(const QVariantMap &reply) void ShareDialog::slotDeleteShareFetched()
{ {
QString message; _share.clear();
int code = OcsShareJob::getJsonReturnCode(reply, message);
if (code != 100) {
displayError(code);
}
_public_share_id = 0;
_pi_link->stopAnimation(); _pi_link->stopAnimation();
_ui->lineEdit_password->clear(); _ui->lineEdit_password->clear();
_ui->_labelShareLink->clear(); _ui->_labelShareLink->clear();
@ -440,7 +399,6 @@ void ShareDialog::slotDeleteShareFetched(const QVariantMap &reply)
_shareUrl.clear(); _shareUrl.clear();
setShareCheckBoxTitle(false); setShareCheckBoxTitle(false);
} }
void ShareDialog::slotCheckBoxShareLinkClicked() void ShareDialog::slotCheckBoxShareLinkClicked()
@ -458,6 +416,8 @@ void ShareDialog::slotCheckBoxShareLinkClicked()
_ui->checkBox_password->setChecked(true); _ui->checkBox_password->setChecked(true);
_ui->checkBox_password->setEnabled(false); _ui->checkBox_password->setEnabled(false);
_ui->checkBox_password->setText(tr("Public sh&aring requires a password")); _ui->checkBox_password->setText(tr("Public sh&aring requires a password"));
_ui->checkBox_expire->setEnabled(false);
_ui->checkBox_editing->setEnabled(false);
_ui->lineEdit_password->setFocus(); _ui->lineEdit_password->setFocus();
_ui->pushButton_copy->hide(); _ui->pushButton_copy->hide();
_ui->widget_shareLink->show(); _ui->widget_shareLink->show();
@ -466,24 +426,34 @@ void ShareDialog::slotCheckBoxShareLinkClicked()
return; return;
} }
OcsShareJob *job = new OcsShareJob(_account, this); _ui->checkBox_shareLink->setEnabled(false);
connect(job, SIGNAL(jobFinished(QVariantMap)), this, SLOT(slotCreateShareFetched(QVariantMap))); _manager->createLinkShare(_sharePath);
job->createShare(_sharePath, OcsShareJob::ShareType::Link);
} else { } else {
if (!_share.isNull()) {
// We have a share so delete it
_pi_link->startAnimation(); _pi_link->startAnimation();
OcsShareJob *job = new OcsShareJob(_account, this); _share->deleteShare();
connect(job, SIGNAL(jobFinished(QVariantMap)), this, SLOT(slotDeleteShareFetched(QVariantMap))); } else {
job->deleteShare(_public_share_id); // No share object so we are deleting while a password is required
_ui->widget_shareLink->hide();
}
} }
} }
void ShareDialog::slotCreateShareFetched(const QVariantMap &reply) void ShareDialog::slotCreateShareFetched(const QSharedPointer<LinkShare> share)
{ {
QString message;
int code = OcsShareJob::getJsonReturnCode(reply, message);
_pi_link->stopAnimation(); _pi_link->stopAnimation();
_pi_password->stopAnimation();
if (code == 403) { _share = share;
getShares();
}
void ShareDialog::slotCreateShareRequiresPassword()
{
// there needs to be a password // there needs to be a password
_ui->checkBox_password->setChecked(true); _ui->checkBox_password->setChecked(true);
_ui->checkBox_password->setEnabled(false); _ui->checkBox_password->setEnabled(false);
@ -491,17 +461,10 @@ void ShareDialog::slotCreateShareFetched(const QVariantMap &reply)
_ui->lineEdit_password->setFocus(); _ui->lineEdit_password->setFocus();
_ui->pushButton_copy->hide(); _ui->pushButton_copy->hide();
_ui->widget_shareLink->show(); _ui->widget_shareLink->show();
_ui->checkBox_expire->setEnabled(false);
_ui->checkBox_editing->setEnabled(false);
slotCheckBoxPasswordClicked(); slotCheckBoxPasswordClicked();
return;
} else if (code != 100) {
displayError(code);
return;
}
_public_share_id = reply.value("ocs").toMap().values("data")[0].toMap().value("id").toULongLong();
_ui->pushButton_copy->show();
getShares();
} }
void ShareDialog::slotCheckBoxPasswordClicked() void ShareDialog::slotCheckBoxPasswordClicked()
@ -512,7 +475,7 @@ void ShareDialog::slotCheckBoxPasswordClicked()
_ui->lineEdit_password->setPlaceholderText(tr("Please Set Password")); _ui->lineEdit_password->setPlaceholderText(tr("Please Set Password"));
_ui->lineEdit_password->setFocus(); _ui->lineEdit_password->setFocus();
} else { } else {
ShareDialog::setPassword(QString()); setPassword(QString());
_ui->lineEdit_password->setPlaceholderText(QString()); _ui->lineEdit_password->setPlaceholderText(QString());
_pi_password->startAnimation(); _pi_password->startAnimation();
_ui->lineEdit_password->hide(); _ui->lineEdit_password->hide();
@ -532,7 +495,7 @@ void ShareDialog::slotCheckBoxExpireClicked()
} }
else else
{ {
ShareDialog::setExpireDate(QDate()); setExpireDate(QDate());
_ui->calendar->setEnabled(false); _ui->calendar->setEnabled(false);
} }
} }
@ -561,23 +524,13 @@ void ShareDialog::setPublicUpload(bool publicUpload)
_ui->checkBox_editing->setEnabled(false); _ui->checkBox_editing->setEnabled(false);
_pi_editing->startAnimation(); _pi_editing->startAnimation();
OcsShareJob *job = new OcsShareJob(_account, this); _share->setPublicUpload(publicUpload);
connect(job, SIGNAL(jobFinished(QVariantMap)), this, SLOT(slotPublicUploadSet(QVariantMap)));
job->setPublicUpload(_public_share_id, publicUpload);
} }
void ShareDialog::slotPublicUploadSet(const QVariantMap &reply) void ShareDialog::slotPublicUploadSet()
{ {
QString message;
int code = OcsShareJob::getJsonReturnCode(reply, message);
if (code == 100) {
_ui->checkBox_editing->setEnabled(true);
} else {
qDebug() << Q_FUNC_INFO << reply;
displayError(code);
}
_pi_editing->stopAnimation(); _pi_editing->stopAnimation();
_ui->checkBox_editing->setEnabled(true);
} }
void ShareDialog::setShareCheckBoxTitle(bool haveShares) void ShareDialog::setShareCheckBoxTitle(bool haveShares)
@ -593,6 +546,13 @@ void ShareDialog::setShareCheckBoxTitle(bool haveShares)
} }
void ShareDialog::displayError(int code, const QString &message)
{
const QString arg = QString("%1, %2").arg(code).arg(message);
const QString errMsg = tr("OCS API error code: %1").arg(arg);
displayError(errMsg);
}
void ShareDialog::displayError(const QString& errMsg) void ShareDialog::displayError(const QString& errMsg)
{ {
_ui->errorLabel->setText( errMsg ); _ui->errorLabel->setText( errMsg );
@ -605,120 +565,6 @@ void ShareDialog::displayError(int code)
displayError(errMsg); displayError(errMsg);
} }
#if 0
void ShareDialog::displayInfo( const QString& msg )
{
_ui->label_sharePath->setText(msg);
}
/*
* This code is disabled for now as we do not have answers for all the questions involved
* here, see https://github.com/owncloud/client/issues/2732
*/
bool ShareDialog::uploadExternalFile()
{
bool re = false;
const QString folderName = QString("ownCloud"); // FIXME: get a proper folder name
Folder *folder = 0;
Folder::Map folders = FolderMan::instance()->map();
if( folders.isEmpty() ) {
displayInfo(tr("There is no sync folder configured."));
return false;
}
if( folders.contains( Theme::instance()->appNameGUI()) ) {
folder = folders.value(Theme::instance()->appNameGUI());
}
if( !folder ) {
folder = folders.value( folders.keys().at(0));
}
FolderMan::instance()->folder(folderName);
if( ! folder ) {
qDebug() << "Folder not defined: " << folderName;
displayInfo(tr("Cannot find a folder to upload to."));
return false;
}
QFileInfo fi(_localPath);
if( fi.isDir() ) {
// we can not do this for directories yet.
displayInfo(tr("Sharing of external directories is not yet working."));
return false;
}
_sharePath = folder->remotePath()+QLatin1Char('/')+fi.fileName();
_folderAlias = folderName;
// connect the finish signal of the folder before the file to upload
// is copied to the sync folder.
connect( folder, SIGNAL(syncFinished(SyncResult)), this, SLOT(slotNextSyncFinished(SyncResult)) );
// copy the file
_expectedSyncFile = folder->path()+fi.fileName();
QFileInfo target(_expectedSyncFile);
if( target.exists() ) {
_ui->label_sharePath->setText(tr("A sync file with the same name exists. "
"The file cannot be registered to sync."));
// TODO: Add a file comparison here. If the existing file is still the same
// as the file-to-copy we can share it.
_sharePath.clear();
} else {
_uploadFails = 0;
_ui->pi_share->startAnimation();
QFile file( _localPath);
if( file.copy(_expectedSyncFile) ) {
// copying succeeded.
re = true;
displayInfo(tr("Waiting to upload..."));
} else {
displayInfo(tr("Unable to register in sync space."));
}
}
return re;
}
void ShareDialog::slotNextSyncFinished( const SyncResult& result )
{
// FIXME: Check for state!
SyncFileItemVector itemVector = result.syncFileItemVector();
SyncFileItem targetItem;
Folder *folder = FolderMan::instance()->folder(_folderAlias);
const QString folderPath = folder->path();
_ui->pi_share->stopAnimation();
foreach( SyncFileItem item, itemVector ) {
const QString fullSyncedFile = folderPath + item._file;
if( item._direction == SyncFileItem::Up &&
fullSyncedFile == _expectedSyncFile) {
// found the item!
targetItem = item;
continue;
}
}
if( targetItem.isEmpty() ) {
// The item was not in this sync run. Lets wait for the next one. FIXME
_uploadFails ++;
if( _uploadFails > 2 ) {
// stop the upload job
displayInfo(tr("The file cannot be synced."));
}
} else {
// it's there and the sync was successful.
// The server should be able to generate a share link now.
// Enable the sharing link
if( targetItem._status == SyncFileItem::Success ) {
_ui->checkBox_shareLink->setEnabled(true);
_ui->label_sharePath->setText(tr("%1 path: %2").arg(Theme::instance()->appNameGUI()).arg(_sharePath));
} else {
displayInfo(tr("Sync of registered file was not successful yet."));
}
}
_expectedSyncFile.clear();
}
#endif
void ShareDialog::slotThumbnailFetched(const int &statusCode, const QByteArray &reply) void ShareDialog::slotThumbnailFetched(const int &statusCode, const QByteArray &reply)
{ {
if (statusCode != 200) { if (statusCode != 200) {

View file

@ -19,6 +19,8 @@
#include "QProgressIndicator.h" #include "QProgressIndicator.h"
#include <QDialog> #include <QDialog>
#include <QVariantMap> #include <QVariantMap>
#include <QSharedPointer>
#include <QList>
namespace OCC { namespace OCC {
@ -29,6 +31,9 @@ class ShareDialog;
class AbstractCredentials; class AbstractCredentials;
class QuotaInfo; class QuotaInfo;
class SyncResult; class SyncResult;
class LinkShare;
class Share;
class ShareManager;
/** /**
* @brief The ShareDialog class * @brief The ShareDialog class
@ -45,11 +50,12 @@ public:
void getShares(); void getShares();
private slots: private slots:
void slotSharesFetched(const QVariantMap &reply); void slotSharesFetched(const QList<QSharedPointer<Share>> &shares);
void slotCreateShareFetched(const QVariantMap &reply); void slotCreateShareFetched(const QSharedPointer<LinkShare> share);
void slotDeleteShareFetched(const QVariantMap &reply); void slotCreateShareRequiresPassword();
void slotPasswordSet(const QVariantMap &reply); void slotDeleteShareFetched();
void slotExpireSet(const QVariantMap &reply); void slotPasswordSet();
void slotExpireSet();
void slotCalendarClicked(const QDate &date); void slotCalendarClicked(const QDate &date);
void slotCheckBoxShareLinkClicked(); void slotCheckBoxShareLinkClicked();
void slotCheckBoxPasswordClicked(); void slotCheckBoxPasswordClicked();
@ -59,7 +65,9 @@ private slots:
void slotPushButtonCopyLinkPressed(); void slotPushButtonCopyLinkPressed();
void slotThumbnailFetched(const int &statusCode, const QByteArray &reply); void slotThumbnailFetched(const int &statusCode, const QByteArray &reply);
void slotCheckBoxEditingClicked(); void slotCheckBoxEditingClicked();
void slotPublicUploadSet(const QVariantMap &reply); void slotPublicUploadSet();
void displayError(int code, const QString &message);
void done( int r ); void done( int r );
private: private:
@ -83,8 +91,6 @@ private:
#endif #endif
bool _passwordJobRunning; bool _passwordJobRunning;
QList<QVariant> _shares;
qulonglong _public_share_id;
void setPassword(const QString &password); void setPassword(const QString &password);
void setExpireDate(const QDate &date); void setExpireDate(const QDate &date);
@ -93,6 +99,9 @@ private:
QProgressIndicator *_pi_date; QProgressIndicator *_pi_date;
QProgressIndicator *_pi_editing; QProgressIndicator *_pi_editing;
ShareManager *_manager;
QSharedPointer<LinkShare> _share;
bool _resharingAllowed; bool _resharingAllowed;
bool _isFile; bool _isFile;
}; };

View file

@ -13,6 +13,8 @@
#include "capabilities.h" #include "capabilities.h"
#include "configfile.h"
#include <QVariantMap> #include <QVariantMap>
namespace OCC { namespace OCC {
@ -63,9 +65,28 @@ bool Capabilities::shareResharing() const
return _capabilities["files_sharing"].toMap()["resharing"].toBool(); return _capabilities["files_sharing"].toMap()["resharing"].toBool();
} }
QStringList Capabilities::supportedChecksumTypes() const QList<QByteArray> Capabilities::supportedChecksumTypesAdvertised() const
{ {
return QStringList(); return QList<QByteArray>();
}
QList<QByteArray> Capabilities::supportedChecksumTypes() const
{
auto list = supportedChecksumTypesAdvertised();
QByteArray cfgType = ConfigFile().transmissionChecksum().toLatin1();
if (!cfgType.isEmpty()) {
list.prepend(cfgType);
}
return list;
}
QByteArray Capabilities::preferredChecksumType() const
{
auto list = supportedChecksumTypes();
if (list.isEmpty()) {
return QByteArray();
}
return list.first();
} }
} }

View file

@ -39,7 +39,15 @@ public:
bool sharePublicLinkEnforceExpireDate() const; bool sharePublicLinkEnforceExpireDate() const;
int sharePublicLinkExpireDateDays() const; int sharePublicLinkExpireDateDays() const;
bool shareResharing() const; bool shareResharing() const;
QStringList supportedChecksumTypes() const;
/// Returns the checksum types the server explicitly advertises
QList<QByteArray> supportedChecksumTypesAdvertised() const;
/// Like supportedChecksumTypesRaw(), but includes the type from the config
QList<QByteArray> supportedChecksumTypes() const;
/// Returns the checksum type that should be used for new uploads.
QByteArray preferredChecksumType() const;
private: private:
QVariantMap _capabilities; QVariantMap _capabilities;

View file

@ -588,11 +588,7 @@ void ConfigFile::setNewBigFolderSizeLimit(bool isChecked, quint64 mbytes)
bool ConfigFile::monoIcons() const bool ConfigFile::monoIcons() const
{ {
QSettings settings(configFile(), QSettings::IniFormat); QSettings settings(configFile(), QSettings::IniFormat);
bool deflt = false; return settings.value(QLatin1String(monoIconsC), false).toBool();
if( Utility::isMac() ) {
deflt = true;
}
return settings.value(QLatin1String(monoIconsC), deflt).toBool();
} }
void ConfigFile::setMonoIcons(bool useMonoIcons) void ConfigFile::setMonoIcons(bool useMonoIcons)

View file

@ -207,7 +207,7 @@ int get_errno_from_http_errcode( int err, const QString & reason ) {
DiscoverySingleDirectoryJob::DiscoverySingleDirectoryJob(AccountPtr account, const QString &path, QObject *parent) DiscoverySingleDirectoryJob::DiscoverySingleDirectoryJob(const AccountPtr &account, const QString &path, QObject *parent)
: QObject(parent), _subPath(path), _account(account), _ignoredFirst(false) : QObject(parent), _subPath(path), _account(account), _ignoredFirst(false)
{ {
} }
@ -237,7 +237,7 @@ void DiscoverySingleDirectoryJob::abort()
} }
} }
static csync_vio_file_stat_t* propertyMapToFileStat(QMap<QString,QString> map) static csync_vio_file_stat_t* propertyMapToFileStat(const QMap<QString,QString> &map)
{ {
csync_vio_file_stat_t* file_stat = csync_vio_file_stat_new(); csync_vio_file_stat_t* file_stat = csync_vio_file_stat_new();
@ -289,7 +289,7 @@ static csync_vio_file_stat_t* propertyMapToFileStat(QMap<QString,QString> map)
return file_stat; return file_stat;
} }
void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(QString file,QMap<QString,QString> map) void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(QString file, const QMap<QString,QString> &map)
{ {
//qDebug() << Q_FUNC_INFO << _subPath << file << map.count() << map.keys() << _account->davPath() << _lsColJob->reply()->request().url().path(); //qDebug() << Q_FUNC_INFO << _subPath << file << map.count() << map.keys() << _account->davPath() << _lsColJob->reply()->request().url().path();
if (!_ignoredFirst) { if (!_ignoredFirst) {
@ -392,7 +392,7 @@ void DiscoveryMainThread::setupHooks(DiscoveryJob *discoveryJob, const QString &
} }
// Coming from owncloud_opendir -> DiscoveryJob::vio_opendir_hook -> doOpendirSignal // Coming from owncloud_opendir -> DiscoveryJob::vio_opendir_hook -> doOpendirSignal
void DiscoveryMainThread::doOpendirSlot(QString subPath, DiscoveryDirectoryResult *r) void DiscoveryMainThread::doOpendirSlot(const QString &subPath, DiscoveryDirectoryResult *r)
{ {
QString fullPath = _pathPrefix; QString fullPath = _pathPrefix;
if (!_pathPrefix.endsWith('/')) { if (!_pathPrefix.endsWith('/')) {
@ -445,7 +445,7 @@ void DiscoveryMainThread::singleDirectoryJobResultSlot(const QList<FileStatPoint
_discoveryJob->_vioMutex.unlock(); _discoveryJob->_vioMutex.unlock();
} }
void DiscoveryMainThread::singleDirectoryJobFinishedWithErrorSlot(int csyncErrnoCode, QString msg) void DiscoveryMainThread::singleDirectoryJobFinishedWithErrorSlot(int csyncErrnoCode, const QString &msg)
{ {
if (!_currentDiscoveryDirectoryResult) { if (!_currentDiscoveryDirectoryResult) {
return; // possibly aborted return; // possibly aborted
@ -461,7 +461,7 @@ void DiscoveryMainThread::singleDirectoryJobFinishedWithErrorSlot(int csyncErrno
_discoveryJob->_vioMutex.unlock(); _discoveryJob->_vioMutex.unlock();
} }
void DiscoveryMainThread::singleDirectoryJobFirstDirectoryPermissionsSlot(QString p) void DiscoveryMainThread::singleDirectoryJobFirstDirectoryPermissionsSlot(const QString &p)
{ {
// Should be thread safe since the sync thread is blocked // Should be thread safe since the sync thread is blocked
if (!_discoveryJob->_csync_ctx->remote.root_perms) { if (!_discoveryJob->_csync_ctx->remote.root_perms) {
@ -553,29 +553,29 @@ csync_vio_handle_t* DiscoveryJob::remote_vio_opendir_hook (const char *url,
{ {
DiscoveryJob *discoveryJob = static_cast<DiscoveryJob*>(userdata); DiscoveryJob *discoveryJob = static_cast<DiscoveryJob*>(userdata);
if (discoveryJob) { if (discoveryJob) {
qDebug() << Q_FUNC_INFO << discoveryJob << url << "Calling into main thread..."; qDebug() << discoveryJob << url << "Calling into main thread...";
DiscoveryDirectoryResult *directoryResult = new DiscoveryDirectoryResult(); QScopedPointer<DiscoveryDirectoryResult> directoryResult(new DiscoveryDirectoryResult());
directoryResult->code = EIO; directoryResult->code = EIO;
discoveryJob->_vioMutex.lock(); discoveryJob->_vioMutex.lock();
const QString qurl = QString::fromUtf8(url); const QString qurl = QString::fromUtf8(url);
emit discoveryJob->doOpendirSignal(qurl, directoryResult); emit discoveryJob->doOpendirSignal(qurl, directoryResult.data());
discoveryJob->_vioWaitCondition.wait(&discoveryJob->_vioMutex, ULONG_MAX); // FIXME timeout? discoveryJob->_vioWaitCondition.wait(&discoveryJob->_vioMutex, ULONG_MAX); // FIXME timeout?
discoveryJob->_vioMutex.unlock(); discoveryJob->_vioMutex.unlock();
qDebug() << Q_FUNC_INFO << discoveryJob << url << "...Returned from main thread"; qDebug() << discoveryJob << url << "...Returned from main thread";
// Upon awakening from the _vioWaitCondition, iterator should be a valid iterator. // Upon awakening from the _vioWaitCondition, iterator should be a valid iterator.
if (directoryResult->code != 0) { if (directoryResult->code != 0) {
qDebug() << Q_FUNC_INFO << directoryResult->code << "when opening" << url << "msg=" << directoryResult->msg; qDebug() << directoryResult->code << "when opening" << url << "msg=" << directoryResult->msg;
errno = directoryResult->code; errno = directoryResult->code;
// save the error string to the context // save the error string to the context
discoveryJob->_csync_ctx->error_string = qstrdup( directoryResult->msg.toUtf8().constData() ); discoveryJob->_csync_ctx->error_string = qstrdup( directoryResult->msg.toUtf8().constData() );
return NULL; return NULL;
} }
return (csync_vio_handle_t*) directoryResult; return directoryResult.take();
} }
return NULL; return NULL;
} }

View file

@ -80,7 +80,7 @@ struct DiscoveryDirectoryResult {
class DiscoverySingleDirectoryJob : public QObject { class DiscoverySingleDirectoryJob : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit DiscoverySingleDirectoryJob(AccountPtr account, const QString &path, QObject *parent = 0); explicit DiscoverySingleDirectoryJob(const AccountPtr &account, const QString &path, QObject *parent = 0);
void start(); void start();
void abort(); void abort();
// This is not actually a network job, it is just a job // This is not actually a network job, it is just a job
@ -89,9 +89,9 @@ signals:
void etagConcatenation(const QString &); void etagConcatenation(const QString &);
void etag(const QString &); void etag(const QString &);
void finishedWithResult(const QList<FileStatPointer> &); void finishedWithResult(const QList<FileStatPointer> &);
void finishedWithError(int csyncErrnoCode, QString msg); void finishedWithError(int csyncErrnoCode, const QString &msg);
private slots: private slots:
void directoryListingIteratedSlot(QString,QMap<QString,QString>); void directoryListingIteratedSlot(QString, const QMap<QString,QString>&);
void lsJobFinishedWithoutErrorSlot(); void lsJobFinishedWithoutErrorSlot();
void lsJobFinishedWithErrorSlot(QNetworkReply*); void lsJobFinishedWithErrorSlot(QNetworkReply*);
private: private:
@ -125,13 +125,13 @@ public:
public slots: public slots:
// From DiscoveryJob: // From DiscoveryJob:
void doOpendirSlot(QString url, DiscoveryDirectoryResult* ); void doOpendirSlot(const QString &url, DiscoveryDirectoryResult* );
void doGetSizeSlot(const QString &path ,qint64 *result); void doGetSizeSlot(const QString &path ,qint64 *result);
// From Job: // From Job:
void singleDirectoryJobResultSlot(const QList<FileStatPointer> &); void singleDirectoryJobResultSlot(const QList<FileStatPointer> &);
void singleDirectoryJobFinishedWithErrorSlot(int csyncErrnoCode, QString msg); void singleDirectoryJobFinishedWithErrorSlot(int csyncErrnoCode, const QString &msg);
void singleDirectoryJobFirstDirectoryPermissionsSlot(QString); void singleDirectoryJobFirstDirectoryPermissionsSlot(const QString&);
void slotGetSizeFinishedWithError(); void slotGetSizeFinishedWithError();
void slotGetSizeResult(const QVariantMap&); void slotGetSizeResult(const QVariantMap&);

View file

@ -87,11 +87,11 @@ int OwncloudPropagator::maximumActiveJob()
return max; return max;
} }
/** Updates or creates a blacklist entry for the given item. /** Updates, creates or removes a blacklist entry for the given item.
* *
* Returns whether the file is in the blacklist now. * Returns whether the file is in the blacklist now.
*/ */
static bool blacklist(SyncJournalDb* journal, const SyncFileItem& item) static bool blacklistCheck(SyncJournalDb* journal, const SyncFileItem& item)
{ {
SyncJournalErrorBlacklistRecord oldEntry = journal->errorBlacklistEntry(item._file); SyncJournalErrorBlacklistRecord oldEntry = journal->errorBlacklistEntry(item._file);
SyncJournalErrorBlacklistRecord newEntry = SyncJournalErrorBlacklistRecord::update(oldEntry, item); SyncJournalErrorBlacklistRecord newEntry = SyncJournalErrorBlacklistRecord::update(oldEntry, item);
@ -132,7 +132,7 @@ void PropagateItemJob::done(SyncFileItem::Status status, const QString &errorStr
// do not blacklist in case of soft error or fatal error. // do not blacklist in case of soft error or fatal error.
break; break;
case SyncFileItem::NormalError: case SyncFileItem::NormalError:
if (blacklist(_propagator->_journal, *_item) && _item->_hasBlacklistEntry) { if (blacklistCheck(_propagator->_journal, *_item) && _item->_hasBlacklistEntry) {
// do not error if the item was, and continues to be, blacklisted // do not error if the item was, and continues to be, blacklisted
status = SyncFileItem::FileIgnored; status = SyncFileItem::FileIgnored;
_item->_errorString.prepend(tr("Continue blacklisting:") + " "); _item->_errorString.prepend(tr("Continue blacklisting:") + " ");
@ -277,14 +277,14 @@ void OwncloudPropagator::start(const SyncFileItemVector& items)
/* Check and log the transmission checksum type */ /* Check and log the transmission checksum type */
ConfigFile cfg; ConfigFile cfg;
const QString checksumType = cfg.transmissionChecksum().toUpper(); const QString checksumType = cfg.transmissionChecksum();
/* if the checksum type is empty, it is not sent. No error */ /* if the checksum type is empty, it is not sent. No error */
if( !checksumType.isEmpty() ) { if( !checksumType.isEmpty() ) {
if( checksumType == checkSumAdlerUpperC || if( checksumType == checkSumAdlerC ||
checksumType == checkSumMD5C || checksumType == checkSumMD5C ||
checksumType == checkSumSHA1C ) { checksumType == checkSumSHA1C ) {
qDebug() << "Client sends and expects transmission checksum type" << checksumType; qDebug() << "Client sends transmission checksum type" << checksumType;
} else { } else {
qWarning() << "Unknown transmission checksum type from config" << checksumType; qWarning() << "Unknown transmission checksum type from config" << checksumType;
} }

View file

@ -138,6 +138,7 @@ void SqlDatabase::close()
SQLITE_DO(sqlite3_close(_db) ); SQLITE_DO(sqlite3_close(_db) );
if (_errId != SQLITE_OK) { if (_errId != SQLITE_OK) {
qWarning() << "ERROR When closing DB" << _error; qWarning() << "ERROR When closing DB" << _error;
Q_ASSERT(!"SQLite Close Error");
} }
_db = 0; _db = 0;
} }
@ -211,6 +212,7 @@ int SqlQuery::prepare( const QString& sql)
if( _errId != SQLITE_OK ) { if( _errId != SQLITE_OK ) {
_error = QString::fromUtf8(sqlite3_errmsg(_db)); _error = QString::fromUtf8(sqlite3_errmsg(_db));
qWarning() << "Sqlite prepare statement error:" << _error << "in" <<_sql; qWarning() << "Sqlite prepare statement error:" << _error << "in" <<_sql;
Q_ASSERT(!"SQLITE Prepare error");
} }
} }
return _errId; return _errId;
@ -316,6 +318,11 @@ void SqlQuery::bindValue(int pos, const QVariant& value)
Q_ASSERT( res == SQLITE_OK ); Q_ASSERT( res == SQLITE_OK );
} }
bool SqlQuery::nullValue(int index)
{
return sqlite3_column_type(_stmt, index) == SQLITE_NULL;
}
QString SqlQuery::stringValue(int index) QString SqlQuery::stringValue(int index)
{ {
return QString::fromUtf16(static_cast<const ushort*>(sqlite3_column_text16(_stmt, index))); return QString::fromUtf16(static_cast<const ushort*>(sqlite3_column_text16(_stmt, index)));

View file

@ -66,6 +66,9 @@ public:
~SqlQuery(); ~SqlQuery();
QString error() const; QString error() const;
/// Checks whether the value at the given column index is NULL
bool nullValue(int index);
QString stringValue(int index); QString stringValue(int index);
int intValue(int index); int intValue(int index);
quint64 int64Value(int index); quint64 int64Value(int index);

View file

@ -351,7 +351,10 @@ void PropagateDownloadFileQNAM::start()
if (_resumeStart == _item->_size) { if (_resumeStart == _item->_size) {
qDebug() << "File is already complete, no need to download"; qDebug() << "File is already complete, no need to download";
_tmpFile.close(); _tmpFile.close();
downloadFinished();
// Unfortunately we lost the checksum header, if any...
QByteArray noChecksumData;
downloadFinished(noChecksumData, noChecksumData);
return; return;
} }
} }
@ -359,6 +362,7 @@ void PropagateDownloadFileQNAM::start()
// If there's not enough space to fully download this file, stop. // If there's not enough space to fully download this file, stop.
const auto diskSpaceResult = _propagator->diskSpaceCheck(); const auto diskSpaceResult = _propagator->diskSpaceCheck();
if (diskSpaceResult == OwncloudPropagator::DiskSpaceFailure) { if (diskSpaceResult == OwncloudPropagator::DiskSpaceFailure) {
_item->_errorMayBeBlacklisted = true;
done(SyncFileItem::NormalError, done(SyncFileItem::NormalError,
tr("The download would reduce free disk space below %1").arg( tr("The download would reduce free disk space below %1").arg(
Utility::octetsToString(freeSpaceLimit()))); Utility::octetsToString(freeSpaceLimit())));
@ -531,11 +535,16 @@ void PropagateDownloadFileQNAM::slotGetFinished()
// Do checksum validation for the download. If there is no checksum header, the validator // Do checksum validation for the download. If there is no checksum header, the validator
// will also emit the validated() signal to continue the flow in slot downloadFinished() // will also emit the validated() signal to continue the flow in slot downloadFinished()
// as this is (still) also correct. // as this is (still) also correct.
TransmissionChecksumValidator *validator = new TransmissionChecksumValidator(_tmpFile.fileName(), this); ValidateChecksumHeader *validator = new ValidateChecksumHeader(this);
connect(validator, SIGNAL(validated(QByteArray)), this, SLOT(downloadFinished())); connect(validator, SIGNAL(validated(QByteArray,QByteArray)),
connect(validator, SIGNAL(validationFailed(QString)), this, SLOT(slotChecksumFail(QString))); SLOT(downloadFinished(QByteArray,QByteArray)));
validator->downloadValidation(job->reply()->rawHeader(checkSumHeaderC)); connect(validator, SIGNAL(validationFailed(QString)),
SLOT(slotChecksumFail(QString)));
auto checksumHeader = job->reply()->rawHeader(checkSumHeaderC);
if (!downloadChecksumEnabled()) {
checksumHeader.clear();
}
validator->start(_tmpFile.fileName(), checksumHeader);
} }
void PropagateDownloadFileQNAM::slotChecksumFail( const QString& errMsg ) void PropagateDownloadFileQNAM::slotChecksumFail( const QString& errMsg )
@ -612,8 +621,13 @@ static void handleRecallFile(const QString &fn)
} }
} // end namespace } // end namespace
void PropagateDownloadFileQNAM::downloadFinished() void PropagateDownloadFileQNAM::downloadFinished(const QByteArray& checksumType, const QByteArray& checksum)
{ {
if (!checksumType.isEmpty()) {
_item->_transmissionChecksum = checksum;
_item->_transmissionChecksumType = checksumType;
}
QString fn = _propagator->getFilePath(_item->_file); QString fn = _propagator->getFilePath(_item->_file);
// In case of file name clash, report an error // In case of file name clash, report an error

View file

@ -117,7 +117,7 @@ public:
private slots: private slots:
void slotGetFinished(); void slotGetFinished();
void abort() Q_DECL_OVERRIDE; void abort() Q_DECL_OVERRIDE;
void downloadFinished(); void downloadFinished(const QByteArray& checksumType, const QByteArray& checksum);
void slotDownloadProgress(qint64,qint64); void slotDownloadProgress(qint64,qint64);
void slotChecksumFail( const QString& errMsg ); void slotChecksumFail( const QString& errMsg );

View file

@ -197,6 +197,16 @@ void PropagateUploadFileQNAM::start()
return; return;
} }
if (_propagator->account()->serverVersionInt() < 0x080100) {
// Server version older than 8.1 don't support these character in filename.
static const QRegExp invalidCharRx("[\\\\:?*\"<>|]");
if (_item->_file.contains(invalidCharRx)) {
_item->_httpErrorCode = 400; // So the entry get blacklisted
done(SyncFileItem::NormalError, tr("File name contains at least one invalid character"));
return;
}
}
const QString filePath = _propagator->getFilePath(_item->_file); const QString filePath = _propagator->getFilePath(_item->_file);
// remember the modtime before checksumming to be able to detect a file // remember the modtime before checksumming to be able to detect a file
@ -205,30 +215,48 @@ void PropagateUploadFileQNAM::start()
_stopWatch.start(); _stopWatch.start();
// do whatever is needed to add a checksum to the http upload request. auto supportedChecksumTypes = _propagator->account()->capabilities().supportedChecksumTypes();
// in any case, the validator will emit signal startUpload to let the flow
// continue in slotStartUpload here.
TransmissionChecksumValidator *validator = new TransmissionChecksumValidator(filePath, this);
// If the config file does not specify a checksum type but the // If we already have a checksum header and the checksum type is supported
// server supports it choose a type based on that. // by the server, we keep that - otherwise recompute.
if (validator->checksumType().isEmpty()) { //
QStringList checksumTypes = _propagator->account()->capabilities().supportedChecksumTypes(); // Note: Currently we *always* recompute because we usually only upload
if (!checksumTypes.isEmpty()) { // files that have changed and thus have a new checksum. But if an earlier
// TODO: We might want to prefer some types over others instead // phase computed a checksum, this is where we would make use of it.
// of choosing the first. if (!_item->_transmissionChecksumType.isEmpty()) {
validator->setChecksumType(checksumTypes.first()); if (supportedChecksumTypes.contains(_item->_transmissionChecksumType)) {
// TODO: We could validate the old checksum and thereby determine whether
// an upload is necessary or not.
slotStartUpload(_item->_transmissionChecksumType, _item->_transmissionChecksum);
return;
} }
} }
connect(validator, SIGNAL(validated(QByteArray)), this, SLOT(slotStartUpload(QByteArray))); // Compute a new checksum.
validator->uploadValidation(); auto computeChecksum = new ComputeChecksum(this);
if (uploadChecksumEnabled()) {
computeChecksum->setChecksumType(_propagator->account()->capabilities().preferredChecksumType());
} else {
computeChecksum->setChecksumType(QByteArray());
}
connect(computeChecksum, SIGNAL(done(QByteArray,QByteArray)),
SLOT(slotStartUpload(QByteArray,QByteArray)));
computeChecksum->start(filePath);
} }
void PropagateUploadFileQNAM::slotStartUpload(const QByteArray& checksum) void PropagateUploadFileQNAM::slotStartUpload(const QByteArray& checksumType, const QByteArray& checksum)
{ {
// Store the computed checksum in the database, if different
if (checksumType != _item->_transmissionChecksumType
|| checksum != _item->_transmissionChecksum) {
_item->_transmissionChecksum = checksum;
_item->_transmissionChecksumType = checksumType;
_propagator->_journal->updateFileRecordChecksum(
_item->_file, checksum, checksumType);
}
const QString fullFilePath = _propagator->getFilePath(_item->_file); const QString fullFilePath = _propagator->getFilePath(_item->_file);
_item->_checksum = checksum;
if (!FileSystem::fileExists(fullFilePath)) { if (!FileSystem::fileExists(fullFilePath)) {
done(SyncFileItem::SoftError, tr("File Removed")); done(SyncFileItem::SoftError, tr("File Removed"));
@ -458,6 +486,7 @@ void PropagateUploadFileQNAM::startNextChunk()
UploadDevice *device = new UploadDevice(&_propagator->_bandwidthManager); UploadDevice *device = new UploadDevice(&_propagator->_bandwidthManager);
qint64 chunkStart = 0; qint64 chunkStart = 0;
qint64 currentChunkSize = fileSize; qint64 currentChunkSize = fileSize;
bool isFinalChunk = false;
if (_chunkCount > 1) { if (_chunkCount > 1) {
int sendingChunk = (_currentChunk + _startChunk) % _chunkCount; int sendingChunk = (_currentChunk + _startChunk) % _chunkCount;
// XOR with chunk size to make sure everything goes well if chunk size changes between runs // XOR with chunk size to make sure everything goes well if chunk size changes between runs
@ -474,15 +503,16 @@ void PropagateUploadFileQNAM::startNextChunk()
if( currentChunkSize == 0 ) { // if the last chunk pretends to be 0, its actually the full chunk size. if( currentChunkSize == 0 ) { // if the last chunk pretends to be 0, its actually the full chunk size.
currentChunkSize = chunkSize(); currentChunkSize = chunkSize();
} }
if( !_item->_checksum.isEmpty() ) { isFinalChunk = true;
headers[checkSumHeaderC] = _item->_checksum;
}
} }
} else { } else {
// checksum if its only one chunk // if there's only one chunk, it's the final one
if( !_item->_checksum.isEmpty() ) { isFinalChunk = true;
headers[checkSumHeaderC] = _item->_checksum;
} }
if (isFinalChunk && !_item->_transmissionChecksumType.isEmpty()) {
headers[checkSumHeaderC] = makeChecksumHeader(
_item->_transmissionChecksumType, _item->_transmissionChecksum);
} }
if (! device->prepareAndOpen(_propagator->getFilePath(_item->_file), chunkStart, currentChunkSize)) { if (! device->prepareAndOpen(_propagator->getFilePath(_item->_file), chunkStart, currentChunkSize)) {

View file

@ -195,7 +195,7 @@ private slots:
void startNextChunk(); void startNextChunk();
void finalize(const SyncFileItem&); void finalize(const SyncFileItem&);
void slotJobDestroyed(QObject *job); void slotJobDestroyed(QObject *job);
void slotStartUpload(const QByteArray &checksum); void slotStartUpload(const QByteArray& checksumType, const QByteArray& checksum);
private: private:
void startPollJob(const QString& path); void startPollJob(const QString& path);

View file

@ -32,7 +32,6 @@ static const char checkSumHeaderC[] = "OC-Checksum";
static const char checkSumMD5C[] = "MD5"; static const char checkSumMD5C[] = "MD5";
static const char checkSumSHA1C[] = "SHA1"; static const char checkSumSHA1C[] = "SHA1";
static const char checkSumAdlerC[] = "Adler32"; static const char checkSumAdlerC[] = "Adler32";
static const char checkSumAdlerUpperC[] = "ADLER32";
/** /**
* @brief Declaration of the other propagation jobs * @brief Declaration of the other propagation jobs

View file

@ -468,7 +468,7 @@ int SyncEngine::treewalkFile( TREE_WALK_FILE *file, bool remote )
// the file system in the DB, this is to avoid spurious upload on the next sync // the file system in the DB, this is to avoid spurious upload on the next sync
item->_modtime = file->other.modtime; item->_modtime = file->other.modtime;
_journal->setFileRecord(SyncJournalFileRecord(*item, _localPath + item->_file)); _journal->updateFileRecordMetadata(SyncJournalFileRecord(*item, _localPath + item->_file));
item->_should_update_metadata = false; item->_should_update_metadata = false;
} }
if (item->_isDirectory && file->should_update_metadata) { if (item->_isDirectory && file->should_update_metadata) {
@ -572,7 +572,7 @@ void SyncEngine::handleSyncError(CSYNC *ctx, const char *state) {
} else { } else {
emit csyncError(errStr); emit csyncError(errStr);
} }
finalize(); finalize(false);
} }
void SyncEngine::startSync() void SyncEngine::startSync()
@ -598,7 +598,7 @@ void SyncEngine::startSync()
if (!QDir(_localPath).exists()) { if (!QDir(_localPath).exists()) {
// No _tr, it should only occur in non-mirall // No _tr, it should only occur in non-mirall
emit csyncError("Unable to find local sync folder."); emit csyncError("Unable to find local sync folder.");
finalize(); finalize(false);
return; return;
} }
@ -612,7 +612,7 @@ void SyncEngine::startSync()
emit csyncError(tr("Only %1 are available, need at least %2 to start").arg( emit csyncError(tr("Only %1 are available, need at least %2 to start").arg(
Utility::octetsToString(freeBytes), Utility::octetsToString(freeBytes),
Utility::octetsToString(minFree))); Utility::octetsToString(minFree)));
finalize(); finalize(false);
return; return;
} }
} else { } else {
@ -643,7 +643,7 @@ void SyncEngine::startSync()
if( fileRecordCount == -1 ) { if( fileRecordCount == -1 ) {
qDebug() << "No way to create a sync journal!"; qDebug() << "No way to create a sync journal!";
emit csyncError(tr("Unable to initialize a sync journal.")); emit csyncError(tr("Unable to initialize a sync journal."));
finalize(); finalize(false);
return; return;
// database creation error! // database creation error!
} }
@ -666,7 +666,7 @@ void SyncEngine::startSync()
_discoveryMainThread = new DiscoveryMainThread(account()); _discoveryMainThread = new DiscoveryMainThread(account());
_discoveryMainThread->setParent(this); _discoveryMainThread->setParent(this);
connect(this, SIGNAL(finished()), _discoveryMainThread, SLOT(deleteLater())); connect(this, SIGNAL(finished(bool)), _discoveryMainThread, SLOT(deleteLater()));
qDebug() << "=====Server" << account()->serverVersion() qDebug() << "=====Server" << account()->serverVersion()
<< QString("rootEtagChangesNotOnlySubFolderEtags=%1").arg(account()->rootEtagChangesNotOnlySubFolderEtags()); << QString("rootEtagChangesNotOnlySubFolderEtags=%1").arg(account()->rootEtagChangesNotOnlySubFolderEtags());
if (account()->rootEtagChangesNotOnlySubFolderEtags()) { if (account()->rootEtagChangesNotOnlySubFolderEtags()) {
@ -698,7 +698,7 @@ void SyncEngine::startSync()
QMetaObject::invokeMethod(discoveryJob, "start", Qt::QueuedConnection); QMetaObject::invokeMethod(discoveryJob, "start", Qt::QueuedConnection);
} }
void SyncEngine::slotRootEtagReceived(QString e) { void SyncEngine::slotRootEtagReceived(const QString &e) {
if (_remoteRootEtag.isEmpty()) { if (_remoteRootEtag.isEmpty()) {
qDebug() << Q_FUNC_INFO << e; qDebug() << Q_FUNC_INFO << e;
_remoteRootEtag = e; _remoteRootEtag = e;
@ -721,7 +721,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
if (!_journal->isConnected()) { if (!_journal->isConnected()) {
qDebug() << "Bailing out, DB failure"; qDebug() << "Bailing out, DB failure";
emit csyncError(tr("Cannot open the sync journal")); emit csyncError(tr("Cannot open the sync journal"));
finalize(); finalize(false);
return; return;
} else { } else {
// Commits a possibly existing (should not though) transaction and starts a new one for the propagate phase // Commits a possibly existing (should not though) transaction and starts a new one for the propagate phase
@ -785,7 +785,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
emit aboutToRemoveAllFiles(_syncedItems.first()->_direction, &cancel); emit aboutToRemoveAllFiles(_syncedItems.first()->_direction, &cancel);
if (cancel) { if (cancel) {
qDebug() << Q_FUNC_INFO << "Abort sync"; qDebug() << Q_FUNC_INFO << "Abort sync";
finalize(); finalize(false);
return; return;
} }
} }
@ -833,7 +833,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
void SyncEngine::slotCleanPollsJobAborted(const QString &error) void SyncEngine::slotCleanPollsJobAborted(const QString &error)
{ {
csyncError(error); csyncError(error);
finalize(); finalize(false);
} }
void SyncEngine::setNetworkLimits(int upload, int download) void SyncEngine::setNetworkLimits(int upload, int download)
@ -888,10 +888,10 @@ void SyncEngine::slotFinished()
_journal->commit("All Finished.", false); _journal->commit("All Finished.", false);
emit treeWalkResult(_syncedItems); emit treeWalkResult(_syncedItems);
finalize(); finalize(true); // FIXME: should it be true if there was errors?
} }
void SyncEngine::finalize() void SyncEngine::finalize(bool success)
{ {
_thread.quit(); _thread.quit();
_thread.wait(); _thread.wait();
@ -902,7 +902,7 @@ void SyncEngine::finalize()
_stopWatch.stop(); _stopWatch.stop();
_syncRunning = false; _syncRunning = false;
emit finished(); emit finished(success);
// Delete the propagator only after emitting the signal. // Delete the propagator only after emitting the signal.
_propagator.clear(); _propagator.clear();

View file

@ -94,7 +94,7 @@ signals:
// During update, before reconcile // During update, before reconcile
void rootEtag(QString); void rootEtag(QString);
void folderDiscovered(bool local, QString folderUrl); void folderDiscovered(bool local, const QString &folderUrl);
// before actual syncing (after update+reconcile) for each item // before actual syncing (after update+reconcile) for each item
void syncItemDiscovered(const SyncFileItem&); void syncItemDiscovered(const SyncFileItem&);
@ -109,7 +109,7 @@ signals:
void transmissionProgress( const ProgressInfo& progress ); void transmissionProgress( const ProgressInfo& progress );
void finished(); void finished(bool success);
void started(); void started();
void aboutToRemoveAllFiles(SyncFileItem::Direction direction, bool *cancel); void aboutToRemoveAllFiles(SyncFileItem::Direction direction, bool *cancel);
@ -118,7 +118,7 @@ signals:
void newBigFolder(const QString &folder); void newBigFolder(const QString &folder);
private slots: private slots:
void slotRootEtagReceived(QString); void slotRootEtagReceived(const QString &);
void slotItemCompleted(const SyncFileItem& item, const PropagatorJob & job); void slotItemCompleted(const SyncFileItem& item, const PropagatorJob & job);
void slotFinished(); void slotFinished();
void slotProgress(const SyncFileItem& item, quint64 curent); void slotProgress(const SyncFileItem& item, quint64 curent);
@ -144,7 +144,7 @@ private:
void deleteStaleErrorBlacklistEntries(); void deleteStaleErrorBlacklistEntries();
// cleanup and emit the finished signal // cleanup and emit the finished signal
void finalize(); void finalize(bool success);
static bool _syncRunning; //true when one sync is running somewhere (for debugging) static bool _syncRunning; //true when one sync is running somewhere (for debugging)

View file

@ -64,7 +64,8 @@ public:
}; };
SyncFileItem() : _type(UnknownType), _direction(None), _isDirectory(false), SyncFileItem() : _type(UnknownType), _direction(None), _isDirectory(false),
_serverHasIgnoredFiles(false), _hasBlacklistEntry(false), _status(NoStatus), _serverHasIgnoredFiles(false), _hasBlacklistEntry(false),
_errorMayBeBlacklisted(false), _status(NoStatus),
_isRestoration(false), _should_update_metadata(false), _isRestoration(false), _should_update_metadata(false),
_httpErrorCode(0), _requestDuration(0), _affectedItems(1), _httpErrorCode(0), _requestDuration(0), _affectedItems(1),
_instruction(CSYNC_INSTRUCTION_NONE), _modtime(0), _size(0), _inode(0) _instruction(CSYNC_INSTRUCTION_NONE), _modtime(0), _size(0), _inode(0)
@ -137,6 +138,13 @@ public:
/// without the status being FileIgnored. /// without the status being FileIgnored.
bool _hasBlacklistEntry BITFIELD(1); bool _hasBlacklistEntry BITFIELD(1);
/** If true and NormalError, this error may be blacklisted
*
* Note that non-local errors (httpErrorCode!=0) may also be
* blacklisted independently of this flag.
*/
bool _errorMayBeBlacklisted BITFIELD(1);
// Variables useful to report to the user // Variables useful to report to the user
Status _status BITFIELD(4); Status _status BITFIELD(4);
bool _isRestoration BITFIELD(1); // The original operation was forbidden, and this is a restoration bool _isRestoration BITFIELD(1); // The original operation was forbidden, and this is a restoration
@ -157,7 +165,8 @@ public:
quint64 _inode; quint64 _inode;
QByteArray _fileId; QByteArray _fileId;
QByteArray _remotePerm; QByteArray _remotePerm;
QByteArray _checksum; QByteArray _transmissionChecksum;
QByteArray _transmissionChecksumType;
QString _directDownloadUrl; QString _directDownloadUrl;
QString _directDownloadCookies; QString _directDownloadCookies;

View file

@ -206,6 +206,8 @@ bool SyncJournalDb::checkConnect()
"md5 VARCHAR(32)," /* This is the etag. Called md5 for compatibility */ "md5 VARCHAR(32)," /* This is the etag. Called md5 for compatibility */
// updateDatabaseStructure() will add a fileid column // updateDatabaseStructure() will add a fileid column
// updateDatabaseStructure() will add a remotePerm column // updateDatabaseStructure() will add a remotePerm column
// updateDatabaseStructure() will add a transmissionChecksum column
// updateDatabaseStructure() will add a transmissionChecksumTypeId column
"PRIMARY KEY(phash)" "PRIMARY KEY(phash)"
");"); ");");
@ -271,6 +273,14 @@ bool SyncJournalDb::checkConnect()
return sqlFail("Create table selectivesync", createQuery); return sqlFail("Create table selectivesync", createQuery);
} }
// create the checksumtype table.
createQuery.prepare("CREATE TABLE IF NOT EXISTS checksumtype("
"id INTEGER PRIMARY KEY,"
"name TEXT UNIQUE"
");");
if (!createQuery.exec()) {
return sqlFail("Create table version", createQuery);
}
createQuery.prepare("CREATE TABLE IF NOT EXISTS version(" createQuery.prepare("CREATE TABLE IF NOT EXISTS version("
@ -346,13 +356,30 @@ bool SyncJournalDb::checkConnect()
} }
_getFileRecordQuery.reset(new SqlQuery(_db)); _getFileRecordQuery.reset(new SqlQuery(_db));
_getFileRecordQuery->prepare("SELECT path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote FROM " _getFileRecordQuery->prepare(
"metadata WHERE phash=?1" ); "SELECT path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize,"
" ignoredChildrenRemote, transmissionChecksum, checksumtype.name"
" FROM metadata"
" LEFT JOIN checksumtype ON metadata.transmissionChecksumTypeId == checksumtype.id"
" WHERE phash=?1" );
_setFileRecordQuery.reset(new SqlQuery(_db) ); _setFileRecordQuery.reset(new SqlQuery(_db) );
_setFileRecordQuery->prepare("INSERT OR REPLACE INTO metadata " _setFileRecordQuery->prepare("INSERT OR REPLACE INTO metadata "
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote) " "(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, transmissionChecksum, transmissionChecksumTypeId) "
"VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14);" ); "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16);" );
_setFileRecordChecksumQuery.reset(new SqlQuery(_db) );
_setFileRecordChecksumQuery->prepare(
"UPDATE metadata"
" SET transmissionChecksum = ?2, transmissionChecksumTypeId = ?3"
" WHERE phash == ?1;");
_setFileRecordMetadataQuery.reset(new SqlQuery(_db) );
_setFileRecordMetadataQuery->prepare(
"UPDATE metadata"
" SET inode=?2, mode=?3, modtime=?4, type=?5, md5=?6, fileid=?7,"
" remotePerm=?8, filesize=?9, ignoredChildrenRemote=?10"
" WHERE phash == ?1;");
_getDownloadInfoQuery.reset(new SqlQuery(_db) ); _getDownloadInfoQuery.reset(new SqlQuery(_db) );
_getDownloadInfoQuery->prepare( "SELECT tmpfile, etag, errorcount FROM " _getDownloadInfoQuery->prepare( "SELECT tmpfile, etag, errorcount FROM "
@ -403,6 +430,12 @@ bool SyncJournalDb::checkConnect()
_getSelectiveSyncListQuery.reset(new SqlQuery(_db)); _getSelectiveSyncListQuery.reset(new SqlQuery(_db));
_getSelectiveSyncListQuery->prepare("SELECT path FROM selectivesync WHERE type=?1"); _getSelectiveSyncListQuery->prepare("SELECT path FROM selectivesync WHERE type=?1");
_getChecksumTypeIdQuery.reset(new SqlQuery(_db));
_getChecksumTypeIdQuery->prepare("SELECT id FROM checksumtype WHERE name=?1");
_insertChecksumTypeQuery.reset(new SqlQuery(_db));
_insertChecksumTypeQuery->prepare("INSERT OR IGNORE INTO checksumtype (name) VALUES (?1)");
// don't start a new transaction now // don't start a new transaction now
commitInternal(QString("checkConnect End"), false); commitInternal(QString("checkConnect End"), false);
@ -424,6 +457,8 @@ void SyncJournalDb::close()
_getFileRecordQuery.reset(0); _getFileRecordQuery.reset(0);
_setFileRecordQuery.reset(0); _setFileRecordQuery.reset(0);
_setFileRecordChecksumQuery.reset(0);
_setFileRecordMetadataQuery.reset(0);
_getDownloadInfoQuery.reset(0); _getDownloadInfoQuery.reset(0);
_setDownloadInfoQuery.reset(0); _setDownloadInfoQuery.reset(0);
_deleteDownloadInfoQuery.reset(0); _deleteDownloadInfoQuery.reset(0);
@ -435,6 +470,8 @@ void SyncJournalDb::close()
_getErrorBlacklistQuery.reset(0); _getErrorBlacklistQuery.reset(0);
_setErrorBlacklistQuery.reset(0); _setErrorBlacklistQuery.reset(0);
_getSelectiveSyncListQuery.reset(0); _getSelectiveSyncListQuery.reset(0);
_getChecksumTypeIdQuery.reset(0);
_insertChecksumTypeQuery.reset(0);
_db.close(); _db.close();
_avoidReadFromDbOnNextSyncFilter.clear(); _avoidReadFromDbOnNextSyncFilter.clear();
@ -527,6 +564,27 @@ bool SyncJournalDb::updateMetadataTableStructure()
} }
commitInternal("update database structure: add ignoredChildrenRemote col"); commitInternal("update database structure: add ignoredChildrenRemote col");
} }
if( columns.indexOf(QLatin1String("transmissionChecksum")) == -1 ) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN transmissionChecksum TEXT;");
if( !query.exec()) {
sqlFail("updateMetadataTableStructure: add transmissionChecksum column", query);
re = false;
}
commitInternal("update database structure: add transmissionChecksum col");
}
if( columns.indexOf(QLatin1String("transmissionChecksumTypeId")) == -1 ) {
SqlQuery query(_db);
query.prepare("ALTER TABLE metadata ADD COLUMN transmissionChecksumTypeId INTEGER;");
if( !query.exec()) {
sqlFail("updateMetadataTableStructure: add transmissionChecksumTypeId column", query);
re = false;
}
commitInternal("update database structure: add transmissionChecksumTypeId col");
}
return re; return re;
} }
@ -627,6 +685,7 @@ bool SyncJournalDb::setFileRecord( const SyncJournalFileRecord& _record )
if( fileId.isEmpty() ) fileId = ""; if( fileId.isEmpty() ) fileId = "";
QString remotePerm (record._remotePerm); QString remotePerm (record._remotePerm);
if (remotePerm.isEmpty()) remotePerm = QString(); // have NULL in DB (vs empty) if (remotePerm.isEmpty()) remotePerm = QString(); // have NULL in DB (vs empty)
int checksumTypeId = mapChecksumType(record._transmissionChecksumType);
_setFileRecordQuery->reset(); _setFileRecordQuery->reset();
_setFileRecordQuery->bindValue(1, QString::number(phash)); _setFileRecordQuery->bindValue(1, QString::number(phash));
_setFileRecordQuery->bindValue(2, plen); _setFileRecordQuery->bindValue(2, plen);
@ -642,6 +701,8 @@ bool SyncJournalDb::setFileRecord( const SyncJournalFileRecord& _record )
_setFileRecordQuery->bindValue(12, remotePerm ); _setFileRecordQuery->bindValue(12, remotePerm );
_setFileRecordQuery->bindValue(13, record._fileSize ); _setFileRecordQuery->bindValue(13, record._fileSize );
_setFileRecordQuery->bindValue(14, record._serverHasIgnoredFiles ? 1:0); _setFileRecordQuery->bindValue(14, record._serverHasIgnoredFiles ? 1:0);
_setFileRecordQuery->bindValue(15, record._transmissionChecksum );
_setFileRecordQuery->bindValue(16, checksumTypeId );
if( !_setFileRecordQuery->exec() ) { if( !_setFileRecordQuery->exec() ) {
qWarning() << "Error SQL statement setFileRecord: " << _setFileRecordQuery->lastQuery() << " :" qWarning() << "Error SQL statement setFileRecord: " << _setFileRecordQuery->lastQuery() << " :"
@ -652,7 +713,8 @@ bool SyncJournalDb::setFileRecord( const SyncJournalFileRecord& _record )
qDebug() << _setFileRecordQuery->lastQuery() << phash << plen << record._path << record._inode qDebug() << _setFileRecordQuery->lastQuery() << phash << plen << record._path << record._inode
<< record._mode << record._mode
<< QString::number(Utility::qDateTimeToTime_t(record._modtime)) << QString::number(record._type) << QString::number(Utility::qDateTimeToTime_t(record._modtime)) << QString::number(record._type)
<< record._etag << record._fileId << record._remotePerm << record._fileSize << (record._serverHasIgnoredFiles ? 1:0); << record._etag << record._fileId << record._remotePerm << record._fileSize << (record._serverHasIgnoredFiles ? 1:0)
<< record._transmissionChecksum << record._transmissionChecksumType << checksumTypeId;
_setFileRecordQuery->reset(); _setFileRecordQuery->reset();
return true; return true;
@ -732,6 +794,10 @@ SyncJournalFileRecord SyncJournalDb::getFileRecord( const QString& filename )
rec._remotePerm = _getFileRecordQuery->baValue(9); rec._remotePerm = _getFileRecordQuery->baValue(9);
rec._fileSize = _getFileRecordQuery->int64Value(10); rec._fileSize = _getFileRecordQuery->int64Value(10);
rec._serverHasIgnoredFiles = (_getFileRecordQuery->intValue(11) > 0); rec._serverHasIgnoredFiles = (_getFileRecordQuery->intValue(11) > 0);
rec._transmissionChecksum = _getFileRecordQuery->baValue(12);
if( !_getFileRecordQuery->nullValue(13) ) {
rec._transmissionChecksumType = _getFileRecordQuery->baValue(13);
}
} else { } else {
QString err = _getFileRecordQuery->error(); QString err = _getFileRecordQuery->error();
qDebug() << "No journal entry found for " << filename; qDebug() << "No journal entry found for " << filename;
@ -820,6 +886,86 @@ int SyncJournalDb::getFileRecordCount()
return 0; return 0;
} }
bool SyncJournalDb::updateFileRecordChecksum(const QString& filename,
const QByteArray& transmisisonChecksum,
const QByteArray& transmissionChecksumType)
{
QMutexLocker locker(&_mutex);
qlonglong phash = getPHash(filename);
if( !checkConnect() ) {
qDebug() << "Failed to connect database.";
return false;
}
int checksumTypeId = mapChecksumType(transmissionChecksumType);
auto & query = _setFileRecordChecksumQuery;
query->reset();
query->bindValue(1, QString::number(phash));
query->bindValue(2, transmisisonChecksum);
query->bindValue(3, checksumTypeId);
if( !query->exec() ) {
qWarning() << "Error SQL statement setFileRecordChecksumQuery: "
<< query->lastQuery() << " :"
<< query->error();
return false;
}
qDebug() << query->lastQuery() << phash << transmisisonChecksum
<< transmissionChecksumType << checksumTypeId;
query->reset();
return true;
}
bool SyncJournalDb::updateFileRecordMetadata(const SyncJournalFileRecord& record)
{
QMutexLocker locker(&_mutex);
qlonglong phash = getPHash(record._path);
QString etag( record._etag );
if( etag.isEmpty() ) etag = "";
QString fileId( record._fileId);
if( fileId.isEmpty() ) fileId = "";
QString remotePerm (record._remotePerm);
if (remotePerm.isEmpty()) remotePerm = QString(); // have NULL in DB (vs empty)
if( !checkConnect() ) {
qDebug() << "Failed to connect database.";
return false;
}
auto & query = _setFileRecordMetadataQuery;
query->reset();
query->bindValue(1, QString::number(phash));
query->bindValue(2, record._inode);
query->bindValue(3, record._mode);
query->bindValue(4, QString::number(Utility::qDateTimeToTime_t(record._modtime)));
query->bindValue(5, QString::number(record._type));
query->bindValue(6, etag);
query->bindValue(7, fileId);
query->bindValue(8, remotePerm);
query->bindValue(9, record._fileSize);
query->bindValue(10, record._serverHasIgnoredFiles ? 1 : 0);
if( !query->exec() ) {
qWarning() << "Error SQL statement setFileRecordMetadataQuery: "
<< query->lastQuery() << " :"
<< query->error();
return false;
}
qDebug() << query->lastQuery() << record._path << record._inode << record._mode << record._modtime
<< record._type << etag << fileId << remotePerm << record._fileSize
<< record._serverHasIgnoredFiles;
query->reset();
return true;
}
static void toDownloadInfo(SqlQuery &query, SyncJournalDb::DownloadInfo * res) static void toDownloadInfo(SqlQuery &query, SyncJournalDb::DownloadInfo * res)
{ {
bool ok = true; bool ok = true;
@ -1389,6 +1535,39 @@ void SyncJournalDb::forceRemoteDiscoveryNextSyncLocked()
} }
} }
int SyncJournalDb::mapChecksumType(const QByteArray& checksumType)
{
if (checksumType.isEmpty()) {
return 0;
}
// Ensure the checksum type is in the db
_insertChecksumTypeQuery->reset();
_insertChecksumTypeQuery->bindValue(1, checksumType);
if( !_insertChecksumTypeQuery->exec() ) {
qWarning() << "Error SQL statement insertChecksumType: "
<< _insertChecksumTypeQuery->lastQuery() << " :"
<< _insertChecksumTypeQuery->error();
return 0;
}
// Retrieve the id
_getChecksumTypeIdQuery->reset();
_getChecksumTypeIdQuery->bindValue(1, checksumType);
if( !_getChecksumTypeIdQuery->exec() ) {
qWarning() << "Error SQL statement getChecksumTypeId: "
<< _getChecksumTypeIdQuery->lastQuery() << " :"
<< _getChecksumTypeIdQuery->error();
return 0;
}
if( !_getChecksumTypeIdQuery->next() ) {
qDebug() << "No checksum type mapping found for" << checksumType;
return 0;
}
return _getChecksumTypeIdQuery->intValue(0);
}
void SyncJournalDb::commit(const QString& context, bool startTrans) void SyncJournalDb::commit(const QString& context, bool startTrans)
{ {

View file

@ -42,6 +42,10 @@ public:
bool setFileRecord( const SyncJournalFileRecord& record ); bool setFileRecord( const SyncJournalFileRecord& record );
bool deleteFileRecord( const QString& filename, bool recursively = false ); bool deleteFileRecord( const QString& filename, bool recursively = false );
int getFileRecordCount(); int getFileRecordCount();
bool updateFileRecordChecksum(const QString& filename,
const QByteArray& transmisisonChecksum,
const QByteArray& transmissionChecksumType);
bool updateFileRecordMetadata(const SyncJournalFileRecord& record);
bool exists(); bool exists();
void walCheckpoint(); void walCheckpoint();
@ -153,12 +157,21 @@ private:
// Same as forceRemoteDiscoveryNextSync but without acquiring the lock // Same as forceRemoteDiscoveryNextSync but without acquiring the lock
void forceRemoteDiscoveryNextSyncLocked(); void forceRemoteDiscoveryNextSyncLocked();
// Returns the integer id of the checksum type
//
// Returns 0 on failure and for empty checksum types.
int mapChecksumType(const QByteArray& checksumType);
SqlDatabase _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;
// NOTE! when adding a query, don't forget to reset it in SyncJournalDb::close
QScopedPointer<SqlQuery> _getFileRecordQuery; QScopedPointer<SqlQuery> _getFileRecordQuery;
QScopedPointer<SqlQuery> _setFileRecordQuery; QScopedPointer<SqlQuery> _setFileRecordQuery;
QScopedPointer<SqlQuery> _setFileRecordChecksumQuery;
QScopedPointer<SqlQuery> _setFileRecordMetadataQuery;
QScopedPointer<SqlQuery> _getDownloadInfoQuery; QScopedPointer<SqlQuery> _getDownloadInfoQuery;
QScopedPointer<SqlQuery> _setDownloadInfoQuery; QScopedPointer<SqlQuery> _setDownloadInfoQuery;
QScopedPointer<SqlQuery> _deleteDownloadInfoQuery; QScopedPointer<SqlQuery> _deleteDownloadInfoQuery;
@ -170,6 +183,8 @@ private:
QScopedPointer<SqlQuery> _getErrorBlacklistQuery; QScopedPointer<SqlQuery> _getErrorBlacklistQuery;
QScopedPointer<SqlQuery> _setErrorBlacklistQuery; QScopedPointer<SqlQuery> _setErrorBlacklistQuery;
QScopedPointer<SqlQuery> _getSelectiveSyncListQuery; QScopedPointer<SqlQuery> _getSelectiveSyncListQuery;
QScopedPointer<SqlQuery> _getChecksumTypeIdQuery;
QScopedPointer<SqlQuery> _insertChecksumTypeQuery;
/* 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

@ -35,7 +35,9 @@ SyncJournalFileRecord::SyncJournalFileRecord()
SyncJournalFileRecord::SyncJournalFileRecord(const SyncFileItem &item, const QString &localFileName) SyncJournalFileRecord::SyncJournalFileRecord(const SyncFileItem &item, const QString &localFileName)
: _path(item._file), _modtime(Utility::qDateTimeFromTime_t(item._modtime)), : _path(item._file), _modtime(Utility::qDateTimeFromTime_t(item._modtime)),
_type(item._type), _etag(item._etag), _fileId(item._fileId), _fileSize(item._size), _type(item._type), _etag(item._etag), _fileId(item._fileId), _fileSize(item._size),
_remotePerm(item._remotePerm), _mode(0), _serverHasIgnoredFiles(item._serverHasIgnoredFiles) _remotePerm(item._remotePerm), _mode(0), _serverHasIgnoredFiles(item._serverHasIgnoredFiles),
_transmissionChecksum(item._transmissionChecksum),
_transmissionChecksumType(item._transmissionChecksumType)
{ {
// use the "old" inode coming with the item for the case where the // use the "old" inode coming with the item for the case where the
// filesystem stat fails. That can happen if the the file was removed // filesystem stat fails. That can happen if the the file was removed
@ -108,12 +110,16 @@ SyncJournalErrorBlacklistRecord SyncJournalErrorBlacklistRecord::update(
const SyncJournalErrorBlacklistRecord& old, const SyncFileItem& item) const SyncJournalErrorBlacklistRecord& old, const SyncFileItem& item)
{ {
SyncJournalErrorBlacklistRecord entry; SyncJournalErrorBlacklistRecord entry;
if (item._httpErrorCode == 0 // Do not blacklist local errors. (#1985) bool mayBlacklist =
item._errorMayBeBlacklisted // explicitly flagged for blacklisting
|| (item._httpErrorCode != 0 // or non-local error
#ifdef OWNCLOUD_5XX_NO_BLACKLIST #ifdef OWNCLOUD_5XX_NO_BLACKLIST
|| item._httpErrorCode / 100 == 5 // In this configuration, never blacklist error 5xx && item._httpErrorCode / 100 != 5 // In this configuration, never blacklist error 5xx
#endif #endif
) { );
qDebug() << "This error is not blacklisted " << item._httpErrorCode;
if (!mayBlacklist) {
qDebug() << "This error is not blacklisted " << item._httpErrorCode << item._errorMayBeBlacklisted;
return entry; return entry;
} }
@ -126,7 +132,7 @@ SyncJournalErrorBlacklistRecord SyncJournalErrorBlacklistRecord::update(
entry._lastTryEtag = item._etag; entry._lastTryEtag = item._etag;
entry._lastTryTime = Utility::qDateTimeToTime_t(QDateTime::currentDateTime()); entry._lastTryTime = Utility::qDateTimeToTime_t(QDateTime::currentDateTime());
// The factor of 5 feels natural: 25s, 2 min, 10 min, ~1h, ~5h, ~24h // The factor of 5 feels natural: 25s, 2 min, 10 min, ~1h, ~5h, ~24h
entry._ignoreDuration = qMin(qMax(minBlacklistTime, old._ignoreDuration * 5), maxBlacklistTime); entry._ignoreDuration = qBound(minBlacklistTime, old._ignoreDuration * 5, maxBlacklistTime);
entry._file = item._file; entry._file = item._file;
if( item._httpErrorCode == 403 || item._httpErrorCode == 413 || item._httpErrorCode == 415 ) { if( item._httpErrorCode == 403 || item._httpErrorCode == 413 || item._httpErrorCode == 415 ) {
@ -150,9 +156,12 @@ bool operator==(const SyncJournalFileRecord & lhs,
&& lhs._type == rhs._type && lhs._type == rhs._type
&& lhs._etag == rhs._etag && lhs._etag == rhs._etag
&& lhs._fileId == rhs._fileId && lhs._fileId == rhs._fileId
&& lhs._fileSize == rhs._fileSize
&& lhs._remotePerm == rhs._remotePerm && lhs._remotePerm == rhs._remotePerm
&& lhs._mode == rhs._mode && lhs._mode == rhs._mode
&& lhs._fileSize == rhs._fileSize; && lhs._serverHasIgnoredFiles == rhs._serverHasIgnoredFiles
&& lhs._transmissionChecksum == rhs._transmissionChecksum
&& lhs._transmissionChecksumType == rhs._transmissionChecksumType;
} }
} }

View file

@ -47,6 +47,8 @@ public:
QByteArray _remotePerm; QByteArray _remotePerm;
int _mode; int _mode;
bool _serverHasIgnoredFiles; bool _serverHasIgnoredFiles;
QByteArray _transmissionChecksum;
QByteArray _transmissionChecksumType;
}; };
bool OWNCLOUDSYNC_EXPORT bool OWNCLOUDSYNC_EXPORT

View file

@ -16,133 +16,139 @@
#include "transmissionchecksumvalidator.h" #include "transmissionchecksumvalidator.h"
#include "syncfileitem.h" #include "syncfileitem.h"
#include "propagatorjobs.h" #include "propagatorjobs.h"
#include "configfile.h"
#include "account.h" #include "account.h"
#include <qtconcurrentrun.h> #include <qtconcurrentrun.h>
namespace OCC { namespace OCC {
TransmissionChecksumValidator::TransmissionChecksumValidator(const QString& filePath, QObject *parent) QByteArray makeChecksumHeader(const QByteArray& checksumType, const QByteArray& checksum)
: QObject(parent),
_filePath(filePath)
{ {
// If the config file specifies a checksum type, use that. QByteArray header = checksumType;
ConfigFile cfg; header.append(':');
_checksumType = cfg.transmissionChecksum(); header.append(checksum);
return header;
} }
void TransmissionChecksumValidator::setChecksumType(const QString& type) bool parseChecksumHeader(const QByteArray& header, QByteArray* type, QByteArray* checksum)
{
if (header.isEmpty()) {
type->clear();
checksum->clear();
return true;
}
const auto idx = header.indexOf(':');
if (idx < 0) {
return false;
}
*type = header.left(idx);
*checksum = header.mid(idx + 1);
return true;
}
bool uploadChecksumEnabled()
{
static bool enabled = qgetenv("OWNCLOUD_DISABLE_CHECKSUM_UPLOAD").isEmpty();
return enabled;
}
bool downloadChecksumEnabled()
{
static bool enabled = qgetenv("OWNCLOUD_DISABLE_CHECKSUM_DOWNLOAD").isEmpty();
return enabled;
}
ComputeChecksum::ComputeChecksum(QObject* parent)
: QObject(parent)
{
}
void ComputeChecksum::setChecksumType(const QByteArray& type)
{ {
_checksumType = type; _checksumType = type;
} }
QString TransmissionChecksumValidator::checksumType() const QByteArray ComputeChecksum::checksumType() const
{ {
return _checksumType; return _checksumType;
} }
void TransmissionChecksumValidator::uploadValidation() void ComputeChecksum::start(const QString& filePath)
{ {
const QString csType = checksumType(); const QString csType = checksumType();
if( csType.isEmpty() ) {
// if there is no checksum defined, continue to upload
emit validated(QByteArray());
} else {
// Calculate the checksum in a different thread first. // Calculate the checksum in a different thread first.
connect( &_watcher, SIGNAL(finished()), connect( &_watcher, SIGNAL(finished()),
this, SLOT(slotUploadChecksumCalculated())); this, SLOT(slotCalculationDone()),
Qt::UniqueConnection );
if( csType == checkSumMD5C ) { if( csType == checkSumMD5C ) {
_checksumHeader = checkSumMD5C; _watcher.setFuture(QtConcurrent::run(FileSystem::calcMd5, filePath));
_checksumHeader += ":";
_watcher.setFuture(QtConcurrent::run(FileSystem::calcMd5, _filePath));
} else if( csType == checkSumSHA1C ) { } else if( csType == checkSumSHA1C ) {
_checksumHeader = checkSumSHA1C; _watcher.setFuture(QtConcurrent::run( FileSystem::calcSha1, filePath));
_checksumHeader += ":";
_watcher.setFuture(QtConcurrent::run( FileSystem::calcSha1, _filePath));
} }
#ifdef ZLIB_FOUND #ifdef ZLIB_FOUND
else if( csType == checkSumAdlerC) { else if( csType == checkSumAdlerC) {
_checksumHeader = checkSumAdlerC; _watcher.setFuture(QtConcurrent::run(FileSystem::calcAdler32, filePath));
_checksumHeader += ":";
_watcher.setFuture(QtConcurrent::run(FileSystem::calcAdler32, _filePath));
} }
#endif #endif
else { else {
// for an unknown checksum, continue to upload // for an unknown checksum or no checksum, we're done right now
emit validated(QByteArray()); if( !csType.isEmpty() ) {
qDebug() << "Unknown checksum type:" << csType;
} }
emit done(QByteArray(), QByteArray());
} }
} }
void TransmissionChecksumValidator::slotUploadChecksumCalculated( ) void ComputeChecksum::slotCalculationDone()
{ {
QByteArray checksum = _watcher.future().result(); QByteArray checksum = _watcher.future().result();
emit done(_checksumType, checksum);
if( !checksum.isEmpty() ) {
checksum.prepend( _checksumHeader );
}
emit validated(checksum);
} }
void TransmissionChecksumValidator::downloadValidation( const QByteArray& checksumHeader ) ValidateChecksumHeader::ValidateChecksumHeader(QObject *parent)
: QObject(parent)
{ {
// if the incoming header is empty, there was no checksum header, and }
// no validation can happen. Just continue.
const QString csType = checksumType();
// for empty checksum type, everything is valid. void ValidateChecksumHeader::start(const QString& filePath, const QByteArray& checksumHeader)
if( csType.isEmpty() ) { {
emit validated(QByteArray()); // If the incoming header is empty no validation can happen. Just continue.
if( checksumHeader.isEmpty() ) {
emit validated(QByteArray(), QByteArray());
return; return;
} }
int indx = checksumHeader.indexOf(':'); if( !parseChecksumHeader(checksumHeader, &_expectedChecksumType, &_expectedChecksum) ) {
if( indx < 0 ) {
qDebug() << "Checksum header malformed:" << checksumHeader; qDebug() << "Checksum header malformed:" << checksumHeader;
emit validationFailed(tr("The checksum header is malformed.")); // show must go on - even not validated.
return;
}
const QByteArray type = checksumHeader.left(indx).toUpper();
_expectedHash = checksumHeader.mid(indx+1);
connect( &_watcher, SIGNAL(finished()), this, SLOT(slotDownloadChecksumCalculated()) );
// start the calculation in different thread
if( type == checkSumMD5C ) {
_watcher.setFuture(QtConcurrent::run(FileSystem::calcMd5, _filePath));
} else if( type == checkSumSHA1C ) {
_watcher.setFuture(QtConcurrent::run(FileSystem::calcSha1, _filePath));
}
#ifdef ZLIB_FOUND
else if( type == checkSumAdlerUpperC ) {
_watcher.setFuture(QtConcurrent::run(FileSystem::calcAdler32, _filePath));
}
#endif
else {
qDebug() << "Unknown checksum type" << type;
emit validationFailed(tr("The checksum header is malformed.")); emit validationFailed(tr("The checksum header is malformed."));
return; return;
} }
auto calculator = new ComputeChecksum(this);
calculator->setChecksumType(_expectedChecksumType);
connect(calculator, SIGNAL(done(QByteArray,QByteArray)),
SLOT(slotChecksumCalculated(QByteArray,QByteArray)));
calculator->start(filePath);
} }
void TransmissionChecksumValidator::slotDownloadChecksumCalculated() void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray& checksumType,
const QByteArray& checksum)
{ {
const QByteArray hash = _watcher.future().result(); if( checksumType != _expectedChecksumType ) {
emit validationFailed(tr("The checksum header contained an unknown checksum type '%1'").arg(
if( hash != _expectedHash ) { QString::fromLatin1(_expectedChecksumType)));
emit validationFailed(tr("The downloaded file does not match the checksum, it will be resumed.")); return;
} else {
// qDebug() << "Checksum checked and matching: " << _expectedHash;
emit validated(hash);
} }
if( checksum != _expectedChecksum ) {
emit validationFailed(tr("The downloaded file does not match the checksum, it will be resumed."));
return;
}
emit validated(checksumType, checksum);
} }
} }

View file

@ -23,62 +23,84 @@
namespace OCC { namespace OCC {
/// Creates a checksum header from type and value.
QByteArray makeChecksumHeader(const QByteArray& checksumType, const QByteArray& checksum);
/// Parses a checksum header
bool parseChecksumHeader(const QByteArray& header, QByteArray* type, QByteArray* checksum);
/// Checks OWNCLOUD_DISABLE_CHECKSUM_UPLOAD
bool uploadChecksumEnabled();
/// Checks OWNCLOUD_DISABLE_CHECKSUM_DOWNLOAD
bool downloadChecksumEnabled();
/** /**
* @brief The TransmissionChecksumValidator class * Computes the checksum of a file.
* @ingroup libsync * \ingroup libsync
*/ */
class OWNCLOUDSYNC_EXPORT TransmissionChecksumValidator : public QObject class OWNCLOUDSYNC_EXPORT ComputeChecksum : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit TransmissionChecksumValidator(const QString& filePath, QObject *parent = 0); explicit ComputeChecksum(QObject* parent = 0);
/** /**
* method to prepare a checksum for transmission and save it to the _checksum * Sets the checksum type to be used. The default is empty.
* member of the SyncFileItem *item. */
* The kind of requested checksum is taken from config. No need to set from outside. void setChecksumType(const QByteArray& type);
QByteArray checksumType() const;
/**
* Computes the checksum for the given file path.
* *
* In any case of processing (checksum set, no checksum required and also unusual error) * done() is emitted when the calculation finishes.
* the object will emit the signal validated(). The item->_checksum is than either
* set to a proper value or empty.
*/ */
void uploadValidation(); void start(const QString& filePath);
/**
* method to verify the checksum coming with requests in a checksum header. The required
* checksum method is read from config.
*
* If no checksum is there, or if a correct checksum is there, the signal validated()
* will be emitted. In case of any kind of error, the signal validationFailed() will
* be emitted.
*/
void downloadValidation( const QByteArray& checksumHeader );
/**
* By default the checksum type is read from the config file, but can be overridden
* with this method.
*/
void setChecksumType(const QString& type);
QString checksumType() const;
signals: signals:
void validated(const QByteArray& checksum); void done(const QByteArray& checksumType, const QByteArray& checksum);
void validationFailed( const QString& errMsg );
private slots: private slots:
void slotUploadChecksumCalculated(); void slotCalculationDone();
void slotDownloadChecksumCalculated();
private: private:
QString _checksumType; QByteArray _checksumType;
QByteArray _expectedHash;
QByteArray _checksumHeader;
QString _filePath;
// watcher for the checksum calculation thread // watcher for the checksum calculation thread
QFutureWatcher<QByteArray> _watcher; QFutureWatcher<QByteArray> _watcher;
}; };
/**
* Checks whether a file's checksum matches the expected value.
* @ingroup libsync
*/
class OWNCLOUDSYNC_EXPORT ValidateChecksumHeader : public QObject
{
Q_OBJECT
public:
explicit ValidateChecksumHeader(QObject *parent = 0);
/**
* Check a file's actual checksum against the provided checksumHeader
*
* If no checksum is there, or if a correct checksum is there, the signal validated()
* will be emitted. In case of any kind of error, the signal validationFailed() will
* be emitted.
*/
void start(const QString& filePath, const QByteArray& checksumHeader);
signals:
void validated(const QByteArray& checksumType, const QByteArray& checksum);
void validationFailed( const QString& errMsg );
private slots:
void slotChecksumCalculated(const QByteArray& checksumType, const QByteArray& checksum);
private:
QByteArray _expectedChecksumType;
QByteArray _expectedChecksum;
};
} }

View file

@ -60,16 +60,63 @@ private slots:
record._remotePerm = "744"; record._remotePerm = "744";
record._mode = -17; record._mode = -17;
record._fileSize = 213089055; record._fileSize = 213089055;
record._transmissionChecksum = "mychecksum";
record._transmissionChecksumType = "MD5";
QVERIFY(_db.setFileRecord(record)); QVERIFY(_db.setFileRecord(record));
SyncJournalFileRecord storedRecord = _db.getFileRecord("foo"); SyncJournalFileRecord storedRecord = _db.getFileRecord("foo");
QVERIFY(storedRecord == record); QVERIFY(storedRecord == record);
// Update checksum
record._transmissionChecksum = "newchecksum";
record._transmissionChecksumType = "Adler32";
_db.updateFileRecordChecksum("foo", record._transmissionChecksum, record._transmissionChecksumType);
storedRecord = _db.getFileRecord("foo");
QVERIFY(storedRecord == record);
// Update metadata
record._inode = 12345;
record._modtime = dropMsecs(QDateTime::currentDateTime().addDays(1));
record._type = 7;
record._etag = "789FFF";
record._fileId = "efg";
record._remotePerm = "777";
record._mode = 12;
record._fileSize = 289055;
_db.updateFileRecordMetadata(record);
storedRecord = _db.getFileRecord("foo");
QVERIFY(storedRecord == record);
QVERIFY(_db.deleteFileRecord("foo")); QVERIFY(_db.deleteFileRecord("foo"));
record = _db.getFileRecord("foo"); record = _db.getFileRecord("foo");
QVERIFY(!record.isValid()); QVERIFY(!record.isValid());
} }
void testFileRecordChecksum()
{
// Try with and without a checksum
{
SyncJournalFileRecord record;
record._path = "foo-checksum";
record._remotePerm = "744";
record._transmissionChecksum = "mychecksum";
record._transmissionChecksumType = "MD5";
QVERIFY(_db.setFileRecord(record));
SyncJournalFileRecord storedRecord = _db.getFileRecord("foo-checksum");
QVERIFY(storedRecord == record);
}
{
SyncJournalFileRecord record;
record._path = "foo-nochecksum";
record._remotePerm = "744";
QVERIFY(_db.setFileRecord(record));
SyncJournalFileRecord storedRecord = _db.getFileRecord("foo-nochecksum");
QVERIFY(storedRecord == record);
}
}
void testDownloadInfo() void testDownloadInfo()
{ {
typedef SyncJournalDb::DownloadInfo Info; typedef SyncJournalDb::DownloadInfo Info;

View file

@ -33,14 +33,16 @@ using namespace OCC;
QString _testfile; QString _testfile;
QString _expectedError; QString _expectedError;
QByteArray _expected; QByteArray _expected;
QByteArray _expectedType;
bool _successDown; bool _successDown;
bool _errorSeen; bool _errorSeen;
public slots: public slots:
void slotUpValidated(const QByteArray& checksum) { void slotUpValidated(const QByteArray& type, const QByteArray& checksum) {
qDebug() << "Checksum: " << checksum; qDebug() << "Checksum: " << checksum;
QVERIFY(_expected == checksum ); QVERIFY(_expected == checksum );
QVERIFY(_expectedType == type );
} }
void slotDownValidated() { void slotDownValidated() {
@ -62,23 +64,22 @@ using namespace OCC;
rootDir.mkpath(_root ); rootDir.mkpath(_root );
_testfile = _root+"/csFile"; _testfile = _root+"/csFile";
Utility::writeRandomFile( _testfile); Utility::writeRandomFile( _testfile);
} }
void testUploadChecksummingAdler() { void testUploadChecksummingAdler() {
TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this); ComputeChecksum *vali = new ComputeChecksum(this);
vali->setChecksumType("Adler32"); _expectedType = "Adler32";
vali->setChecksumType(_expectedType);
connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotUpValidated(QByteArray))); connect(vali, SIGNAL(done(QByteArray,QByteArray)), SLOT(slotUpValidated(QByteArray,QByteArray)));
QString testfile = _testfile; _expected = FileSystem::calcAdler32( _testfile );
_expected = "Adler32:"+FileSystem::calcAdler32( testfile );
qDebug() << "XX Expected Checksum: " << _expected; qDebug() << "XX Expected Checksum: " << _expected;
vali->uploadValidation(); vali->start(_testfile);
QEventLoop loop; QEventLoop loop;
connect(vali, SIGNAL(validated(QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection); connect(vali, SIGNAL(done(QByteArray,QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection);
loop.exec(); loop.exec();
delete vali; delete vali;
@ -86,16 +87,16 @@ using namespace OCC;
void testUploadChecksummingMd5() { void testUploadChecksummingMd5() {
TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this); ComputeChecksum *vali = new ComputeChecksum(this);
vali->setChecksumType( OCC::checkSumMD5C ); _expectedType = OCC::checkSumMD5C;
connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotUpValidated(QByteArray))); vali->setChecksumType(_expectedType);
connect(vali, SIGNAL(done(QByteArray,QByteArray)), this, SLOT(slotUpValidated(QByteArray,QByteArray)));
_expected = checkSumMD5C; _expected = FileSystem::calcMd5( _testfile );
_expected.append(":"+FileSystem::calcMd5( _testfile )); vali->start(_testfile);
vali->uploadValidation();
QEventLoop loop; QEventLoop loop;
connect(vali, SIGNAL(validated(QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection); connect(vali, SIGNAL(done(QByteArray,QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection);
loop.exec(); loop.exec();
delete vali; delete vali;
@ -103,17 +104,17 @@ using namespace OCC;
void testUploadChecksummingSha1() { void testUploadChecksummingSha1() {
TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this); ComputeChecksum *vali = new ComputeChecksum(this);
vali->setChecksumType( OCC::checkSumSHA1C ); _expectedType = OCC::checkSumSHA1C;
connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotUpValidated(QByteArray))); vali->setChecksumType(_expectedType);
connect(vali, SIGNAL(done(QByteArray,QByteArray)), this, SLOT(slotUpValidated(QByteArray,QByteArray)));
_expected = checkSumSHA1C; _expected = FileSystem::calcSha1( _testfile );
_expected.append(":"+FileSystem::calcSha1( _testfile ));
vali->uploadValidation(); vali->start(_testfile);
QEventLoop loop; QEventLoop loop;
connect(vali, SIGNAL(validated(QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection); connect(vali, SIGNAL(done(QByteArray,QByteArray)), &loop, SLOT(quit()), Qt::QueuedConnection);
loop.exec(); loop.exec();
delete vali; delete vali;
@ -126,22 +127,21 @@ using namespace OCC;
adler.append(FileSystem::calcAdler32( _testfile )); adler.append(FileSystem::calcAdler32( _testfile ));
_successDown = false; _successDown = false;
TransmissionChecksumValidator *vali = new TransmissionChecksumValidator(_testfile, this); ValidateChecksumHeader *vali = new ValidateChecksumHeader(this);
vali->setChecksumType("Adler32"); connect(vali, SIGNAL(validated(QByteArray,QByteArray)), this, SLOT(slotDownValidated()));
connect(vali, SIGNAL(validated(QByteArray)), this, SLOT(slotDownValidated()));
connect(vali, SIGNAL(validationFailed(QString)), this, SLOT(slotDownError(QString))); connect(vali, SIGNAL(validationFailed(QString)), this, SLOT(slotDownError(QString)));
vali->downloadValidation(adler); vali->start(_testfile, adler);
QTRY_VERIFY(_successDown); QTRY_VERIFY(_successDown);
_expectedError = QLatin1String("The downloaded file does not match the checksum, it will be resumed."); _expectedError = QLatin1String("The downloaded file does not match the checksum, it will be resumed.");
_errorSeen = false; _errorSeen = false;
vali->downloadValidation("Adler32:543345"); vali->start(_testfile, "Adler32:543345");
QTRY_VERIFY(_errorSeen); QTRY_VERIFY(_errorSeen);
_expectedError = QLatin1String("The checksum header is malformed."); _expectedError = QLatin1String("The checksum header contained an unknown checksum type 'Klaas32'");
_errorSeen = false; _errorSeen = false;
vali->downloadValidation("Klaas32:543345"); vali->start(_testfile, "Klaas32:543345");
QTRY_VERIFY(_errorSeen); QTRY_VERIFY(_errorSeen);
delete vali; delete vali;

View file

@ -2155,7 +2155,7 @@ Il est déconseillé de l&apos;utiliser.</translation>
<message> <message>
<location filename="../src/gui/sharedialog.ui" line="77"/> <location filename="../src/gui/sharedialog.ui" line="77"/>
<source>Share link</source> <source>Share link</source>
<translation>Partage par lien</translation> <translation>Partager par lien</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/sharedialog.ui" line="125"/> <location filename="../src/gui/sharedialog.ui" line="125"/>
@ -2211,7 +2211,7 @@ Il est déconseillé de l&apos;utiliser.</translation>
<location filename="../src/gui/sharedialog.cpp" line="564"/> <location filename="../src/gui/sharedialog.cpp" line="564"/>
<location filename="../src/gui/sharedialog.cpp" line="565"/> <location filename="../src/gui/sharedialog.cpp" line="565"/>
<source>&amp;Share link</source> <source>&amp;Share link</source>
<translation>&amp;Partage par lien</translation> <translation>&amp;Partager par lien</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/sharedialog.cpp" line="583"/> <location filename="../src/gui/sharedialog.cpp" line="583"/>

View file

@ -83,7 +83,7 @@
<message> <message>
<location filename="../src/gui/accountsettings.ui" line="97"/> <location filename="../src/gui/accountsettings.ui" line="97"/>
<source>Storage space: ...</source> <source>Storage space: ...</source>
<translation type="unfinished"/> <translation>ストレージ空き容量: ...</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/accountsettings.ui" line="167"/> <location filename="../src/gui/accountsettings.ui" line="167"/>
@ -240,7 +240,7 @@
<message> <message>
<location filename="../src/gui/accountsettings.cpp" line="448"/> <location filename="../src/gui/accountsettings.cpp" line="448"/>
<source>%1 of %2 in use</source> <source>%1 of %2 in use</source>
<translation type="unfinished"/> <translation>%2 %1 使</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/accountsettings.cpp" line="453"/> <location filename="../src/gui/accountsettings.cpp" line="453"/>
@ -517,7 +517,9 @@ Please go in the settings to select it if you wish to download it.</source>
<source>This sync would remove all the files in the sync folder '%1'. <source>This sync would remove all the files in the sync folder '%1'.
This might be because the folder was silently reconfigured, or that all the files were manually removed. This might be because the folder was silently reconfigured, or that all the files were manually removed.
Are you sure you want to perform this operation?</source> Are you sure you want to perform this operation?</source>
<translation type="unfinished"/> <translation> &apos;%1&apos;
</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/folder.cpp" line="1119"/> <location filename="../src/gui/folder.cpp" line="1119"/>
@ -615,7 +617,7 @@ Are you sure you want to perform this operation?</source>
<message> <message>
<location filename="../src/gui/folderman.cpp" line="1142"/> <location filename="../src/gui/folderman.cpp" line="1142"/>
<source>The selected path is not a folder!</source> <source>The selected path is not a folder!</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/gui/folderman.cpp" line="1146"/> <location filename="../src/gui/folderman.cpp" line="1146"/>
@ -625,27 +627,27 @@ Are you sure you want to perform this operation?</source>
<message> <message>
<location filename="../src/gui/folderman.cpp" line="1161"/> <location filename="../src/gui/folderman.cpp" line="1161"/>
<source>The local folder %1 is already used in a folder sync connection. Please pick another one!</source> <source>The local folder %1 is already used in a folder sync connection. Please pick another one!</source>
<translation type="unfinished"/> <translation> %1 </translation>
</message> </message>
<message> <message>
<location filename="../src/gui/folderman.cpp" line="1166"/> <location filename="../src/gui/folderman.cpp" line="1166"/>
<source>The local folder %1 already contains a folder used in a folder sync connection. Please pick another one!</source> <source>The local folder %1 already contains a folder used in a folder sync connection. Please pick another one!</source>
<translation type="unfinished"/> <translation> %1 </translation>
</message> </message>
<message> <message>
<location filename="../src/gui/folderman.cpp" line="1173"/> <location filename="../src/gui/folderman.cpp" line="1173"/>
<source>The local folder %1 is a symbolic link. The link target already contains a folder used in a folder sync connection. Please pick another one!</source> <source>The local folder %1 is a symbolic link. The link target already contains a folder used in a folder sync connection. Please pick another one!</source>
<translation type="unfinished"/> <translation> %1 </translation>
</message> </message>
<message> <message>
<location filename="../src/gui/folderman.cpp" line="1180"/> <location filename="../src/gui/folderman.cpp" line="1180"/>
<source>The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one!</source> <source>The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one!</source>
<translation type="unfinished"/> <translation> %1 </translation>
</message> </message>
<message> <message>
<location filename="../src/gui/folderman.cpp" line="1186"/> <location filename="../src/gui/folderman.cpp" line="1186"/>
<source>The local folder %1 is a symbolic link. The link target is already contained in a folder used in a folder sync connection. Please pick another one!</source> <source>The local folder %1 is a symbolic link. The link target is already contained in a folder used in a folder sync connection. Please pick another one!</source>
<translation type="unfinished"/> <translation> %1 </translation>
</message> </message>
</context> </context>
<context> <context>
@ -653,7 +655,7 @@ Are you sure you want to perform this operation?</source>
<message> <message>
<location filename="../src/gui/folderstatusdelegate.cpp" line="33"/> <location filename="../src/gui/folderstatusdelegate.cpp" line="33"/>
<source>Add Folder Sync Connection</source> <source>Add Folder Sync Connection</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/gui/folderstatusdelegate.cpp" line="85"/> <location filename="../src/gui/folderstatusdelegate.cpp" line="85"/>
@ -766,7 +768,7 @@ Total time left %5</source>
<message numerus="yes"> <message numerus="yes">
<location filename="../src/gui/folderstatusmodel.cpp" line="872"/> <location filename="../src/gui/folderstatusmodel.cpp" line="872"/>
<source>Waiting for %n other folder(s)...</source> <source>Waiting for %n other folder(s)...</source>
<translation type="unfinished"><numerusform></numerusform></translation> <translation><numerusform>%n ...</numerusform></translation>
</message> </message>
<message> <message>
<location filename="../src/gui/folderstatusmodel.cpp" line="878"/> <location filename="../src/gui/folderstatusmodel.cpp" line="878"/>
@ -798,7 +800,7 @@ Total time left %5</source>
<message> <message>
<location filename="../src/gui/folderwizard.cpp" line="81"/> <location filename="../src/gui/folderwizard.cpp" line="81"/>
<source>The folder alias is a descriptive name for this sync connection.</source> <source>The folder alias is a descriptive name for this sync connection.</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/gui/folderwizard.cpp" line="115"/> <location filename="../src/gui/folderwizard.cpp" line="115"/>
@ -846,7 +848,7 @@ Total time left %5</source>
<message> <message>
<location filename="../src/gui/folderwizard.cpp" line="271"/> <location filename="../src/gui/folderwizard.cpp" line="271"/>
<source>Failed to list a folder. Error: %1</source> <source>Failed to list a folder. Error: %1</source>
<translation type="unfinished"/> <translation>: %1</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/folderwizard.cpp" line="351"/> <location filename="../src/gui/folderwizard.cpp" line="351"/>
@ -1068,7 +1070,9 @@ Account: %3
<source>Files or folders matching a pattern will not be synchronized. <source>Files or folders matching a pattern will not be synchronized.
Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data.</source> Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data.</source>
<translation type="unfinished"/> <translation>
</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/ignorelisteditor.cpp" line="109"/> <location filename="../src/gui/ignorelisteditor.cpp" line="109"/>
@ -1390,12 +1394,12 @@ for additional privileges during the process.</source>
<message> <message>
<location filename="../src/gui/wizard/owncloudadvancedsetuppage.cpp" line="142"/> <location filename="../src/gui/wizard/owncloudadvancedsetuppage.cpp" line="142"/>
<source>Sync the folder &apos;%1&apos;</source> <source>Sync the folder &apos;%1&apos;</source>
<translation type="unfinished"/> <translation>&apos;%1&apos; </translation>
</message> </message>
<message> <message>
<location filename="../src/gui/wizard/owncloudadvancedsetuppage.cpp" line="147"/> <location filename="../src/gui/wizard/owncloudadvancedsetuppage.cpp" line="147"/>
<source>&lt;p&gt;&lt;small&gt;&lt;strong&gt;Warning:&lt;/strong&gt; The local folder is not empty. Pick a resolution!&lt;/small&gt;&lt;/p&gt;</source> <source>&lt;p&gt;&lt;small&gt;&lt;strong&gt;Warning:&lt;/strong&gt; The local folder is not empty. Pick a resolution!&lt;/small&gt;&lt;/p&gt;</source>
<translation type="unfinished"/> <translation>&lt;p&gt;&lt;small&gt;&lt;strong&gt;:&lt;/strong&gt; &lt;/small&gt;&lt;/p&gt;</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/wizard/owncloudadvancedsetuppage.cpp" line="246"/> <location filename="../src/gui/wizard/owncloudadvancedsetuppage.cpp" line="246"/>
@ -1742,7 +1746,7 @@ It is not advisable to use it.</source>
<message> <message>
<location filename="../src/libsync/owncloudpropagator.cpp" line="211"/> <location filename="../src/libsync/owncloudpropagator.cpp" line="211"/>
<source>A file or folder was removed from a read only share, but restoring failed: %1</source> <source>A file or folder was removed from a read only share, but restoring failed: %1</source>
<translation type="unfinished"/> <translation>: %1</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1755,7 +1759,7 @@ It is not advisable to use it.</source>
<message> <message>
<location filename="../src/libsync/propagatorjobs.cpp" line="151"/> <location filename="../src/libsync/propagatorjobs.cpp" line="151"/>
<source>could not create folder %1</source> <source>could not create folder %1</source>
<translation type="unfinished"/> <translation> %1 </translation>
</message> </message>
</context> </context>
<context> <context>
@ -1868,7 +1872,7 @@ It is not advisable to use it.</source>
<message> <message>
<location filename="../src/libsync/propagateupload.cpp" line="558"/> <location filename="../src/libsync/propagateupload.cpp" line="558"/>
<source>Forcing job abort on HTTP connection reset with Qt &lt; 5.4.2.</source> <source>Forcing job abort on HTTP connection reset with Qt &lt; 5.4.2.</source>
<translation type="unfinished"/> <translation>5.4.2 Qt HTTP </translation>
</message> </message>
<message> <message>
<location filename="../src/libsync/propagateupload.cpp" line="566"/> <location filename="../src/libsync/propagateupload.cpp" line="566"/>
@ -2402,7 +2406,7 @@ It is not advisable to use it.</source>
<message> <message>
<location filename="../src/gui/sslbutton.cpp" line="219"/> <location filename="../src/gui/sslbutton.cpp" line="219"/>
<source>No support for SSL session tickets/identifiers</source> <source>No support for SSL session tickets/identifiers</source>
<translation type="unfinished"/> <translation>SSLセッションチケット/</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/sslbutton.cpp" line="230"/> <location filename="../src/gui/sslbutton.cpp" line="230"/>
@ -2575,27 +2579,27 @@ It is not advisable to use it.</source>
<message> <message>
<location filename="../src/libsync/syncengine.cpp" line="164"/> <location filename="../src/libsync/syncengine.cpp" line="164"/>
<source>The mounted folder is temporarily not available on the server</source> <source>The mounted folder is temporarily not available on the server</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/libsync/syncengine.cpp" line="167"/> <location filename="../src/libsync/syncengine.cpp" line="167"/>
<source>An error occurred while opening a folder</source> <source>An error occurred while opening a folder</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/libsync/syncengine.cpp" line="170"/> <location filename="../src/libsync/syncengine.cpp" line="170"/>
<source>Error while reading folder.</source> <source>Error while reading folder.</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/libsync/syncengine.cpp" line="996"/> <location filename="../src/libsync/syncengine.cpp" line="996"/>
<source>Not allowed because you don&apos;t have permission to add parent folder</source> <source>Not allowed because you don&apos;t have permission to add parent folder</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/libsync/syncengine.cpp" line="1003"/> <location filename="../src/libsync/syncengine.cpp" line="1003"/>
<source>Not allowed because you don&apos;t have permission to add files in that folder</source> <source>Not allowed because you don&apos;t have permission to add files in that folder</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/libsync/syncengine.cpp" line="152"/> <location filename="../src/libsync/syncengine.cpp" line="152"/>
@ -2625,12 +2629,12 @@ It is not advisable to use it.</source>
<message> <message>
<location filename="../src/libsync/syncengine.cpp" line="100"/> <location filename="../src/libsync/syncengine.cpp" line="100"/>
<source>CSync failed to load or create the journal file. Make sure you have read and write permissions in the local sync folder.</source> <source>CSync failed to load or create the journal file. Make sure you have read and write permissions in the local sync folder.</source>
<translation type="unfinished"/> <translation>CSyncはジャーナルファイルの読み込みや作成に失敗しました</translation>
</message> </message>
<message> <message>
<location filename="../src/libsync/syncengine.cpp" line="149"/> <location filename="../src/libsync/syncengine.cpp" line="149"/>
<source>CSync tried to create a folder that already exists.</source> <source>CSync tried to create a folder that already exists.</source>
<translation type="unfinished"/> <translation>CSyncはすでに存在するフォルダーを作成しようとしました</translation>
</message> </message>
<message> <message>
<location filename="../src/libsync/syncengine.cpp" line="161"/> <location filename="../src/libsync/syncengine.cpp" line="161"/>
@ -2701,7 +2705,7 @@ It is not advisable to use it.</source>
<message> <message>
<location filename="../src/libsync/syncengine.cpp" line="990"/> <location filename="../src/libsync/syncengine.cpp" line="990"/>
<source>Not allowed because you don&apos;t have permission to add subfolders to that folder</source> <source>Not allowed because you don&apos;t have permission to add subfolders to that folder</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/libsync/syncengine.cpp" line="1023"/> <location filename="../src/libsync/syncengine.cpp" line="1023"/>
@ -2812,7 +2816,7 @@ It is not advisable to use it.</source>
<location filename="../src/gui/owncloudgui.cpp" line="459"/> <location filename="../src/gui/owncloudgui.cpp" line="459"/>
<location filename="../src/gui/owncloudgui.cpp" line="526"/> <location filename="../src/gui/owncloudgui.cpp" line="526"/>
<source>Log in...</source> <source>Log in...</source>
<translation type="unfinished"/> <translation>...</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/owncloudgui.cpp" line="354"/> <location filename="../src/gui/owncloudgui.cpp" line="354"/>
@ -2849,12 +2853,12 @@ It is not advisable to use it.</source>
<message> <message>
<location filename="../src/gui/owncloudgui.cpp" line="449"/> <location filename="../src/gui/owncloudgui.cpp" line="449"/>
<source>Log out everywhere</source> <source>Log out everywhere</source>
<translation type="unfinished"/> <translation></translation>
</message> </message>
<message> <message>
<location filename="../src/gui/owncloudgui.cpp" line="457"/> <location filename="../src/gui/owncloudgui.cpp" line="457"/>
<source>Log in everywhere...</source> <source>Log in everywhere...</source>
<translation type="unfinished"/> <translation>...</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/owncloudgui.cpp" line="513"/> <location filename="../src/gui/owncloudgui.cpp" line="513"/>
@ -2967,7 +2971,7 @@ It is not advisable to use it.</source>
<message> <message>
<location filename="../src/gui/wizard/owncloudadvancedsetuppage.ui" line="200"/> <location filename="../src/gui/wizard/owncloudadvancedsetuppage.ui" line="200"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.&lt;/p&gt;&lt;p&gt;Do not check this if the local content should be uploaded to the servers folder.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source> <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.&lt;/p&gt;&lt;p&gt;Do not check this if the local content should be uploaded to the servers folder.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"/> <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message> </message>
<message> <message>
<location filename="../src/gui/wizard/owncloudadvancedsetuppage.ui" line="203"/> <location filename="../src/gui/wizard/owncloudadvancedsetuppage.ui" line="203"/>
@ -3161,7 +3165,7 @@ It is not advisable to use it.</source>
<message> <message>
<location filename="../src/libsync/utility.cpp" line="125"/> <location filename="../src/libsync/utility.cpp" line="125"/>
<source>%L1 KB</source> <source>%L1 KB</source>
<translation type="unfinished"/> <translation>%L1 KB</translation>
</message> </message>
<message> <message>
<location filename="../src/libsync/utility.cpp" line="128"/> <location filename="../src/libsync/utility.cpp" line="128"/>

File diff suppressed because it is too large Load diff