Commit f4e89f80 authored by Daniel Vrátil's avatar Daniel Vrátil 🤖
Browse files

First take on user-friendly names using PathCache

parent 379e1cb7
......@@ -29,6 +29,7 @@ using namespace KGAPI2::Drive;
#define VND_GOOGLE_APPS_FORM QLatin1String("application/vnd.google-apps.form")
#define VND_GOOGLE_APPS_PRESENTATION QLatin1String("application/vnd.google-apps.presentation")
#define VND_GOOGLE_APPS_SPREADSHEET QLatin1String("application/vnd.google-apps.spreadsheet")
#define VND_GOOGLE_APPS_FOLDER QLatin1String("application/vnd.google-apps.folder")
#define VND_OASIS_OPENDOCUMENT_TEXT QLatin1String("application/vnd.oasis.opendocument.text")
#define VND_OASIS_OPENDOCUMENT_PRESENTATION QLatin1String("application/vnd.oasis.opendocument.presentation")
......@@ -93,6 +94,11 @@ static const QMap<QString /* mimetype */, QStringList /* target mimetypes */ > C
}
QString GDriveHelper::folderMimeType() const
{
return VND_GOOGLE_APPS_FOLDER;
}
bool GDriveHelper::isGDocsDocument(const KGAPI2::Drive::FilePtr &file)
{
return GDriveHelper::ConversionMap.contains(file->mimeType());
......
......@@ -24,6 +24,7 @@
namespace GDriveHelper
{
QString folderMimeType() const;
bool isGDocsDocument(const KGAPI2::Drive::FilePtr &file);
......@@ -31,4 +32,5 @@ namespace GDriveHelper
}
#endif // GDRIVEHELPER_H
\ No newline at end of file
......@@ -52,9 +52,14 @@ using namespace KGAPI2;
using namespace Drive;
#define RUN_KGAPI_JOB(job) RUN_KGAPI_JOB_PARAMS(job, url, accountId)
#define RUN_KGAPI_JOB(job) \
RUN_KGAPI_JOB_PARAMS(job, url, accountId)
#define RUN_KGAPI_JOB_PARAMS(job, url, accountId) \
RUN_KGAPI_JOB_IMPL(job, url, accountId, )
#define RUN_KGAPI_JOB_RETVAL(job, retval) \
RUN_KGAPI_JOB_IMPL(job, url, accountId, retval)
#define RUN_KGAPI_JOB_IMPL(job, url, accountId, retval) \
{ \
KIOGDrive::Action action = KIOGDrive::Fail; \
Q_FOREVER { \
......@@ -67,7 +72,7 @@ using namespace Drive;
if (action == KIOGDrive::Success) { \
break; \
} else if (action == KIOGDrive::Fail) { \
return; \
return retval; \
} \
job.setAccount(getAccount(accountId)); \
job.restart(); \
......@@ -157,7 +162,7 @@ KIO::UDSEntry KIOGDrive::fileToUDSEntry(const FilePtr &origFile) const
GDriveHelper::convertFromGDocs(file);
}
entry.insert(KIO::UDSEntry::UDS_NAME, file->id());
entry.insert(KIO::UDSEntry::UDS_NAME, file->title());
entry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, file->title());
if (file->isFolder()) {
......@@ -191,33 +196,109 @@ KIO::UDSEntry KIOGDrive::fileToUDSEntry(const FilePtr &origFile) const
return entry;
}
QString KIOGDrive::lastPathComponent(const KUrl &url) const
void KIOGDrive::openConnection()
{
const QString path = url.path(KUrl::KUrl::RemoveTrailingSlash);
const QStringList components = path.split(QLatin1Char('/'), QString::SkipEmptyParts);
if (components.isEmpty()) {
return QString();
}
if (components.size() == 1) {
return QLatin1String("root");
}
return components.last();
kDebug() << "Ready to talk to GDrive";
}
QStringList KIOGDrive::pathComponents(const QString &path) const
{
return path.split(QLatin1Char('/'), QString::SkipEmptyParts);
}
QString KIOGDrive::accountFromPath(const KUrl& url) const
{
const QString path = url.path(KUrl::RemoveTrailingSlash);
const QStringList components = path.split(QLatin1Char('/'), QString::SkipEmptyParts);
const QStringList components = pathComponents(url);
if (components.isEmpty()) {
return QString();
}
return components[0];
}
bool KIOGDrive::isRoot(const KUrl& url) const
{
return pathComponents(url).length() == 0;
}
void KIOGDrive::openConnection()
bool KIOGDrive::isAccountRoot(const KUrl& url) const
{
kDebug() << "Ready to talk to GDrive";
return pathComponents(url).length() == 1;
}
void KIOGDrive::createAccount()
{
const KGAPI2::AccountPtr account = m_accountManager.account(QString());
redirection(KUrl(QString::fromLatin1("gdrive:/%1").arg(account->accountName())));
finished();
}
void KIOGDrive::listAccounts()
{
const QString accounts = m_accountManager.accounts();
if (accounts.isEmpty()) {
createAccount();
return;
}
for (const QString &account : accounts) {
const KIO::UDSEntry entry = AccountManager::accountToUDSEntry(account);
listEntry(entry, false);
}
KIO::UDSEntry newAccountEntry;
newAccountEntry.insert(KIO::UDSEntry::UDS_NAME, QLatin1String("new-account"));
newAccountEntry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("New account"));
newAccountEntry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
newAccountEntry.insert(KIO::UDSEntry::UDS_ICON_NAME, QLatin1String("list-add-user"));
listEntry(newAccountEntry, false);
listEntry(KIO::UDSEntry(), true);
finished();
return;
}
QString KIOGDrive::resolveFileIdFromPath(const QString &path, bool isFolder)
{
QString fileId = m_cache.idForPath(path);
if (!fileId.isEmpty()) {
return fileId;
}
const int index = path.lastIndexOf(QLatin1Char('/'));
if (index < 1) {
return QLatin1String("root");
}
const QString fileName = path.midRef(index + 1);
const QString parentPath = m_cache.idForPath(path.midRef(0, index - 1));
// Try to recursively resolve ID of parent path - either from cache, or by
// querying Google
const QString parentId = resolveFileIdFromPath(parentPath, true);
FileSearchQuery query;
query.addQuery(FileSearchQuery::MimeType,
isFolder ? FileSearchQuery::Equals : FileSearchQuery::NotEquals,
GDriveHelper::folderMimeType());
query.addQuery(FileSearchQuery::Title, FileSearchQuery::Equals, x);
query.addQuery(FileSearchQuery::Parents, FileSearchQuery::In, parentId);
const QString accountId = accountFromPath(path);
FileFetchJob fetchJob(query, getAccount(accountId));
fetchJob.setFields(FileFetchJob::Id | FileFetchJob::Title);
RUN_KGAPI_JOB_RETVAL(fetchJob, QString());
const ObjectsList objects = fetchJob.items();
if (objects.count() != 1) {
return QString();
}
const FilePtr file = objects[0].dynamicCast<File>();
m_cache.insertPath(path, file->id());
return file->id();
}
......@@ -226,35 +307,33 @@ void KIOGDrive::listDir(const KUrl &url)
{
kDebug() << url;
const QString folderId = lastPathComponent(url);
const QString accountId = accountFromPath(url);
// When listing root, list available accounts
const QStringList accounts = m_accountManager.accounts();
if (folderId.isEmpty() && !accounts.isEmpty()) {
for (const QString &account : accounts) {
const KIO::UDSEntry entry = AccountManager::accountToUDSEntry(account);
listEntry(entry, false);
}
KIO::UDSEntry newAccountEntry;
newAccountEntry.insert(KIO::UDSEntry::UDS_NAME, QLatin1String("new-account"));
newAccountEntry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("New account"));
newAccountEntry.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
newAccountEntry.insert(KIO::UDSEntry::UDS_ICON_NAME, QLatin1String("list-add-user"));
listEntry(newAccountEntry, false);
listEntry(KIO::UDSEntry(), true);
finished();
if (isRoot(url)) {
listAccounts();
return;
} else if (accounts.isEmpty() || accountId == QLatin1String("new-account")) {
// when there are no accounts, or user requested a new one, ask user to
// authenticate and redirect to that account
const KGAPI2::AccountPtr account = m_accountManager.account(QString());
redirection(KUrl(QString::fromLatin1("gdrive:/%1").arg(account->accountName())));
finished();
}
const QString accountId = accountFromPath(url);
if (accountId == QLatin1String("new-account")) {
createAccount();
return;
}
const bool listingTrash = (folderId == QLatin1String("trash"));
const QString folderName = url.fileName();
const bool listingTrash = (folderName == QLatin1String("trash"));
QString folderId;
if (isAccountRoot(url)) {
folderId = QLatin1String("root");
} else {
folderId = m_cache.idForPath(url.path());
if (folderId.isEmpty()) {
folderId = resolveFileIdFromPath(url.path(KUrl::RemoveTrailingSlash), true);
}
if (folderId.isEmpty()) {
error(KIO::ERR_DOES_NOT_EXIST, url.path());
return;
}
}
FileSearchQuery query;
if (listingTrash) {
......@@ -276,9 +355,11 @@ void KIOGDrive::listDir(const KUrl &url)
const KIO::UDSEntry entry = fileToUDSEntry(file);
listEntry(entry, false);
m_cache.insertPath(url.path(KUrl::AddTrailingSlash) + file->title(), file->id());
}
if (folderId == QLatin1String("root")) {
if (isAccountRoot(url)) {
KIO::UDSEntry trashEntry;
trashEntry.insert(KIO::UDSEntry::UDS_NAME, QLatin1String("trash"));
trashEntry.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Trash"));
......
......@@ -62,9 +62,21 @@ public:
virtual void mimetype(const KUrl &url);
private:
void listAccounts();
void createAccount();
QString resolveFileIdFromPath(const QString &path, bool isFolder);
Action handleError(KGAPI2::Job *job, const KUrl &url);
KIO::UDSEntry fileToUDSEntry(const KGAPI2::Drive::FilePtr &file) const;
QString lastPathComponent(const KUrl &url) const;
QStringList pathComponents(const QString &path) const;
QStringList pathComponents(const KUrl &url) const {
return pathComponents(url.path(KUrl::RemoveTrailingSlash));
}
bool isRoot(const KUrl &url) const;
bool isAccountRoot(const KUrl &url) const;
QString accountFromPath(const KUrl &url) const;
const KGAPI2::AccountPtr getAccount(const QString &accountName) {
......
......@@ -20,26 +20,7 @@
#include "pathcache.h"
#include <QDateTime>
PathCache::Record::Record()
: timestamp(-1)
{
}
PathCache::Record::Record(const PathCache::Record &other)
: value(other.value)
, timestamp(other.timestamp)
{
}
PathCache::Record::Record(const QString &value, const QString &parentId)
: value(value)
, ancestor(parentId)
, timestamp(QDateTime::currentMSecsSinceEpoch())
{
}
PathCache::PathCache(int expireSeconds)
: m_expireSeconds(expireSeconds)
PathCache::PathCache()
{
}
......@@ -47,72 +28,34 @@ PathCache::~PathCache()
{
}
bool PathCache::isRecordExpired(const PathCache::Record& record)
void PathCache::insertPath(const QString &path, const QString &fileId)
{
const qint64 now = QDateTime::currentMSecsSinceEpoch();
Q_ASSERT(record.timestamp <= now); // records must originate in the past
return ((now - record.timestamp) >= m_expireSeconds);
m_pathIdMap.insert(path, fileId);
}
QString PathCache::valueForKey(QHash<QString, PathCache::Record> &map, const QString &key)
QString PathCache::idForPath(const QString &path) const
{
if (!map.contains(key)) {
return QString();
}
const Record record = map.value(key);
if (isRecordExpired(record)) {
map.remove(key);
return QString();
}
return record.value;
return m_pathIdMap[path];
}
void PathCache::insertPath(const QString &id, const QString &name, const QString &parentId)
QStringList PathCache::descendants(const QString &path) const
{
m_idNameMap.insert(id, Record(name, parentId));
m_nameIdMap.insert(name, Record(id, parentId));
m_parentChildrenMap.insert(parentId, Record(id, QString()));
}
const QString fullPath = path.endsWith(QLatin1Char('/')) ? path : path + QLatin1Char('/');
QString PathCache::idForName(const QString &name)
{
return valueForKey(m_nameIdMap, name);
}
QString PathCache::nameForId(const QString &id)
{
return valueForKey(m_idNameMap, id);
}
QStringList PathCache::descendants(const QString &parentId)
{
if (!m_parentChildrenMap.contains(parentId)) {
return QStringList();
}
QHash<QString, Record>::Iterator it = m_parentChildrenMap.find(parentId);
bool expired = false;
QStringList ids;
while (it != m_parentChildrenMap.end()) {
if (isRecordExpired(*it)) {
it = m_parentChildrenMap.erase(it);
// The idea is that when the list contains at least one expired item,
// we continue scanning to find other expired entries and we return empty
// list to force cache update (even if there are non-expired entries)
ids.clear();
expired = true;
QStringList descendants;
for (auto iter = m_pathIdMap.begin(); iter != m_pathIdMap.end(); ++iter) {
if (!iter.key().startsWith(fullPath)) {
// Not a descendant at all
continue;
}
if (!expired) {
ids.append(it->value);
if (iter.key().lastIndexOf(QLatin1Char('/')) >= fullPath.size()) {
// Not a direct descendat
continue;
}
++it;
descendants.append(iter.key());
}
return ids;
return descendants;
}
......@@ -26,39 +26,16 @@
class PathCache
{
public:
PathCache(int expireSeconds = 30);
PathCache();
~PathCache();
void insertPath(const QString &id, const QString &name, const QString &parentId);
void insertPath(const QString &path, const QString &fileId);
QString nameForId(const QString &id);
QString idForName(const QString &name);
QStringList descendants(const QString &parentId);
QString idForPath(const QString &path) const;
QStringList descendants(const QString &path) const;
private:
class Record {
public:
Record();
Record(const QString &value, const QString &parentId);
Record(const Record &other);
bool isNull() const {
return timestamp == -1;
}
QString value;
QString ancestor;
qint64 timestamp;
};
bool isRecordExpired(const Record &record);
QString valueForKey(QHash<QString, Record> &map, const QString &key);
QHash<QString /* name */, Record > m_nameIdMap;
QHash<QString /* id */, Record > m_idNameMap;
QMultiHash<QString /* parentId */, Record > m_parentChildrenMap;
int m_expireSeconds;
QHash<QString /* path */, QString> m_pathIdMap;
};
#endif // PATHCACHE_H
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment