Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/owncloud/client.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/ownsql.h1
-rw-r--r--src/common/syncjournaldb.cpp4
-rw-r--r--src/common/syncjournaldb.h12
-rw-r--r--src/common/syncjournalfilerecord.h13
-rw-r--r--src/common/utility.cpp67
-rw-r--r--src/crashreporter/CMakeLists.txt24
-rw-r--r--src/crashreporter/resources.qrc5
-rw-r--r--src/crashreporter/resources.qrc.in5
-rw-r--r--src/csync/csync_exclude.cpp4
-rw-r--r--src/csync/csync_update.cpp8
-rw-r--r--src/gui/CMakeLists.txt6
-rw-r--r--src/gui/activitydata.h5
-rw-r--r--src/gui/activitywidget.h2
-rw-r--r--src/gui/application.cpp16
-rw-r--r--src/gui/folder.cpp38
-rw-r--r--src/gui/folder.h3
-rw-r--r--src/gui/folderstatusmodel.cpp28
-rw-r--r--src/gui/folderstatusmodel.h5
-rw-r--r--src/gui/generalsettings.cpp2
-rw-r--r--src/gui/logbrowser.cpp28
-rw-r--r--src/gui/logbrowser.h2
-rw-r--r--src/gui/notificationwidget.cpp11
-rw-r--r--src/gui/ocsshareejob.cpp2
-rw-r--r--src/gui/ocssharejob.h2
-rw-r--r--src/gui/owncloudgui.cpp9
-rw-r--r--src/gui/owncloudgui.h7
-rw-r--r--src/gui/sharedialog.cpp5
-rw-r--r--src/gui/sharedialog.h3
-rw-r--r--src/gui/sharelinkwidget.ui25
-rw-r--r--src/gui/socketapi.cpp451
-rw-r--r--src/gui/socketapi.h37
-rw-r--r--src/gui/sslbutton.cpp10
-rw-r--r--src/gui/sslbutton.h2
-rw-r--r--src/gui/updater/sparkleupdater_mac.mm6
-rw-r--r--src/libsync/account.h2
-rw-r--r--src/libsync/configfile.cpp13
-rw-r--r--src/libsync/configfile.h3
-rw-r--r--src/libsync/logger.cpp104
-rw-r--r--src/libsync/logger.h21
-rw-r--r--src/libsync/networkjobs.cpp2
-rw-r--r--src/libsync/owncloudpropagator.cpp8
-rw-r--r--src/libsync/owncloudpropagator.h2
-rw-r--r--src/libsync/owncloudtheme.cpp66
-rw-r--r--src/libsync/owncloudtheme.h12
-rw-r--r--src/libsync/propagateupload.cpp10
-rw-r--r--src/libsync/propagateupload.h2
-rw-r--r--src/libsync/propagateuploadng.cpp19
-rw-r--r--src/libsync/propagateuploadv1.cpp19
-rw-r--r--src/libsync/syncengine.cpp6
-rw-r--r--src/libsync/syncengine.h4
-rw-r--r--src/libsync/syncfileitem.h5
-rw-r--r--src/libsync/theme.cpp47
-rw-r--r--src/libsync/theme.h15
53 files changed, 821 insertions, 387 deletions
diff --git a/src/common/ownsql.h b/src/common/ownsql.h
index 250fa109b..a5e53e8d0 100644
--- a/src/common/ownsql.h
+++ b/src/common/ownsql.h
@@ -21,6 +21,7 @@
#include <QObject>
#include <QVariant>
+#include <QSet>
#include "ocsynclib.h"
diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp
index 1e59a7947..8a41a4ac6 100644
--- a/src/common/syncjournaldb.cpp
+++ b/src/common/syncjournaldb.cpp
@@ -1683,6 +1683,8 @@ void SyncJournalDb::setSelectiveSyncList(SyncJournalDb::SelectiveSyncListType ty
return;
}
+ startTransaction();
+
//first, delete all entries of this type
SqlQuery delQuery("DELETE FROM selectivesync WHERE type == ?1", _db);
delQuery.bindValue(1, int(type));
@@ -1699,6 +1701,8 @@ void SyncJournalDb::setSelectiveSyncList(SyncJournalDb::SelectiveSyncListType ty
qCWarning(lcDb) << "SQL error when inserting into selective sync" << type << path << delQuery.error();
}
}
+
+ commitInternal("setSelectiveSyncList");
}
void SyncJournalDb::avoidRenamesOnNextSync(const QByteArray &path)
diff --git a/src/common/syncjournaldb.h b/src/common/syncjournaldb.h
index 2ba00dc45..4db859bbe 100644
--- a/src/common/syncjournaldb.h
+++ b/src/common/syncjournaldb.h
@@ -113,6 +113,12 @@ public:
int _errorCount;
bool _valid;
QByteArray _contentChecksum;
+ /**
+ * Returns true if this entry refers to a chunked upload that can be continued.
+ * (As opposed to a small file transfer which is stored in the db so we can detect the case
+ * when the upload succeeded, but the connection was dropped before we got the answer)
+ */
+ bool isChunked() const { return _transferid != 0; }
};
struct PollInfo
@@ -222,13 +228,13 @@ public:
/// Store a new or updated record in the database
void setConflictRecord(const ConflictRecord &record);
- /// Retrieve a conflict record by path of the _conflict- file
+ /// Retrieve a conflict record by path of the file with the conflict tag
ConflictRecord conflictRecord(const QByteArray &path);
- /// Delete a conflict record by path of the _conflict- file
+ /// Delete a conflict record by path of the file with the conflict tag
void deleteConflictRecord(const QByteArray &path);
- /// Return all paths of _conflict- files with records in the db
+ /// Return all paths of files with a conflict tag in the name and records in the db
QByteArrayList conflictRecordPaths();
diff --git a/src/common/syncjournalfilerecord.h b/src/common/syncjournalfilerecord.h
index b09006365..43259c1e6 100644
--- a/src/common/syncjournalfilerecord.h
+++ b/src/common/syncjournalfilerecord.h
@@ -114,18 +114,15 @@ public:
/** Represents a conflict in the conflicts table.
*
- * In the following the "conflict file" is the file with the "_conflict-"
- * tag and the base file is the file that its a conflict for. So if
- * a/foo.txt is the base file, its conflict file could be
- * a/foo_conflict-1234.txt.
+ * In the following the "conflict file" is the file that has the conflict
+ * tag in the filename, and the base file is the file that it's a conflict for.
+ * So if "a/foo.txt" is the base file, its conflict file could be
+ * "a/foo (conflicted copy 1234).txt".
*/
class OCSYNC_EXPORT ConflictRecord
{
public:
- /** Path to the _conflict- file
- *
- * So if a/foo.txt has a conflict, this path would point to
- * a/foo_conflict-1234.txt.
+ /** Path to the file with the conflict tag in the name
*
* The path is sync-folder relative.
*/
diff --git a/src/common/utility.cpp b/src/common/utility.cpp
index 70e7a5ae7..d2a46bb7c 100644
--- a/src/common/utility.cpp
+++ b/src/common/utility.cpp
@@ -548,19 +548,23 @@ QString Utility::makeConflictFileName(
const QString &fn, const QDateTime &dt, const QString &user)
{
QString conflictFileName(fn);
- // Add _conflict-XXXX before the extension.
+ // Add conflict tag before the extension.
int dotLocation = conflictFileName.lastIndexOf('.');
// If no extension, add it at the end (take care of cases like foo/.hidden or foo.bar/file)
if (dotLocation <= conflictFileName.lastIndexOf('/') + 1) {
dotLocation = conflictFileName.size();
}
- QString conflictMarker = QStringLiteral("_conflict-");
+ QString conflictMarker = QStringLiteral(" (conflicted copy ");
if (!user.isEmpty()) {
- conflictMarker.append(sanitizeForFileName(user));
- conflictMarker.append('-');
+ // Don't allow parens in the user name, to ensure
+ // we can find the beginning and end of the conflict tag.
+ const auto userName = sanitizeForFileName(user).replace('(', '_').replace(')', '_');
+ conflictMarker.append(userName);
+ conflictMarker.append(' ');
}
- conflictMarker.append(dt.toString("yyyyMMdd-hhmmss"));
+ conflictMarker.append(dt.toString("yyyy-MM-dd hhmmss"));
+ conflictMarker.append(')');
conflictFileName.insert(dotLocation, conflictMarker);
return conflictFileName;
@@ -575,13 +579,28 @@ bool Utility::isConflictFile(const char *name)
bname = name;
}
- return std::strstr(bname, "_conflict-");
+ // Old pattern
+ if (std::strstr(bname, "_conflict-"))
+ return true;
+
+ // New pattern
+ if (std::strstr(bname, "(conflicted copy"))
+ return true;
+
+ return false;
}
bool Utility::isConflictFile(const QString &name)
{
auto bname = name.midRef(name.lastIndexOf('/') + 1);
- return bname.contains("_conflict-", Utility::fsCasePreserving() ? Qt::CaseInsensitive : Qt::CaseSensitive);
+
+ if (bname.contains(QStringLiteral("_conflict-")))
+ return true;
+
+ if (bname.contains(QStringLiteral("(conflicted copy")))
+ return true;
+
+ return false;
}
QByteArray Utility::conflictFileBaseName(const QByteArray &conflictName)
@@ -589,19 +608,29 @@ QByteArray Utility::conflictFileBaseName(const QByteArray &conflictName)
// This function must be able to deal with conflict files for conflict files.
// To do this, we scan backwards, for the outermost conflict marker and
// strip only that to generate the conflict file base name.
- int from = conflictName.size();
- while (from != -1) {
- auto start = conflictName.lastIndexOf("_conflict-", from);
- if (start == -1)
- return "";
- from = start - 1;
-
- auto end = conflictName.indexOf('.', start);
- if (end == -1)
- end = conflictName.size();
- return conflictName.left(start) + conflictName.mid(end);
+ auto startOld = conflictName.lastIndexOf("_conflict-");
+
+ // A single space before "(conflicted copy" is considered part of the tag
+ auto startNew = conflictName.lastIndexOf("(conflicted copy");
+ if (startNew > 0 && conflictName[startNew - 1] == ' ')
+ startNew -= 1;
+
+ // The rightmost tag is relevant
+ auto tagStart = qMax(startOld, startNew);
+ if (tagStart == -1)
+ return "";
+
+ // Find the end of the tag
+ auto tagEnd = conflictName.size();
+ auto dot = conflictName.lastIndexOf('.'); // dot could be part of user name for new tag!
+ if (dot > tagStart)
+ tagEnd = dot;
+ if (tagStart == startNew) {
+ auto paren = conflictName.indexOf(')', tagStart);
+ if (paren != -1)
+ tagEnd = paren + 1;
}
- return "";
+ return conflictName.left(tagStart) + conflictName.mid(tagEnd);
}
QString Utility::sanitizeForFileName(const QString &name)
diff --git a/src/crashreporter/CMakeLists.txt b/src/crashreporter/CMakeLists.txt
index 693fd9efd..40b37e95a 100644
--- a/src/crashreporter/CMakeLists.txt
+++ b/src/crashreporter/CMakeLists.txt
@@ -1,9 +1,6 @@
PROJECT( CrashReporter )
cmake_policy(SET CMP0017 NEW)
-list(APPEND crashreporter_SOURCES main.cpp)
-list(APPEND crashreporter_RC resources.qrc)
-
# TODO: differentiate release channel
# if(BUILD_RELEASE)
# set(CRASHREPORTER_RELEASE_CHANNEL "release")
@@ -11,9 +8,27 @@ list(APPEND crashreporter_RC resources.qrc)
set(CRASHREPORTER_RELEASE_CHANNEL "nightly")
# endif()
+# Theme
+if(DEFINED OEM_THEME_DIR AND EXISTS "${OEM_THEME_DIR}/theme/colored")
+ set(CRASHREPORTER_ICON_DIR "${OEM_THEME_DIR}/theme/colored")
+else()
+ set(CRASHREPORTER_ICON_DIR "${CMAKE_SOURCE_DIR}/theme/colored")
+endif()
+
+set(CRASHREPORTER_ICON_FILENAME "${APPLICATION_ICON_NAME}-icon.png")
+set(CRASHREPORTER_ICON ":/${CRASHREPORTER_ICON_FILENAME}")
+set(CRASHREPORTER_ICON_SIZE "128")
+set(CRASHREPORTER_ICON_PATH "${CRASHREPORTER_ICON_DIR}/${CRASHREPORTER_ICON_SIZE}-${CRASHREPORTER_ICON_FILENAME}")
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/resources.qrc.in
+ ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc)
+
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CrashReporterConfig.h.in
${CMAKE_CURRENT_BINARY_DIR}/CrashReporterConfig.h)
+# Sources
+list(APPEND crashreporter_SOURCES main.cpp)
+list(APPEND crashreporter_RC "${CMAKE_CURRENT_BINARY_DIR}/resources.qrc")
if(NOT BUILD_LIBRARIES_ONLY)
@@ -21,13 +36,14 @@ if(NOT BUILD_LIBRARIES_ONLY)
${crashreporter_SOURCES}
${crashreporter_HEADERS_MOC}
${crashreporter_UI_HEADERS}
- ${crashreporter_RC_RCC}
+ ${crashreporter_RC}
)
find_package(Qt5 REQUIRED COMPONENTS Widgets)
target_include_directories(${CRASHREPORTER_EXECUTABLE} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
set_target_properties(${CRASHREPORTER_EXECUTABLE} PROPERTIES AUTOMOC ON)
+ set_target_properties(${CRASHREPORTER_EXECUTABLE} PROPERTIES AUTORCC ON)
set_target_properties(${CRASHREPORTER_EXECUTABLE} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} )
set_target_properties(${CRASHREPORTER_EXECUTABLE} PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE}" )
target_link_libraries(${CRASHREPORTER_EXECUTABLE}
diff --git a/src/crashreporter/resources.qrc b/src/crashreporter/resources.qrc
deleted file mode 100644
index 830281e77..000000000
--- a/src/crashreporter/resources.qrc
+++ /dev/null
@@ -1,5 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file alias="owncloud-icon.png">../../theme/colored/owncloud-icon-128.png</file>
- </qresource>
-</RCC>
diff --git a/src/crashreporter/resources.qrc.in b/src/crashreporter/resources.qrc.in
new file mode 100644
index 000000000..8afaf4327
--- /dev/null
+++ b/src/crashreporter/resources.qrc.in
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file alias="@CRASHREPORTER_ICON_FILENAME@">@CRASHREPORTER_ICON_PATH@</file>
+ </qresource>
+</RCC>
diff --git a/src/csync/csync_exclude.cpp b/src/csync/csync_exclude.cpp
index 5a0933c31..c64589fff 100644
--- a/src/csync/csync_exclude.cpp
+++ b/src/csync/csync_exclude.cpp
@@ -220,8 +220,8 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const char *path, bool excludeC
}
#endif
- /* We create a desktop.ini on Windows for the sidebar icon, make sure we don't sync them. */
- if (blen == 11) {
+ /* We create a Desktop.ini on Windows for the sidebar icon, make sure we don't sync it. */
+ if (blen == 11 && path == bname) {
rc = csync_fnmatch("Desktop.ini", bname, 0);
if (rc == 0) {
match = CSYNC_FILE_SILENTLY_EXCLUDED;
diff --git a/src/csync/csync_update.cpp b/src/csync/csync_update.cpp
index 4de7dcb90..2f180dda4 100644
--- a/src/csync/csync_update.cpp
+++ b/src/csync/csync_update.cpp
@@ -207,13 +207,17 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
/* we have an update! */
qCInfo(lcUpdate, "Database entry found, compare: %" PRId64 " <-> %" PRId64
", etag: %s <-> %s, inode: %" PRId64 " <-> %" PRId64
- ", size: %" PRId64 " <-> %" PRId64 ", perms: %x <-> %x"
- ", type: %d <-> %d, ignore: %d",
+ ", size: %" PRId64 " <-> %" PRId64
+ ", perms: %x <-> %x"
+ ", type: %d <-> %d"
+ ", checksum: %s <-> %s"
+ ", ignore: %d",
((int64_t) fs->modtime), ((int64_t) base._modtime),
fs->etag.constData(), base._etag.constData(), (uint64_t) fs->inode, (uint64_t) base._inode,
(uint64_t) fs->size, (uint64_t) base._fileSize,
*reinterpret_cast<short*>(&fs->remotePerm), *reinterpret_cast<short*>(&base._remotePerm),
fs->type, base._type,
+ fs->checksumHeader.constData(), base._checksumHeader.constData(),
base._serverHasIgnoredFiles );
// If the db suggests a placeholder should be downloaded,
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index d3edef9aa..1745e1b31 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -216,12 +216,6 @@ endif()
# list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(ECMAddAppIcon)
-# For historical reasons we can not use the application_shortname
-# for ownCloud but must rather set it manually.
-if (NOT DEFINED APPLICATION_ICON_NAME)
- set(APPLICATION_ICON_NAME ${APPLICATION_SHORTNAME})
-endif()
-
file(GLOB_RECURSE OWNCLOUD_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-icon*")
if(APPLE)
file(GLOB_RECURSE OWNCLOUD_SIDEBAR_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-sidebar*")
diff --git a/src/gui/activitydata.h b/src/gui/activitydata.h
index 40cd35e05..7d14600c1 100644
--- a/src/gui/activitydata.h
+++ b/src/gui/activitydata.h
@@ -58,9 +58,10 @@ public:
QString _file;
QUrl _link;
QDateTime _dateTime;
- QString _accName;
+ QString _accName; /* display name of the account involved */
- QVector<ActivityLink> _links;
+ QVector<ActivityLink> _links; /* These links are transformed into buttons that
+ * call links as reactions on the activity */
/**
* @brief Sort operator to sort the list youngest first.
* @param val
diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h
index 89fd6c08c..1195e7ffb 100644
--- a/src/gui/activitywidget.h
+++ b/src/gui/activitywidget.h
@@ -92,7 +92,7 @@ private slots:
void slotNotifyNetworkError(QNetworkReply *);
void slotNotifyServerFinished(const QString &reply, int replyCode);
void endNotificationRequest(NotificationWidget *widget, int replyCode);
- void scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds = 4500);
+ void scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds = 100);
void slotCheckToCleanWidgets();
private:
diff --git a/src/gui/application.cpp b/src/gui/application.cpp
index 34f811375..703f1f843 100644
--- a/src/gui/application.cpp
+++ b/src/gui/application.cpp
@@ -377,13 +377,17 @@ void Application::slotownCloudWizardDone(int res)
void Application::setupLogging()
{
// might be called from second instance
- Logger::instance()->setLogFile(_logFile);
- Logger::instance()->setLogDir(_logDir);
- Logger::instance()->setLogExpire(_logExpire);
- Logger::instance()->setLogFlush(_logFlush);
- Logger::instance()->setLogDebug(_logDebug);
+ auto logger = Logger::instance();
+ logger->setLogFile(_logFile);
+ logger->setLogDir(_logDir);
+ logger->setLogExpire(_logExpire);
+ logger->setLogFlush(_logFlush);
+ logger->setLogDebug(_logDebug);
+ if (!logger->isLoggingToFile() && ConfigFile().automaticLogDir()) {
+ logger->setupTemporaryFolderLogDir();
+ }
- Logger::instance()->enterNextLogFile();
+ logger->enterNextLogFile();
qCInfo(lcApplication) << QString::fromLatin1("################## %1 locale:[%2] ui_lang:[%3] version:[%4] os:[%5]").arg(_theme->appName()).arg(QLocale::system().name()).arg(property("ui_lang").toString()).arg(_theme->version()).arg(Utility::platformName());
}
diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp
index b9ed12928..32f562a09 100644
--- a/src/gui/folder.cpp
+++ b/src/gui/folder.cpp
@@ -122,6 +122,10 @@ void Folder::checkLocalPath()
{
const QFileInfo fi(_definition.localPath);
_canonicalLocalPath = fi.canonicalFilePath();
+#ifdef Q_OS_MAC
+ // Workaround QTBUG-55896 (Should be fixed in Qt 5.8)
+ _canonicalLocalPath = _canonicalLocalPath.normalized(QString::NormalizationForm_C);
+#endif
if (_canonicalLocalPath.isEmpty()) {
qCWarning(lcFolder) << "Broken symlink:" << _definition.localPath;
_canonicalLocalPath = _definition.localPath;
@@ -488,6 +492,8 @@ void Folder::slotWatchedPathChanged(const QString &path)
return; // probably a spurious notification
}
+ warnOnNewExcludedItem(record, relativePath);
+
emit watchedFileChangedExternally(path);
// Also schedule this folder for a sync, but only after some delay:
@@ -1001,6 +1007,38 @@ void Folder::slotFolderConflicts(const QString &folder, const QStringList &confl
r.setNumOldConflictItems(conflictPaths.size() - r.numNewConflictItems());
}
+void Folder::warnOnNewExcludedItem(const SyncJournalFileRecord &record, const QStringRef &path)
+{
+ // Never warn for items in the database
+ if (record.isValid())
+ return;
+
+ // Don't warn for items that no longer exist.
+ // Note: This assumes we're getting file watcher notifications
+ // for folders only on creation and deletion - if we got a notification
+ // on content change that would create spurious warnings.
+ QFileInfo fi(_canonicalLocalPath + path);
+ if (!fi.exists())
+ return;
+
+ bool ok = false;
+ auto blacklist = _journal.getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok);
+ if (!ok)
+ return;
+ if (!blacklist.contains(path + "/"))
+ return;
+
+ const auto message = fi.isDir()
+ ? tr("The folder %1 was created but was excluded from synchronization previously. "
+ "Data inside it will not be synchronized.")
+ .arg(fi.filePath())
+ : tr("The file %1 was created but was excluded from synchronization previously. "
+ "It will not be synchronized.")
+ .arg(fi.filePath());
+
+ Logger::instance()->postOptionalGuiLog(Theme::instance()->appNameGUI(), message);
+}
+
void Folder::scheduleThisFolderSoon()
{
if (!_scheduleSelfTimer.isActive()) {
diff --git a/src/gui/folder.h b/src/gui/folder.h
index 2bd8a2882..276cb2d9d 100644
--- a/src/gui/folder.h
+++ b/src/gui/folder.h
@@ -328,6 +328,9 @@ private slots:
*/
void slotFolderConflicts(const QString &folder, const QStringList &conflictPaths);
+ /** Warn users if they create a file or folder that is selective-sync excluded */
+ void warnOnNewExcludedItem(const SyncJournalFileRecord &record, const QStringRef &path);
+
private:
bool reloadExcludes();
diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp
index ec2104c7e..2e784101b 100644
--- a/src/gui/folderstatusmodel.cpp
+++ b/src/gui/folderstatusmodel.cpp
@@ -245,9 +245,15 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
} else if (status == SyncResult::Undefined) {
return theme->syncStateIcon(SyncResult::SyncRunning);
} else {
- // keep the previous icon for the prepare phase.
- if (status == SyncResult::Problem) {
- return theme->syncStateIcon(SyncResult::Success);
+ // The "Problem" *result* just means some files weren't
+ // synced, so we show "Success" in these cases. But we
+ // do use the "Problem" *icon* for unresolved conflicts.
+ if (status == SyncResult::Success || status == SyncResult::Problem) {
+ if (f->syncResult().hasUnresolvedConflicts()) {
+ return theme->syncStateIcon(SyncResult::Problem);
+ } else {
+ return theme->syncStateIcon(SyncResult::Success);
+ }
} else {
return theme->syncStateIcon(status);
}
@@ -526,7 +532,7 @@ bool FolderStatusModel::canFetchMore(const QModelIndex &parent) const
return false;
}
auto info = infoForIndex(parent);
- if (!info || info->_fetched || info->_fetching)
+ if (!info || info->_fetched || info->_fetchingJob)
return false;
if (info->_hasError) {
// Keep showing the error to the user, it will be hidden when the account reconnects
@@ -540,10 +546,9 @@ void FolderStatusModel::fetchMore(const QModelIndex &parent)
{
auto info = infoForIndex(parent);
- if (!info || info->_fetched || info->_fetching)
+ if (!info || info->_fetched || info->_fetchingJob)
return;
info->resetSubs(this, parent);
- info->_fetching = true;
QString path = info->_folder->remotePath();
if (info->_path != QLatin1String("/")) {
if (!path.endsWith(QLatin1Char('/'))) {
@@ -552,6 +557,7 @@ void FolderStatusModel::fetchMore(const QModelIndex &parent)
path += info->_path;
}
LsColJob *job = new LsColJob(_accountState->account(), path, this);
+ info->_fetchingJob = job;
job->setProperties(QList<QByteArray>() << "resourcetype"
<< "http://owncloud.org/ns:size"
<< "http://owncloud.org/ns:permissions");
@@ -596,18 +602,18 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
if (!parentInfo) {
return;
}
- ASSERT(parentInfo->_fetching); // we should only get a result if we were doing a fetch
+ ASSERT(parentInfo->_fetchingJob == job);
ASSERT(parentInfo->_subs.isEmpty());
if (parentInfo->hasLabel()) {
beginRemoveRows(idx, 0, 0);
- parentInfo->_lastErrorString.clear();
parentInfo->_hasError = false;
parentInfo->_fetchingLabel = false;
endRemoveRows();
}
- parentInfo->_fetching = false;
+ parentInfo->_lastErrorString.clear();
+ parentInfo->_fetchingJob = nullptr;
parentInfo->_fetched = true;
QUrl url = parentInfo->_folder->remoteUrl();
@@ -1197,7 +1203,7 @@ void FolderStatusModel::slotShowFetchProgress()
if (it.value().elapsed() > 800) {
auto idx = it.key();
auto *info = infoForIndex(idx);
- if (info && info->_fetching) {
+ if (info && info->_fetchingJob) {
bool add = !info->hasLabel();
if (add) {
beginInsertRows(idx, 0, 0);
@@ -1220,7 +1226,7 @@ bool FolderStatusModel::SubFolderInfo::hasLabel() const
void FolderStatusModel::SubFolderInfo::resetSubs(FolderStatusModel *model, QModelIndex index)
{
_fetched = false;
- _fetching = false;
+ delete _fetchingJob;
if (hasLabel()) {
model->beginRemoveRows(index, 0, 0);
_fetchingLabel = false;
diff --git a/src/gui/folderstatusmodel.h b/src/gui/folderstatusmodel.h
index 7cdf49c7f..dc451f1e5 100644
--- a/src/gui/folderstatusmodel.h
+++ b/src/gui/folderstatusmodel.h
@@ -20,6 +20,7 @@
#include <QLoggingCategory>
#include <QVector>
#include <QElapsedTimer>
+#include <QPointer>
class QNetworkReply;
namespace OCC {
@@ -28,6 +29,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcFolderStatus)
class Folder;
class ProgressInfo;
+class LsColJob;
/**
* @brief The FolderStatusModel class
@@ -59,7 +61,6 @@ public:
, _size(0)
, _isExternal(false)
, _fetched(false)
- , _fetching(false)
, _hasError(false)
, _fetchingLabel(false)
, _isUndecided(false)
@@ -75,7 +76,7 @@ public:
bool _isExternal;
bool _fetched; // If we did the LSCOL for this folder already
- bool _fetching; // Whether a LSCOL job is currently running
+ QPointer<LsColJob> _fetchingJob; // Currently running LsColJob
bool _hasError; // If the last fetching job ended in an error
QString _lastErrorString;
bool _fetchingLabel; // Whether a 'fetching in progress' label is shown.
diff --git a/src/gui/generalsettings.cpp b/src/gui/generalsettings.cpp
index 69baaa84b..e224fca47 100644
--- a/src/gui/generalsettings.cpp
+++ b/src/gui/generalsettings.cpp
@@ -55,7 +55,7 @@ GeneralSettings::GeneralSettings(QWidget *parent)
_ui->aboutGroupBox->hide();
} else {
_ui->aboutLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
- _ui->aboutLabel->setText(about);
+ _ui->aboutLabel->setText("<qt><style> p{padding-bottom:0; margin-bottom:0;padding-top:2; margin-top: 2; font-size:smaller;};</style>"+about+"</qt>");
_ui->aboutLabel->setWordWrap(true);
_ui->aboutLabel->setOpenExternalLinks(true);
}
diff --git a/src/gui/logbrowser.cpp b/src/gui/logbrowser.cpp
index a7303b0f0..7abfeb22d 100644
--- a/src/gui/logbrowser.cpp
+++ b/src/gui/logbrowser.cpp
@@ -96,6 +96,20 @@ LogBrowser::LogBrowser(QWidget *parent)
mainLayout->addWidget(btnbox);
+ // button to permanently save logs
+ _permanentLogging = new QCheckBox;
+ _permanentLogging->setText(tr("Permanently save logs"));
+ _permanentLogging->setToolTip(
+ tr("When this option is enabled and no other logging is configured, "
+ "logs will be written to a temporary folder and expire after a few hours. "
+ "This setting persists across client restarts.\n"
+ "\n"
+ "Logs will be written to %1")
+ .arg(Logger::instance()->temporaryFolderLogDirPath()));
+ _permanentLogging->setChecked(ConfigFile().automaticLogDir());
+ btnbox->addButton(_permanentLogging, QDialogButtonBox::ActionRole);
+ connect(_permanentLogging, &QCheckBox::toggled, this, &LogBrowser::togglePermanentLogging);
+
// clear button
_clearBtn = new QPushButton;
_clearBtn->setText(tr("Clear"));
@@ -216,4 +230,18 @@ void LogBrowser::slotClearLog()
_logWidget->clear();
}
+void LogBrowser::togglePermanentLogging(bool enabled)
+{
+ ConfigFile().setAutomaticLogDir(enabled);
+
+ auto logger = Logger::instance();
+ if (enabled) {
+ if (!logger->isLoggingToFile()) {
+ logger->setupTemporaryFolderLogDir();
+ }
+ } else {
+ logger->disableTemporaryFolderLogDir();
+ }
+}
+
} // namespace
diff --git a/src/gui/logbrowser.h b/src/gui/logbrowser.h
index a1c5c6da0..b7e2d58ac 100644
--- a/src/gui/logbrowser.h
+++ b/src/gui/logbrowser.h
@@ -66,11 +66,13 @@ protected slots:
void search(const QString &);
void slotSave();
void slotClearLog();
+ void togglePermanentLogging(bool enabled);
private:
LogWidget *_logWidget;
QLineEdit *_findTermEdit;
QCheckBox *_logDebugCheckBox;
+ QCheckBox *_permanentLogging;
QPushButton *_saveBtn;
QPushButton *_clearBtn;
QLabel *_statusLabel;
diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp
index cd879451e..04573f74a 100644
--- a/src/gui/notificationwidget.cpp
+++ b/src/gui/notificationwidget.cpp
@@ -44,7 +44,16 @@ void NotificationWidget::setActivity(const Activity &activity)
_ui._subjectLabel->setVisible(!activity._subject.isEmpty());
_ui._messageLabel->setVisible(!activity._message.isEmpty());
- _ui._subjectLabel->setText(activity._subject);
+ if (activity._link.isEmpty()) {
+ _ui._subjectLabel->setText(activity._subject);
+ } else {
+ _ui._subjectLabel->setText( QString("<a href=\"%1\">%2</a>")
+ .arg(activity._link.toString(QUrl::FullyEncoded),
+ activity._subject.toHtmlEscaped() ));
+ _ui._subjectLabel->setTextFormat(Qt::RichText);
+ _ui._subjectLabel->setOpenExternalLinks(true);
+ }
+
_ui._messageLabel->setText(activity._message);
_ui._notifIcon->setPixmap(QPixmap(":/client/resources/bell.png"));
diff --git a/src/gui/ocsshareejob.cpp b/src/gui/ocsshareejob.cpp
index 40bb70d91..0d1b6d44b 100644
--- a/src/gui/ocsshareejob.cpp
+++ b/src/gui/ocsshareejob.cpp
@@ -14,6 +14,8 @@
#include "ocsshareejob.h"
+#include <QJsonDocument>
+
namespace OCC {
OcsShareeJob::OcsShareeJob(AccountPtr account)
diff --git a/src/gui/ocssharejob.h b/src/gui/ocssharejob.h
index 09c69410d..5760085fc 100644
--- a/src/gui/ocssharejob.h
+++ b/src/gui/ocssharejob.h
@@ -21,6 +21,8 @@
#include <QList>
#include <QPair>
+class QJsonDocument;
+
namespace OCC {
/**
diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp
index 16c82f19a..e6e520401 100644
--- a/src/gui/owncloudgui.cpp
+++ b/src/gui/owncloudgui.cpp
@@ -165,7 +165,10 @@ void ownCloudGui::slotSyncStateChange(Folder *folder)
qCInfo(lcApplication) << "Sync state changed for folder " << folder->remoteUrl().toString() << ": " << result.statusString();
- if (result.status() == SyncResult::Success || result.status() == SyncResult::Error) {
+ if (result.status() == SyncResult::Success
+ || result.status() == SyncResult::Problem
+ || result.status() == SyncResult::SyncAbortRequested
+ || result.status() == SyncResult::Error) {
Logger::instance()->enterNextLogFile();
}
@@ -1029,7 +1032,7 @@ void ownCloudGui::raiseDialog(QWidget *raiseWidget)
}
-void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath)
+void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage)
{
QString file;
const auto folder = FolderMan::instance()->folderForPath(localPath, &file);
@@ -1073,7 +1076,7 @@ void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &l
w = _shareDialogs[localPath];
} else {
qCInfo(lcApplication) << "Opening share dialog" << sharePath << localPath << maxSharingPermissions;
- w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions, fileRecord.numericFileId());
+ w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions, fileRecord.numericFileId(), startPage);
w->setAttribute(Qt::WA_DeleteOnClose, true);
_shareDialogs[localPath] = w;
diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h
index d1b60ae0e..9bc136472 100644
--- a/src/gui/owncloudgui.h
+++ b/src/gui/owncloudgui.h
@@ -37,6 +37,11 @@ class Application;
class LogBrowser;
class AccountState;
+enum class ShareDialogStartPage {
+ UsersAndGroups,
+ PublicLinks,
+};
+
/**
* @brief The ownCloudGui class
* @ingroup gui
@@ -93,7 +98,7 @@ public slots:
* localPath is the absolute local path to it (so not relative
* to the folder).
*/
- void slotShowShareDialog(const QString &sharePath, const QString &localPath);
+ void slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage);
void slotRemoveDestroyedShareDialogs();
diff --git a/src/gui/sharedialog.cpp b/src/gui/sharedialog.cpp
index 5c070a125..7b4610500 100644
--- a/src/gui/sharedialog.cpp
+++ b/src/gui/sharedialog.cpp
@@ -39,6 +39,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
const QString &localPath,
SharePermissions maxSharingPermissions,
const QByteArray &numericFileId,
+ ShareDialogStartPage startPage,
QWidget *parent)
: QDialog(parent)
, _ui(new Ui::ShareDialog)
@@ -47,6 +48,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
, _localPath(localPath)
, _maxSharingPermissions(maxSharingPermissions)
, _privateLinkUrl(accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded))
+ , _startPage(startPage)
, _linkWidget(NULL)
, _userGroupWidget(NULL)
, _progressIndicator(NULL)
@@ -217,6 +219,9 @@ void ShareDialog::showSharingUi()
_linkWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
_ui->shareWidgets->addTab(_linkWidget, tr("Public Links"));
_linkWidget->getShares();
+
+ if (_startPage == ShareDialogStartPage::PublicLinks)
+ _ui->shareWidgets->setCurrentWidget(_linkWidget);
}
}
diff --git a/src/gui/sharedialog.h b/src/gui/sharedialog.h
index 8af3f3fff..7aa93cfde 100644
--- a/src/gui/sharedialog.h
+++ b/src/gui/sharedialog.h
@@ -17,6 +17,7 @@
#include "accountstate.h"
#include "sharepermissions.h"
+#include "owncloudgui.h"
#include <QPointer>
#include <QString>
@@ -44,6 +45,7 @@ public:
const QString &localPath,
SharePermissions maxSharingPermissions,
const QByteArray &numericFileId,
+ ShareDialogStartPage startPage,
QWidget *parent = 0);
~ShareDialog();
@@ -64,6 +66,7 @@ private:
SharePermissions _maxSharingPermissions;
QByteArray _numericFileId;
QString _privateLinkUrl;
+ ShareDialogStartPage _startPage;
ShareLinkWidget *_linkWidget;
ShareUserGroupWidget *_userGroupWidget;
diff --git a/src/gui/sharelinkwidget.ui b/src/gui/sharelinkwidget.ui
index ef321bfaf..5c4b2e648 100644
--- a/src/gui/sharelinkwidget.ui
+++ b/src/gui/sharelinkwidget.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>394</width>
- <height>534</height>
+ <width>441</width>
+ <height>568</height>
</rect>
</property>
<property name="windowTitle">
@@ -94,7 +94,7 @@
<property name="bottomMargin">
<number>0</number>
</property>
- <item row="7" column="0">
+ <item row="6" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_expire">
<property name="leftMargin">
<number>0</number>
@@ -115,7 +115,7 @@
</item>
</layout>
</item>
- <item row="5" column="0">
+ <item row="4" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>20</number>
@@ -142,7 +142,7 @@
</item>
</layout>
</item>
- <item row="4" column="0">
+ <item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_password">
<item>
<widget class="QCheckBox" name="checkBox_password">
@@ -173,13 +173,6 @@
</layout>
</item>
<item row="2" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Link properties:</string>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
<widget class="QWidget" name="widget_editing" native="true">
<layout class="QGridLayout" name="layout_editing">
<property name="leftMargin">
@@ -203,7 +196,7 @@
</sizepolicy>
</property>
<property name="text">
- <string>Users can view and download contents.</string>
+ <string>Recipients can view or download contents.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -252,14 +245,14 @@
<item row="0" column="0">
<widget class="QRadioButton" name="radio_readOnly">
<property name="text">
- <string>Read only</string>
+ <string>Download / View</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QRadioButton" name="radio_readWrite">
<property name="text">
- <string>Read &amp;&amp; Write</string>
+ <string>Download / View / Upload</string>
</property>
</widget>
</item>
@@ -272,7 +265,7 @@
</sizepolicy>
</property>
<property name="text">
- <string>Users can view, download, edit and upload contents.</string>
+ <string>Recipients can view, download, edit, delete and upload contents.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp
index 1a663ad05..585e4e833 100644
--- a/src/gui/socketapi.cpp
+++ b/src/gui/socketapi.cpp
@@ -32,6 +32,9 @@
#include "capabilities.h"
#include "common/asserts.h"
#include "guiutility.h"
+#ifndef OWNCLOUD_TEST
+#include "sharemanager.h"
+#endif
#include <array>
#include <QBitArray>
@@ -45,6 +48,7 @@
#include <QApplication>
#include <QLocalSocket>
#include <QStringBuilder>
+#include <QMessageBox>
#include <QClipboard>
@@ -82,6 +86,7 @@ static QString buildMessage(const QString &verb, const QString &path, const QStr
namespace OCC {
Q_LOGGING_CATEGORY(lcSocketApi, "gui.socketapi", QtInfoMsg)
+Q_LOGGING_CATEGORY(lcPublicLink, "gui.socketapi.publiclink", QtInfoMsg)
class BloomFilter
{
@@ -181,6 +186,10 @@ SocketApi::SocketApi(QObject *parent)
// Example for developer builds (with ad-hoc signing identity): "" "com.owncloud.desktopclient" ".socketApi"
// Example for official signed packages: "9B5WD74GWJ." "com.owncloud.desktopclient" ".socketApi"
socketPath = SOCKETAPI_TEAM_IDENTIFIER_PREFIX APPLICATION_REV_DOMAIN ".socketApi";
+#ifdef Q_OS_MAC
+ // Tell Finder to use the Extension (checking it from System Preferences -> Extensions)
+ system("pluginkit -e use -i " APPLICATION_REV_DOMAIN ".FinderSyncExt &");
+#endif
} else if (Utility::isLinux() || Utility::isBSD()) {
QString runtimeDir;
runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
@@ -218,10 +227,17 @@ SocketApi::~SocketApi()
// All remaining sockets will be destroyed with _localServer, their parent
ASSERT(_listeners.isEmpty() || _listeners.first().socket->parent() == &_localServer);
_listeners.clear();
+
+#ifdef Q_OS_MAC
+ // Unload the extension (uncheck from System Preferences -> Extensions)
+ system("pluginkit -e ignore -i " APPLICATION_REV_DOMAIN ".FinderSyncExt &");
+#endif
}
void SocketApi::slotNewConnection()
{
+ // Note that on macOS this is not actually a line-based QIODevice, it's a SocketApiSocket which is our
+ // custom message based macOS IPC.
QIODevice *socket = _localServer.nextPendingConnection();
if (!socket) {
@@ -341,6 +357,49 @@ void SocketApi::broadcastMessage(const QString &msg, bool doWait)
}
}
+void SocketApi::processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage)
+{
+ auto theme = Theme::instance();
+
+ auto fileData = FileData::get(localFile);
+ auto shareFolder = fileData.folder;
+ if (!shareFolder) {
+ const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile);
+ // files that are not within a sync folder are not synced.
+ listener->sendMessage(message);
+ } else if (!shareFolder->accountState()->isConnected()) {
+ const QString message = QLatin1String("SHARE:NOTCONNECTED:") + QDir::toNativeSeparators(localFile);
+ // if the folder isn't connected, don't open the share dialog
+ listener->sendMessage(message);
+ } else if (!theme->linkSharing() && (!theme->userGroupSharing() || shareFolder->accountState()->account()->serverVersionInt() < Account::makeServerVersion(8, 2, 0))) {
+ const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile);
+ listener->sendMessage(message);
+ } else {
+ SyncFileStatus fileStatus = fileData.syncFileStatus();
+
+ // Verify the file is on the server (to our knowledge of course)
+ if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) {
+ const QString message = QLatin1String("SHARE:NOTSYNCED:") + QDir::toNativeSeparators(localFile);
+ listener->sendMessage(message);
+ return;
+ }
+
+ auto &remotePath = fileData.accountRelativePath;
+
+ // Can't share root folder
+ if (remotePath == "/") {
+ const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:") + QDir::toNativeSeparators(localFile);
+ listener->sendMessage(message);
+ return;
+ }
+
+ const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile);
+ listener->sendMessage(message);
+
+ emit shareCommandReceived(remotePath, fileData.localPath, startPage);
+ }
+}
+
void SocketApi::broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus)
{
QString msg = buildMessage(QLatin1String("STATUS"), systemPath, fileStatus.toSocketAPIString());
@@ -361,23 +420,17 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList
{
QString statusString;
- QString relativePath;
- Folder *syncFolder = FolderMan::instance()->folderForPath(argument, &relativePath);
- if (!syncFolder) {
+ auto fileData = FileData::get(argument);
+ if (!fileData.folder) {
// this can happen in offline mode e.g.: nothing to worry about
statusString = QLatin1String("NOP");
} else {
- QString systemPath = QDir::cleanPath(argument);
- if (systemPath.endsWith(QLatin1Char('/'))) {
- systemPath.truncate(systemPath.length() - 1);
- qCWarning(lcSocketApi) << "Removed trailing slash for directory: " << systemPath << "Status pushes won't have one.";
- }
// The user probably visited this directory in the file shell.
// Let the listener know that it should now send status pushes for sibblings of this file.
- QString directory = systemPath.left(systemPath.lastIndexOf('/'));
+ QString directory = fileData.localPath.left(fileData.localPath.lastIndexOf('/'));
listener->registerMonitoredDirectory(qHash(directory));
- SyncFileStatus fileStatus = syncFolder->syncEngine().syncFileStatusTracker().fileStatus(relativePath);
+ SyncFileStatus fileStatus = fileData.syncFileStatus();
statusString = fileStatus.toSocketAPIString();
}
@@ -387,142 +440,220 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList
void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener)
{
- auto theme = Theme::instance();
+ processShareRequest(localFile, listener, ShareDialogStartPage::UsersAndGroups);
+}
- QString file;
- Folder *shareFolder = FolderMan::instance()->folderForPath(localFile, &file);
- if (!shareFolder) {
- const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile);
- // files that are not within a sync folder are not synced.
+void SocketApi::command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener)
+{
+ processShareRequest(localFile, listener, ShareDialogStartPage::PublicLinks);
+}
+
+void SocketApi::command_VERSION(const QString &, SocketListener *listener)
+{
+ listener->sendMessage(QLatin1String("VERSION:" MIRALL_VERSION_STRING ":" MIRALL_SOCKET_API_VERSION));
+}
+
+void SocketApi::command_SHARE_STATUS(const QString &localFile, SocketListener *listener)
+{
+ auto fileData = FileData::get(localFile);
+ if (!fileData.folder) {
+ const QString message = QLatin1String("SHARE_STATUS:NOP:") + QDir::toNativeSeparators(localFile);
listener->sendMessage(message);
- } else if (!shareFolder->accountState()->isConnected()) {
- const QString message = QLatin1String("SHARE:NOTCONNECTED:") + QDir::toNativeSeparators(localFile);
- // if the folder isn't connected, don't open the share dialog
+ return;
+ }
+
+ SyncFileStatus fileStatus = fileData.syncFileStatus();
+
+ // Verify the file is on the server (to our knowledge of course)
+ if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) {
+ const QString message = QLatin1String("SHARE_STATUS:NOTSYNCED:") + QDir::toNativeSeparators(localFile);
listener->sendMessage(message);
- } else if (!theme->linkSharing() && (!theme->userGroupSharing() || shareFolder->accountState()->account()->serverVersionInt() < Account::makeServerVersion(8, 2, 0))) {
- const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile);
+ return;
+ }
+
+ const Capabilities capabilities = fileData.folder->accountState()->account()->capabilities();
+
+ if (!capabilities.shareAPI()) {
+ const QString message = QLatin1String("SHARE_STATUS:DISABLED:") + QDir::toNativeSeparators(localFile);
listener->sendMessage(message);
} else {
- const QString localFileClean = QDir::cleanPath(localFile);
- SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file);
+ auto theme = Theme::instance();
+ QString available;
- // Verify the file is on the server (to our knowledge of course)
- if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) {
- const QString message = QLatin1String("SHARE:NOTSYNCED:") + QDir::toNativeSeparators(localFile);
- listener->sendMessage(message);
- return;
+ if (theme->userGroupSharing()) {
+ available = "USER,GROUP";
}
- const QString remotePath = QDir(shareFolder->remotePath()).filePath(file);
+ if (theme->linkSharing() && capabilities.sharePublicLink()) {
+ if (available.isEmpty()) {
+ available = "LINK";
+ } else {
+ available += ",LINK";
+ }
+ }
- // Can't share root folder
- if (remotePath == "/") {
- const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:") + QDir::toNativeSeparators(localFile);
+ if (available.isEmpty()) {
+ const QString message = QLatin1String("SHARE_STATUS:DISABLED") + ":" + QDir::toNativeSeparators(localFile);
+ listener->sendMessage(message);
+ } else {
+ const QString message = QLatin1String("SHARE_STATUS:") + available + ":" + QDir::toNativeSeparators(localFile);
listener->sendMessage(message);
- return;
}
-
- const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile);
- listener->sendMessage(message);
-
- emit shareCommandReceived(remotePath, localFileClean);
}
}
-void SocketApi::command_VERSION(const QString &, SocketListener *listener)
+void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener)
{
- listener->sendMessage(QLatin1String("VERSION:" MIRALL_VERSION_STRING ":" MIRALL_SOCKET_API_VERSION));
+ listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is ownCloud").arg(Theme::instance()->appNameGUI()));
}
-void SocketApi::command_SHARE_STATUS(const QString &localFile, SocketListener *listener)
+// don't pull the share manager into socketapi unittests
+#ifndef OWNCLOUD_TEST
+
+class GetOrCreatePublicLinkShare : public QObject
{
- QString file;
- Folder *shareFolder = FolderMan::instance()->folderForPath(localFile, &file);
+ Q_OBJECT
+public:
+ GetOrCreatePublicLinkShare(const AccountPtr &account, const QString &localFile,
+ std::function<void(const QString &link)> targetFun, QObject *parent)
+ : QObject(parent)
+ , _shareManager(account)
+ , _localFile(localFile)
+ , _targetFun(targetFun)
+ {
+ connect(&_shareManager, &ShareManager::sharesFetched,
+ this, &GetOrCreatePublicLinkShare::sharesFetched);
+ connect(&_shareManager, &ShareManager::linkShareCreated,
+ this, &GetOrCreatePublicLinkShare::linkShareCreated);
+ connect(&_shareManager, &ShareManager::serverError,
+ this, &GetOrCreatePublicLinkShare::serverError);
+ }
- if (!shareFolder) {
- const QString message = QLatin1String("SHARE_STATUS:NOP:") + QDir::toNativeSeparators(localFile);
- listener->sendMessage(message);
- } else {
- SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file);
+ void run()
+ {
+ qCDebug(lcPublicLink) << "Fetching shares";
+ _shareManager.fetchShares(_localFile);
+ }
- // Verify the file is on the server (to our knowledge of course)
- if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) {
- const QString message = QLatin1String("SHARE_STATUS:NOTSYNCED:") + QDir::toNativeSeparators(localFile);
- listener->sendMessage(message);
- return;
+private slots:
+ void sharesFetched(const QList<QSharedPointer<Share>> &shares)
+ {
+ auto shareName = SocketApi::tr("Context menu share");
+ // If there already is a context menu share, reuse it
+ for (const auto &share : shares) {
+ const auto linkShare = qSharedPointerDynamicCast<LinkShare>(share);
+ if (!linkShare)
+ continue;
+
+ if (linkShare->getName() == shareName) {
+ qCDebug(lcPublicLink) << "Found existing share, reusing";
+ return success(linkShare->getLink().toString());
+ }
}
- const Capabilities capabilities = shareFolder->accountState()->account()->capabilities();
+ // otherwise create a new one
+ qCDebug(lcPublicLink) << "Creating new share";
+ _shareManager.createLinkShare(_localFile, shareName, QString());
+ }
- if (!capabilities.shareAPI()) {
- const QString message = QLatin1String("SHARE_STATUS:DISABLED:") + QDir::toNativeSeparators(localFile);
- listener->sendMessage(message);
- } else {
- auto theme = Theme::instance();
- QString available;
+ void linkShareCreated(const QSharedPointer<LinkShare> &share)
+ {
+ qCDebug(lcPublicLink) << "New share created";
+ success(share->getLink().toString());
+ }
- if (theme->userGroupSharing()) {
- available = "USER,GROUP";
- }
+ void serverError(int code, const QString &message)
+ {
+ qCWarning(lcPublicLink) << "Share fetch/create error" << code << message;
+ QMessageBox::warning(
+ 0,
+ tr("Sharing error"),
+ tr("Could not retrieve or create the public link share. Error:\n\n%1").arg(message),
+ QMessageBox::Ok,
+ QMessageBox::NoButton);
+ deleteLater();
+ }
- if (theme->linkSharing() && capabilities.sharePublicLink()) {
- if (available.isEmpty()) {
- available = "LINK";
- } else {
- available += ",LINK";
- }
- }
+private:
+ void success(const QString &link)
+ {
+ _targetFun(link);
+ deleteLater();
+ }
- if (available.isEmpty()) {
- const QString message = QLatin1String("SHARE_STATUS:DISABLED") + ":" + QDir::toNativeSeparators(localFile);
- listener->sendMessage(message);
- } else {
- const QString message = QLatin1String("SHARE_STATUS:") + available + ":" + QDir::toNativeSeparators(localFile);
- listener->sendMessage(message);
- }
- }
+ ShareManager _shareManager;
+ QString _localFile;
+ std::function<void(const QString &url)> _targetFun;
+};
+
+#else
+
+class GetOrCreatePublicLinkShare : public QObject
+{
+ Q_OBJECT
+public:
+ GetOrCreatePublicLinkShare(const AccountPtr &, const QString &,
+ std::function<void(const QString &link)>, QObject *)
+ {
}
-}
-void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener)
+ void run()
+ {
+ }
+};
+
+#endif
+
+void SocketApi::command_COPY_PUBLIC_LINK(const QString &localFile, SocketListener *)
{
- listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is ownCloud").arg(Theme::instance()->appNameGUI()));
+ auto fileData = FileData::get(localFile);
+ if (!fileData.folder)
+ return;
+
+ AccountPtr account = fileData.folder->accountState()->account();
+ auto job = new GetOrCreatePublicLinkShare(account, fileData.accountRelativePath, [](const QString &url) { copyUrlToClipboard(url); }, this);
+ job->run();
}
// Fetches the private link url asynchronously and then calls the target slot
-static void fetchPrivateLinkUrlHelper(const QString &localFile, SocketApi *target, void (SocketApi::*targetFun)(const QString &url) const)
+void SocketApi::fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun)
{
- QString file;
- Folder *shareFolder = FolderMan::instance()->folderForPath(localFile, &file);
- if (!shareFolder) {
+ auto fileData = FileData::get(localFile);
+ if (!fileData.folder) {
qCWarning(lcSocketApi) << "Unknown path" << localFile;
return;
}
- AccountPtr account = shareFolder->accountState()->account();
-
- SyncJournalFileRecord rec;
- if (!shareFolder->journalDb()->getFileRecord(file, &rec) || !rec.isValid())
+ auto record = fileData.journalRecord();
+ if (!record.isValid())
return;
- fetchPrivateLinkUrl(account, file, rec.numericFileId(), target, [=](const QString &url) {
- (target->*targetFun)(url);
- });
+ fetchPrivateLinkUrl(
+ fileData.folder->accountState()->account(),
+ fileData.accountRelativePath,
+ record.numericFileId(),
+ this,
+ targetFun);
}
void SocketApi::command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *)
{
- fetchPrivateLinkUrlHelper(localFile, this, &SocketApi::copyPrivateLinkToClipboard);
+ fetchPrivateLinkUrlHelper(localFile, &SocketApi::copyUrlToClipboard);
}
void SocketApi::command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *)
{
- fetchPrivateLinkUrlHelper(localFile, this, &SocketApi::emailPrivateLink);
+ fetchPrivateLinkUrlHelper(localFile, &SocketApi::emailPrivateLink);
}
void SocketApi::command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *)
{
- fetchPrivateLinkUrlHelper(localFile, this, &SocketApi::openPrivateLink);
+ fetchPrivateLinkUrlHelper(localFile, &SocketApi::openPrivateLink);
+}
+
+void SocketApi::copyUrlToClipboard(const QString &link)
+{
+ QApplication::clipboard()->setText(link);
}
void SocketApi::command_DOWNLOAD_PLACEHOLDER(const QString &filesArg, SocketListener *)
@@ -541,12 +672,7 @@ void SocketApi::command_DOWNLOAD_PLACEHOLDER(const QString &filesArg, SocketList
}
}
-void SocketApi::copyPrivateLinkToClipboard(const QString &link) const
-{
- QApplication::clipboard()->setText(link);
-}
-
-void SocketApi::emailPrivateLink(const QString &link) const
+void SocketApi::emailPrivateLink(const QString &link)
{
Utility::openEmailComposer(
tr("I shared something with you"),
@@ -554,7 +680,7 @@ void SocketApi::emailPrivateLink(const QString &link) const
0);
}
-void OCC::SocketApi::openPrivateLink(const QString &link) const
+void OCC::SocketApi::openPrivateLink(const QString &link)
{
Utility::openBrowser(link, nullptr);
}
@@ -576,6 +702,79 @@ void SocketApi::command_GET_STRINGS(const QString &argument, SocketListener *lis
listener->sendMessage(QString("GET_STRINGS:END"));
}
+void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener)
+{
+ auto record = fileData.journalRecord();
+ bool isOnTheServer = record.isValid();
+ auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:");
+
+ auto capabilities = fileData.folder->accountState()->account()->capabilities();
+ auto theme = Theme::instance();
+ if (!capabilities.shareAPI() || !(theme->userGroupSharing() || (theme->linkSharing() && capabilities.sharePublicLink())))
+ return;
+
+ // If sharing is globally disabled, do not show any sharing entries.
+ // If there is no permission to share for this file, add a disabled entry saying so
+ if (isOnTheServer && !record._remotePerm.isNull() && !record._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
+ listener->sendMessage(QLatin1String("MENU_ITEM:DISABLED:d:") + tr("Resharing this file is not allowed"));
+ } else {
+ listener->sendMessage(QLatin1String("MENU_ITEM:SHARE") + flagString + tr("Share..."));
+
+ // Do we have public links?
+ bool publicLinksEnabled = theme->linkSharing() && capabilities.sharePublicLink();
+
+ // Is is possible to create a public link without user choices?
+ bool canCreateDefaultPublicLink = publicLinksEnabled
+ && !capabilities.sharePublicLinkEnforceExpireDate()
+ && !capabilities.sharePublicLinkEnforcePassword();
+
+ if (canCreateDefaultPublicLink) {
+ listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PUBLIC_LINK") + flagString + tr("Copy public link to clipboard"));
+ } else if (publicLinksEnabled) {
+ listener->sendMessage(QLatin1String("MENU_ITEM:MANAGE_PUBLIC_LINKS") + flagString + tr("Copy public link to clipboard"));
+ }
+ }
+
+ listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PRIVATE_LINK") + flagString + tr("Copy private link to clipboard"));
+
+ // Disabled: only providing email option for private links would look odd,
+ // and the copy option is more general.
+ //listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email..."));
+}
+
+SocketApi::FileData SocketApi::FileData::get(const QString &localFile)
+{
+ FileData data;
+
+ data.localPath = QDir::cleanPath(localFile);
+ if (data.localPath.endsWith(QLatin1Char('/')))
+ data.localPath.chop(1);
+
+ data.folder = FolderMan::instance()->folderForPath(data.localPath, &data.folderRelativePath);
+ if (!data.folder)
+ return data;
+
+ data.accountRelativePath = QDir(data.folder->remotePath()).filePath(data.folderRelativePath);
+
+ return data;
+}
+
+SyncFileStatus SocketApi::FileData::syncFileStatus() const
+{
+ if (!folder)
+ return SyncFileStatus::StatusNone;
+ return folder->syncEngine().syncFileStatusTracker().fileStatus(folderRelativePath);
+}
+
+SyncJournalFileRecord SocketApi::FileData::journalRecord() const
+{
+ SyncJournalFileRecord record;
+ if (!folder)
+ return record;
+ folder->journalDb()->getFileRecord(folderRelativePath, &record);
+ return record;
+}
+
void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListener *listener)
{
listener->sendMessage(QString("GET_MENU_ITEMS:BEGIN"));
@@ -583,51 +782,33 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
// Find the common sync folder.
// syncFolder will be null if files are in different folders.
- Folder *syncFolder = nullptr;
+ Folder *folder = nullptr;
for (const auto &file : files) {
- auto folder = FolderMan::instance()->folderForPath(file);
- if (folder != syncFolder) {
- if (!syncFolder) {
- syncFolder = folder;
+ auto f = FolderMan::instance()->folderForPath(file);
+ if (f != folder) {
+ if (!folder) {
+ folder = f;
} else {
- syncFolder = nullptr;
+ folder = nullptr;
break;
}
}
}
- // Sharing actions show for single files only
- if (syncFolder && files.size() == 1 && syncFolder->accountState()->isConnected()) {
- QString systemPath = QDir::cleanPath(argument);
- if (systemPath.endsWith(QLatin1Char('/'))) {
- systemPath.truncate(systemPath.length() - 1);
- }
-
- SyncJournalFileRecord rec;
- QString relativePath = systemPath.mid(syncFolder->cleanPath().length() + 1);
- // If the file is on the DB, it is on the server
- bool isOnTheServer = syncFolder->journalDb()->getFileRecord(relativePath, &rec) && rec.isValid();
+ // Some options only show for single files
+ if (files.size() == 1) {
+ FileData fileData = FileData::get(files.first());
+ bool isOnTheServer = fileData.journalRecord().isValid();
auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:");
- auto capabilities = syncFolder->accountState()->account()->capabilities();
- auto theme = Theme::instance();
- if (capabilities.shareAPI() && (theme->userGroupSharing() || (theme->linkSharing() && capabilities.sharePublicLink()))) {
- // If sharing is globally disabled, do not show any sharing entries.
- // If there is no permission to share for this file, add a disabled entry saying so
- if (isOnTheServer && !rec._remotePerm.isNull() && !rec._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
- listener->sendMessage(QLatin1String("MENU_ITEM:DISABLED:d:") + tr("Resharing this file is not allowed"));
- } else {
- listener->sendMessage(QLatin1String("MENU_ITEM:SHARE") + flagString + tr("Share..."));
- }
- listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email..."));
- listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PRIVATE_LINK") + flagString + tr("Copy private link to clipboard"));
+ if (fileData.folder && fileData.folder->accountState()->isConnected()) {
+ sendSharingContextMenuOptions(fileData, listener);
+ listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
}
-
- listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
}
// Placeholder download action
- if (syncFolder) {
+ if (folder) {
auto placeholderSuffix = QStringLiteral(APPLICATION_DOTPLACEHOLDER_SUFFIX);
bool hasPlaceholderFile = false;
for (const auto &file : files) {
@@ -650,3 +831,5 @@ QString SocketApi::buildRegisterPathMessage(const QString &path)
}
} // namespace OCC
+
+#include "socketapi.moc"
diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h
index e3f395ff3..20ab82672 100644
--- a/src/gui/socketapi.h
+++ b/src/gui/socketapi.h
@@ -18,7 +18,8 @@
#include "syncfileitem.h"
#include "syncfilestatus.h"
-// #include "ownsql.h"
+#include "sharedialog.h" // for the ShareDialogStartPage
+#include "common/syncjournalfilerecord.h"
#if defined(Q_OS_MAC)
#include "socketapisocket_mac.h"
@@ -56,7 +57,7 @@ public slots:
void broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus);
signals:
- void shareCommandReceived(const QString &sharePath, const QString &localPath);
+ void shareCommandReceived(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage);
private slots:
void slotNewConnection();
@@ -64,13 +65,31 @@ private slots:
void slotSocketDestroyed(QObject *obj);
void slotReadSocket();
- void copyPrivateLinkToClipboard(const QString &link) const;
- void emailPrivateLink(const QString &link) const;
- void openPrivateLink(const QString &link) const;
+ static void copyUrlToClipboard(const QString &link);
+ static void emailPrivateLink(const QString &link);
+ static void openPrivateLink(const QString &link);
private:
+ // Helper structure for getting information on a file
+ // based on its local path - used for nearly all remote
+ // actions.
+ struct FileData
+ {
+ static FileData get(const QString &localFile);
+ SyncFileStatus syncFileStatus() const;
+ SyncJournalFileRecord journalRecord() const;
+
+ Folder *folder;
+ QString localPath;
+ QString folderRelativePath;
+ QString accountRelativePath;
+ };
+
void broadcastMessage(const QString &msg, bool doWait = false);
+ // opens share dialog, sends reply
+ void processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage);
+
Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener);
Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString &argument, SocketListener *listener);
@@ -81,14 +100,22 @@ private:
// The context menu actions
Q_INVOKABLE void command_SHARE(const QString &localFile, SocketListener *listener);
+ Q_INVOKABLE void command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener);
+ Q_INVOKABLE void command_COPY_PUBLIC_LINK(const QString &localFile, SocketListener *listener);
Q_INVOKABLE void command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *listener);
Q_INVOKABLE void command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *listener);
Q_INVOKABLE void command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *listener);
Q_INVOKABLE void command_DOWNLOAD_PLACEHOLDER(const QString &filesArg, SocketListener *listener);
+ // Fetch the private link and call targetFun
+ void fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun);
+
/** Sends translated/branded strings that may be useful to the integration */
Q_INVOKABLE void command_GET_STRINGS(const QString &argument, SocketListener *listener);
+ // Sends the context menu options relating to sharing to listener
+ void sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener);
+
/** Send the list of menu item. (added in version 1.1)
* argument is a list of files for which the menu should be shown, separated by '\x1e'
* Reply with GET_MENU_ITEMS:BEGIN
diff --git a/src/gui/sslbutton.cpp b/src/gui/sslbutton.cpp
index e387f885e..b41076ba4 100644
--- a/src/gui/sslbutton.cpp
+++ b/src/gui/sslbutton.cpp
@@ -58,7 +58,7 @@ static bool isSelfSigned(const QSslCertificate &certificate)
}
QMenu *SslButton::buildCertMenu(QMenu *parent, const QSslCertificate &cert,
- const QList<QSslCertificate> &userApproved, int pos)
+ const QList<QSslCertificate> &userApproved, int pos, const QList<QSslCertificate> &systemCaCertificates)
{
QString cn = QStringList(cert.subjectInfo(QSslCertificate::CommonName)).join(QChar(';'));
QString ou = QStringList(cert.subjectInfo(QSslCertificate::OrganizationalUnitName)).join(QChar(';'));
@@ -129,7 +129,7 @@ QMenu *SslButton::buildCertMenu(QMenu *parent, const QSslCertificate &cert,
QString certId = cn.isEmpty() ? ou : cn;
- if (QSslConfiguration::systemCaCertificates().contains(cert)) {
+ if (systemCaCertificates.contains(cert)) {
txt += certId;
} else {
if (isSelfSigned(cert)) {
@@ -189,6 +189,10 @@ void SslButton::slotUpdateMenu()
AccountPtr account = _accountState->account();
+ if (account->isHttp2Supported()) {
+ _menu->addAction("HTTP/2")->setEnabled(false);
+ }
+
if (account->url().scheme() == QLatin1String("https")) {
QString sslVersion = account->_sessionCipher.protocolString()
+ ", " + account->_sessionCipher.authenticationMethod()
@@ -232,7 +236,7 @@ void SslButton::slotUpdateMenu()
it.toBack();
int i = 0;
while (it.hasPrevious()) {
- _menu->addMenu(buildCertMenu(_menu, it.previous(), account->approvedCerts(), i));
+ _menu->addMenu(buildCertMenu(_menu, it.previous(), account->approvedCerts(), i, systemCerts));
i++;
}
}
diff --git a/src/gui/sslbutton.h b/src/gui/sslbutton.h
index c48f7c3a7..906f4b846 100644
--- a/src/gui/sslbutton.h
+++ b/src/gui/sslbutton.h
@@ -43,7 +43,7 @@ public slots:
private:
QMenu *buildCertMenu(QMenu *parent, const QSslCertificate &cert,
- const QList<QSslCertificate> &userApproved, int pos);
+ const QList<QSslCertificate> &userApproved, int pos, const QList<QSslCertificate> &systemCaCertificates);
QPointer<AccountState> _accountState;
QMenu *_menu;
};
diff --git a/src/gui/updater/sparkleupdater_mac.mm b/src/gui/updater/sparkleupdater_mac.mm
index a1e3e4f05..88c3541ff 100644
--- a/src/gui/updater/sparkleupdater_mac.mm
+++ b/src/gui/updater/sparkleupdater_mac.mm
@@ -30,6 +30,7 @@
// Only possible in later versions, we're not up to date here.
- (BOOL)updaterMayCheckForUpdates:(SUUpdater *)bundle
{
+ Q_UNUSED(bundle)
qCDebug(OCC::lcUpdater) << "may check: YES";
return YES;
}
@@ -37,17 +38,22 @@
// Sent when a valid update is found by the update driver.
- (void)updater:(SUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)update
{
+ Q_UNUSED(updater)
+ Q_UNUSED(update)
}
// Sent when a valid update is not found.
// Does not seem to get called ever.
- (void)updaterDidNotFindUpdate:(SUUpdater *)update
{
+ Q_UNUSED(update)
}
// Sent immediately before installing the specified update.
- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update
{
+ Q_UNUSED(updater)
+ Q_UNUSED(update)
}
// Tried implementing those methods, but they never ever seem to get called
diff --git a/src/libsync/account.h b/src/libsync/account.h
index 025dca907..a7879f4d0 100644
--- a/src/libsync/account.h
+++ b/src/libsync/account.h
@@ -218,7 +218,7 @@ public:
/** Detects a specific bug in older server versions */
bool rootEtagChangesNotOnlySubFolderEtags();
- /** True when the server supports HTTP2 */
+ /** True when the server connection is using HTTP2 */
bool isHttp2Supported() { return _http2Supported; }
void setHttp2Supported(bool value) { _http2Supported = value; }
diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp
index 82c2bc5f3..d0dadbf0f 100644
--- a/src/libsync/configfile.cpp
+++ b/src/libsync/configfile.cpp
@@ -64,6 +64,7 @@ static const char chunkSizeC[] = "chunkSize";
static const char minChunkSizeC[] = "minChunkSize";
static const char maxChunkSizeC[] = "maxChunkSize";
static const char targetChunkUploadDurationC[] = "targetChunkUploadDuration";
+static const char automaticLogDirC[] = "logToTemporaryLogDir";
static const char showExperimentalOptionsC[] = "showExperimentalOptions";
static const char proxyHostC[] = "Proxy/host";
@@ -737,6 +738,18 @@ void ConfigFile::setCrashReporter(bool enabled)
settings.setValue(QLatin1String(crashReporterC), enabled);
}
+bool ConfigFile::automaticLogDir() const
+{
+ QSettings settings(configFile(), QSettings::IniFormat);
+ return settings.value(QLatin1String(automaticLogDirC), false).toBool();
+}
+
+void ConfigFile::setAutomaticLogDir(bool enabled)
+{
+ QSettings settings(configFile(), QSettings::IniFormat);
+ settings.setValue(QLatin1String(automaticLogDirC), enabled);
+}
+
bool ConfigFile::showExperimentalOptions() const
{
QSettings settings(configFile(), QSettings::IniFormat);
diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h
index 8877bb6dc..99aa6d5f1 100644
--- a/src/libsync/configfile.h
+++ b/src/libsync/configfile.h
@@ -89,6 +89,9 @@ public:
bool crashReporter() const;
void setCrashReporter(bool enabled);
+ bool automaticLogDir() const;
+ void setAutomaticLogDir(bool enabled);
+
// Whether experimental UI options should be shown
bool showExperimentalOptions() const;
diff --git a/src/libsync/logger.cpp b/src/libsync/logger.cpp
index cfba000ed..7033bd851 100644
--- a/src/libsync/logger.cpp
+++ b/src/libsync/logger.cpp
@@ -14,11 +14,15 @@
#include "logger.h"
+#include "config.h"
+
#include <QDir>
#include <QStringList>
#include <QThread>
#include <qmetaobject.h>
+#include <zlib.h>
+
namespace OCC {
static void mirallLogCatcher(QtMsgType type, const QMessageLogContext &ctx, const QString &message)
@@ -95,10 +99,15 @@ void Logger::log(Log log)
*/
bool Logger::isNoop() const
{
- QMutexLocker lock(const_cast<QMutex *>(&_mutex));
+ QMutexLocker lock(&_mutex);
return !_logstream && !_logWindowActivated;
}
+bool Logger::isLoggingToFile() const
+{
+ QMutexLocker lock(&_mutex);
+ return _logstream;
+}
void Logger::doLog(const QString &msg)
{
@@ -181,34 +190,99 @@ void Logger::setLogDebug(bool debug)
_logDebug = debug;
}
+QString Logger::temporaryFolderLogDirPath() const
+{
+ QString dirName = APPLICATION_SHORTNAME + QString("-logdir");
+ return QDir::temp().filePath(dirName);
+}
+
+void Logger::setupTemporaryFolderLogDir()
+{
+ auto dir = temporaryFolderLogDirPath();
+ if (!QDir().mkpath(dir))
+ return;
+ setLogDebug(true);
+ setLogExpire(4 /*hours*/);
+ setLogDir(dir);
+ _temporaryFolderLogDir = true;
+}
+
+void Logger::disableTemporaryFolderLogDir()
+{
+ if (!_temporaryFolderLogDir)
+ return;
+
+ enterNextLogFile();
+ setLogDir(QString());
+ setLogDebug(false);
+ setLogFile(QString());
+ _temporaryFolderLogDir = false;
+}
+
+static bool compressLog(const QString &originalName, const QString &targetName)
+{
+ QFile original(originalName);
+ if (!original.open(QIODevice::ReadOnly))
+ return false;
+ auto compressed = gzopen(targetName.toUtf8(), "wb");
+ if (!compressed) {
+ return false;
+ }
+
+ while (!original.atEnd()) {
+ auto data = original.read(1024 * 1024);
+ auto written = gzwrite(compressed, data.data(), data.size());
+ if (written != data.size()) {
+ gzclose(compressed);
+ return false;
+ }
+ }
+ gzclose(compressed);
+ return true;
+}
+
void Logger::enterNextLogFile()
{
if (!_logDirectory.isEmpty()) {
+
QDir dir(_logDirectory);
if (!dir.exists()) {
dir.mkpath(".");
}
- // Find out what is the file with the highest number if any
- QStringList files = dir.entryList(QStringList("owncloud.log.*"),
+ // Tentative new log name, will be adjusted if one like this already exists
+ QDateTime now = QDateTime::currentDateTime();
+ QString newLogName = now.toString("yyyyMMdd_HHmm") + "_owncloud.log";
+
+ // Expire old log files and deal with conflicts
+ QStringList files = dir.entryList(QStringList("*owncloud.log.*"),
QDir::Files);
- QRegExp rx("owncloud.log.(\\d+)");
- uint maxNumber = 0;
- QDateTime now = QDateTime::currentDateTimeUtc();
+ QRegExp rx(R"(.*owncloud\.log\.(\d+).*)");
+ int maxNumber = -1;
foreach (const QString &s, files) {
- if (rx.exactMatch(s)) {
- maxNumber = qMax(maxNumber, rx.cap(1).toUInt());
- if (_logExpire > 0) {
- QFileInfo fileInfo = dir.absoluteFilePath(s);
- if (fileInfo.lastModified().addSecs(60 * 60 * _logExpire) < now) {
- dir.remove(s);
- }
+ if (_logExpire > 0) {
+ QFileInfo fileInfo(dir.absoluteFilePath(s));
+ if (fileInfo.lastModified().addSecs(60 * 60 * _logExpire) < now) {
+ dir.remove(s);
}
}
+ if (s.startsWith(newLogName) && rx.exactMatch(s)) {
+ maxNumber = qMax(maxNumber, rx.cap(1).toInt());
+ }
}
+ newLogName.append("." + QString::number(maxNumber + 1));
- QString filename = _logDirectory + "/owncloud.log." + QString::number(maxNumber + 1);
- setLogFile(filename);
+ auto previousLog = _logFile.fileName();
+ setLogFile(dir.filePath(newLogName));
+
+ if (!previousLog.isEmpty()) {
+ QString compressedName = previousLog + ".gz";
+ if (compressLog(previousLog, compressedName)) {
+ QFile::remove(previousLog);
+ } else {
+ QFile::remove(compressedName);
+ }
+ }
}
}
diff --git a/src/libsync/logger.h b/src/libsync/logger.h
index 9d6ef6b26..7ae278e49 100644
--- a/src/libsync/logger.h
+++ b/src/libsync/logger.h
@@ -43,6 +43,8 @@ class OWNCLOUDSYNC_EXPORT Logger : public QObject
Q_OBJECT
public:
bool isNoop() const;
+ bool isLoggingToFile() const;
+
void log(Log log);
void doLog(const QString &log);
@@ -65,6 +67,22 @@ public:
bool logDebug() const { return _logDebug; }
void setLogDebug(bool debug);
+ /** Returns where the automatic logdir would be */
+ QString temporaryFolderLogDirPath() const;
+
+ /** Sets up default dir log setup.
+ *
+ * logdir: a temporary folder
+ * logexpire: 4 hours
+ * logdebug: true
+ *
+ * Used in conjunction with ConfigFile::automaticLogDir
+ */
+ void setupTemporaryFolderLogDir();
+
+ /** For switching off via logwindow */
+ void disableTemporaryFolderLogDir();
+
signals:
void logWindowLog(const QString &);
@@ -86,8 +104,9 @@ private:
int _logExpire;
bool _logDebug;
QScopedPointer<QTextStream> _logstream;
- QMutex _mutex;
+ mutable QMutex _mutex;
QString _logDirectory;
+ bool _temporaryFolderLogDir = false;
};
} // namespace OCC
diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp
index a3881cd48..44fdc4553 100644
--- a/src/libsync/networkjobs.cpp
+++ b/src/libsync/networkjobs.cpp
@@ -879,6 +879,8 @@ void DetermineAuthTypeJob::start()
get->setFollowRedirects(false);
}
#else
+ Q_UNUSED(this)
+ Q_UNUSED(get)
Q_UNUSED(target)
#endif
});
diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp
index dafaec99c..3cd8ab0be 100644
--- a/src/libsync/owncloudpropagator.cpp
+++ b/src/libsync/owncloudpropagator.cpp
@@ -517,8 +517,7 @@ bool OwncloudPropagator::localFileNameClash(const QString &relFile)
re = false;
qCWarning(lcPropagator) << "No valid fileinfo";
} else {
- // Need to normalize to composited form because of
- // https://bugreports.qt-project.org/browse/QTBUG-39622
+ // Need to normalize to composited form because of QTBUG-39622/QTBUG-55896
const QString cName = fileInfo.canonicalFilePath().normalized(QString::NormalizationForm_C);
bool equal = (file == cName);
re = (!equal && !cName.endsWith(relFile, Qt::CaseSensitive));
@@ -840,10 +839,13 @@ void PropagatorCompositeJob::slotSubJobFinished(SyncFileItem::Status status)
ASSERT(i >= 0);
_runningJobs.remove(i);
+ // Any sub job error will cause the whole composite to fail. This is important
+ // for knowing whether to update the etag in PropagateDirectory, for example.
if (status == SyncFileItem::FatalError
|| status == SyncFileItem::NormalError
|| status == SyncFileItem::SoftError
- || status == SyncFileItem::DetailError) {
+ || status == SyncFileItem::DetailError
+ || status == SyncFileItem::BlacklistedError) {
_hasError = status;
}
diff --git a/src/libsync/owncloudpropagator.h b/src/libsync/owncloudpropagator.h
index c272a06d2..b4bc470d2 100644
--- a/src/libsync/owncloudpropagator.h
+++ b/src/libsync/owncloudpropagator.h
@@ -156,7 +156,7 @@ class PropagateItemJob : public PropagatorJob
{
Q_OBJECT
protected:
- void done(SyncFileItem::Status status, const QString &errorString = QString());
+ virtual void done(SyncFileItem::Status status, const QString &errorString = QString());
/*
* set a custom restore job message that is used if the restore job succeeded.
diff --git a/src/libsync/owncloudtheme.cpp b/src/libsync/owncloudtheme.cpp
index 873257c19..1e142b962 100644
--- a/src/libsync/owncloudtheme.cpp
+++ b/src/libsync/owncloudtheme.cpp
@@ -19,8 +19,6 @@
#ifndef TOKEN_AUTH_ONLY
#include <QPixmap>
#include <QIcon>
-#include <QStyle>
-#include <QApplication>
#endif
#include <QCoreApplication>
@@ -35,63 +33,13 @@ ownCloudTheme::ownCloudTheme()
{
}
-QString ownCloudTheme::configFileName() const
-{
- return QLatin1String("owncloud.cfg");
-}
-
-QString ownCloudTheme::about() const
-{
- QString devString;
- devString = tr("<p>Version %2. For more information visit <a href=\"%3\">https://%4</a></p>"
- "<p>For known issues and help, please visit: <a href=\"https://central.owncloud.org/c/desktop-client\">https://central.owncloud.org</a></p>"
- "<p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, "
- " Jan-Christoph Borchardt, and others.</small></p>"
- "<p>Copyright ownCloud GmbH</p>"
- "<p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>"
- "ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH "
- "in the United States, other countries, or both.</p>")
- .arg(Utility::escape(MIRALL_VERSION_STRING),
- Utility::escape("https://" MIRALL_STRINGIFY(APPLICATION_DOMAIN)),
- Utility::escape(MIRALL_STRINGIFY(APPLICATION_DOMAIN)));
-
- devString += gitSHA1();
- return devString;
-}
-
#ifndef TOKEN_AUTH_ONLY
-QIcon ownCloudTheme::trayFolderIcon(const QString &) const
-{
- QPixmap fallback = qApp->style()->standardPixmap(QStyle::SP_FileDialogNewFolder);
- return QIcon::fromTheme("folder", fallback);
-}
-
-QIcon ownCloudTheme::applicationIcon() const
-{
- return themeIcon(QLatin1String("owncloud-icon"));
-}
-
-
-QVariant ownCloudTheme::customMedia(Theme::CustomMediaType type)
+QVariant ownCloudTheme::customMedia(CustomMediaType)
{
- if (type == Theme::oCSetupTop) {
- // return QCoreApplication::translate("ownCloudTheme",
- // "If you don't have an ownCloud server yet, "
- // "see <a href=\"https://owncloud.com\">owncloud.com</a> for more info.",
- // "Top text in setup wizard. Keep short!");
- return QVariant();
- } else {
- return QVariant();
- }
+ return QVariant();
}
-
#endif
-QString ownCloudTheme::helpUrl() const
-{
- return QString::fromLatin1("https://doc.owncloud.org/desktop/%1.%2/").arg(MIRALL_VERSION_MAJOR).arg(MIRALL_VERSION_MINOR);
-}
-
#ifndef TOKEN_AUTH_ONLY
QColor ownCloudTheme::wizardHeaderBackgroundColor() const
{
@@ -109,14 +57,4 @@ QPixmap ownCloudTheme::wizardHeaderLogo() const
}
#endif
-
-QString ownCloudTheme::appName() const
-{
- return QLatin1String("ownCloud");
-}
-
-QString ownCloudTheme::appNameGUI() const
-{
- return QLatin1String("ownCloud");
-}
}
diff --git a/src/libsync/owncloudtheme.h b/src/libsync/owncloudtheme.h
index 327c96e5f..99143fe93 100644
--- a/src/libsync/owncloudtheme.h
+++ b/src/libsync/owncloudtheme.h
@@ -28,18 +28,6 @@ class ownCloudTheme : public Theme
Q_OBJECT
public:
ownCloudTheme();
-
- QString configFileName() const Q_DECL_OVERRIDE;
- QString about() const Q_DECL_OVERRIDE;
-
-#ifndef TOKEN_AUTH_ONLY
- QIcon trayFolderIcon(const QString &) const Q_DECL_OVERRIDE;
- QIcon applicationIcon() const Q_DECL_OVERRIDE;
-#endif
- QString appName() const Q_DECL_OVERRIDE;
- QString appNameGUI() const Q_DECL_OVERRIDE;
-
- QString helpUrl() const Q_DECL_OVERRIDE;
#ifndef TOKEN_AUTH_ONLY
QVariant customMedia(CustomMediaType type) Q_DECL_OVERRIDE;
diff --git a/src/libsync/propagateupload.cpp b/src/libsync/propagateupload.cpp
index 4579139ce..155eed8cc 100644
--- a/src/libsync/propagateupload.cpp
+++ b/src/libsync/propagateupload.cpp
@@ -471,7 +471,6 @@ void PropagateUploadFileCommon::slotPollFinished()
propagator()->_activeJobList.removeOne(this);
if (job->_item->_status != SyncFileItem::Success) {
- _finished = true;
done(job->_item->_status, job->_item->_errorString);
return;
}
@@ -479,6 +478,12 @@ void PropagateUploadFileCommon::slotPollFinished()
finalize();
}
+void PropagateUploadFileCommon::done(SyncFileItem::Status status, const QString &errorString)
+{
+ _finished = true;
+ PropagateItemJob::done(status, errorString);
+}
+
void PropagateUploadFileCommon::checkResettingErrors()
{
if (_item->_httpErrorCode == 412
@@ -561,7 +566,6 @@ void PropagateUploadFileCommon::abort(PropagatorJob::AbortType abortType)
// This function is used whenever there is an error occuring and jobs might be in progress
void PropagateUploadFileCommon::abortWithError(SyncFileItem::Status status, const QString &error)
{
- _finished = true;
abort(AbortType::Synchronous);
done(status, error);
}
@@ -610,8 +614,6 @@ QMap<QByteArray, QByteArray> PropagateUploadFileCommon::headers()
void PropagateUploadFileCommon::finalize()
{
- _finished = true;
-
// Update the quota, if known
auto quotaIt = propagator()->_folderQuota.find(QFileInfo(_item->_file).path());
if (quotaIt != propagator()->_folderQuota.end())
diff --git a/src/libsync/propagateupload.h b/src/libsync/propagateupload.h
index fbbbc14f1..c7e8b4854 100644
--- a/src/libsync/propagateupload.h
+++ b/src/libsync/propagateupload.h
@@ -257,6 +257,8 @@ private slots:
void slotPollFinished();
protected:
+ void done(SyncFileItem::Status status, const QString &errorString = QString()) override;
+
/**
* Prepares the abort e.g. connects proper signals and slots
* to the subjobs to abort asynchronously
diff --git a/src/libsync/propagateuploadng.cpp b/src/libsync/propagateuploadng.cpp
index a679887a5..13dfe513b 100644
--- a/src/libsync/propagateuploadng.cpp
+++ b/src/libsync/propagateuploadng.cpp
@@ -83,7 +83,7 @@ void PropagateUploadFileNG::doStartUpload()
propagator()->_activeJobList.append(this);
const SyncJournalDb::UploadInfo progressInfo = propagator()->_journal->getUploadInfo(_item->_file);
- if (progressInfo._valid && progressInfo._modtime == _item->_modtime) {
+ if (progressInfo._valid && progressInfo.isChunked() && progressInfo._modtime == _item->_modtime) {
_transferId = progressInfo._transferid;
auto url = chunkUrl();
auto job = new LsColJob(propagator()->account(), url, this);
@@ -98,7 +98,7 @@ void PropagateUploadFileNG::doStartUpload()
this, &PropagateUploadFileNG::slotPropfindIterate);
job->start();
return;
- } else if (progressInfo._valid) {
+ } else if (progressInfo._valid && progressInfo.isChunked()) {
// The upload info is stale. remove the stale chunks on the server
_transferId = progressInfo._transferid;
// Fire and forget. Any error will be ignored.
@@ -143,6 +143,12 @@ void PropagateUploadFileNG::slotPropfindFinished()
qCCritical(lcPropagateUpload) << "Inconsistency while resuming " << _item->_file
<< ": the size on the server (" << _sent << ") is bigger than the size of the file ("
<< _item->_size << ")";
+
+ // Wipe the old chunking data.
+ // Fire and forget. Any error will be ignored.
+ (new DeleteJob(propagator()->account(), chunkUrl(), this))->start();
+
+ propagator()->_activeJobList.append(this);
startNewUpload();
return;
}
@@ -274,6 +280,7 @@ void PropagateUploadFileNG::startNextChunk()
if (_currentChunkSize == 0) {
Q_ASSERT(_jobs.isEmpty()); // There should be no running job anymore
_finished = true;
+
// Finish with a MOVE
QString destination = QDir::cleanPath(propagator()->account()->url().path() + QLatin1Char('/')
+ propagator()->account()->davPath() + propagator()->_remoteFolder + _item->_file);
@@ -389,12 +396,12 @@ void PropagateUploadFileNG::slotPutFinished()
<< propagator()->_chunkSize << "bytes";
}
- bool finished = _sent == _item->_size;
+ _finished = _sent == _item->_size;
// Check if the file still exists
const QString fullFilePath(propagator()->getFilePath(_item->_file));
if (!FileSystem::fileExists(fullFilePath)) {
- if (!finished) {
+ if (!_finished) {
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
return;
} else {
@@ -405,13 +412,13 @@ void PropagateUploadFileNG::slotPutFinished()
// Check whether the file changed since discovery.
if (!FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
propagator()->_anotherSyncNeeded = true;
- if (!finished) {
+ if (!_finished) {
abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync."));
return;
}
}
- if (!finished) {
+ if (!_finished) {
// Deletes an existing blacklist entry on successful chunk upload
if (_item->_hasBlacklistEntry) {
propagator()->_journal->wipeErrorBlacklistEntry(_item->_file);
diff --git a/src/libsync/propagateuploadv1.cpp b/src/libsync/propagateuploadv1.cpp
index 062b17634..8bd9489f5 100644
--- a/src/libsync/propagateuploadv1.cpp
+++ b/src/libsync/propagateuploadv1.cpp
@@ -43,7 +43,7 @@ void PropagateUploadFileV1::doStartUpload()
const SyncJournalDb::UploadInfo progressInfo = propagator()->_journal->getUploadInfo(_item->_file);
- if (progressInfo._valid && progressInfo._modtime == _item->_modtime
+ if (progressInfo._valid && progressInfo.isChunked() && progressInfo._modtime == _item->_modtime
&& (progressInfo._contentChecksum == _item->_checksumHeader || progressInfo._contentChecksum.isEmpty() || _item->_checksumHeader.isEmpty())) {
_startChunk = progressInfo._chunk;
_transferId = progressInfo._transferid;
@@ -55,7 +55,7 @@ void PropagateUploadFileV1::doStartUpload()
SyncJournalDb::UploadInfo pi;
pi._valid = true;
pi._chunk = 0;
- pi._transferid = _transferId;
+ pi._transferid = 0; // We set a null transfer id because it is not chunked.
pi._modtime = _item->_modtime;
pi._errorCount = 0;
pi._contentChecksum = _item->_checksumHeader;
@@ -206,12 +206,12 @@ void PropagateUploadFileV1::slotPutFinished()
// The server needs some time to process the request and provide us with a poll URL
if (_item->_httpErrorCode == 202) {
- _finished = true;
QString path = QString::fromUtf8(job->reply()->rawHeader("OC-Finish-Poll"));
if (path.isEmpty()) {
done(SyncFileItem::NormalError, tr("Poll URL missing"));
return;
}
+ _finished = true;
startPollJob(path);
return;
}
@@ -226,12 +226,12 @@ void PropagateUploadFileV1::slotPutFinished()
// yet, the upload can be stopped and an error can be displayed, because
// the server hasn't registered the new file yet.
QByteArray etag = getEtagFromReply(job->reply());
- bool finished = etag.length() > 0;
+ _finished = etag.length() > 0;
// Check if the file still exists
const QString fullFilePath(propagator()->getFilePath(_item->_file));
if (!FileSystem::fileExists(fullFilePath)) {
- if (!finished) {
+ if (!_finished) {
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
return;
} else {
@@ -242,7 +242,7 @@ void PropagateUploadFileV1::slotPutFinished()
// Check whether the file changed since discovery.
if (!FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
propagator()->_anotherSyncNeeded = true;
- if (!finished) {
+ if (!_finished) {
abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync."));
// FIXME: the legacy code was retrying for a few seconds.
// and also checking that after the last chunk, and removed the file in case of INSTRUCTION_NEW
@@ -250,14 +250,13 @@ void PropagateUploadFileV1::slotPutFinished()
}
}
- if (!finished) {
+ if (!_finished) {
// Proceed to next chunk.
if (_currentChunk >= _chunkCount) {
if (!_jobs.empty()) {
// just wait for the other job to finish.
return;
}
- _finished = true;
done(SyncFileItem::NormalError, tr("The server did not acknowledge the last chunk. (No e-tag was present)"));
return;
}
@@ -287,9 +286,8 @@ void PropagateUploadFileV1::slotPutFinished()
startNextChunk();
return;
}
-
// the following code only happens after all chunks were uploaded.
- _finished = true;
+
// the file id should only be empty for new files up- or downloaded
QByteArray fid = job->reply()->rawHeader("OC-FileID");
if (!fid.isEmpty()) {
@@ -309,6 +307,7 @@ void PropagateUploadFileV1::slotPutFinished()
qCWarning(lcPropagateUpload) << "Server does not support X-OC-MTime" << job->reply()->rawHeader("X-OC-MTime");
// Well, the mtime was not set
done(SyncFileItem::SoftError, "Server does not support X-OC-MTime");
+ return;
}
finalize();
diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp
index 0f9907ca4..64481ca60 100644
--- a/src/libsync/syncengine.cpp
+++ b/src/libsync/syncengine.cpp
@@ -303,6 +303,8 @@ void SyncEngine::deleteStaleUploadInfos(const SyncFileItemVector &syncItems)
// Delete the stales chunk on the server.
if (account()->capabilities().chunkingNg()) {
foreach (uint transferId, ids) {
+ if (!transferId)
+ continue; // Was not a chunked upload
QUrl url = Utility::concatUrlPath(account()->url(), QLatin1String("remote.php/dav/uploads/") + account()->davUser() + QLatin1Char('/') + QString::number(transferId));
(new DeleteJob(account(), url, this))->start();
}
@@ -1630,10 +1632,10 @@ AccountPtr SyncEngine::account() const
return _account;
}
-void SyncEngine::setLocalDiscoveryOptions(LocalDiscoveryStyle style, std::set<QByteArray> dirs)
+void SyncEngine::setLocalDiscoveryOptions(LocalDiscoveryStyle style, std::set<QByteArray> paths)
{
_localDiscoveryStyle = style;
- _localDiscoveryPaths = std::move(dirs);
+ _localDiscoveryPaths = std::move(paths);
}
bool SyncEngine::shouldDiscoverLocally(const QByteArray &path) const
diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h
index f43e2f26b..e00034f4d 100644
--- a/src/libsync/syncengine.h
+++ b/src/libsync/syncengine.h
@@ -104,7 +104,7 @@ public:
/**
* Control whether local discovery should read from filesystem or db.
*
- * If style is DatabaseAndFilesystem, dirs a set of file paths relative to
+ * If style is DatabaseAndFilesystem, paths a set of file paths relative to
* the synced folder. All the parent directories of these paths will not
* be read from the db and scanned on the filesystem.
*
@@ -112,7 +112,7 @@ public:
* revert afterwards. Use _lastLocalDiscoveryStyle to discover the last
* sync's style.
*/
- void setLocalDiscoveryOptions(LocalDiscoveryStyle style, std::set<QByteArray> dirs = {});
+ void setLocalDiscoveryOptions(LocalDiscoveryStyle style, std::set<QByteArray> paths = {});
/**
* Returns whether the given folder-relative path should be locally discovered
diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h
index 4272bf9e5..88421c39d 100644
--- a/src/libsync/syncfileitem.h
+++ b/src/libsync/syncfileitem.h
@@ -75,10 +75,9 @@ public:
/** For files whose errors were blacklisted
*
* If an file is blacklisted due to an error it isn't even reattempted. These
- * errors should appear in the issues tab, but not on the account settings and
- * should not cause the sync run to fail.
+ * errors should appear in the issues tab but should be silent otherwise.
*
- * A DetailError that doesn't cause sync failure.
+ * A SoftError caused by blacklisting.
*/
BlacklistedError
};
diff --git a/src/libsync/theme.cpp b/src/libsync/theme.cpp
index e9e389fd6..da6e28344 100644
--- a/src/libsync/theme.cpp
+++ b/src/libsync/theme.cpp
@@ -20,6 +20,8 @@
#include <QtCore>
#ifndef TOKEN_AUTH_ONLY
#include <QtGui>
+#include <QStyle>
+#include <QApplication>
#endif
#include <QSslSocket>
@@ -105,12 +107,16 @@ QString Theme::version() const
return MIRALL_VERSION_STRING;
}
+QString Theme::configFileName() const
+{
+ return QStringLiteral(APPLICATION_EXECUTABLE ".cfg");
+}
+
#ifndef TOKEN_AUTH_ONLY
-QIcon Theme::trayFolderIcon(const QString &backend) const
+QIcon Theme::applicationIcon() const
{
- Q_UNUSED(backend)
- return applicationIcon();
+ return themeIcon(QStringLiteral(APPLICATION_ICON_NAME "-icon"));
}
/*
@@ -217,6 +223,11 @@ QString Theme::defaultServerFolder() const
return QLatin1String("/");
}
+QString Theme::helpUrl() const
+{
+ return QString::fromLatin1("https://doc.owncloud.org/desktop/%1.%2/").arg(MIRALL_VERSION_MAJOR).arg(MIRALL_VERSION_MINOR);
+}
+
QString Theme::overrideServerUrl() const
{
return QString();
@@ -311,21 +322,21 @@ QString Theme::gitSHA1() const
QString Theme::about() const
{
- QString re;
- re = tr("<p>Version %1. For more information please visit <a href='%2'>%3</a>.</p>")
- .arg(MIRALL_VERSION_STRING)
- .arg("http://" MIRALL_STRINGIFY(APPLICATION_DOMAIN))
- .arg(MIRALL_STRINGIFY(APPLICATION_DOMAIN));
-
- re += tr("<p>Copyright ownCloud GmbH</p>");
- re += tr("<p>Distributed by %1 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>"
- "%2 and the %2 logo are registered trademarks of %1 in the "
- "United States, other countries, or both.</p>")
- .arg(APPLICATION_VENDOR)
- .arg(APPLICATION_NAME);
-
- re += gitSHA1();
- return re;
+ QString devString;
+ devString = tr("<p>Version %2. For more information visit <a href=\"%3\">https://%4</a></p>"
+ "<p>For known issues and help, please visit: <a href=\"https://central.owncloud.org/c/desktop-client\">https://central.owncloud.org</a></p>"
+ "<p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, "
+ " Jan-Christoph Borchardt, and others.</small></p>"
+ "<p>Copyright ownCloud GmbH</p>"
+ "<p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>"
+ "ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH "
+ "in the United States, other countries, or both.</p>")
+ .arg(Utility::escape(MIRALL_VERSION_STRING),
+ Utility::escape("https://" MIRALL_STRINGIFY(APPLICATION_DOMAIN)),
+ Utility::escape(MIRALL_STRINGIFY(APPLICATION_DOMAIN)));
+
+ devString += gitSHA1();
+ return devString;
}
#ifndef TOKEN_AUTH_ONLY
diff --git a/src/libsync/theme.h b/src/libsync/theme.h
index c2cd4922f..90ddd7ef1 100644
--- a/src/libsync/theme.h
+++ b/src/libsync/theme.h
@@ -84,24 +84,19 @@ public:
* @brief configFileName
* @return the name of the config file.
*/
- virtual QString configFileName() const = 0;
+ virtual QString configFileName() const;
#ifndef TOKEN_AUTH_ONLY
static QString hidpiFileName(const QString &fileName, QPaintDevice *dev = 0);
/**
- * the icon that is shown in the tray context menu left of the folder name
- */
- virtual QIcon trayFolderIcon(const QString &) const;
-
- /**
* get an sync state icon
*/
virtual QIcon syncStateIcon(SyncResult::Status, bool sysTray = false, bool sysTrayMenuVisible = false) const;
virtual QIcon folderDisabledIcon() const;
virtual QIcon folderOfflineIcon(bool sysTray = false, bool sysTrayMenuVisible = false) const;
- virtual QIcon applicationIcon() const = 0;
+ virtual QIcon applicationIcon() const;
#endif
virtual QString statusHeaderText(SyncResult::Status) const;
@@ -118,9 +113,11 @@ public:
virtual bool multiAccount() const;
/**
- * URL to help file
+ * URL to documentation.
+ * This is opened in the browser when the "Help" action is selected from the tray menu.
+ * (If it is an empty stringn the action is removed from the menu. Defaults to ownCloud's help)
*/
- virtual QString helpUrl() const { return QString(); }
+ virtual QString helpUrl() const;
/**
* Setting a value here will pre-define the server url.