Skip to content

Commit

Permalink
Vfs: Add 'availability', a simplified, user-facing pin state #7111
Browse files Browse the repository at this point in the history
The idea is that the user's question is "is this folder's data available
offline?" and not "does this folder have AlwaysLocal pin state?".
The the answers to the two questions can differ: an always-local
folder can have subitems that are not always-local and are dehydrated.

The new availability enum intends to describe the answer to the user's
actual question and can be derived from pin states. If pin states aren't
stored in the database the way of calculating availability will depend
on the vfs plugin.
  • Loading branch information
ckamm committed Apr 5, 2019
1 parent d399f4c commit c507fa9
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 53 deletions.
25 changes: 25 additions & 0 deletions src/common/pinstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,31 @@ enum class PinState {
Unspecified = 3,
};

/** A user-facing version of PinState.
*
* PinStates communicate availability intent for an item, but particular
* situations can get complex: An AlwaysLocal folder can have OnlineOnly
* files or directories.
*
* For users this is condensed to a few useful cases.
*/
enum class VfsItemAvailability {
/** The item is neither fully AlwaysLocal nor fully OnlineOnly. */
Mixed,

/** The item and all its subitems are AlwaysLocal.
*
* This guarantees that all contents will be kept in sync.
*/
AlwaysLocal,

/** The item and all its subitems are OnlineOnly.
*
* This guarantees that contents will not take up space.
*/
OnlineOnly,
};

}

#endif
47 changes: 46 additions & 1 deletion src/common/syncjournaldb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2084,7 +2084,7 @@ Optional<PinState> SyncJournalDb::PinStateInterface::effectiveForPath(const QByt
// (it'd be great if paths started with a / and "/" could be the root)
" (" IS_PREFIX_PATH_OR_EQUAL("path", "?1") " OR path == '')"
" AND pinState is not null AND pinState != 0"
" ORDER BY length(path) DESC;"),
" ORDER BY length(path) DESC LIMIT 1;"),
_db->_db));
query.bindValue(1, path);
query.exec();
Expand All @@ -2099,6 +2099,51 @@ Optional<PinState> SyncJournalDb::PinStateInterface::effectiveForPath(const QByt
return static_cast<PinState>(query.intValue(0));
}

Optional<VfsItemAvailability> SyncJournalDb::PinStateInterface::availabilityForPath(const QByteArray &path)
{
// Get the item's effective pin state. We'll compare subitem's pin states
// against this.
const auto basePin = effectiveForPath(path);
if (!basePin)
return {};

QMutexLocker lock(&_db->_mutex);
if (!_db->checkConnect())
return {};

// Find all the non-inherited pin states below the item
auto &query = _db->_getAvailabilityStateQuery;
ASSERT(query.initOrReset(QByteArrayLiteral(
"SELECT DISTINCT pinState FROM flags WHERE"
" (" IS_PREFIX_PATH_OF("?1", "path") " OR ?1 == '')"
" AND pinState is not null and pinState != 0;"),
_db->_db));
query.bindValue(1, path);
query.exec();

// Check if they are all identical
forever {
auto next = query.next();
if (!next.ok)
return {};
if (!next.hasData)
break;
const auto subPin = static_cast<PinState>(query.intValue(0));
if (subPin != *basePin)
return VfsItemAvailability::Mixed;
}

switch (*basePin)
{
case PinState::AlwaysLocal:
return VfsItemAvailability::AlwaysLocal;
case PinState::OnlineOnly:
return VfsItemAvailability::OnlineOnly;
default:
return VfsItemAvailability::Mixed;
}
}

void SyncJournalDb::PinStateInterface::setForPath(const QByteArray &path, PinState state)
{
QMutexLocker lock(&_db->_mutex);
Expand Down
14 changes: 14 additions & 0 deletions src/common/syncjournaldb.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,19 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject
*/
Optional<PinState> effectiveForPath(const QByteArray &path);

/**
* Computes availability based on path's pin states.
*
* Looks at the path's effective pin state and the sub-item's states
* to determine whether the path fits any of the VfsItemAvailability
* categories.
*
* It's valid to use the root path "".
*
* Returns none on db error.
*/
Optional<VfsItemAvailability> availabilityForPath(const QByteArray &path);

/**
* Sets a path's pin state.
*
Expand Down Expand Up @@ -396,6 +409,7 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject
SqlQuery _deleteConflictRecordQuery;
SqlQuery _getRawPinStateQuery;
SqlQuery _getEffectivePinStateQuery;
SqlQuery _getAvailabilityStateQuery;
SqlQuery _setPinStateQuery;
SqlQuery _wipePinStateQuery;

Expand Down
5 changes: 5 additions & 0 deletions src/common/vfs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ Optional<PinState> Vfs::pinStateInDb(const QString &folderPath)
return _setupParams.journal->internalPinStates().effectiveForPath(folderPath.toUtf8());
}

Optional<VfsItemAvailability> Vfs::availabilityInDb(const QString &folderPath)
{
return _setupParams.journal->internalPinStates().availabilityForPath(folderPath.toUtf8());
}

VfsOff::VfsOff(QObject *parent)
: Vfs(parent)
{
Expand Down
17 changes: 15 additions & 2 deletions src/common/vfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,13 @@ class OCSYNC_EXPORT Vfs : public QObject
virtual bool statTypeVirtualFile(csync_file_stat_t *stat, void *stat_data) = 0;

/** Sets the pin state for the item at a path.
*
* The pin state is set on the item and for all items below it.
*
* Usually this would forward to setting the pin state flag in the db table,
* but some vfs plugins will store the pin state in file attributes instead.
*
* folderPath is relative to the sync folder.
* folderPath is relative to the sync folder. Can be "" for root folder.
*/
virtual bool setPinState(const QString &folderPath, PinState state) = 0;

Expand All @@ -201,10 +203,19 @@ class OCSYNC_EXPORT Vfs : public QObject
* Usually backed by the db's effectivePinState() function but some vfs
* plugins will override it to retrieve the state from elsewhere.
*
* folderPath is relative to the sync folder.
* folderPath is relative to the sync folder. Can be "" for root folder.
*/
virtual Optional<PinState> pinState(const QString &folderPath) = 0;

/** Returns availability status of an item at a path.
*
* The availability is a condensed user-facing version of PinState. See
* VfsItemAvailability for details.
*
* folderPath is relative to the sync folder. Can be "" for root folder.
*/
virtual Optional<VfsItemAvailability> availability(const QString &folderPath) = 0;

public slots:
/** Update in-sync state based on SyncFileStatusTracker signal.
*
Expand Down Expand Up @@ -235,6 +246,7 @@ public slots:
// Db-backed pin state handling. Derived classes may use it to implement pin states.
bool setPinStateInDb(const QString &folderPath, PinState state);
Optional<PinState> pinStateInDb(const QString &folderPath);
Optional<VfsItemAvailability> availabilityInDb(const QString &folderPath);

// the parameters passed to start()
VfsSetupParams _setupParams;
Expand Down Expand Up @@ -269,6 +281,7 @@ class OCSYNC_EXPORT VfsOff : public Vfs

bool setPinState(const QString &, PinState) override { return true; }
Optional<PinState> pinState(const QString &) override { return PinState::AlwaysLocal; }
Optional<VfsItemAvailability> availability(const QString &) override { return VfsItemAvailability::AlwaysLocal; }

public slots:
void fileStatusChanged(const QString &, SyncFileStatus) override {}
Expand Down
12 changes: 10 additions & 2 deletions src/gui/accountsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,14 +340,22 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)

if (folder->supportsVirtualFiles()) {
auto availabilityMenu = menu->addMenu(tr("Availability"));
auto availability = folder->vfs().availability(QString());
if (availability && *availability == VfsItemAvailability::Mixed) {
ac = availabilityMenu->addAction(tr("Mixed"));
ac->setCheckable(true);
ac->setChecked(true);
ac->setEnabled(false);
}

ac = availabilityMenu->addAction(tr("Local"));
ac->setCheckable(true);
ac->setChecked(!folder->newFilesAreVirtual());
ac->setChecked(availability && *availability == VfsItemAvailability::AlwaysLocal);
connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::AlwaysLocal); });

ac = availabilityMenu->addAction(tr("Online only"));
ac->setCheckable(true);
ac->setChecked(folder->newFilesAreVirtual());
ac->setChecked(availability && *availability == VfsItemAvailability::OnlineOnly);
connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::OnlineOnly); });

ac = menu->addAction(tr("Disable virtual file support..."));
Expand Down
79 changes: 31 additions & 48 deletions src/gui/socketapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -946,30 +946,24 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
if (folder
&& folder->supportsVirtualFiles()
&& folder->vfs().socketApiPinStateActionsShown()) {
bool hasAlwaysLocal = false;
bool hasOnlineOnly = false;
bool hasHydratedOnlineOnly = false;
bool hasDehydratedOnlineOnly = false;
// Determine the combined availability status of the files
auto combined = VfsItemAvailability::Mixed;
bool firstFile = true;
for (const auto &file : files) {
auto fileData = FileData::get(file);
auto path = fileData.folderRelativePathNoVfsSuffix();
auto pinState = folder->vfs().pinState(path);
if (!pinState) {
// db error
hasAlwaysLocal = true;
hasOnlineOnly = true;
} else if (*pinState == PinState::AlwaysLocal) {
hasAlwaysLocal = true;
} else if (*pinState == PinState::OnlineOnly) {
hasOnlineOnly = true;
auto record = fileData.journalRecord();
if (record._type == ItemTypeFile)
hasHydratedOnlineOnly = true;
if (record.isVirtualFile())
hasDehydratedOnlineOnly = true;
auto availability = folder->vfs().availability(path);
if (!availability)
availability = VfsItemAvailability::Mixed; // db error
if (firstFile) {
combined = *availability;
firstFile = false;
} else if (*availability != combined) {
combined = VfsItemAvailability::Mixed;
}
}

// TODO: Should be a submenu, should use menu item checkmarks where available, should use icons
auto makePinContextMenu = [listener](QString currentState, QString availableLocally, QString onlineOnly) {
listener->sendMessage(QLatin1String("MENU_ITEM:CURRENT_PIN:d:") + currentState);
if (!availableLocally.isEmpty())
Expand All @@ -978,36 +972,25 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
listener->sendMessage(QLatin1String("MENU_ITEM:MAKE_ONLINE_ONLY::") + onlineOnly);
};

// TODO: Should be a submenu, should use menu item checkmarks where available, should use icons
if (hasAlwaysLocal) {
if (!hasOnlineOnly) {
makePinContextMenu(
tr("Currently available locally"),
QString(),
tr("Make available online only"));
} else { // local + online
makePinContextMenu(
tr("Current availability is mixed"),
tr("Make all available locally"),
tr("Make all available online only"));
}
} else if (hasOnlineOnly) {
if (hasDehydratedOnlineOnly && !hasHydratedOnlineOnly) {
makePinContextMenu(
tr("Currently available online only"),
tr("Make available locally"),
QString());
} else if (hasHydratedOnlineOnly && !hasDehydratedOnlineOnly) {
makePinContextMenu(
tr("Currently available, but marked online only"),
tr("Make available locally"),
tr("Make available online only"));
} else { // hydrated + dehydrated
makePinContextMenu(
tr("Some currently available, all marked online only"),
tr("Make available locally"),
tr("Make available online only"));
}
switch (combined) {
case VfsItemAvailability::Mixed:
makePinContextMenu(
tr("Current availability is mixed"),
tr("Make all available locally"),
tr("Make all available online only"));
break;
case VfsItemAvailability::OnlineOnly:
makePinContextMenu(
tr("Currently available online only"),
tr("Make available locally"),
QString());
break;
case VfsItemAvailability::AlwaysLocal:
makePinContextMenu(
tr("Currently available locally"),
QString(),
tr("Make available online only"));
break;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/libsync/vfs/suffix/vfs_suffix.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class VfsSuffix : public Vfs
{ return setPinStateInDb(folderPath, state); }
Optional<PinState> pinState(const QString &folderPath) override
{ return pinStateInDb(folderPath); }
Optional<VfsItemAvailability> availability(const QString &folderPath) override
{ return availabilityInDb(folderPath); }

public slots:
void fileStatusChanged(const QString &, SyncFileStatus) override {}
Expand Down

0 comments on commit c507fa9

Please sign in to comment.