Set up network disk cache for articleNetMgr

When a Wikipedia article is already cached, this change reduces the
amount of sent and received network data almost tenfold.

Setting up a network disk cache in the same way for dictNetMgr does not
noticeably impact the amount of network traffic. Either this network
access manager sends and receives very little data or the data is never
the same. So dictNetMgr does not need a disk cache.

Use QNetworkDiskCache's default maximum size of 50 MiB as the default
network cache size. This size is large enough to accommodate tens of
huge MediaWiki articles. It is also small enough that the user is
unlikely to run out of disk space because of the cache.

Clear network cache on exit by default because most users probably
don't load the same online articles after restarting GoldenDict. Plus
storing the network cache on disk indefinitely by default would be a new
and unexpected to the users privacy risk.

Nikita Moor came up with the idea and wrote an initial network disk
cache implementation in #1310.
This commit is contained in:
Igor Kushnir 2020-11-12 17:57:10 +02:00
parent 8364b04dc0
commit 193aa4e31d
7 changed files with 157 additions and 2 deletions

View file

@ -22,14 +22,25 @@
#include "atomic_rename.hh" #include "atomic_rename.hh"
#include "qt4x5.hh" #include "qt4x5.hh"
#if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
#include <QStandardPaths>
#else
#include <QDesktopServices>
#endif
namespace Config { namespace Config {
namespace namespace
{ {
QString portableHomeDirPath()
{
return QCoreApplication::applicationDirPath() + "/portable";
}
QDir getHomeDir() QDir getHomeDir()
{ {
if ( isPortableVersion() ) if ( isPortableVersion() )
return QDir( QCoreApplication::applicationDirPath() + "/portable" ); return QDir( portableHomeDirPath() );
QDir result; QDir result;
@ -199,6 +210,8 @@ Preferences::Preferences():
disallowContentFromOtherSites( false ), disallowContentFromOtherSites( false ),
enableWebPlugins( false ), enableWebPlugins( false ),
hideGoldenDictHeader( false ), hideGoldenDictHeader( false ),
maxNetworkCacheSize( 50 ),
clearNetworkCacheOnExit( true ),
zoomFactor( 1 ), zoomFactor( 1 ),
helpZoomFactor( 1 ), helpZoomFactor( 1 ),
wordsZoomLevel( 0 ), wordsZoomLevel( 0 ),
@ -897,6 +910,12 @@ Class load() THROW_SPEC( exError )
if ( !preferences.namedItem( "hideGoldenDictHeader" ).isNull() ) if ( !preferences.namedItem( "hideGoldenDictHeader" ).isNull() )
c.preferences.hideGoldenDictHeader = ( preferences.namedItem( "hideGoldenDictHeader" ).toElement().text() == "1" ); c.preferences.hideGoldenDictHeader = ( preferences.namedItem( "hideGoldenDictHeader" ).toElement().text() == "1" );
if ( !preferences.namedItem( "maxNetworkCacheSize" ).isNull() )
c.preferences.maxNetworkCacheSize = preferences.namedItem( "maxNetworkCacheSize" ).toElement().text().toInt();
if ( !preferences.namedItem( "clearNetworkCacheOnExit" ).isNull() )
c.preferences.clearNetworkCacheOnExit = ( preferences.namedItem( "clearNetworkCacheOnExit" ).toElement().text() == "1" );
if ( !preferences.namedItem( "maxStringsInHistory" ).isNull() ) if ( !preferences.namedItem( "maxStringsInHistory" ).isNull() )
c.preferences.maxStringsInHistory = preferences.namedItem( "maxStringsInHistory" ).toElement().text().toUInt() ; c.preferences.maxStringsInHistory = preferences.namedItem( "maxStringsInHistory" ).toElement().text().toUInt() ;
@ -1859,6 +1878,14 @@ void save( Class const & c ) THROW_SPEC( exError )
opt.appendChild( dd.createTextNode( c.preferences.hideGoldenDictHeader ? "1" : "0" ) ); opt.appendChild( dd.createTextNode( c.preferences.hideGoldenDictHeader ? "1" : "0" ) );
preferences.appendChild( opt ); preferences.appendChild( opt );
opt = dd.createElement( "maxNetworkCacheSize" );
opt.appendChild( dd.createTextNode( QString::number( c.preferences.maxNetworkCacheSize ) ) );
preferences.appendChild( opt );
opt = dd.createElement( "clearNetworkCacheOnExit" );
opt.appendChild( dd.createTextNode( c.preferences.clearNetworkCacheOnExit ? "1" : "0" ) );
preferences.appendChild( opt );
opt = dd.createElement( "maxStringsInHistory" ); opt = dd.createElement( "maxStringsInHistory" );
opt.appendChild( dd.createTextNode( QString::number( c.preferences.maxStringsInHistory ) ) ); opt.appendChild( dd.createTextNode( QString::number( c.preferences.maxStringsInHistory ) ) );
preferences.appendChild( opt ); preferences.appendChild( opt );
@ -2246,7 +2273,7 @@ bool isPortableVersion() throw()
{ {
bool isPortable; bool isPortable;
IsPortable(): isPortable( QFileInfo( QCoreApplication::applicationDirPath() + "/portable" ).isDir() ) IsPortable(): isPortable( QFileInfo( portableHomeDirPath() ).isDir() )
{} {}
}; };
@ -2283,4 +2310,19 @@ QString getStylesDir() throw()
return result.path() + QDir::separator(); return result.path() + QDir::separator();
} }
QString getCacheDir() throw()
{
return isPortableVersion() ? portableHomeDirPath() + "/cache"
#if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 )
: QStandardPaths::writableLocation( QStandardPaths::CacheLocation );
#else
: QDesktopServices::storageLocation( QDesktopServices::CacheLocation );
#endif
}
QString getNetworkCacheDir() throw()
{
return getCacheDir() + "/network";
}
} }

View file

@ -302,6 +302,8 @@ struct Preferences
bool disallowContentFromOtherSites; bool disallowContentFromOtherSites;
bool enableWebPlugins; bool enableWebPlugins;
bool hideGoldenDictHeader; bool hideGoldenDictHeader;
int maxNetworkCacheSize;
bool clearNetworkCacheOnExit;
qreal zoomFactor; qreal zoomFactor;
qreal helpZoomFactor; qreal helpZoomFactor;
@ -792,6 +794,12 @@ QString getPortableVersionMorphoDir() throw();
/// Returns the add-on styles directory. /// Returns the add-on styles directory.
QString getStylesDir() throw(); QString getStylesDir() throw();
/// Returns the directory where user-specific non-essential (cached) data should be written.
QString getCacheDir() throw();
/// Returns the article network disk cache directory.
QString getNetworkCacheDir() throw();
} }
#endif #endif

View file

@ -763,6 +763,8 @@ MainWindow::MainWindow( Config::Class & cfg_ ):
connect( &articleNetMgr, SIGNAL( proxyAuthenticationRequired( QNetworkProxy, QAuthenticator * ) ), connect( &articleNetMgr, SIGNAL( proxyAuthenticationRequired( QNetworkProxy, QAuthenticator * ) ),
this, SLOT( proxyAuthentication( QNetworkProxy, QAuthenticator * ) ) ); this, SLOT( proxyAuthentication( QNetworkProxy, QAuthenticator * ) ) );
setupNetworkCache( cfg.preferences.maxNetworkCacheSize );
makeDictionaries(); makeDictionaries();
// After we have dictionaries and groups, we can populate history // After we have dictionaries and groups, we can populate history
@ -1068,6 +1070,10 @@ void MainWindow::commitData( QSessionManager & )
void MainWindow::commitData() void MainWindow::commitData()
{ {
if( cfg.preferences.clearNetworkCacheOnExit )
if( QAbstractNetworkCache * cache = articleNetMgr.cache() )
cache->clear();
try try
{ {
// Save MainWindow state and geometry // Save MainWindow state and geometry
@ -1287,6 +1293,33 @@ void MainWindow::applyWebSettings()
defaultSettings->setAttribute( QWebSettings::DeveloperExtrasEnabled, true ); defaultSettings->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
} }
void MainWindow::setupNetworkCache( int maxSize )
{
// x << 20 == x * 2^20 converts mebibytes to bytes.
qint64 const maxCacheSizeInBytes = maxSize <= 0 ? qint64( 0 ) : static_cast< qint64 >( maxSize ) << 20;
if( QAbstractNetworkCache * abstractCache = articleNetMgr.cache() )
{
QNetworkDiskCache * const diskCache = qobject_cast< QNetworkDiskCache * >( abstractCache );
Q_ASSERT_X( diskCache, Q_FUNC_INFO, "Unexpected network cache type." );
diskCache->setMaximumCacheSize( maxCacheSizeInBytes );
return;
}
if( maxCacheSizeInBytes == 0 )
return; // There is currently no cache and it is not needed.
QString const cacheDirectory = Config::getNetworkCacheDir();
if( !QDir().mkpath( cacheDirectory ) )
{
gdWarning( "Cannot create a cache directory %s. Disabling network cache.", cacheDirectory.toUtf8().constData() );
return;
}
QNetworkDiskCache * const diskCache = new QNetworkDiskCache( this );
diskCache->setMaximumCacheSize( maxCacheSizeInBytes );
diskCache->setCacheDirectory( cacheDirectory );
articleNetMgr.setCache( diskCache );
}
void MainWindow::makeDictionaries() void MainWindow::makeDictionaries()
{ {
Q_ASSERT( !scanPopup && "Scan popup must not exist while dictionaries are initialized. " Q_ASSERT( !scanPopup && "Scan popup must not exist while dictionaries are initialized. "
@ -2139,6 +2172,8 @@ void MainWindow::editPreferences()
if( cfg.preferences.favoritesStoreInterval != p.favoritesStoreInterval ) if( cfg.preferences.favoritesStoreInterval != p.favoritesStoreInterval )
ui.favoritesPaneWidget->setSaveInterval( p.favoritesStoreInterval ); ui.favoritesPaneWidget->setSaveInterval( p.favoritesStoreInterval );
if( cfg.preferences.maxNetworkCacheSize != p.maxNetworkCacheSize )
setupNetworkCache( p.maxNetworkCacheSize );
cfg.preferences = p; cfg.preferences = p;
audioPlayerFactory.setPreferences( cfg.preferences ); audioPlayerFactory.setPreferences( cfg.preferences );

View file

@ -201,6 +201,7 @@ private:
void applyProxySettings(); void applyProxySettings();
void applyWebSettings(); void applyWebSettings();
void setupNetworkCache( int maxSize );
void makeDictionaries(); void makeDictionaries();
void updateStatusLine(); void updateStatusLine();
void updateGroupList(); void updateGroupList();

View file

@ -153,6 +153,12 @@ Preferences::Preferences( QWidget * parent, Config::Class & cfg_ ):
break; break;
} }
#ifdef Q_OS_WIN32
// 1 MB stands for 2^20 bytes on Windows. "MiB" is never used by this OS.
ui.maxNetworkCacheSize->setSuffix( tr( " MB" ) );
#endif
ui.maxNetworkCacheSize->setToolTip( ui.maxNetworkCacheSize->toolTip().arg( Config::getNetworkCacheDir() ) );
ui.newTabsOpenAfterCurrentOne->setChecked( p.newTabsOpenAfterCurrentOne ); ui.newTabsOpenAfterCurrentOne->setChecked( p.newTabsOpenAfterCurrentOne );
ui.newTabsOpenInBackground->setChecked( p.newTabsOpenInBackground ); ui.newTabsOpenInBackground->setChecked( p.newTabsOpenInBackground );
ui.hideSingleTab->setChecked( p.hideSingleTab ); ui.hideSingleTab->setChecked( p.hideSingleTab );
@ -319,6 +325,8 @@ Preferences::Preferences( QWidget * parent, Config::Class & cfg_ ):
ui.disallowContentFromOtherSites->setChecked( p.disallowContentFromOtherSites ); ui.disallowContentFromOtherSites->setChecked( p.disallowContentFromOtherSites );
ui.enableWebPlugins->setChecked( p.enableWebPlugins ); ui.enableWebPlugins->setChecked( p.enableWebPlugins );
ui.hideGoldenDictHeader->setChecked( p.hideGoldenDictHeader ); ui.hideGoldenDictHeader->setChecked( p.hideGoldenDictHeader );
ui.maxNetworkCacheSize->setValue( p.maxNetworkCacheSize );
ui.clearNetworkCacheOnExit->setChecked( p.clearNetworkCacheOnExit );
// Add-on styles // Add-on styles
ui.addonStylesLabel->setVisible( ui.addonStyles->count() > 1 ); ui.addonStylesLabel->setVisible( ui.addonStyles->count() > 1 );
@ -449,6 +457,8 @@ Config::Preferences Preferences::getPreferences()
p.disallowContentFromOtherSites = ui.disallowContentFromOtherSites->isChecked(); p.disallowContentFromOtherSites = ui.disallowContentFromOtherSites->isChecked();
p.enableWebPlugins = ui.enableWebPlugins->isChecked(); p.enableWebPlugins = ui.enableWebPlugins->isChecked();
p.hideGoldenDictHeader = ui.hideGoldenDictHeader->isChecked(); p.hideGoldenDictHeader = ui.hideGoldenDictHeader->isChecked();
p.maxNetworkCacheSize = ui.maxNetworkCacheSize->value();
p.clearNetworkCacheOnExit = ui.clearNetworkCacheOnExit->isChecked();
p.addonStyle = ui.addonStyles->getCurrentStyle(); p.addonStyle = ui.addonStyles->getCurrentStyle();
@ -639,6 +649,11 @@ void Preferences::customProxyToggled( bool )
&& ui.useProxyServer->isChecked() ); && ui.useProxyServer->isChecked() );
} }
void Preferences::on_maxNetworkCacheSize_valueChanged( int value )
{
ui.clearNetworkCacheOnExit->setEnabled( value != 0 );
}
void Preferences::helpRequested() void Preferences::helpRequested()
{ {
if( !helpWindow ) if( !helpWindow )

View file

@ -52,6 +52,7 @@ private slots:
void on_useExternalPlayer_toggled( bool enabled ); void on_useExternalPlayer_toggled( bool enabled );
void customProxyToggled( bool ); void customProxyToggled( bool );
void on_maxNetworkCacheSize_valueChanged( int value );
void helpRequested(); void helpRequested();
void closeHelp(); void closeHelp();

View file

@ -1152,6 +1152,59 @@ Enable this option to workaround the problem.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QLabel" name="label_20">
<property name="text">
<string>Maximum network cache size:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="maxNetworkCacheSize">
<property name="toolTip">
<string>Maximum disk space occupied by GoldenDict's network cache in
%1
If set to 0 the network disk cache will be disabled.</string>
</property>
<property name="suffix">
<string> MiB</string>
</property>
<property name="maximum">
<number>2000</number>
</property>
<property name="value">
<number>50</number>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="clearNetworkCacheOnExit">
<property name="toolTip">
<string>When this option is enabled, GoldenDict
clears its network cache from disk during exit.</string>
</property>
<property name="text">
<string>Clear network cache on exit</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_15">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item> <item>
<spacer name="verticalSpacer_10"> <spacer name="verticalSpacer_10">
<property name="orientation"> <property name="orientation">