1 #include <QDir>
2 #include <QImage>
3 #include <QQueue>
4 #include <QHash>
5 #include <QTimer>
6 #include <QDateTime>
7
8 #include "../seafile-applet.h"
9 #include "../configurator.h"
10 #include "../account-mgr.h"
11 #include "../api/requests.h"
12 #include "../utils/paint-utils.h"
13 #include "../utils/utils.h"
14 #include "../utils/file-utils.h"
15
16 #include "thumbnail-service.h"
17
18 static const int kCheckPendingInterval = 1000; // 1s
19 static const qint64 kExpireTimeIntevalMsec = 300 * 1000; // 5min
20
21 struct PendingRequestInfo {
22 int last_wait;
23 int time_to_wait;
24
backoffPendingRequestInfo25 void backoff() {
26 last_wait = qMax(last_wait, 1) * 2;
27 time_to_wait = last_wait;
28 }
29
isReadyPendingRequestInfo30 bool isReady() {
31 return time_to_wait == 0;
32 }
33
tickPendingRequestInfo34 void tick() {
35 time_to_wait = qMax(0, time_to_wait - 1);
36 }
37 };
38
39 struct ThumbnailKey {
40 QString repo_id;
41 QString path;
42 QString dirent_id;
43 uint size;
44
operator ==ThumbnailKey45 bool operator == (const ThumbnailKey &key) const {
46 return repo_id == key.repo_id &&
47 path == key.path &&
48 dirent_id == key.dirent_id &&
49 size == key.size;
50 }
51 };
52
qHash(const ThumbnailKey & key)53 uint qHash(const ThumbnailKey &key) {
54 QString size;
55 size.setNum(key.size);
56 const QString key_str = key.repo_id +
57 key.path +
58 key.dirent_id +
59 size;
60 return qHash(key_str);
61 }
62
63 class PendingThumbnailRequestQueue
64 {
65 public:
PendingThumbnailRequestQueue()66 PendingThumbnailRequestQueue() {};
67
enqueue(const QString & repo_id,const QString & path,const QString & dirent_id,uint size)68 void enqueue(const QString& repo_id,
69 const QString& path,
70 const QString& dirent_id,
71 uint size)
72 {
73 ThumbnailKey key;
74 key.repo_id = repo_id;
75 key.path = path;
76 key.dirent_id = dirent_id;
77 key.size = size;
78
79 if (q_.contains(key)) {
80 return;
81 }
82 // if we have set an expire time, and we haven't reached it yet
83 if (expire_time_.contains(key) &&
84 QDateTime::currentMSecsSinceEpoch() <= expire_time_[key]) {
85 return;
86 }
87 // update expire time
88 expire_time_[key] = QDateTime::currentMSecsSinceEpoch() + kExpireTimeIntevalMsec;
89
90 q_.enqueue(key);
91 }
92
enqueueAndBackoff(const QString & repo_id,const QString & path,const QString & dirent_id,uint size)93 void enqueueAndBackoff(const QString& repo_id,
94 const QString& path,
95 const QString& dirent_id,
96 uint size)
97 {
98 ThumbnailKey key;
99 key.repo_id = repo_id;
100 key.path = path;
101 key.dirent_id = dirent_id;
102 key.size = size;
103
104 PendingRequestInfo& info = wait_[key];
105 info.backoff();
106
107 enqueue(repo_id, path, dirent_id, size);
108 }
109
clearWait(const QString & repo_id,const QString & path,const QString & dirent_id,uint size)110 void clearWait(const QString& repo_id,
111 const QString& path,
112 const QString& dirent_id,
113 uint size)
114 {
115 ThumbnailKey key;
116 key.repo_id = repo_id;
117 key.path = path;
118 key.dirent_id = dirent_id;
119 key.size = size;
120
121 wait_.remove(key);
122 }
123
tick()124 void tick() {
125 if (q_.isEmpty())
126 return;
127
128 QListIterator<ThumbnailKey> iter(q_);
129
130 while (iter.hasNext()) {
131 ThumbnailKey key;
132 key.repo_id = iter.peekNext().repo_id;
133 key.path = iter.peekNext().path;
134 key.dirent_id = iter.peekNext().dirent_id;
135 key.size = iter.next().size;
136
137 if (wait_.contains(key)) {
138 PendingRequestInfo& info = wait_[key];
139 info.tick();
140 }
141 }
142 }
143
dequeue()144 ThumbnailKey dequeue() {
145 ThumbnailKey key, key_default;
146 key_default.repo_id = QString();
147 key_default.path = QString();
148 key_default.dirent_id = QString();
149 key_default.size = 0;
150
151 int i = 0, n = q_.size();
152 while (i++ < n) {
153 if (q_.isEmpty()) {
154 return key_default;
155 }
156
157 key = q_.dequeue();
158
159 PendingRequestInfo info = wait_.value(key);
160 if (info.isReady()) {
161 return key;
162 } else {
163 q_.enqueue(key);
164 }
165 }
166
167 return key_default;
168 }
169
reset()170 void reset() {
171 q_.clear();
172 wait_.clear();
173 expire_time_.clear();
174 }
175
176 private:
177 QQueue<ThumbnailKey> q_;
178
179 QHash<ThumbnailKey, PendingRequestInfo> wait_;
180 QHash<ThumbnailKey, qint64> expire_time_;
181 };
182
183 ThumbnailService* ThumbnailService::singleton_ = NULL;
184
instance()185 ThumbnailService* ThumbnailService::instance()
186 {
187 if (singleton_ == NULL) {
188 static ThumbnailService instance;
189 singleton_ = &instance;
190 }
191
192 return singleton_;
193 }
194
195
ThumbnailService(QObject * parent)196 ThumbnailService::ThumbnailService(QObject *parent)
197 : QObject(parent),
198 get_thumbnail_req_(NULL)
199 {
200 queue_ = new PendingThumbnailRequestQueue;
201
202 timer_ = new QTimer(this);
203
204 connect(timer_, SIGNAL(timeout()), this, SLOT(checkPendingRequests()));
205
206 }
207
start()208 void ThumbnailService::start()
209 {
210 timer_->start(kCheckPendingInterval);
211 }
212
213 // check in-memory-cache
loadThumbnailFromLocal(const QString & repo_id,const QString & path,const QString & dirent_id,uint size)214 QPixmap ThumbnailService::loadThumbnailFromLocal(const QString& repo_id,
215 const QString& path,
216 const QString& dirent_id,
217 uint size)
218 {
219 QPixmap ret;
220
221 QString key = getPixmapCacheKey(repo_id, path, dirent_id, size);
222 if (cache_.find(key, &ret)) {
223 return ret;
224 }
225
226 return ret;
227 }
228
getPixmapCacheKey(const QString & repo_id,const QString & path,const QString & dirent_id,uint size)229 QString ThumbnailService::getPixmapCacheKey(const QString& repo_id,
230 const QString& path,
231 const QString& dirent_id,
232 uint size)
233 {
234 const Account& account = seafApplet->accountManager()->accounts().front();
235 QString size_str;
236 size_str.setNum(size);
237 return QDir(thumbnails_dir_).filePath(::md5(account.serverUrl.host()
238 + account.username
239 + repo_id
240 + path
241 + dirent_id
242 + size_str));
243 }
244
fetchImageFromServer(const QString & repo_id,const QString & path,const QString & dirent_id,uint size)245 void ThumbnailService::fetchImageFromServer(const QString& repo_id,
246 const QString& path,
247 const QString& dirent_id,
248 uint size)
249 {
250 if (get_thumbnail_req_) {
251 if (repo_id == get_thumbnail_req_->repoId() &&
252 path == get_thumbnail_req_->path() &&
253 dirent_id == get_thumbnail_req_->direntId() &&
254 size == get_thumbnail_req_->size()) {
255 return;
256 }
257 queue_->enqueue(repo_id, path, dirent_id, size);
258 return;
259 }
260
261 const Account& account = seafApplet->accountManager()->accounts().front();
262
263 get_thumbnail_req_ = new GetThumbnailRequest(account,
264 repo_id,
265 path,
266 dirent_id,
267 size);
268
269 connect(get_thumbnail_req_, SIGNAL(success(const QPixmap&)),
270 this, SLOT(onGetThumbnailSuccess(const QPixmap&)));
271 connect(get_thumbnail_req_, SIGNAL(failed(const ApiError&)),
272 this, SLOT(onGetThumbnailFailed(const ApiError&)));
273
274 get_thumbnail_req_->send();
275 }
276
onGetThumbnailSuccess(const QPixmap & img)277 void ThumbnailService::onGetThumbnailSuccess(const QPixmap& img)
278 {
279 const QString repo_id = get_thumbnail_req_->repoId();
280 const QString path_in_repo = get_thumbnail_req_->path();
281 const QString dirent_id = get_thumbnail_req_->direntId();
282 const uint size = get_thumbnail_req_->size();
283
284 QString key = getPixmapCacheKey(repo_id, path_in_repo, dirent_id, size);
285
286 cache_.insert(key, img);
287
288 emit thumbnailUpdated(img, path_in_repo);
289
290 get_thumbnail_req_->deleteLater();
291 get_thumbnail_req_ = NULL;
292
293 queue_->clearWait(repo_id, path_in_repo, dirent_id, size);
294 }
295
onGetThumbnailFailed(const ApiError & error)296 void ThumbnailService::onGetThumbnailFailed(const ApiError& error)
297 {
298 const QString repo_id = get_thumbnail_req_->repoId();
299 const QString path = get_thumbnail_req_->path();
300 const QString dirent_id = get_thumbnail_req_->direntId();
301 const uint size = get_thumbnail_req_->size();
302
303 get_thumbnail_req_->deleteLater();
304 get_thumbnail_req_ = NULL;
305
306 queue_->enqueueAndBackoff(repo_id, path, dirent_id, size);
307 }
308
getThumbnail(const QString & repo_id,const QString & path,const QString & dirent_id,uint size)309 QPixmap ThumbnailService::getThumbnail(const QString& repo_id,
310 const QString& path,
311 const QString& dirent_id,
312 uint size)
313 {
314 QPixmap img = loadThumbnailFromLocal(repo_id, path, dirent_id, size);
315
316 // update all thumbnails if img is null
317 if (img.isNull()) {
318 if (!get_thumbnail_req_ ||
319 get_thumbnail_req_->repoId() != repo_id ||
320 get_thumbnail_req_->path() != path ||
321 get_thumbnail_req_->direntId() != dirent_id ||
322 get_thumbnail_req_->size() != size)
323 {
324 queue_->enqueue(repo_id, path, dirent_id, size);
325 }
326 }
327 if (img.isNull()) {
328 return QIcon(":/images/files_v2/file_image.png").pixmap(24);
329 } else {
330 return img;
331 }
332 }
333
checkPendingRequests()334 void ThumbnailService::checkPendingRequests()
335 {
336 queue_->tick();
337
338 if (get_thumbnail_req_ != NULL) {
339 return;
340 }
341
342 ThumbnailKey key = queue_->dequeue();
343 QString repo_id = key.repo_id;
344 QString path = key.path;
345 QString dirent_id = key.dirent_id;
346 uint size = key.size;
347 if (!repo_id.isEmpty()) {
348 fetchImageFromServer(repo_id, path, dirent_id, size);
349 }
350 }
351