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 9, 2019
1 parent 04268be commit 88b1a8e
Show file tree
Hide file tree
Showing 11 changed files with 336 additions and 65 deletions.
41 changes: 41 additions & 0 deletions src/common/pinstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,47 @@ 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.
*
* Note that this is only about *intent*. The file could still be out of date,
* or not have been synced for other reasons, like errors.
*/
enum class VfsItemAvailability {
/** The item and all its subitems are hydrated and pinned AlwaysLocal.
*
* This guarantees that all contents will be kept in sync.
*/
AlwaysLocal,

/** The item and all its subitems are hydrated.
*
* This may change if the platform or client decide to dehydrate items
* that have Unspecified pin state.
*
* A folder with no file contents will have this availability.
*/
AllHydrated,

/** There are dehydrated items but the pin state isn't all OnlineOnly.
*
* This would happen if a dehydration happens to a Unspecified item that
* used to be hydrated.
*/
SomeDehydrated,

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

}

#endif
64 changes: 63 additions & 1 deletion src/common/syncjournaldb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,31 @@ bool SyncJournalDb::updateLocalMetadata(const QString &filename,
return _setFileRecordLocalMetadataQuery.exec();
}

Optional<bool> SyncJournalDb::hasDehydratedFiles(const QByteArray &filename)
{
QMutexLocker locker(&_mutex);
if (!checkConnect())
return {};

auto &query = _countDehydratedFilesQuery;
static_assert(ItemTypeVirtualFile == 4 && ItemTypeVirtualFileDownload == 5, "");
if (!query.initOrReset(QByteArrayLiteral(
"SELECT count(*) FROM metadata"
" WHERE (" IS_PREFIX_PATH_OR_EQUAL("?1", "path") " OR ?1 == '')"
" AND (type == 4 OR type == 5);"), _db)) {
return {};
}

query.bindValue(1, filename);
if (!query.exec())
return {};

if (!query.next().hasData)
return {};

return query.intValue(0) > 0;
}

static void toDownloadInfo(SqlQuery &query, SyncJournalDb::DownloadInfo *res)
{
bool ok = true;
Expand Down Expand Up @@ -2084,7 +2109,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 +2124,43 @@ Optional<PinState> SyncJournalDb::PinStateInterface::effectiveForPath(const QByt
return static_cast<PinState>(query.intValue(0));
}

Optional<PinState> SyncJournalDb::PinStateInterface::effectiveForPathRecursive(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->_getSubPinsQuery;
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 PinState::Inherited;
}

return *basePin;
}

void SyncJournalDb::PinStateInterface::setForPath(const QByteArray &path, PinState state)
{
QMutexLocker lock(&_db->_mutex);
Expand Down
21 changes: 21 additions & 0 deletions src/common/syncjournaldb.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject
const QByteArray &contentChecksumType);
bool updateLocalMetadata(const QString &filename,
qint64 modtime, qint64 size, quint64 inode);

/** Returns whether the item or any subitems are dehydrated */
Optional<bool> hasDehydratedFiles(const QByteArray &filename);

bool exists();
void walCheckpoint();

Expand Down Expand Up @@ -295,6 +299,21 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject
*/
Optional<PinState> effectiveForPath(const QByteArray &path);

/**
* Like effectiveForPath() but also considers subitem pin states.
*
* If the path's pin state and all subitem's pin states are identical
* then that pin state will be returned.
*
* If some subitem's pin state is different from the path's state,
* PinState::Inherited will be returned. Inherited isn't returned in
* any other cases.
*
* It's valid to use the root path "".
* Returns none on db error.
*/
Optional<PinState> effectiveForPathRecursive(const QByteArray &path);

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

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

Optional<VfsItemAvailability> Vfs::availabilityInDb(const QString &folderPath, const QString &pinPath)
{
auto pin = _setupParams.journal->internalPinStates().effectiveForPathRecursive(pinPath.toUtf8());
// not being able to retrieve the pin state isn't too bad
Optional<bool> hasDehydrated = _setupParams.journal->hasDehydratedFiles(folderPath.toUtf8());
if (!hasDehydrated)
return {};

if (*hasDehydrated) {
if (pin && *pin == PinState::OnlineOnly)
return VfsItemAvailability::OnlineOnly;
else
return VfsItemAvailability::SomeDehydrated;
} else {
if (pin && *pin == PinState::AlwaysLocal)
return VfsItemAvailability::AlwaysLocal;
else
return VfsItemAvailability::AllHydrated;
}
}

VfsOff::VfsOff(QObject *parent)
: Vfs(parent)
{
Expand Down Expand Up @@ -184,3 +205,22 @@ std::unique_ptr<Vfs> OCC::createVfsFromPlugin(Vfs::Mode mode)
qCInfo(lcPlugin) << "Created VFS instance from plugin" << pluginPath;
return vfs;
}

QString OCC::vfsItemAvailabilityToString(VfsItemAvailability availability, bool forFolder)
{
switch(availability) {
case VfsItemAvailability::AlwaysLocal:
return Vfs::tr("Always available locally");
case VfsItemAvailability::AllHydrated:
return Vfs::tr("Available locally");
case VfsItemAvailability::SomeDehydrated:
if (forFolder) {
return Vfs::tr("Some available online only");
} else {
return Vfs::tr("Available online only");
}
case VfsItemAvailability::OnlineOnly:
return Vfs::tr("Available online only");
}
ENFORCE(false);
}
21 changes: 19 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,8 @@ 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);
// sadly for virtual files the path in the metadata table can differ from path in 'flags'
Optional<VfsItemAvailability> availabilityInDb(const QString &folderPath, const QString &pinPath);

// the parameters passed to start()
VfsSetupParams _setupParams;
Expand Down Expand Up @@ -269,6 +282,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 All @@ -286,4 +300,7 @@ OCSYNC_EXPORT Vfs::Mode bestAvailableVfsMode();
/// Create a VFS instance for the mode, returns nullptr on failure.
OCSYNC_EXPORT std::unique_ptr<Vfs> createVfsFromPlugin(Vfs::Mode mode);

/// Convert availability to translated string
OCSYNC_EXPORT QString vfsItemAvailabilityToString(VfsItemAvailability availability, bool forFolder);

} // namespace OCC
16 changes: 10 additions & 6 deletions src/gui/accountsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,14 +340,18 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)

if (folder->supportsVirtualFiles()) {
auto availabilityMenu = menu->addMenu(tr("Availability"));
ac = availabilityMenu->addAction(tr("Local"));
ac->setCheckable(true);
ac->setChecked(!folder->newFilesAreVirtual());
auto availability = folder->vfs().availability(QString());
if (availability) {
ac = availabilityMenu->addAction(vfsItemAvailabilityToString(*availability, true));
ac->setEnabled(false);
}

ac = availabilityMenu->addAction(tr("Make always available locally"));
ac->setEnabled(!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 = availabilityMenu->addAction(tr("Free up local space"));
ac->setEnabled(!availability || *availability != VfsItemAvailability::OnlineOnly);
connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::OnlineOnly); });

ac = menu->addAction(tr("Disable virtual file support..."));
Expand Down
Loading

0 comments on commit 88b1a8e

Please sign in to comment.