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