1 #include <QDir>
2 #include <sqlite3.h>
3 #include <errno.h>
4 #include <stdio.h>
5 
6 #include <QDateTime>
7 #include <QCache>
8 
9 #include "utils/file-utils.h"
10 #include "utils/utils.h"
11 #include "configurator.h"
12 #include "seafile-applet.h"
13 #include "data-cache.h"
14 #include "data-mgr.h"
15 #include "account-mgr.h"
16 
17 namespace {
18 
19 const int kDirentsCacheExpireTime = 60 * 1000;
20 
filecache_entry_from_sqlite3_result(sqlite3_stmt * stmt,FileCache::CacheEntry * entry)21 void filecache_entry_from_sqlite3_result(sqlite3_stmt *stmt, FileCache::CacheEntry* entry)
22 {
23     entry->repo_id = (const char *)sqlite3_column_text (stmt, 0);
24     entry->path = QString::fromUtf8((const char *)sqlite3_column_text (stmt, 1));
25     entry->account_sig = (const char *)sqlite3_column_text (stmt, 2);
26     entry->file_id = (const char *)sqlite3_column_text (stmt, 3);
27     entry->seafile_mtime = sqlite3_column_int64 (stmt, 4);
28     entry->seafile_size = sqlite3_column_int64 (stmt, 5);
29 }
30 
31 } // namespace
32 
SINGLETON_IMPL(DirentsCache)33 SINGLETON_IMPL(DirentsCache)
34 DirentsCache::DirentsCache()
35 {
36     cache_ = new QCache<QString, CacheEntry>;
37 }
~DirentsCache()38 DirentsCache::~DirentsCache()
39 {
40     delete cache_;
41 }
42 
43 DirentsCache::ReturnEntry
getCachedDirents(const QString & repo_id,const QString & path)44 DirentsCache::getCachedDirents(const QString& repo_id,
45                                const QString& path)
46 {
47     QString cache_key = repo_id + path;
48     CacheEntry *e = cache_->object(cache_key);
49     if (e != NULL) {
50         qint64 now = QDateTime::currentMSecsSinceEpoch();
51         if (now < e->timestamp + kDirentsCacheExpireTime) {
52             return ReturnEntry(e->current_readonly, &(e->dirents));
53         }
54     }
55 
56     return ReturnEntry(false, NULL);
57 }
58 
expireCachedDirents(const QString & repo_id,const QString & path)59 void DirentsCache::expireCachedDirents(const QString& repo_id, const QString& path)
60 {
61     cache_->remove(repo_id + path);
62 }
63 
saveCachedDirents(const QString & repo_id,const QString & path,bool current_readonly,const QList<SeafDirent> & dirents)64 void DirentsCache::saveCachedDirents(const QString& repo_id,
65                                      const QString& path,
66                                      bool current_readonly,
67                                      const QList<SeafDirent>& dirents)
68 {
69     CacheEntry *val = new CacheEntry;
70     val->timestamp = QDateTime::currentMSecsSinceEpoch();
71     val->current_readonly = current_readonly;
72     val->dirents = dirents;
73     QString cache_key = repo_id + path;
74     cache_->insert(cache_key, val);
75 }
76 
77 #if 0
78 SINGLETON_IMPL(FileCache)
79 FileCache::FileCache()
80 {
81     cache_ = new QHash<QString, CacheEntry>;
82 }
83 
84 FileCache::~FileCache()
85 {
86     delete cache_;
87 }
88 
89 QString FileCache::getCachedFileId(const QString& repo_id,
90                                      const QString& path)
91 {
92     return getCacheEntry(repo_id, path).file_id;
93 }
94 
95 FileCache::CacheEntry FileCache::getCacheEntry(const QString& repo_id,
96                                                    const QString& path)
97 {
98     QString cache_key = repo_id + path;
99 
100     return cache_->value(cache_key);
101 }
102 
103 void FileCache::saveCachedFileId(const QString& repo_id,
104                                    const QString& path,
105                                    const QString& file_id,
106                                    const QString& account_sig)
107 {
108     CacheEntry val;
109     QString cache_key = repo_id + path;
110 
111     val.repo_id = repo_id;
112     val.path = path;
113     val.file_id = file_id;
114     val.account_sig = account_sig;
115 
116     cache_->insert(cache_key, val);
117 }
118 
119 QList<FileCache::CacheEntry> FileCache::getAllCachedFiles()
120 {
121     return cache_->values();
122 }
123 
124 void FileCache::cleanCurrentAccountCache()
125 {
126     const Account cur_account = seafApplet->accountManager()->currentAccount();
127     foreach(const QString& key, cache_->keys()) {
128         const Account account = seafApplet->accountManager()
129                                 ->getAccountBySignature(cache_->value(key).account_sig);
130         if (account == cur_account)
131             cache_->remove(key);
132     }
133 }
134 #endif
135 
SINGLETON_IMPL(FileCache)136 SINGLETON_IMPL(FileCache)
137 FileCache::FileCache()
138 {
139     db_ = NULL;
140 }
141 
~FileCache()142 FileCache::~FileCache()
143 {
144     if (db_ != NULL)
145         sqlite3_close(db_);
146 }
147 
start()148 void FileCache::start()
149 {
150     const char *errmsg;
151     const char *sql;
152     sqlite3 *db;
153 
154     QString db_path = QDir(seafApplet->configurator()->seafileDir()).filePath("autoupdate-cache.db");
155     if (sqlite3_open (toCStr(db_path), &db)) {
156         errmsg = sqlite3_errmsg (db);
157         qDebug("failed to open file cache database %s: %s",
158                toCStr(db_path), errmsg ? errmsg : "no error given");
159 
160         seafApplet->errorAndExit(QObject::tr("failed to open file cache database"));
161         return;
162     }
163 
164     // Drop the old table.
165     // XX(lins05): This is not ideal. Should we invent a table schema upgrade mechanism?
166     sql = "DROP TABLE IF EXISTS FileCache;";
167     sqlite_query_exec (db, sql);
168     sql = "DROP TABLE IF EXISTS FileCacheV1;";
169     sqlite_query_exec (db, sql);
170 
171     sql = "CREATE TABLE IF NOT EXISTS FileCacheV2 ("
172         "     repo_id VARCHAR(36), "
173         "     path VARCHAR(4096), "
174         "     account_sig VARCHAR(40) NOT NULL, "
175         "     file_id VARCHAR(40) NOT NULL, "
176         "     seafile_mtime integer NOT NULL, "
177         "     seafile_size integer NOT NULL, "
178         "     PRIMARY KEY (repo_id, path))";
179     sqlite_query_exec (db, sql);
180 
181     db_ = db;
182 }
183 
getCacheEntryCB(sqlite3_stmt * stmt,void * data)184 bool FileCache::getCacheEntryCB(sqlite3_stmt *stmt, void *data)
185 {
186     CacheEntry *entry = (CacheEntry *)data;
187     filecache_entry_from_sqlite3_result(stmt, entry);
188     return true;
189 }
190 
getCacheEntry(const QString & repo_id,const QString & path,FileCache::CacheEntry * entry)191 bool FileCache::getCacheEntry(const QString& repo_id,
192                               const QString& path,
193                               FileCache::CacheEntry *entry)
194 {
195     char *zql = sqlite3_mprintf("SELECT *"
196                                 "  FROM FileCacheV2"
197                                 " WHERE repo_id = %Q"
198                                 "   AND path = %Q",
199                                 repo_id.toUtf8().data(), path.toUtf8().data());
200     bool ret = sqlite_foreach_selected_row(db_, zql, getCacheEntryCB, entry) > 0;
201     sqlite3_free(zql);
202     return ret;
203 }
204 
saveCachedFileId(const QString & repo_id,const QString & path,const QString & account_sig,const QString & file_id,const QString & local_file_path)205 void FileCache::saveCachedFileId(const QString& repo_id,
206                                  const QString& path,
207                                  const QString& account_sig,
208                                  const QString& file_id,
209                                  const QString& local_file_path)
210 {
211     QFileInfo finfo (local_file_path);
212     qint64 mtime = finfo.lastModified().toMSecsSinceEpoch();
213     qint64 fsize = finfo.size();
214     char *zql = sqlite3_mprintf("REPLACE INTO FileCacheV2( "
215                                 "repo_id, path, account_sig, file_id, "
216                                 "seafile_mtime, seafile_size "
217                                 ") VALUES (%Q, %Q, %Q, %Q, %lld, %lld)",
218                                 toCStr(repo_id),
219                                 toCStr(path),
220                                 toCStr(account_sig),
221                                 toCStr(file_id),
222                                 mtime,
223                                 fsize);
224     sqlite_query_exec(db_, zql);
225     sqlite3_free(zql);
226 }
227 
collectCachedFile(sqlite3_stmt * stmt,void * data)228 bool FileCache::collectCachedFile(sqlite3_stmt *stmt, void *data)
229 {
230     QList<CacheEntry> *list = (QList<CacheEntry> *)data;
231 
232     CacheEntry entry;
233     filecache_entry_from_sqlite3_result(stmt, &entry);
234     list->append(entry);
235     return true;
236 }
237 
getAllCachedFiles()238 QList<FileCache::CacheEntry> FileCache::getAllCachedFiles()
239 {
240     const char* sql = "SELECT * FROM FileCacheV2";
241     QList<CacheEntry> list;
242     sqlite_foreach_selected_row(db_, sql, collectCachedFile, &list);
243     return list;
244 }
245 
cleanUnModifiedCacheItemInDatabase(const QString file_id)246 void FileCache::cleanUnModifiedCacheItemInDatabase(const QString file_id) {
247     char* sql = sqlite3_mprintf("DELETE FROM FileCacheV2 WHERE file_id = %Q", toCStr(file_id));
248     sqlite_query_exec(db_, sql);
249     sqlite3_free(sql);
250 }
251 
getCachedFilesForDirectory(const QString & account_sig,const QString & repo_id,const QString & parent_dir_in)252 QList<FileCache::CacheEntry> FileCache::getCachedFilesForDirectory(const QString& account_sig,
253                                                                    const QString& repo_id,
254                                                                    const QString& parent_dir_in)
255 {
256     QString parent_dir = parent_dir_in;
257     // Strip the trailing slash
258     if (parent_dir.length() > 1 && parent_dir.endsWith("/")) {
259         parent_dir = parent_dir.left(parent_dir.length() - 1);
260     }
261 
262     QList<CacheEntry> entries;
263     char* sql = sqlite3_mprintf("SELECT * FROM FileCacheV2 "
264                                 "WHERE repo_id = %Q "
265                                 "  AND path like %Q "
266                                 "  AND account_sig = %Q; ",
267                                 toCStr(repo_id),
268                                 toCStr(QString("%1%").arg(parent_dir == "/" ? parent_dir : parent_dir + "/")),
269                                 toCStr(account_sig));
270 
271     sqlite_foreach_selected_row(db_, sql, collectCachedFile, &entries);
272 
273     // Even if we filtered the path in the above sql query, the returned entries
274     // may still belong to subdirectory of "parent_dir" instead of parent_dir
275     // itself. So we need to fitler again.
276     QList<CacheEntry> ret;
277     foreach(const CacheEntry& entry, entries) {
278         if (::getParentPath(entry.path) == parent_dir) {
279             QString localpath = DataManager::instance()->getLocalCacheFilePath(entry.repo_id, entry.path);
280             if (QFileInfo(localpath).exists()) {
281                 ret.append(entry);
282             }
283         }
284     }
285     return ret;
286 }
287