-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Use SQLite to store torrents and fastresumes #10099
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Special shoutout to DB Browser for SQLite. It is a great lightweight that lets you inspect/modify/administer and SQLite db. Very useful for debugging. |
c4e89b8
to
0d565dd
Compare
@sledgehammer999, that's fine! |
0d565dd
to
d14cef6
Compare
src/base/utils/db.h
Outdated
|
||
#pragma once | ||
|
||
namespace Utils |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no database related utilities in this file. It's just database configuration for some particular application component (BitTorrent). So it should be under "base/bittorrent".
Too much. |
I am all for decoupling our data from libtorrent's fastresume. |
At least all current "fastresume" saving events that are not fired by timer. |
src/base/bittorrent/session.cpp
Outdated
@@ -535,7 +538,8 @@ Session::Session(QObject *parent) | |||
connect(&m_networkManager, &QNetworkConfigurationManager::configurationChanged, this, &Session::networkConfigurationChange); | |||
|
|||
m_ioThread = new QThread(this); | |||
m_resumeDataSavingManager = new ResumeDataSavingManager {m_resumeFolderPath}; | |||
const QDir resumeDataDir(m_resumeFolderPath); | |||
m_resumeDataSavingManager = new ResumeDataSavingManager{resumeDataDir.absoluteFilePath(QLatin1String {Utils::DB::FILENAME})}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really want to have database in BT_backup folder? Why? Not only it has the inappropriate name (we kept it for compatibility purposes only), a separate folder is not needed at all now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A reason I did it is because we have the lock file there which kinda guarantees that we have permission to read/write in that folder. Which in turn will make exporting the torrents back to the all system more manageable.
I open to suggestions for a new location. Also for a better name for the DB/table.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned earlier (in other words), you should not interfere any extra logic with the basic one anywhere. The main way is to work with the new "saving system". That is, if we simply discard all additional import/export logic, then the main one should not be affected. The same applies to all user visible manifestations of the application.
Your current code assumes that user had previous qBittorrent version before. It's incorrect! You should check for it first and then select what you should do.
I open to suggestions for a new location.
The same folder where legacy BT_backup were placed.
Also for a better name for the DB/table.
Just something more specific than "data." E.g. "torrents".
A reason I did it is because we have the lock file there which kinda guarantees that we have permission to read/write in that folder.
"Lock file" logic should be dropped at all. It's meaningless in case of new "saving system". In addition, as it turned out, it is inefficient on *nix systems.
src/app/upgrade.cpp
Outdated
|
||
// Table doesn't exist. Probably 1st run. | ||
// Input PRAGMAs and create table | ||
query.exec(QLatin1String("PRAGMA auto_vacuum = FULL")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure you really want it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. It shrinks the file small when removing torrents. Why should we use it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shrinks the file small when removing torrents.
Is that really necessary? The empty pages can be reused further. Additionally the doc says:
However, using auto_vacuum can lead to extra database file fragmentation. And auto_vacuum does not compact partially filled pages of the database
Maybe it's better to use VACUUM command from time to time or allow user to perform it?
src/app/upgrade.cpp
Outdated
libt::bencode(std::back_inserter(preparedResumeData), resumeData); | ||
} | ||
|
||
// This should be moved back into Session::initResumeFolder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this upgrade applies entirely to BitTorrent component, I would not extract this code deliberately into a separate file. This breaks the logic of the code structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to do this check before trying to do any kind of upgrade. The upgrade code assumes that the db exists and is accessible and that the table exists in it.
The upgrade code is called before the Session is constructed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised at this answer. I even thought for a moment that I was debating with another person...
The upgrade code assumes that the db exists and is accessible and that the table exists in it.
This does not contradict the fact that this code should be in a different place.
The upgrade code is called before the Session is constructed.
It's incorrect. At least it's done incorrectly. You ripped main Session code as well.
Main Session logic should be the following:
- If database doesn't exist create it
- Load torrents from database and go ahead.
When you add upgrade logic to it:
- If database doesn't exist create it
- If older version was used before this run try to import torrents from old "saving system"
- Load torrents from database and go ahead.
All this logic should be done in Session code!
One thing that should be done before the Session is created is asking the user for accept the upgrade. To do this, we do not need to know anything about the existence of the database. All you need to know is whether the old qBittorrent version was used just before the current application run.
My main idea is "not to break the basic logic of the application for the sake of some additional".
src/app/upgrade.cpp
Outdated
|
||
// Check if our table exists | ||
QSqlQuery query(db); | ||
if (query.exec(QString("SELECT name FROM sqlite_master WHERE type='table' AND name='%1'").arg(TABLE_NAME))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be done easier:
if (db.tables().contains(TABLE_NAME))
return;
const QString backupFolderPath = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + "BT_backup"); | ||
const QDir backupFolderDir(backupFolderPath); | ||
const QString dbPath = backupFolderDir.absoluteFilePath(QLatin1String{FILENAME}); | ||
// If db exists don't try to upgrade |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid logic!
A user who has never used older qBittorrent versions should not care about the old saving system.
We need to implement more advanced version control. For example, store the current version somewhere in the application data. Then newer versions will be able to know more precisely what needs to be updated, and older versions will be able to warn the user about possible conflicts as a result of downgrade. In addition, it will avoid conflicts as a result of multiple upgrades/downgrades, when it is difficult to estimate based on some artifacts and we will know exactly which version was installed before (e.g. when both fastresumes and database exist).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you misunderstood it here.
At the current moment this tries to updrade from the non-db way to the db-way. So if a db exists we don't try to migrate older files (.torrents+.fastresumes). It is assumed that the upgrade was done already and the user dropped new files at a later date.
Unless you suggest that we should always input into the db any kind of .torrent/.fastresume we find during startup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also in case you missed it: Each row has a version
column. During startup if the version number is different from the supported one then that row isn't loaded. New versions will upgrade smaller version, while refusing to load bigger version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also in case you missed it: Each row has a
version
column. During startup if the version number is different from the supported one then that row isn't loaded. New versions will upgrade smaller version, while refusing to load bigger version.
Now, I realize that his approach has a drawback. If newer versions require another table name, or different number of columns, older versions might not be able to extract the version number.
Should we instead create a 2nd table named "version" with a single col+row that will hold the version of the whole db?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you misunderstood it here.
Is it? Here's your code:
// If db exists don't try to upgrade
if (QFile::exists(dbPath)) {
initiliazeDB(dbPath);
return true;
}
else {
if (ask && !userAcceptsUpgrade()) return false;
}
You ask the user for upgrade if the database doesn't exist. But it is incorrect if the database does not exist due to the fact that the user has never used qBittorrent (on this system).
I've suggested correct logic in another comment.
Now, I realize that his approach has a drawback.
Yes, it is.
Should we instead create a 2nd table named "version" with a single col+row that will hold the version of the whole db?
I think "overall application versioning" I suggested in other comment should fit us. There is no need to have separate versions for each thing.
initiliazeDB(dbPath); | ||
|
||
QSqlDatabase db = QSqlDatabase::database(); | ||
db.transaction(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have to handle errors. E.g:
if (!db.transaction())
throw RuntimeError(db.lastError().text()); // or return error status
try {
// ...
bool ok = query.prepare("...");
if (!ok)
throw RuntimeError(query.lastError().text());
// and so on...
if (!db.commit())
throw RuntimeError(db.lastError().text());
}
catch (const RuntimeError &err) {
db.rollback();
thow err; // or return error status
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Truth is I was in a hurry to make everything work and forgot to come back to this later.
d14cef6
to
3701f74
Compare
Pushed new version of commits with these changes:
As for the other issues I'll comment inline. |
@glassez Finally, loadTorrentResumeData() will be changed to load stuff from the DB instead. Do you think another way is more appropriate? |
@glassez I pushed a few new commits. I didn't merge them back into the others in hope it is easier to review the changes. I need your opinion. The last commit is unfinished and still WIP. I didn't have more time to tidy it up. I assume your answer will be that the Session should do that. It's more logical. In any case, as this last commit is very WIP, I have nothing solid. I merely was exploring the possibilities. So if you want to propose a better approach let me hear it (with a pseudo-example if possible). |
f3e9dab
to
6718aa4
Compare
As I commented in #10115, I'm optimizing my RasPi setup to avoid using the SDcard or other slow storage units. I tried to follow up this PR changes to check where the new database file will be stored, it's need for persistence across boots and the interaction with the portable settings; but I was unable to. @sledgehammer999 do you mind to comment on it? |
Right.
Although some logic is currently delegated to TorrentHandle class, I would prefer to have it centrally in the Session class and leave TorrentHandle only as a convenient interface.
I don't mind doing it that way to begin with. There may be a better way later.
Ok. |
Would recommend pushing this to a 4.5 or even a 5.0 Milestone. |
I agree, I'm not sure why this is in the 4.2.6 milestone. |
for ref (previous discussion/thoughts) that I came acrosss -> #7565 (comment) onwards.. |
Why is this continuously being pushed forward? What's the holdup? |
In fact, now there are even more prerequisites to use SQLite (for example, we now often save resume data when changing a particular field, but we still have to generate all the data and overwrite the file). |
Any chance the current open issues in 4.3.4 can be moved to their respective 4.4 or 5.0 milestones? |
I am currently working on optimizing the resume data storage subsystem. The implementation of SQLite database storage support is part of my plans. So I still close this PR for the sake of future implementation. |
I recently played with SQLite and I was fascinated by it. Then I realized how easy things could become for us if we used to save stuff.
Pros:
I plugged in the new functionality by keeping the current design as much as possible. I didn't attempt to engineer a better code architecture around the new system.
Most of the code you see is old code moved around.
PS: Is anyone more knowledgeable with SQL? I wonder if we should split the table into 2 for performance reasons. One saving hash+metadata and one saving hash+fastresume+queue.