libtorrent has a relatively heavy headers, that take lots of time to
process. This commit removes unnecessary includes of libtorrent headers
and replaces them with forward declarations.
I had to move some functions in QBtSession from slots to regular
functions because moc'ed file want to see complete types of all
parameters of slots.
"time make" of full rebuild before this series of commits:
real 13m35.937s
user 12m1.295s
sys 1m25.908s
after:
real 10m54.390s
user 9m31.167s
sys 1m12.580s
As we never use {push,pop}_front std::vector works here perfectly.
Also reserve memory for std::vector out of lock.
This could be considered as an optimization, but in reality this is just
using right container in right place. According to my measurements total
speedup is under 0.2%.
* also had to account for "Append the label of the torrent to the save path",
but again, this was only an issue when "Keep incomplete torrents in:" is
selected
* A multi-file torrent with only one file (ie: a single file within a folder),
was being treated as a single-file torrent, making it impossible to import.
Multi-file torrent detection code was copied from libtorrent. The
information is available in libtorrent (under torrent_info::m_multifile),
however it's a private member and I chose to go with copying the code that
determines it, rather than modifying a library qBittorrent depends on.
Conflicts:
src/torrentimportdlg.cpp
This commit implements a map where qbittorrent store a state of
current torrent movings. This commit speed up
torrents moving a bit and also fix a bug when qbittorrent doesn't do
cleanup action when a single torrent is moved several times without
waiting for a previous move to complete.
How it worked before.
Libtorrent has a function torrent_handle::move_storage() that allows to move a
torrent to a specific directory. This function is asynchorous. It means that
this function quits instantaneously and when the actual operation
completes the alert 'storage_moved_alert' or
'storage_moved_failed_alert' will be sent. The storage_moved_alert contains a
torrent_handle and a new path to where the torrent is moved.
During handling of storage_moved_alert, qbittorrent needs not only new path,
but also an old path to perform some of cleanup actions (like removing an old
folder if it is empty). This was achieved by storing a value named
'previous save path' in TorrentPersistentData. A previous save path is
written when move_storage() is issued and is read when
storage_moved_alert is received.
Problems.
This mechanism has two negative aspects:
1. TorrentPersistentData is very slow. As torrent_handle::move_storage() is asynchoronous,
TorrentPersistentData is responsible for more that 99.8% of time
QTorrentHandle::move_storage(). This percent could be higher when there
are lots of torrents and lower when there are few of them.
2. TorrentPersistentData stores only one previous path. But many
move_storage()'s could be issued without waiting for previous to
complete. Subsequent move_storage()'s overwrites previous save path of a
previous move.
A fix.
The fix is simple. Before issueing move_storage() the oldPath is stored in
a special map called 'torrentMoveStates'. When a storage_moved_alert
is received the map is consulted and an alert is handled.
When user moves torrent when previous moving have not yet finished, the
new location is saved in a field 'queuedPath' the same map. When
torrent moving is completed (or failed) qbittorrent attemps to perform
move again to the queued location.
Future direction.
This fix removes one slow read and one slow write to
TorrentPersistentData on torrent moving, but there is still exists
TorrentPersistentData::saveSavePath in handleStorageMovedAlert(), so
overall time for UI hang should be reduced only threefold. A speeding up
TorrentPersistentData should be addressed in a separate commit.
I don't know if I should clean up torrentMoveStates when torrent is
deleted. In any case, torrent could be deleted when corresponding alert
is in alert queue. So if we decide to clean up torrentMoveStates, then
we should not treat receiving alert from unknown torrent as a error.
libtorrent allows setting a custom dispatch handler that is invoked in
libtorrent thread when new alerts are incoming. QAlertDispatcher is a
class that allows to translate these alerts to UI thread.
The concept is very simple:
1. On initialization QAlertDispatcher constructor calls set_alert_dispatch() passing
QAlertDispatcher::dispatch as argument.
2. On deinitialization destructor calls set_alert_dispatch() passing a empty
function. (line 25) libtorrent handles thos and switches back to queuing
alerts in queue.
3. QAlertDispatcher::dispatch() adds alert to queue and notifies UI thread that new
alerts are incoming. Enqueuing is done in function enqueueToMainThread().
The invariant of class is the following:
if alert queue is not empty, in message loop of UI thread contains a queued
invocation of deliverSignal().
4. When message loop is pumped UI thread execute deliverSignal() function.
It emit appropriate signal and if queue is still not empty (for example
if slot doesn't grab alerts) rewind enqueuing to main thread.
This is a idea. But here is some details.
1. When QAlertDispatcher is destoyed, libtorrent still can call
QAlertDispatcher::dispatch a few times after destruction. This is
handled by passing a "tag". A tag is a object that references QAlertDispatch.
Tag could be invalidated. So on destruction QAlertDispatcher invalidates a tag
and then unsubscribes from alerts. When QAlertDispatcher::dispatch is called
with invalid tag it simply discard an alert.
Therefore we could drop a few alerts during unsubscription. So we unsubscribe
only at exit when missing some alerts is not a problem.
2. Another problem is in QBtSession::saveFastResumeData(). It pumps alert
queue synchronously. My first attempt was to destroy QAlertDispatcher
and then pump libtorrent queue. But as I was afraid of losing alerts I
supported synchronous querying of alerts in QAlertDispatcher.
(QAlertDispatcher::getPendingAlerts)
Conflicts:
src/qtlibtorrent/qbtsession.cpp