Skip to content

Commit

Permalink
Minimal libcloudproviders support #7209
Browse files Browse the repository at this point in the history
- Add it as an optional dependency for linux builds,
  controlled by WITH_LIBCLOUDPROVIDERS, which is enabled automatically
  if the dependencies are available. Note that >=0.3.0 is required.

- Add code to export sync folders through the library and to add a
  minimal "Settings..." menu action.

- Set up cloud provider registration by installing a file into a
  path like /usr/share/cloud-providers/.

- DBus name and path are derived from APPLICATION_REV_DOMAIN by default
  and may be overridden with APPLICATION_CLOUDPROVIDERS_DBUS_NAME and
  APPLICATION_CLOUDPROVIDERS_DBUS_PATH if necessary.
  • Loading branch information
ckamm committed Jun 5, 2019
1 parent 0a03fc2 commit f377d91
Show file tree
Hide file tree
Showing 12 changed files with 462 additions and 2 deletions.
9 changes: 9 additions & 0 deletions THEME.cmake
Expand Up @@ -10,6 +10,15 @@ if(NOT DEFINED APPLICATION_VIRTUALFILE_SUFFIX)
set(APPLICATION_VIRTUALFILE_SUFFIX "${APPLICATION_SHORTNAME}_virtual" CACHE STRING "Virtual file suffix (not including the .)")
endif()

# Default dbus name and path
if(NOT DEFINED APPLICATION_CLOUDPROVIDERS_DBUS_NAME)
set(APPLICATION_CLOUDPROVIDERS_DBUS_NAME ${APPLICATION_REV_DOMAIN})
endif()
if(NOT DEFINED APPLICATION_CLOUDPROVIDERS_DBUS_PATH)
set(APPLICATION_CLOUDPROVIDERS_DBUS_PATH "/${APPLICATION_CLOUDPROVIDERS_DBUS_NAME}")
string(REPLACE "." "/" APPLICATION_CLOUDPROVIDERS_DBUS_PATH ${APPLICATION_CLOUDPROVIDERS_DBUS_PATH})
endif()

# need this logic to not mess with re/uninstallations via macosx.pkgproj
if(${APPLICATION_REV_DOMAIN} STREQUAL "com.owncloud.desktopclient")
set(APPLICATION_REV_DOMAIN_INSTALLER "com.ownCloud.client")
Expand Down
42 changes: 42 additions & 0 deletions cmake/modules/FindLibcloudproviders.cmake
@@ -0,0 +1,42 @@
# (c) 2019 Copyright ownCloud GmbH
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING* file.

find_path(LIBCLOUDPROVIDERS_INCLUDE_DIR
NAMES
cloudprovidersaccountexporter.h
cloudprovidersproviderexporter.h
PATH_SUFFIXES
cloudproviders
)

find_library(LIBCLOUDPROVIDERS_LIBRARY
NAMES
cloudproviders
PATHS
/usr/lib
/usr/lib/${CMAKE_ARCH_TRIPLET}
/usr/local/lib
${CMAKE_LIBRARY_PATH}
${CMAKE_INSTALL_PREFIX}/lib
)

# Using version <0.3.0 would lead to crashes during runtime when accounts are unexported.
get_filename_component(LIBCLOUDPROVIDERS_LIBRARY_REALPATH ${LIBCLOUDPROVIDERS_LIBRARY} REALPATH)
if(${LIBCLOUDPROVIDERS_LIBRARY_REALPATH} MATCHES "\.([0-9]*)\.([0-9]*)\.([0-9]*)$")
if ((${CMAKE_MATCH_1} EQUAL 0) AND (${CMAKE_MATCH_2} LESS 3))
message("libcloudproviders version is older than 0.3.0, not enabling it")
set(LIBCLOUDPROVIDERS_LIBRARY "")
endif()
else()
message("Can't determine libcloudproviders version, not enabling it")
set(LIBCLOUDPROVIDERS_LIBRARY "")
endif()

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
LIBCLOUDPROVIDERS
DEFAULT_MSG
LIBCLOUDPROVIDERS_LIBRARY LIBCLOUDPROVIDERS_INCLUDE_DIR)

mark_as_advanced(LIBCLOUDPROVIDERS_INCLUDE_DIR LIBCLOUDPROVIDERS_LIBRARY)
2 changes: 2 additions & 0 deletions config.h.in
Expand Up @@ -20,6 +20,8 @@
#cmakedefine APPLICATION_ICON_NAME "@APPLICATION_ICON_NAME@"
#cmakedefine APPLICATION_VIRTUALFILE_SUFFIX "@APPLICATION_VIRTUALFILE_SUFFIX@"
#define APPLICATION_DOTVIRTUALFILE_SUFFIX "." APPLICATION_VIRTUALFILE_SUFFIX
#cmakedefine APPLICATION_CLOUDPROVIDERS_DBUS_NAME "@APPLICATION_CLOUDPROVIDERS_DBUS_NAME@"
#cmakedefine APPLICATION_CLOUDPROVIDERS_DBUS_PATH "@APPLICATION_CLOUDPROVIDERS_DBUS_PATH@"

#cmakedefine ZLIB_FOUND @ZLIB_FOUND@

Expand Down
1 change: 1 addition & 0 deletions src/gui/CMakeLists.txt
Expand Up @@ -317,6 +317,7 @@ endif()
# application.cpp still uses QDesktopServices::storageLocation
target_compile_definitions(${APPLICATION_EXECUTABLE} PRIVATE "QT_DISABLE_DEPRECATED_BEFORE=0")

include(libcloudproviders/libcloudproviders.cmake)

install(TARGETS ${APPLICATION_EXECUTABLE}
RUNTIME DESTINATION bin
Expand Down
7 changes: 6 additions & 1 deletion src/gui/folderman.cpp
Expand Up @@ -93,11 +93,16 @@ FolderMan::~FolderMan()
_instance = 0;
}

OCC::Folder::Map FolderMan::map()
OCC::Folder::Map FolderMan::map() const
{
return _folderMap;
}

QList<Folder *> FolderMan::list() const
{
return _folderMap.values();
}

void FolderMan::unloadFolder(Folder *f)
{
if (!f) {
Expand Down
3 changes: 2 additions & 1 deletion src/gui/folderman.h
Expand Up @@ -87,7 +87,8 @@ class FolderMan : public QObject
*/
static void backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys);

OCC::Folder::Map map();
OCC::Folder::Map map() const;
QList<Folder *> list() const;

/** Adds a folder for an account, ensures the journal is gone and saves it in the settings.
*/
Expand Down
4 changes: 4 additions & 0 deletions src/gui/libcloudproviders/cloud-provider.ini.in
@@ -0,0 +1,4 @@
[Cloud Providers]
BusName=@APPLICATION_CLOUDPROVIDERS_DBUS_NAME@
ObjectPath=@APPLICATION_CLOUDPROVIDERS_DBUS_PATH@
Version=1
38 changes: 38 additions & 0 deletions src/gui/libcloudproviders/libcloudproviders.cmake
@@ -0,0 +1,38 @@
find_package(Libcloudproviders)
find_package(PkgConfig REQUIRED)
pkg_search_module(GIO gio-2.0)

# The cloudproviders feature can only be enabled if the libcloudproviders
# and gio-2.0 libraries are available
set(LIBCLOUDPROVIDERS_POSSIBLE "")
if(LIBCLOUDPROVIDERS_FOUND AND GIO_FOUND)
set(LIBCLOUDPROVIDERS_POSSIBLE "1")
endif()

# User visible config switch
set(WITH_LIBCLOUDPROVIDERS ${LIBCLOUDPROVIDERS_POSSIBLE} CACHE BOOL "Whether to bulid with libcloudproviders")

if(WITH_LIBCLOUDPROVIDERS AND NOT LIBCLOUDPROVIDERS_POSSIBLE)
message(FATAL_ERROR "Trying to enable libcloudproviders but dependencies are missing")
endif()

if(WITH_LIBCLOUDPROVIDERS)
target_sources(${APPLICATION_EXECUTABLE} PRIVATE
libcloudproviders/libcloudproviders.cpp
)
target_include_directories(${APPLICATION_EXECUTABLE} SYSTEM PRIVATE ${GIO_INCLUDE_DIRS})
target_compile_definitions(${APPLICATION_EXECUTABLE} PRIVATE WITH_LIBCLOUDPROVIDERS)
target_link_libraries(${APPLICATION_EXECUTABLE}
cloudproviders
${GIO_LDFLAGS}
)

configure_file(libcloudproviders/cloud-provider.ini.in ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_CLOUDPROVIDERS_DBUS_NAME}.ini)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_CLOUDPROVIDERS_DBUS_NAME}.ini
DESTINATION "${DATADIR}/cloud-providers")

message("Building with libcloudproviders")
elseif(UNIX AND NOT APPLE)
message("Building without libcloudproviders")
endif()
237 changes: 237 additions & 0 deletions src/gui/libcloudproviders/libcloudproviders.cpp
@@ -0,0 +1,237 @@
/*
* Copyright (C) by Christian Kamm <mail@ckamm.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/

extern "C" {
#include <cloudproviders/cloudprovidersaccountexporter.h>
#include <cloudproviders/cloudprovidersproviderexporter.h>
#include <gio/gio.h>
#include <glib.h>
}

#include "libcloudproviders.h"
#include "libcloudproviders_p.h"

#include <QMap>
#include "folder.h"
#include "folderman.h"
#include "theme.h"
#include "accountstate.h"
#include "config.h"
#include "account.h"
#include "accountmanager.h"

// These must match the name and path defined in a file in $DATADIR/cloud-providers/
const gchar dbusName[] = APPLICATION_CLOUDPROVIDERS_DBUS_NAME;
const gchar dbusPath[] = APPLICATION_CLOUDPROVIDERS_DBUS_PATH;

namespace OCC {

LibCloudProvidersPrivate::~LibCloudProvidersPrivate()
{
for (const auto folder : _folderExports.keys())
unexportFolder(folder);
g_clear_object(&_exporter);
if (_busOwnerId)
g_bus_unown_name(_busOwnerId);
}

static void onBusAcquired(GDBusConnection *connection, const gchar *, gpointer userData)
{
auto d = static_cast<LibCloudProvidersPrivate *>(userData);

d->_exporter = cloud_providers_provider_exporter_new(connection, dbusName, dbusPath);
cloud_providers_provider_exporter_set_name(d->_exporter, Theme::instance()->appNameGUI().toUtf8().constData());

d->updateExportedFolderList();
}

static void onNameLost(GDBusConnection *, const gchar *, gpointer userData)
{
auto d = static_cast<LibCloudProvidersPrivate *>(userData);

d->_folderExports.clear();
g_clear_object(&d->_exporter);
}

void LibCloudProvidersPrivate::start()
{
_busOwnerId = g_bus_own_name(
G_BUS_TYPE_SESSION, dbusName, G_BUS_NAME_OWNER_FLAGS_NONE,
&onBusAcquired,
nullptr, // onNameAcquired
&onNameLost,
this, // user data
nullptr // user data free func
);

auto folderMan = FolderMan::instance();
connect(folderMan, &FolderMan::folderListChanged,
this, &LibCloudProvidersPrivate::updateExportedFolderList);
connect(folderMan, &FolderMan::folderSyncStateChange,
this, &LibCloudProvidersPrivate::updateFolderExport);
}

void LibCloudProvidersPrivate::updateExportedFolderList()
{
const auto newFolders = FolderMan::instance()->list();
const auto oldFolders = _folderExports.keys();

// Remove folders that are no longer exported
for (const auto old : oldFolders) {
if (!newFolders.contains(old))
unexportFolder(old);
}

// Add new folders
for (const auto n : newFolders) {
if (!oldFolders.contains(n))
exportFolder(n);
}
}

static void actionDispatcher(GSimpleAction *action, GVariant *, gpointer userData)
{
gchar *strval;
g_object_get(action, "name", &strval, NULL);
QByteArray name(strval);
g_free(strval);

auto d = static_cast<LibCloudProvidersPrivate *>(userData);
auto q = d->_q;
if (name == "settings")
q->showSettings();
else
ASSERT(false, "unknown action string");
}


void LibCloudProvidersPrivate::exportFolder(Folder *folder)
{
if (!_exporter)
return;

GError *error = nullptr;
auto icon = g_icon_new_for_string(APPLICATION_ICON_NAME, &error);
if (error) {
qWarning() << "Could not create icon for" << APPLICATION_ICON_NAME << "error" << error->message;
g_error_free(error);
}

auto exporter = cloud_providers_account_exporter_new(_exporter, folder->alias().toUtf8().constData());
cloud_providers_account_exporter_set_path(exporter, folder->path().toUtf8().constData());
cloud_providers_account_exporter_set_icon(exporter, icon);
cloud_providers_account_exporter_set_status(exporter, CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE);

auto menu = g_menu_new();
// The "cloudprovider" scope is hardcoded into the gtk code that uses this data.
// Different scopes will not work.
g_menu_append(menu, tr("Settings").toUtf8().constData(), "cloudprovider.settings");

auto actionGroup = g_simple_action_group_new();
const GActionEntry entries[] = {
{ "settings", actionDispatcher, nullptr, nullptr, nullptr, {}},
};
g_action_map_add_action_entries(G_ACTION_MAP(actionGroup), entries, G_N_ELEMENTS(entries), this);

cloud_providers_account_exporter_set_menu_model(exporter, G_MENU_MODEL(menu));
cloud_providers_account_exporter_set_action_group(exporter, G_ACTION_GROUP(actionGroup));

// Currently there's no reason for us to keep these around: no further modifications are done
g_clear_object(&menu);
g_clear_object(&actionGroup);

_folderExports[folder] = FolderExport{folder, exporter};
updateFolderExport();
}

void LibCloudProvidersPrivate::unexportFolder(Folder *folder)
{
if (!_folderExports.contains(folder))
return;
auto folderExporter = _folderExports[folder]._exporter;
cloud_providers_provider_exporter_remove_account(_exporter, folderExporter);
// the remove_account already calls _unref
_folderExports.remove(folder);
}

void LibCloudProvidersPrivate::updateFolderExport()
{
for (auto folderExport : _folderExports) {
if (!folderExport._folder)
continue;
Folder *folder = folderExport._folder;

// Update the name, may change if accounts are added/removed
QString displayName = folder->shortGuiRemotePathOrAppName();
if (AccountManager::instance()->accounts().size() > 1) {
displayName = QStringLiteral("%1 (%2)").arg(
displayName, folder->accountState()->account()->displayName());
}
cloud_providers_account_exporter_set_name(folderExport._exporter, displayName.toUtf8().constData());

CloudProvidersAccountStatus status = CLOUD_PROVIDERS_ACCOUNT_STATUS_INVALID;
auto syncResult = folder->syncResult();
switch (syncResult.status()) {
case SyncResult::Undefined:
status = CLOUD_PROVIDERS_ACCOUNT_STATUS_INVALID;
break;
case SyncResult::NotYetStarted:
case SyncResult::Success:
case SyncResult::Problem:
status = CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE;
break;
case SyncResult::SyncPrepare:
case SyncResult::SyncRunning:
case SyncResult::SyncAbortRequested:
status = CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING;
break;
case SyncResult::Error:
case SyncResult::SetupError:
status = CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR;
break;
case SyncResult::Paused:
// There's no status that fits exactly. If our choice is only
// between IDLE And ERROR, let's go for ERROR to show that no
// syncing is happening.
status = CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR;
break;
}

// Similarly to Paused: If disconnected, show something's wrong!
if (!folder->accountState()->isConnected())
status = CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR;

auto message = FolderMan::trayTooltipStatusString(
syncResult.status(),
syncResult.hasUnresolvedConflicts(),
folder->syncPaused());

cloud_providers_account_exporter_set_status(folderExport._exporter, status);
cloud_providers_account_exporter_set_status_details(folderExport._exporter, message.toUtf8().constData());
}
}

LibCloudProviders::LibCloudProviders(QObject *parent)
: QObject(parent)
, d_ptr(new LibCloudProvidersPrivate)
{
d_ptr->_q = this;
}

void LibCloudProviders::start()
{
d_ptr->start();
}

} // namespace OCC

0 comments on commit f377d91

Please sign in to comment.