image_cache.cpp
Go to the documentation of this file.
1 // *****************************************************************************
2 //
3 // Copyright (c) 2014, Southwest Research Institute® (SwRI®)
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of Southwest Research Institute® (SwRI®) nor the
14 // names of its contributors may be used to endorse or promote products
15 // derived from this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 //
28 // *****************************************************************************
29 
30 #include <tile_map/image_cache.h>
31 
32 #include <boost/make_shared.hpp>
33 
34 #include <QtAlgorithms>
35 #include <QByteArray>
36 #include <QList>
37 #include <QNetworkAccessManager>
38 #include <QNetworkDiskCache>
39 #include <QUrl>
40 
41 #include <ros/ros.h>
42 
43 namespace tile_map
44 {
45  bool ComparePriority(const ImagePtr left, const ImagePtr right)
46  {
47  return left->Priority() > right->Priority();
48  }
49 
50  const int Image::MAXIMUM_FAILURES = 5;
51 
52  Image::Image(const QString& uri, size_t uri_hash, uint64_t priority) :
53  uri_(uri),
54  uri_hash_(uri_hash),
55  loading_(false),
56  failures_(0),
57  failed_(false),
58  priority_(priority)
59  {
60  }
61 
63  {
64  }
65 
67  {
68  image_ = boost::make_shared<QImage>();
69  }
70 
72  {
73  image_.reset();
74  }
75 
77  {
78  failures_++;
80  }
81 
83 
84  ImageCache::ImageCache(const QString& cache_dir, size_t size) :
85  network_manager_(this),
86  cache_dir_(cache_dir),
87  cache_(size),
88  exit_(false),
89  tick_(0),
90  cache_thread_(new CacheThread(this)),
91  network_request_semaphore_(MAXIMUM_NETWORK_REQUESTS)
92  {
93  QNetworkDiskCache* disk_cache = new QNetworkDiskCache(this);
94  disk_cache->setCacheDirectory(cache_dir_);
95  network_manager_.setCache(disk_cache);
96 
97  connect(&network_manager_, SIGNAL(finished(QNetworkReply*)), this, SLOT(ProcessReply(QNetworkReply*)));
98  connect(cache_thread_, SIGNAL(RequestImage(QString)), this, SLOT(ProcessRequest(QString)));
99 
100  cache_thread_->start();
101  cache_thread_->setPriority(QThread::NormalPriority);
102  }
103 
105  {
106  // After setting our exit flag to true, release any conditions the cache thread
107  // might be waiting on so that it will exit.
108  exit_ = true;
111  cache_thread_->wait();
112  delete cache_thread_;
113  }
114 
116  {
117  cache_.clear();
118  network_manager_.cache()->clear();
119  }
120 
121  ImagePtr ImageCache::GetImage(size_t uri_hash, const QString& uri, int32_t priority)
122  {
123  ImagePtr image;
124 
125  // Retrieve the image reference from the cache, updating the freshness.
126  cache_mutex_.lock();
127 
128  if (failed_.contains(uri_hash))
129  {
130  cache_mutex_.unlock();
131  return image;
132  }
133 
134  ImagePtr* image_ptr = cache_.take(uri_hash);
135  if (!image_ptr)
136  {
137  // If the image is not in the cache, create a new reference.
138  image_ptr = new ImagePtr(boost::make_shared<Image>(uri, uri_hash));
139  image = *image_ptr;
140  if (!cache_.insert(uri_hash, image_ptr))
141  {
142  ROS_ERROR("FAILED TO CREATE HANDLE: %s", uri.toStdString().c_str());
143  image_ptr = 0;
144  }
145  }
146  else
147  {
148  image = *image_ptr;
149 
150  // Add raw pointer back to cache.
151  cache_.insert(uri_hash, image_ptr);
152  }
153 
154  cache_mutex_.unlock();
155 
156  unprocessed_mutex_.lock();
157  if (image && !image->GetImage())
158  {
159  if (!image->Failed())
160  {
161  if (!unprocessed_.contains(uri_hash))
162  {
163  // Set an image's starting priority so that it's higher than the
164  // starting priority of every other image we've requested so
165  // far; that ensures that, all other things being equal, the
166  // most recently requested images will be loaded first.
167  image->SetPriority(priority + tick_++);
168  unprocessed_[uri_hash] = image;
169  uri_to_hash_map_[uri] = uri_hash;
171  }
172  else
173  {
174  // Every time an image is requested but hasn't been loaded yet,
175  // increase its priority. Tiles within the visible area will
176  // be requested more frequently, so this will make them load faster
177  // than tiles the user can't see.
178  image->SetPriority(priority + tick_++);
179  }
180  }
181  else
182  {
183  failed_.insert(uri_hash);
184  }
185  }
186 
187  unprocessed_mutex_.unlock();
188 
189  return image;
190  }
191 
192  void ImageCache::ProcessRequest(QString uri)
193  {
194  QNetworkRequest request;
195  request.setUrl(QUrl(uri));
196  request.setRawHeader("User-Agent", "mapviz-1.0");
197  request.setAttribute(
198  QNetworkRequest::CacheLoadControlAttribute,
199  QNetworkRequest::PreferCache);
200  request.setAttribute(
201  QNetworkRequest::HttpPipeliningAllowedAttribute,
202  true);
203 
204  QNetworkReply *reply = network_manager_.get(request);
205  connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
206  this, SLOT(NetworkError(QNetworkReply::NetworkError)));
207  }
208 
209  void ImageCache::ProcessReply(QNetworkReply* reply)
210  {
211  QString url = reply->url().toString();
212 
213  ImagePtr image;
214  unprocessed_mutex_.lock();
215 
216  size_t hash = uri_to_hash_map_[url];
217  image = unprocessed_[hash];
218  if (image)
219  {
220  if (reply->error() == QNetworkReply::NoError)
221  {
222  QByteArray data = reply->readAll();
223  image->InitializeImage();
224  if (!image->GetImage()->loadFromData(data))
225  {
226  image->ClearImage();
227  image->AddFailure();
228  }
229  }
230  else
231  {
232  image->AddFailure();
233  }
234  }
235 
236  unprocessed_.remove(hash);
237  uri_to_hash_map_.remove(url);
238  if (image)
239  {
240  image->SetLoading(false);
241  }
242  network_request_semaphore_.release();
243 
244  unprocessed_mutex_.unlock();
245 
246  reply->deleteLater();
247  }
248 
249  void ImageCache::NetworkError(QNetworkReply::NetworkError error)
250  {
251  ROS_ERROR("NETWORK ERROR");
252  // TODO add failure
253  }
254 
256 
258  p(parent),
259  waiting_mutex_()
260  {
261  waiting_mutex_.lock();
262  }
263 
265  {
266  waiting_mutex_.unlock();
267  }
268 
270  {
271  while (!p->exit_)
272  {
273  // Wait until we're told there are images we need to request.
274  waiting_mutex_.lock();
275 
276  // Next, get all of them and sort them by priority.
277  p->unprocessed_mutex_.lock();
278  QList<ImagePtr> images = p->unprocessed_.values();
279  p->unprocessed_mutex_.unlock();
280 
281  qSort(images.begin(), images.end(), ComparePriority);
282 
283  // Go through all of them and request them. Qt's network manager will
284  // only handle six simultaneous requests at once, so we use a semaphore
285  // to limit ourselves to that many.
286  // Each individual image will release the semaphore when it is done loading.
287  // Also, only load up to a certain number at a time in this loop. If there
288  // are more left afterward, we'll start over. This ensures that we
289  // concentrate on processing the highest-priority images.
290  int count = 0;
291  while (!p->exit_ && !images.empty() && count < MAXIMUM_SEQUENTIAL_REQUESTS)
292  {
293  p->network_request_semaphore_.acquire();
294 
295  ImagePtr image = images.front();
296  p->unprocessed_mutex_.lock();
297  if (!image->Loading() && !image->Failed())
298  {
299  count++;
300  image->SetLoading(true);
301  images.pop_front();
302 
303  QString uri = image->Uri();
304  size_t hash = p->uri_to_hash_map_[uri];
305  if (uri.startsWith(QString("file:///")))
306  {
307  image->InitializeImage();
308  QString filepath = uri.replace(QString("file:///"), QString("/"));
309  if (!image->GetImage()->load(filepath))
310  {
311  image->ClearImage();
312  image->AddFailure();
313  }
314 
315  p->unprocessed_.remove(hash);
316  p->uri_to_hash_map_.remove(uri);
317  image->SetLoading(false);
318  p->network_request_semaphore_.release();
319  }
320  else
321  {
322  Q_EMIT RequestImage(image->Uri());
323  }
324  }
325  else
326  {
327  images.pop_front();
328  }
329  p->unprocessed_mutex_.unlock();
330 
331  }
332  if (!images.empty())
333  {
334  waiting_mutex_.unlock();
335  }
336  }
337  }
338 }
QMap< size_t, ImagePtr > unprocessed_
Definition: image_cache.h:122
static const int MAXIMUM_FAILURES
Definition: image_cache.h:96
virtual void run()
void NetworkError(QNetworkReply::NetworkError error)
void ProcessReply(QNetworkReply *reply)
int32_t failures_
Definition: image_cache.h:90
QSet< size_t > failed_
Definition: image_cache.h:123
boost::shared_ptr< QImage > image_
Definition: image_cache.h:94
bool ComparePriority(const ImagePtr left, const ImagePtr right)
Definition: image_cache.cpp:45
QNetworkAccessManager network_manager_
Definition: image_cache.h:117
data
void InitializeImage()
Definition: image_cache.cpp:66
QSemaphore network_request_semaphore_
Definition: image_cache.h:134
QCache< size_t, ImagePtr > cache_
Definition: image_cache.h:121
Image(const QString &uri, size_t uri_hash, uint64_t priority=0)
Definition: image_cache.cpp:52
ImageCache(const QString &cache_dir, size_t size=4096)
Definition: image_cache.cpp:84
boost::shared_ptr< Image > ImagePtr
Definition: image_cache.h:98
ImagePtr GetImage(size_t uri_hash, const QString &uri, int32_t priority=0)
static const int MAXIMUM_NETWORK_REQUESTS
Definition: image_cache.h:138
CacheThread(ImageCache *parent)
void RequestImage(QString)
QMap< QString, size_t > uri_to_hash_map_
Definition: image_cache.h:124
void ProcessRequest(QString uri)
#define ROS_ERROR(...)
static const int MAXIMUM_SEQUENTIAL_REQUESTS
Definition: image_cache.h:158
CacheThread * cache_thread_
Definition: image_cache.h:132


tile_map
Author(s): Marc Alban
autogenerated on Thu Jun 6 2019 19:25:35