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