Skip to content

Commit e7f7ad7

Browse files
authored
Merge pull request #1735 from nextcloud/feature/context-menu-direct-edit
Add direct editing to the file manager extension
2 parents 2039872 + c0863ed commit e7f7ad7

9 files changed

Lines changed: 267 additions & 4 deletions

File tree

src/gui/connectionvalidator.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,11 @@ void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json)
249249
return;
250250
}
251251

252+
// Check for the directEditing capability
253+
QUrl directEditingURL = QUrl(caps["files"].toObject()["directEditing"].toObject()["url"].toString());
254+
QString directEditingETag = caps["files"].toObject()["directEditing"].toObject()["etag"].toString();
255+
_account->fetchDirectEditors(directEditingURL, directEditingETag);
256+
252257
fetchUser();
253258
}
254259

src/gui/socketapi.cpp

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,42 @@ void SocketApi::command_VERSION(const QString &, SocketListener *listener)
455455

456456
void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener)
457457
{
458-
listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is Nextcloud").arg(Theme::instance()->appNameGUI()));
458+
//listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is Nextcloud").arg(Theme::instance()->appNameGUI()));
459+
listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + Theme::instance()->appNameGUI());
460+
}
461+
462+
void SocketApi::command_EDIT(const QString &localFile, SocketListener *listener)
463+
{
464+
auto fileData = FileData::get(localFile);
465+
if (!fileData.folder) {
466+
qCWarning(lcSocketApi) << "Unknown path" << localFile;
467+
return;
468+
}
469+
470+
auto record = fileData.journalRecord();
471+
if (!record.isValid())
472+
return;
473+
474+
DirectEditor* editor = getDirectEditorForLocalFile(fileData.localPath);
475+
if (!editor)
476+
return;
477+
478+
JsonApiJob *job = new JsonApiJob(fileData.folder->accountState()->account(), QLatin1String("ocs/v2.php/apps/files/api/v1/directEditing/open"), this);
479+
480+
QUrlQuery params;
481+
params.addQueryItem("path", fileData.accountRelativePath);
482+
params.addQueryItem("editorId", editor->id());
483+
job->addQueryParams(params);
484+
job->usePOST();
485+
486+
QObject::connect(job, &JsonApiJob::jsonReceived, [](const QJsonDocument &json){
487+
auto data = json.object().value("ocs").toObject().value("data").toObject();
488+
auto url = QUrl(data.value("url").toString());
489+
490+
if(!url.isEmpty())
491+
Utility::openBrowser(url, nullptr);
492+
});
493+
job->start();
459494
}
460495

461496
// don't pull the share manager into socketapi unittests
@@ -644,7 +679,7 @@ void SocketApi::command_GET_STRINGS(const QString &argument, SocketListener *lis
644679
{
645680
static std::array<std::pair<const char *, QString>, 5> strings { {
646681
{ "SHARE_MENU_TITLE", tr("Share options") },
647-
{ "CONTEXT_MENU_TITLE", tr("Share via %1").arg(Theme::instance()->appNameGUI())},
682+
{ "CONTEXT_MENU_TITLE", Theme::instance()->appNameGUI() },
648683
{ "COPY_PRIVATE_LINK_MENU_TITLE", tr("Copy private link to clipboard") },
649684
{ "EMAIL_PRIVATE_LINK_MENU_TITLE", tr("Send private link by email …") },
650685
} };
@@ -738,13 +773,41 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
738773
FileData fileData = hasSeveralFiles ? FileData{} : FileData::get(argument);
739774
bool isOnTheServer = fileData.journalRecord().isValid();
740775
auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:");
776+
auto capabilities = fileData.folder->accountState()->account()->capabilities();
777+
741778
if (fileData.folder && fileData.folder->accountState()->isConnected()) {
779+
DirectEditor* editor = getDirectEditorForLocalFile(fileData.localPath);
780+
if (editor) {
781+
//listener->sendMessage(QLatin1String("MENU_ITEM:EDIT") + flagString + tr("Edit via ") + editor->name());
782+
listener->sendMessage(QLatin1String("MENU_ITEM:EDIT") + flagString + tr("Edit"));
783+
} else {
784+
listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
785+
}
786+
742787
sendSharingContextMenuOptions(fileData, listener);
743-
listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
744788
}
745789
listener->sendMessage(QString("GET_MENU_ITEMS:END"));
746790
}
747791

792+
DirectEditor* SocketApi::getDirectEditorForLocalFile(const QString &localFile)
793+
{
794+
FileData fileData = FileData::get(localFile);
795+
auto capabilities = fileData.folder->accountState()->account()->capabilities();
796+
797+
if (fileData.folder && fileData.folder->accountState()->isConnected()) {
798+
QMimeDatabase db;
799+
QMimeType type = db.mimeTypeForFile(localFile);
800+
801+
DirectEditor* editor = capabilities.getDirectEditorForMimetype(type);
802+
if (!editor) {
803+
editor = capabilities.getDirectEditorForOptionalMimetype(type);
804+
}
805+
return editor;
806+
}
807+
808+
return nullptr;
809+
}
810+
748811
QString SocketApi::buildRegisterPathMessage(const QString &path)
749812
{
750813
QFileInfo fi(path);

src/gui/socketapi.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ namespace OCC {
3737
class SyncFileStatus;
3838
class Folder;
3939
class SocketListener;
40+
class DirectEditor;
4041

4142
/**
4243
* @brief The SocketApi class
@@ -123,6 +124,10 @@ private slots:
123124
*/
124125
Q_INVOKABLE void command_GET_MENU_ITEMS(const QString &argument, SocketListener *listener);
125126

127+
/// Direct Editing
128+
Q_INVOKABLE void command_EDIT(const QString &localFile, SocketListener *listener);
129+
DirectEditor* getDirectEditorForLocalFile(const QString &localFile);
130+
126131
QString buildRegisterPathMessage(const QString &path);
127132

128133
QSet<QString> _registeredAliases;

src/libsync/account.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
#include <QSslKey>
3636
#include <QAuthenticator>
3737
#include <QStandardPaths>
38+
#include <QJsonDocument>
39+
#include <QJsonObject>
40+
#include <QJsonArray>
3841

3942
#include <keychain.h>
4043
#include "creds/abstractcredentials.h"
@@ -600,4 +603,49 @@ void Account::deleteAppPassword(){
600603
job->start();
601604
}
602605

606+
void Account::fetchDirectEditors(const QUrl &directEditingURL, const QString &directEditingETag)
607+
{
608+
if(directEditingURL.isEmpty() || directEditingETag.isEmpty())
609+
return;
610+
611+
// Check for the directEditing capability
612+
if (!directEditingURL.isEmpty() &&
613+
(directEditingETag.isEmpty() || directEditingETag != _lastDirectEditingETag)) {
614+
// Fetch the available editors and their mime types
615+
JsonApiJob *job = new JsonApiJob(sharedFromThis(), QLatin1String("ocs/v2.php/apps/files/api/v1/directEditing"), this);
616+
QObject::connect(job, &JsonApiJob::jsonReceived, this, &Account::slotDirectEditingRecieved);
617+
job->start();
618+
}
619+
}
620+
621+
void Account::slotDirectEditingRecieved(const QJsonDocument &json)
622+
{
623+
auto data = json.object().value("ocs").toObject().value("data").toObject();
624+
auto editors = data.value("editors").toObject();
625+
626+
foreach (auto editorKey, editors.keys()) {
627+
auto editor = editors.value(editorKey).toObject();
628+
629+
const QString id = editor.value("id").toString();
630+
const QString name = editor.value("name").toString();
631+
632+
if(!id.isEmpty() && !name.isEmpty()) {
633+
auto mimeTypes = editor.value("mimetypes").toArray();
634+
auto optionalMimeTypes = editor.value("optionalMimetypes").toArray();
635+
636+
DirectEditor *directEditor = new DirectEditor(id, name);
637+
638+
foreach(auto mimeType, mimeTypes) {
639+
directEditor->addMimetype(mimeType.toString().toLatin1());
640+
}
641+
642+
foreach(auto optionalMimeType, optionalMimeTypes) {
643+
directEditor->addOptionalMimetype(optionalMimeType.toString().toLatin1());
644+
}
645+
646+
_capabilities.addDirectEditor(directEditor);
647+
}
648+
}
649+
}
650+
603651
} // namespace OCC

src/libsync/account.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject
246246
void writeAppPasswordOnce(QString appPassword);
247247
void deleteAppPassword();
248248

249+
/// Direct Editing
250+
// Check for the directEditing capability
251+
void fetchDirectEditors(const QUrl &directEditingURL, const QString &directEditingETag);
252+
249253
public slots:
250254
/// Used when forgetting credentials
251255
void clearQNAMCache();
@@ -278,6 +282,7 @@ public slots:
278282
protected Q_SLOTS:
279283
void slotCredentialsFetched();
280284
void slotCredentialsAsked();
285+
void slotDirectEditingRecieved(const QJsonDocument &json);
281286

282287
private:
283288
Account(QObject *parent = nullptr);
@@ -324,6 +329,9 @@ protected Q_SLOTS:
324329

325330
friend class AccountManager;
326331

332+
// Direct Editing
333+
QString _lastDirectEditingETag;
334+
327335
/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
328336
* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
329337
*

src/libsync/capabilities.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,85 @@ bool Capabilities::uploadConflictFiles() const
176176

177177
return _capabilities["uploadConflictFiles"].toBool();
178178
}
179+
180+
/*-------------------------------------------------------------------------------------*/
181+
182+
// Direct Editing
183+
void Capabilities::addDirectEditor(DirectEditor* directEditor)
184+
{
185+
if(directEditor)
186+
_directEditors.append(directEditor);
187+
}
188+
189+
DirectEditor* Capabilities::getDirectEditorForMimetype(const QMimeType &mimeType)
190+
{
191+
foreach(DirectEditor* editor, _directEditors) {
192+
if(editor->hasMimetype(mimeType))
193+
return editor;
194+
}
195+
196+
return nullptr;
197+
}
198+
199+
DirectEditor* Capabilities::getDirectEditorForOptionalMimetype(const QMimeType &mimeType)
200+
{
201+
foreach(DirectEditor* editor, _directEditors) {
202+
if(editor->hasOptionalMimetype(mimeType))
203+
return editor;
204+
}
205+
206+
return nullptr;
207+
}
208+
209+
/*-------------------------------------------------------------------------------------*/
210+
211+
DirectEditor::DirectEditor(const QString &id, const QString &name, QObject* parent)
212+
: QObject(parent)
213+
, _id(id)
214+
, _name(name)
215+
{
216+
}
217+
218+
QString DirectEditor::id() const
219+
{
220+
return _id;
221+
}
222+
223+
QString DirectEditor::name() const
224+
{
225+
return _name;
226+
}
227+
228+
void DirectEditor::addMimetype(const QByteArray &mimeType)
229+
{
230+
_mimeTypes.append(mimeType);
231+
}
232+
233+
void DirectEditor::addOptionalMimetype(const QByteArray &mimeType)
234+
{
235+
_optionalMimeTypes.append(mimeType);
236+
}
237+
238+
QList<QByteArray> DirectEditor::mimeTypes() const
239+
{
240+
return _mimeTypes;
241+
}
242+
243+
QList<QByteArray> DirectEditor::optionalMimeTypes() const
244+
{
245+
return _optionalMimeTypes;
246+
}
247+
248+
bool DirectEditor::hasMimetype(const QMimeType &mimeType)
249+
{
250+
return _mimeTypes.contains(mimeType.name().toLatin1());
251+
}
252+
253+
bool DirectEditor::hasOptionalMimetype(const QMimeType &mimeType)
254+
{
255+
return _optionalMimeTypes.contains(mimeType.name().toLatin1());
256+
}
257+
258+
/*-------------------------------------------------------------------------------------*/
259+
179260
}

src/libsync/capabilities.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020

2121
#include <QVariantMap>
2222
#include <QStringList>
23+
#include <QMimeDatabase>
2324

2425
namespace OCC {
2526

27+
class DirectEditor;
28+
2629
/**
2730
* @brief The Capabilities class represents the capabilities of an ownCloud
2831
* server
@@ -127,9 +130,47 @@ class OWNCLOUDSYNC_EXPORT Capabilities
127130
*/
128131
bool uploadConflictFiles() const;
129132

133+
// Direct Editing
134+
void addDirectEditor(DirectEditor* directEditor);
135+
DirectEditor* getDirectEditorForMimetype(const QMimeType &mimeType);
136+
DirectEditor* getDirectEditorForOptionalMimetype(const QMimeType &mimeType);
137+
130138
private:
131139
QVariantMap _capabilities;
140+
141+
QList<DirectEditor*> _directEditors;
132142
};
143+
144+
/*-------------------------------------------------------------------------------------*/
145+
146+
class OWNCLOUDSYNC_EXPORT DirectEditor : public QObject
147+
{
148+
Q_OBJECT
149+
public:
150+
DirectEditor(const QString &id, const QString &name, QObject* parent = 0);
151+
152+
void addMimetype(const QByteArray &mimeType);
153+
void addOptionalMimetype(const QByteArray &mimeType);
154+
155+
bool hasMimetype(const QMimeType &mimeType);
156+
bool hasOptionalMimetype(const QMimeType &mimeType);
157+
158+
QString id() const;
159+
QString name() const;
160+
161+
QList<QByteArray> mimeTypes() const;
162+
QList<QByteArray> optionalMimeTypes() const;
163+
164+
private:
165+
QString _id;
166+
QString _name;
167+
168+
QList<QByteArray> _mimeTypes;
169+
QList<QByteArray> _optionalMimeTypes;
170+
};
171+
172+
/*-------------------------------------------------------------------------------------*/
173+
133174
}
134175

135176
#endif //CAPABILITIES_H

src/libsync/networkjobs.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ void JsonApiJob::start()
801801
auto query = _additionalParams;
802802
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
803803
QUrl url = Utility::concatUrlPath(account()->url(), path(), query);
804-
sendRequest("GET", url, _request);
804+
sendRequest(_usePOST ? "POST" : "GET", url, _request);
805805
AbstractNetworkJob::start();
806806
}
807807

src/libsync/networkjobs.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,16 @@ class OWNCLOUDSYNC_EXPORT JsonApiJob : public AbstractNetworkJob
373373
void addQueryParams(const QUrlQuery &params);
374374
void addRawHeader(const QByteArray &headerName, const QByteArray &value);
375375

376+
/**
377+
* @brief usePOST - allow job to do an anonymous POST request instead of GET
378+
* @param params: (optional) true for POST, false for GET (default).
379+
*
380+
* This function needs to be called before start() obviously.
381+
*/
382+
void usePOST(bool usePOST = true) {
383+
_usePOST = usePOST;
384+
}
385+
376386
public slots:
377387
void start() override;
378388

@@ -398,6 +408,8 @@ public slots:
398408
private:
399409
QUrlQuery _additionalParams;
400410
QNetworkRequest _request;
411+
412+
bool _usePOST = false;
401413
};
402414

403415
/**

0 commit comments

Comments
 (0)