Skip to content

WebUI search API. Closes #2495#8584

Merged
glassez merged 2 commits into
qbittorrent:masterfrom
Piccirello:new-search-api-2
Oct 24, 2018
Merged

WebUI search API. Closes #2495#8584
glassez merged 2 commits into
qbittorrent:masterfrom
Piccirello:new-search-api-2

Conversation

@Piccirello

Copy link
Copy Markdown
Member

Continuation of #8152

@Piccirello Piccirello mentioned this pull request Mar 12, 2018
@glassez glassez added WebUI WebUI-related issues/changes Search engine Issues related to the search engine/search plugins functionality Feature Implement new feature/subsystem labels Mar 12, 2018
@Piccirello Piccirello force-pushed the new-search-api-2 branch 2 times, most recently from ffad20c to ca0248b Compare March 19, 2018 03:47
Comment thread src/webui/api/torrentscontroller.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the user specifies "Skip checking", it means that qBittorrent will not check the files, but will begin to seed them, assuming they already exist. This comment means "it would be nice to check the files existence before".
Your changes is meaningless since qBittorrent creates save folder when add torrent to session.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the WebUI the user can specify whatever path they'd like. There's no guarantee that the path will actually exist- and that it isn't complete gibberish- so that's what this check is for. This has nothing to do with the "Skip checking" option.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has nothing to do with the "Skip checking" option.

Why did you delete this comment? It's just about "Skip checking" option.

There's no guarantee that the path will actually exist- and that it isn't complete gibberish- so that's what this check is for.

Then you should try to create it, but not check for its existence. As I said, it's allowed case when path doesn't exist before we add torrent.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then you should try to create it, but not check for its existence. As I said, it's allowed case when path doesn't exist before we add torrent.

I think you're describing the case where the last directory doesn't exist. I'm focusing on the issue where the user specifies a path /mnt1/Disk/Torrents/Downloads where /mnt1 doesn't exist. Surely in this case we shouldn't create that entire path, but throw an error.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want this commit to be the focus of this PR. If it's that contentious a change I'll gladly remove it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want this commit to be the focus of this PR.

Me too. I just wanted to clear the space for the main review.

If it's that contentious a change I'll gladly remove it.

It is not as unambiguous as it may seem at first glance. It is better to move it in a separate PR.

I think you're describing the case where the last directory doesn't exist.

No, I'm describing common case. It should create save path even if it multilevel.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not as unambiguous as it may seem at first glance. It is better to move it in a separate PR.

I'll move it to a separate PR.

No, I'm describing common case. It should create save path even if it multilevel.

In the case I described (/mnt1/Disk/Torrents/Downloads) would you expect qBittorrent to create all directories, or would you expect an error for /mnt1 not existing? I personally would expect the latter.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't say what I expected. I was talking about how it works now.
But, IMO, this behavior is quite expected, otherwise users (especially, remote users) will get a hell of a headache (it breaks some automations).

P.S. Let's continue this discussion in its own topic.

@Piccirello Piccirello force-pushed the new-search-api-2 branch 4 times, most recently from d1da8cd to 5574420 Compare March 20, 2018 22:38
Comment thread src/base/search/searchpluginmanager.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you omit these?
If there are any messages should print out, it should go through Logger.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Plugin is not supported." without any particular information is quite meaningless message.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there are any messages should print out, it should go through Logger.

qDebug and Logger are both used pretty extensively in the codebase. Is the qDebug way of logging deprecated?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the qDebug way of logging deprecated?

IMO yes, we don't need to distribute these code/messages unless we want users to help debug something, even so, we can provide a discrete build for them instead of using qDebug.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll use the logger so that they'll be visible when the Log is added to the webui. I'll also do this for when plugin updates fail - though this is admittedly a poor solution.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, qDebug is still usefull for dev-eyes-only messages (e.g. to indicate some important execution points etc.). Also you free to use it as you want when you test some new code, but many of them are redundant in result and should be removed.

Comment thread src/webui/api/searchcontroller.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const QJsonObject result = QJsonObject::fromVariantMap({ {"status", "Ok."}, {"id", id} });

should be possible...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this intermediate object? I mean QVariantMap.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be possible...

I didn't know about this inline form. Thanks.

Why do you need this intermediate object? I mean QVariantMap.

Because I don't know of another way. I'm still very new to C++, so when pointing out that there are other ways, suggestions are welcome.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestions are welcome

You should use QJson types directly, unless you already have data in different format.
QJsonObject has similar interface with QHash/QMap.
QJsonArray has similar interface with QList.

You can do either:

const QJsonObject result = {{"status", "Ok."}, {"id", id}};
setResult(result);

or (if you don't need intermediate variable):

setResult(QJsonObject {{"status", "Ok."}, {"id", id}});

So please don't create QVariantXXX types only to convert it to QJsonXXX types (everywhere in this code).

Comment thread src/webui/api/searchcontroller.cpp Outdated

@Chocobo1 Chocobo1 Mar 22, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would omit curly braces here and other places.

Comment thread src/webui/api/searchcontroller.cpp Outdated

@Chocobo1 Chocobo1 Mar 22, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refactor, use a variable or two here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and use {} replacing QList<SearchResult>()

Comment thread src/webui/api/searchcontroller.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use range-based for()

Comment thread src/webui/api/searchcontroller.cpp Outdated

@Chocobo1 Chocobo1 Mar 22, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QSet<QString>?

@Piccirello Piccirello Mar 25, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QSet doesn't maintain order of its elements, which is required for the webui implementation of categories.

Comment thread src/webui/api/searchcontroller.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain your plan? or just remove it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't thought of a good way of handling this. Removing.

Comment thread src/webui/api/searchcontroller.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use range-based for, this applies to all similar instances.

Comment thread src/webui/api/searchcontroller.cpp Outdated

@Chocobo1 Chocobo1 Mar 22, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is wrong (in webapplication.cpp too), the do while() should be replaced by if()

Nevermind.

Comment thread src/webui/api/searchcontroller.cpp Outdated

@Chocobo1 Chocobo1 Mar 22, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

searchResultsVariantList += QMap<QString, QVariant> {
    { "fileName", searchResult.fileName }
    //...
};

// or alternative
const QMap<QString, QVariant> searchResultMap {
    { "fileName", searchResult.fileName }
    //...
};

searchResultsVariantList << searchResultMap;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get this error when compiling with += and <<

webui/api/searchcontroller.cpp:233:34: error: no match for ‘operator+=’ (operand types are ‘QVariantList {aka QList<QVariant>}’ and ‘<brace-enclosed initializer list>’)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get this error when compiling with += and <<

pardon, I updated the code above.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pardon, I updated the code above.

As I requested before, it should be QJsonObject/QJsonArray here too.

Comment thread src/webui/api/searchcontroller.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const QMap<QString, QVariant> resultMap = {
   {"status", ((isSearchActive) || (queueSize > 0)) ? "Loading." : "Done."}
   // ...
};

Comment thread src/webui/api/searchcontroller.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const QStringList &plugins

Comment thread src/webui/api/searchcontroller.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const QList<SearchResult> &searchResults

Comment thread src/webui/api/searchcontroller.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/webui/api/searchcontroller.h Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base headers should go first, please move up

Comment thread src/webui/websearchhandler.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

Comment thread src/webui/websearchhandler.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++i

Comment thread src/webui/websearchhandler.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QQueue inherits from QList, if you used QList m_stdoutQueue instead, you can just return m_stdoutQueue; below.

@Chocobo1 Chocobo1 Mar 22, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or if you can predict some upper bound of the size of m_stdoutQueue, you should use QVector + QVector::reserve()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QQueue inherits from QList, if you used QList m_stdoutQueue instead, you can just return m_stdoutQueue; below.

I limit the number of results to 500 (arbitrary number) so that the response isn't too large. Though I'm switching to using QList::mid() for this.

Comment thread src/webui/websearchhandler.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bool WebSearchHandler::isActive() const

@Piccirello

Copy link
Copy Markdown
Member Author

Preliminary webapi documentation. This isn't finished yet and is missing fields

Search

Start search

POST /api/v2/search/start HTTP/1.1
Host: 127.0.0.1
Cookie: SID=your_sid
Content-Type: application/x-www-form-urlencoded
Content-Length: length

pattern=test&category=all&plugins=enabled

Stop search

POST /api/v2/search/stop HTTP/1.1
Host: 127.0.0.1
Cookie: SID=your_sid
Content-Type: application/x-www-form-urlencoded
Content-Length: length

id=123456

Get search status

GET /api/v2/search/status?id=123456 HTTP/1.1
Host: 127.0.0.1

Get search results

POST /api/v2/search/results HTTP/1.1
Host: 127.0.0.1
Cookie: SID=your_sid
Content-Type: application/x-www-form-urlencoded
Content-Length: length

id=123456

Delete search

POST /api/v2/search/delete HTTP/1.1
Host: 127.0.0.1
Cookie: SID=your_sid
Content-Type: application/x-www-form-urlencoded
Content-Length: length

id=123456

Get search categories

GET /api/v2/search/categories?pluginName=legittorrents HTTP/1.1
Host: 127.0.0.1

Get search plugins

GET /api/v2/search/plugins HTTP/1.1
Host: 127.0.0.1

Install search plugin

POST /api/v2/search/installPlugin HTTP/1.1
Host: 127.0.0.1
Cookie: SID=your_sid
Content-Type: application/x-www-form-urlencoded
Content-Length: length

sources=https://url.to/plugin

Uninstall search plugin

POST /api/v2/search/uninstallPlugin HTTP/1.1
Host: 127.0.0.1
Cookie: SID=your_sid
Content-Type: application/x-www-form-urlencoded
Content-Length: length

names=legittorrents

Enable search plugin

POST /api/v2/search/enablePlugin HTTP/1.1
Host: 127.0.0.1
Cookie: SID=your_sid
Content-Type: application/x-www-form-urlencoded
Content-Length: length

names=legittorrents&enabled=true

Update search plugins

POST /api/v2/search/updatePlugins HTTP/1.1
Host: 127.0.0.1
Cookie: SID=your_sid
Content-Type: application/x-www-form-urlencoded
Content-Length: length

@glassez

glassez commented Oct 24, 2018

Copy link
Copy Markdown
Member

I never liked the (legacy) format of the Web API documentation. Why do you keep repeating all these HTTP headers etc.? It's so obvious, and therefore it is absolutely unnecessary here. Please consider RSS Web API documentation format.

@glassez

glassez commented Oct 24, 2018

Copy link
Copy Markdown
Member

The sooner this is merged the sooner I can start looking into WebUI RSS support

Well, blackmail is a powerful weapon!
I actually wanted to postpone it until the next release. But I assumed v4.0.4 would come out soon. Unfortunately, the release cycle is "frozen" again. I see no reason to delay it any longer.

@glassez glassez merged commit 7e36cc7 into qbittorrent:master Oct 24, 2018
@glassez

glassez commented Oct 24, 2018

Copy link
Copy Markdown
Member

Thank you all!

@Piccirello

Piccirello commented Oct 25, 2018

Copy link
Copy Markdown
Member Author

@glassez I've updated the wiki with Search info using a slightly modified version of your RSS documentation format. Namely, I've added HTTP status codes. This format is definitely more concise than the old style with all the extra headers.

@FranciscoPombal

Copy link
Copy Markdown
Member

@glassez @Piccirello I quite like the new Web API documentation format, but wouldn't it be good to also specify the type of method in the description (GET, POST, ...)? I agree that the other headers are unnecessary.

@glassez

glassez commented Oct 26, 2018

Copy link
Copy Markdown
Member

Currently both methods are allowed.

@FranciscoPombal

Copy link
Copy Markdown
Member

@glassez So to be clear, the request type can be either GET or POST for all methods of the WebAPI?

@glassez

glassez commented Oct 26, 2018

Copy link
Copy Markdown
Member

Yes.

@FranciscoPombal

Copy link
Copy Markdown
Member

@glassez Thanks for the clarification. Perhaps that should be mentioned in the Wiki page for the Web API? I could edit that now and perhaps star converting the documentation of older methods into the new format.

@glassez

glassez commented Oct 26, 2018

Copy link
Copy Markdown
Member

converting the documentation of older methods into the new format.

It would be nice!

@Piccirello

Copy link
Copy Markdown
Member Author

I’ve avoided documenting this because I actually think it’s something we should be more strict about, and something I’d like to enforce. Any non-idempotent methods should be a POST, and all others GET.

@FranciscoPombal

Copy link
Copy Markdown
Member

@Piccirello I have started the conversion to the new documentation format. However, there are some things I don't know.

For example, in the old documentation format, a lot of methods had examples of successful execution. In the new documentation format, there is an "HTTP status code/scenario" table instead of such examples.

The problem is that I often did not know what to put in the "HTTP status code/Scenario" table, since I do not know what all methods do in all scenarios (and the old documentation format did not specify what happened in all scenarios), so I marked such entries as TODO. I preferred this approach rather than add a single entry to all with "200 | Success" since this way it is more obvious that it needs to be better documented.

Can you take a look and fill those entries in, since you know the WebAPI much better? Just Ctrl+F TODO. Thanks.

@Piccirello

Piccirello commented Oct 31, 2018

Copy link
Copy Markdown
Member Author

@FranciscoPombal I've filled in all HTTP status codes marked TODO. There's only one remaining TODO, which is JSON response info for /sync/torrentPeers. Also discovered that I hadn't documented /torrents/editCategory from a previous PR, so that's in there now.

I noticed there were several APIs erroneously documented as always returning 200, despite them also returning other status codes. Please be careful with how liberally you're assigning the 200-only info. I've updated these codes as well.

I'm not sure if there's value in keeping the Requires authentication field in our documentation. We only have two Web APIs that don't require auth, while all others do (I actually think it's three and our documentation is just outdated- I'll look into this further). Since these are the special case, we should probably just mark these specifically and omit the info elsewhere.

Thanks again for transitioning this info to the new format. I really think the updated format and status codes will be useful to people leveraging the qBittorrent Web API.

@Piccirello

Copy link
Copy Markdown
Member Author

It appears /api/v2/app/version and /api/v2/app/webapiVersion do in fact require authentication. It's the legacy APIs /version/api, /version/api_min, and /version/qbittorrent (which we still support) that do not require auth.

@glassez @Chocobo1 What are your thoughts on the best way forward? Should we just document this as-is? Ideally the three /app/ API calls wouldn't require auth, but this is a slightly bigger change involving webapplication.cpp. I'm fine making these changes, but want to get your thoughts before doing so.

@Chocobo1

Chocobo1 commented Nov 1, 2018

Copy link
Copy Markdown
Member

What are your thoughts on the best way forward?

IIRC it has discussed before, personally I don't think it is wise to give out version number to everyone for free. So ideally getting version number would require auth when the legacy APIs are deprecated.

@FranciscoPombal

FranciscoPombal commented Nov 1, 2018

Copy link
Copy Markdown
Member

@Piccirello

I noticed there were several APIs erroneously documented as always returning 200, despite them also returning other status codes. Please be careful with how liberally you're assigning the 200-only info. I've updated these codes as well.

I only copied the content that was there before. I would advise you to actually revise the document thoroughly after it is fully converted to the new format; other things may be wrong/missing.

@Piccirello

Copy link
Copy Markdown
Member Author

@FranciscoPombal Oh yeah, I do see that now. This documentation is seriously outdated. A mass cleanup is probably required to fix everything.

@Piccirello

Copy link
Copy Markdown
Member Author

@Chocobo1 Could there be benefit to exposing the API version/min without auth? If we ever change the login API, other apps won’t be able to determine which login flow to use without trying + catching failure.

@Chocobo1

Chocobo1 commented Nov 1, 2018

Copy link
Copy Markdown
Member

Could there be benefit to exposing the API version/min without auth?

Other than letting strangers fingerprint you easily, none I am aware of.

If we ever change the login API...

I doubt we will easily do backward-incompatible changes to this part...

@sledgehammer999

Copy link
Copy Markdown
Member

@Piccirello this PR instatiates the SearchPluginManager at program start. This is a bad idea.
The SearchPluginManager does initialization stuff which depend on python being present. This might not be the case on windows. It results in warning messages in the log. Remember that search+python is optional in qbt. User might not choose to enable it--> the search tab might be disabled/hidden. User might not have python installed.
Searching for the interpreter on startup might introduce delays.
Please revise this approach and make it optional upon user's explicit enablement.

@glassez

glassez commented Nov 20, 2018

Copy link
Copy Markdown
Member

User might not choose to enable it--> the search tab might be disabled/hidden.

Depending core Search feature from its UI it was legacy qBittorrent behavior. Now it doesn't fit since SearchPluginManager should be accessible from different places.

Remember that search+python is optional in qbt.

We should make it optional at all. I.e. we should allow to disable entire Search subsystem, not only its UI (search tab).

@glassez

glassez commented Nov 20, 2018

Copy link
Copy Markdown
Member

The SearchPluginManager does initialization stuff which depend on python being present. This might not be the case on windows.

There is some legacy (bad) code related to Search subsystem in qBittorrent. E.g. SearchPluginManager doesn't handle all the management and some other code parts do python related stuff directly. It should be really improved.

@sledgehammer999

Copy link
Copy Markdown
Member

We should make it optional at all. I.e. we should allow to disable entire Search subsystem, not only its UI (search tab).

IIRC, before this PR the subsystem was entirely disabled if the tab wasn't enabled.

I am not against disengaging the connection between the subsystem and the UI tab. We could have it as a config option. But still an option.

@Piccirello

Copy link
Copy Markdown
Member Author

Opened #9889 to rectify this. Let's discuss the implementation over there.

@Piccirello Piccirello deleted the new-search-api-2 branch July 15, 2019 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Implement new feature/subsystem Search engine Issues related to the search engine/search plugins functionality WebAPI WebAPI-related issues/changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants