Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
Loading...
Searching...
No Matches
qgeofiletilecache.cpp
Go to the documentation of this file.
1
// Copyright (C) 2022 The Qt Company Ltd.
2
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
// Qt-Security score:critical reason:filesystem-manipulation
4
#
include
"qgeofiletilecache_p.h"
5
6
#
include
"qgeotilespec_p.h"
7
8
#
include
"qgeomappingmanager_p.h"
9
10
#
include
<
QDir
>
11
#
include
<
QStandardPaths
>
12
#
include
<
QMetaType
>
13
#
include
<
QPixmap
>
14
#
include
<
QDebug
>
15
16
#
include
<
QtCore
/
qsavefile
.
h
>
17
18
QT_BEGIN_NAMESPACE
19
20
using
namespace
Qt::StringLiterals;
21
22
class
QGeoCachedTileMemory
23
{
24
public
:
25
~
QGeoCachedTileMemory
()
26
{
27
if
(
cache
)
28
cache
->evictFromMemoryCache(
this
);
29
}
30
31
QGeoTileSpec
spec
;
32
QGeoFileTileCache *
cache
;
33
QByteArray
bytes
;
34
QString
format
;
35
};
36
37
void
QCache3QTileEvictionPolicy::aboutToBeRemoved(
const
QGeoTileSpec &key, QSharedPointer<QGeoCachedTileDisk> obj)
38
{
39
Q_UNUSED(key);
40
// set the cache pointer to zero so we can't call evictFromDiskCache
41
obj->cache =
nullptr
;
42
}
43
44
void
QCache3QTileEvictionPolicy::aboutToBeEvicted(
const
QGeoTileSpec &key, QSharedPointer<QGeoCachedTileDisk> obj)
45
{
46
Q_UNUSED(key);
47
Q_UNUSED(obj);
48
// leave the pointer set if it's a real eviction
49
}
50
51
QGeoCachedTileDisk
::~
QGeoCachedTileDisk
()
52
{
53
if
(
cache
)
54
cache
->evictFromDiskCache(
this
);
55
}
56
57
QGeoFileTileCache::QGeoFileTileCache(
const
QString &directory, QObject *parent)
58
: QAbstractGeoTileCache(parent), directory_(directory)
59
{
60
}
61
62
void
QGeoFileTileCache::init()
63
{
64
const
QString basePath = baseCacheDirectory() + QLatin1String(
"QtLocation/"
);
65
66
// delete old tiles from QtLocation 5.7 or prior
67
// Newer version use plugin-specific subdirectories, versioned with qt version so those are not affected.
68
// TODO Remove cache cleanup in Qt 6
69
QDir baseDir(basePath);
70
if
(baseDir.exists()) {
71
const
QStringList oldCacheFiles = baseDir.entryList(QDir::Files);
72
for
(
const
QString& file : oldCacheFiles)
73
baseDir.remove(file);
74
const
QStringList oldCacheDirs = { QStringLiteral(
"osm"
), QStringLiteral(
"mapbox"
), QStringLiteral(
"here"
) };
75
for
(
const
QString& d : oldCacheDirs) {
76
QDir oldCacheDir(basePath + QLatin1Char(
'/'
) + d);
77
if
(oldCacheDir.exists())
78
oldCacheDir.removeRecursively();
79
}
80
}
81
82
if
(directory_.isEmpty()) {
83
directory_ = baseLocationCacheDirectory();
84
qWarning() <<
"Plugin uses uninitialized QGeoFileTileCache directory which was deleted during startup"
;
85
}
86
87
const
bool
directoryCreated = QDir::root().mkpath(directory_);
88
if
(!directoryCreated)
89
qWarning() <<
"Failed to create cache directory "
<< directory_;
90
91
// default values
92
if
(!isDiskCostSet_) {
// If setMaxDiskUsage has not been called yet
93
if
(costStrategyDisk_ == ByteSize)
94
setMaxDiskUsage(50 * 1024 * 1024);
95
else
96
setMaxDiskUsage(1000);
97
}
98
99
if
(!isMemoryCostSet_) {
// If setMaxMemoryUsage has not been called yet
100
if
(costStrategyMemory_ == ByteSize)
101
setMaxMemoryUsage(3 * 1024 * 1024);
102
else
103
setMaxMemoryUsage(100);
104
}
105
106
if
(!isTextureCostSet_) {
// If setExtraTextureUsage has not been called yet
107
if
(costStrategyTexture_ == ByteSize)
108
setExtraTextureUsage(6 * 1024 * 1024);
109
else
110
setExtraTextureUsage(30);
// byte size of texture is >> compressed image, hence unitary cost should be lower
111
}
112
113
loadTiles();
114
}
115
116
void
QGeoFileTileCache::loadTiles()
117
{
118
QStringList formats;
119
formats << QLatin1String(
"*.*"
);
120
121
QDir dir(directory_);
122
const
QStringList files = dir.entryList(formats, QDir::Files);
123
#
if
0
// workaround for QTBUG-60581
124
// Method:
125
// 1. read each queue file then, if each file exists, deserialize the data into the appropriate
126
// cache queue.
127
for
(
int
i = 1; i<=4; i++) {
128
QString filename = dir.filePath(QString::fromLatin1(
"queue"
) + QString::number(i));
129
QFile file(filename);
130
if
(!file.open(QIODevice::ReadOnly))
131
continue
;
132
QList<QSharedPointer<QGeoCachedTileDisk> > queue;
133
QList<QGeoTileSpec> specs;
134
QList<
int
> costs;
135
while
(!file.atEnd()) {
136
QByteArray line = file.readLine().trimmed();
137
QString filename = QString::fromLatin1(line.constData(), line.length());
138
if
(dir.exists(filename)){
139
files.removeOne(filename);
140
QGeoTileSpec spec = filenameToTileSpec(filename);
141
if
(spec.zoom() == -1)
142
continue
;
143
QSharedPointer<QGeoCachedTileDisk> tileDisk(
new
QGeoCachedTileDisk);
144
tileDisk->filename = dir.filePath(filename);
145
tileDisk->cache =
this
;
146
tileDisk->spec = spec;
147
QFileInfo fi(tileDisk->filename);
148
specs.append(spec);
149
queue.append(tileDisk);
150
if
(costStrategyDisk_ == ByteSize)
151
costs.append(fi.size());
152
else
153
costs.append(1);
154
155
}
156
}
157
158
diskCache_.deserializeQueue(i, specs, queue, costs);
159
file.close();
160
}
161
#
endif
162
// 2. remaining tiles that aren't registered in a queue get pushed into cache here
163
// this is a backup, in case the queue manifest files get deleted or out of sync due to
164
// the application not closing down properly
165
for
(
const
auto
&file : files) {
166
QGeoTileSpec spec = filenameToTileSpec(file);
167
if
(spec.zoom() == -1)
168
continue
;
169
QString filename = dir.filePath(file);
170
addToDiskCache(spec, filename);
171
}
172
}
173
174
QGeoFileTileCache::~QGeoFileTileCache()
175
{
176
#
if
0
// workaround for QTBUG-60581
177
// write disk cache queues to disk
178
QDir dir(directory_);
179
for
(
int
i = 1; i<=4; i++) {
180
QString filename = dir.filePath(QString::fromLatin1(
"queue"
) + QString::number(i));
181
QFile file(filename);
182
if
(!file.open(QIODevice::WriteOnly)){
183
qWarning() <<
"Unable to write tile cache file "
<< filename;
184
continue
;
185
}
186
QList<QSharedPointer<QGeoCachedTileDisk> > queue;
187
diskCache_.serializeQueue(i, queue);
188
for
(
const
QSharedPointer<QGeoCachedTileDisk> &tile : queue) {
189
if
(tile.isNull())
190
continue
;
191
192
// we just want the filename here, not the full path
193
int
index = tile->filename.lastIndexOf(QLatin1Char(
'/'
));
194
QByteArray filename = tile->filename.mid(index + 1).toLatin1() +
'\n'
;
195
file.write(filename);
196
}
197
file.close();
198
}
199
#
endif
200
}
201
202
void
QGeoFileTileCache::printStats()
203
{
204
textureCache_.printStats();
205
memoryCache_.printStats();
206
diskCache_.printStats();
207
}
208
209
void
QGeoFileTileCache::setMaxDiskUsage(
int
diskUsage)
210
{
211
diskCache_.setMaxCost(diskUsage);
212
isDiskCostSet_ =
true
;
213
}
214
215
int
QGeoFileTileCache::maxDiskUsage()
const
216
{
217
return
diskCache_.maxCost();
218
}
219
220
int
QGeoFileTileCache::diskUsage()
const
221
{
222
return
diskCache_.totalCost();
223
}
224
225
void
QGeoFileTileCache::setMaxMemoryUsage(
int
memoryUsage)
226
{
227
memoryCache_.setMaxCost(memoryUsage);
228
isMemoryCostSet_ =
true
;
229
}
230
231
int
QGeoFileTileCache::maxMemoryUsage()
const
232
{
233
return
memoryCache_.maxCost();
234
}
235
236
int
QGeoFileTileCache::memoryUsage()
const
237
{
238
return
memoryCache_.totalCost();
239
}
240
241
void
QGeoFileTileCache::setExtraTextureUsage(
int
textureUsage)
242
{
243
extraTextureUsage_ = textureUsage;
244
textureCache_.setMaxCost(minTextureUsage_ + extraTextureUsage_);
245
isTextureCostSet_ =
true
;
246
}
247
248
void
QGeoFileTileCache::setMinTextureUsage(
int
textureUsage)
249
{
250
minTextureUsage_ = textureUsage;
251
textureCache_.setMaxCost(minTextureUsage_ + extraTextureUsage_);
252
}
253
254
int
QGeoFileTileCache::maxTextureUsage()
const
255
{
256
return
textureCache_.maxCost();
257
}
258
259
int
QGeoFileTileCache::minTextureUsage()
const
260
{
261
return
minTextureUsage_;
262
}
263
264
265
int
QGeoFileTileCache::textureUsage()
const
266
{
267
return
textureCache_.totalCost();
268
}
269
270
void
QGeoFileTileCache::clearAll()
271
{
272
textureCache_.clear();
273
memoryCache_.clear();
274
diskCache_.clear();
275
QDir dir(directory_);
276
dir.setNameFilters(QStringList() << QLatin1String(
"*-*-*-*.*"
));
277
dir.setFilter(QDir::Files);
278
const
auto
entries = dir.entryList();
279
for
(
const
QString &dirFile : entries) {
280
dir.remove(dirFile);
281
}
282
}
283
284
void
QGeoFileTileCache::clearMapId(
const
int
mapId)
285
{
286
{
287
const
auto
keys = diskCache_.keys();
288
for
(
const
QGeoTileSpec &k : keys)
289
if
(k.mapId() == mapId)
290
diskCache_.remove(k,
true
);
291
}
292
{
293
const
auto
keys = memoryCache_.keys();
294
for
(
const
QGeoTileSpec &k : keys)
295
if
(k.mapId() == mapId)
296
memoryCache_.remove(k);
297
}
298
{
299
const
auto
keys = textureCache_.keys();
300
for
(
const
QGeoTileSpec &k : keys)
301
if
(k.mapId() == mapId)
302
textureCache_.remove(k);
303
}
304
305
// TODO: It seems the cache leaves residues, like some tiles do not get picked up.
306
// After the above calls, files that shouldnt be left behind are still on disk.
307
// Do an additional pass and make sure what has to be deleted gets deleted.
308
QDir dir(directory_);
309
QStringList formats;
310
formats << QLatin1String(
"*.*"
);
311
const
QStringList files = dir.entryList(formats, QDir::Files);
312
qWarning() <<
"Old tile data detected. Cache eviction left out "
<< files.size() <<
"tiles"
;
313
for
(
const
QString &tileFileName : files) {
314
QGeoTileSpec spec = filenameToTileSpec(tileFileName);
315
if
(spec.mapId() != mapId)
316
continue
;
317
QFile::remove(dir.filePath(tileFileName));
318
}
319
}
320
321
void
QGeoFileTileCache::setCostStrategyDisk(QAbstractGeoTileCache::CostStrategy costStrategy)
322
{
323
costStrategyDisk_ = costStrategy;
324
}
325
326
QAbstractGeoTileCache::CostStrategy QGeoFileTileCache::costStrategyDisk()
const
327
{
328
return
costStrategyDisk_;
329
}
330
331
void
QGeoFileTileCache::setCostStrategyMemory(QAbstractGeoTileCache::CostStrategy costStrategy)
332
{
333
costStrategyMemory_ = costStrategy;
334
}
335
336
QAbstractGeoTileCache::CostStrategy QGeoFileTileCache::costStrategyMemory()
const
337
{
338
return
costStrategyMemory_;
339
}
340
341
void
QGeoFileTileCache::setCostStrategyTexture(QAbstractGeoTileCache::CostStrategy costStrategy)
342
{
343
costStrategyTexture_ = costStrategy;
344
}
345
346
QAbstractGeoTileCache::CostStrategy QGeoFileTileCache::costStrategyTexture()
const
347
{
348
return
costStrategyTexture_;
349
}
350
351
QSharedPointer<QGeoTileTexture> QGeoFileTileCache::get(
const
QGeoTileSpec &spec)
352
{
353
QSharedPointer<QGeoTileTexture> tt = getFromMemory(spec);
354
if
(tt)
355
return
tt;
356
return
getFromDisk(spec);
357
}
358
359
void
QGeoFileTileCache::insert(
const
QGeoTileSpec &spec,
360
const
QByteArray &bytes,
361
const
QString &format,
362
QAbstractGeoTileCache::CacheAreas areas)
363
{
364
if
(bytes.isEmpty())
365
return
;
366
367
if
(areas & QAbstractGeoTileCache::DiskCache) {
368
QString filename = tileSpecToFilename(spec, format, directory_);
369
addToDiskCache(spec, filename, bytes);
370
}
371
372
if
(areas & QAbstractGeoTileCache::MemoryCache) {
373
addToMemoryCache(spec, bytes, format);
374
}
375
376
/* inserts do not hit the texture cache -- this actually reduces overall
377
* cache hit rates because many tiles come too late to be useful
378
* and act as a poison */
379
}
380
381
QString QGeoFileTileCache::tileSpecToFilenameDefault(
const
QGeoTileSpec &spec,
const
QString &format,
const
QString &directory)
382
{
383
QString filename = spec.plugin();
384
filename += QLatin1String(
"-"
);
385
filename += QString::number(spec.mapId());
386
filename += QLatin1String(
"-"
);
387
filename += QString::number(spec.zoom());
388
filename += QLatin1String(
"-"
);
389
filename += QString::number(spec.x());
390
filename += QLatin1String(
"-"
);
391
filename += QString::number(spec.y());
392
393
//Append version if real version number to ensure backwards compatibility and eviction of old tiles
394
if
(spec.version() != -1) {
395
filename += QLatin1String(
"-"
);
396
filename += QString::number(spec.version());
397
}
398
399
filename += QLatin1String(
"."
);
400
filename += format;
401
402
QDir dir = QDir(directory);
403
404
return
dir.filePath(filename);
405
}
406
407
QGeoTileSpec QGeoFileTileCache::filenameToTileSpecDefault(
const
QString &filename)
408
{
409
QGeoTileSpec emptySpec;
410
411
const
QStringList parts = filename.split(QLatin1Char(
'.'
));
412
413
if
(parts.length() != 2)
414
return
emptySpec;
415
416
const
QString name = parts.at(0);
417
const
QStringList fields = name.split(QLatin1Char(
'-'
));
418
419
qsizetype length = fields.length();
420
if
(length != 5 && length != 6)
421
return
emptySpec;
422
423
QList<
int
> numbers;
424
425
bool
ok =
false
;
426
for
(qsizetype i = 1; i < length; ++i) {
427
ok =
false
;
428
int
value = fields.at(i).toInt(&ok);
429
if
(!ok)
430
return
emptySpec;
431
numbers.append(value);
432
}
433
434
//File name without version, append default
435
if
(numbers.length() < 5)
436
numbers.append(-1);
437
438
return
QGeoTileSpec(fields.at(0),
439
numbers.at(0),
440
numbers.at(1),
441
numbers.at(2),
442
numbers.at(3),
443
numbers.at(4));
444
}
445
446
void
QGeoFileTileCache::evictFromDiskCache(QGeoCachedTileDisk *td)
447
{
448
QFile::remove(td->filename);
449
}
450
451
void
QGeoFileTileCache::evictFromMemoryCache(QGeoCachedTileMemory *
/* tm */
)
452
{
453
}
454
455
QSharedPointer<QGeoCachedTileDisk> QGeoFileTileCache::addToDiskCache(
const
QGeoTileSpec &spec,
const
QString &filename)
456
{
457
QSharedPointer<QGeoCachedTileDisk> td(
new
QGeoCachedTileDisk);
458
td->spec = spec;
459
td->filename = filename;
460
td->cache =
this
;
461
462
int
cost = 1;
463
if
(costStrategyDisk_ == ByteSize) {
464
QFileInfo fi(filename);
465
cost = fi.size();
466
}
467
diskCache_.insert(spec, td, cost);
468
return
td;
469
}
470
471
bool
QGeoFileTileCache::addToDiskCache(
const
QGeoTileSpec &spec,
const
QString &filename,
const
QByteArray &bytes)
472
{
473
int
cost = 1;
474
if
(costStrategyDisk_ == ByteSize)
475
cost = bytes.size();
476
477
if
(cost > diskCache_.maxCost())
478
return
false
;
// insert() would fail anyway, so don't even try
479
480
QSaveFile file(filename);
481
if
(!file.open(QIODevice::WriteOnly))
482
return
false
;
483
file.write(bytes);
484
if
(!file.commit())
485
return
false
;
486
487
QSharedPointer<QGeoCachedTileDisk> td(
new
QGeoCachedTileDisk);
488
td->spec = spec;
489
td->filename = filename;
490
td->cache =
this
;
491
492
[[maybe_unused]]
const
bool
inserted = diskCache_.insert(spec, td, cost);
493
Q_ASSERT(inserted);
494
495
return
true
;
496
}
497
498
void
QGeoFileTileCache::addToMemoryCache(
const
QGeoTileSpec &spec,
const
QByteArray &bytes,
const
QString &format)
499
{
500
if
(isTileBogus(bytes))
501
return
;
502
503
QSharedPointer<QGeoCachedTileMemory> tm(
new
QGeoCachedTileMemory);
504
tm->spec = spec;
505
tm->cache =
this
;
506
tm->bytes = bytes;
507
tm->format = format;
508
509
int
cost = 1;
510
if
(costStrategyMemory_ == ByteSize)
511
cost = bytes.size();
512
memoryCache_.insert(spec, tm, cost);
513
}
514
515
QSharedPointer<QGeoTileTexture> QGeoFileTileCache::addToTextureCache(
const
QGeoTileSpec &spec,
const
QImage &image)
516
{
517
QSharedPointer<QGeoTileTexture> tt(
new
QGeoTileTexture);
518
tt->spec = spec;
519
tt->image = image;
520
521
int
cost = 1;
522
if
(costStrategyTexture_ == ByteSize)
523
cost = image.width() * image.height() * image.depth() / 8;
524
textureCache_.insert(spec, tt, cost);
525
526
return
tt;
527
}
528
529
QSharedPointer<QGeoTileTexture> QGeoFileTileCache::getFromMemory(
const
QGeoTileSpec &spec)
530
{
531
QSharedPointer<QGeoTileTexture> tt = textureCache_.object(spec);
532
if
(tt)
533
return
tt;
534
535
QSharedPointer<QGeoCachedTileMemory> tm = memoryCache_.object(spec);
536
if
(tm) {
537
QImage image;
538
if
(!image.loadFromData(tm->bytes)) {
539
handleError(spec, QLatin1String(
"Problem with tile image"
));
540
return
QSharedPointer<QGeoTileTexture>();
541
}
542
QSharedPointer<QGeoTileTexture> tt = addToTextureCache(spec, image);
543
if
(tt)
544
return
tt;
545
}
546
return
QSharedPointer<QGeoTileTexture>();
547
}
548
549
QSharedPointer<QGeoTileTexture> QGeoFileTileCache::getFromDisk(
const
QGeoTileSpec &spec)
550
{
551
QSharedPointer<QGeoCachedTileDisk> td = diskCache_.object(spec);
552
if
(td) {
553
const
QString format = QFileInfo(td->filename).suffix();
554
QFile file(td->filename);
555
if
(!file.open(QIODevice::ReadOnly)) {
556
handleError(spec,
"Cannot open file %1: %2"_L1
.arg(file.fileName(), file.errorString()));
557
return
nullptr
;
558
}
559
QByteArray bytes = file.readAll();
560
file.close();
561
562
QImage image;
563
// Some tiles from the servers could be valid images but the tile fetcher
564
// might be able to recognize them as tiles that should not be shown.
565
// If that's the case, the tile fetcher should write "NoRetry" inside the file.
566
if
(isTileBogus(bytes)) {
567
QSharedPointer<QGeoTileTexture> tt(
new
QGeoTileTexture);
568
tt->spec = spec;
569
tt->image = image;
570
return
tt;
571
}
572
573
// This is a truly invalid image. The fetcher should try again.
574
if
(!image.loadFromData(bytes)) {
575
handleError(spec, QLatin1String(
"Problem with tile image"
));
576
return
QSharedPointer<QGeoTileTexture>();
577
}
578
579
// Converting it here, instead of in each QSGTexture::bind()
580
if
(image.format() != QImage::Format_RGB32 && image.format() != QImage::Format_ARGB32_Premultiplied)
581
image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
582
583
addToMemoryCache(spec, bytes, format);
584
QSharedPointer<QGeoTileTexture> tt = addToTextureCache(td->spec, image);
585
if
(tt)
586
return
tt;
587
}
588
589
return
QSharedPointer<QGeoTileTexture>();
590
}
591
592
bool
QGeoFileTileCache::isTileBogus(
const
QByteArray &bytes)
const
593
{
594
if
(bytes.size() == 7 && bytes == QByteArrayLiteral(
"NoRetry"
))
595
return
true
;
596
return
false
;
597
}
598
599
QString QGeoFileTileCache::tileSpecToFilename(
const
QGeoTileSpec &spec,
const
QString &format,
const
QString &directory)
const
600
{
601
return
tileSpecToFilenameDefault(spec, format, directory);
602
}
603
604
QGeoTileSpec QGeoFileTileCache::filenameToTileSpec(
const
QString &filename)
const
605
{
606
return
filenameToTileSpecDefault(filename);
607
}
608
609
QString QGeoFileTileCache::directory()
const
610
{
611
return
directory_;
612
}
613
614
QT_END_NAMESPACE
QGeoCachedTileDisk
Definition
qgeofiletilecache_p.h:38
QGeoCachedTileDisk::cache
QGeoFileTileCache * cache
Definition
qgeofiletilecache_p.h:45
QGeoCachedTileDisk::~QGeoCachedTileDisk
~QGeoCachedTileDisk()
Definition
qgeofiletilecache.cpp:51
QGeoCachedTileMemory
Definition
qgeofiletilecache.cpp:23
QGeoCachedTileMemory::cache
QGeoFileTileCache * cache
Definition
qgeofiletilecache.cpp:32
QGeoCachedTileMemory::format
QString format
Definition
qgeofiletilecache.cpp:34
QGeoCachedTileMemory::~QGeoCachedTileMemory
~QGeoCachedTileMemory()
Definition
qgeofiletilecache.cpp:25
QGeoCachedTileMemory::bytes
QByteArray bytes
Definition
qgeofiletilecache.cpp:33
QGeoCachedTileMemory::spec
QGeoTileSpec spec
Definition
qgeofiletilecache.cpp:31
QT_BEGIN_NAMESPACE
Combined button and popup list for selecting options.
Definition
qsequentialanimationgroup.cpp:47
qtlocation
src
location
maps
qgeofiletilecache.cpp
Generated on
for Qt by
1.18.0