561faf0689f91a10b730dfc0e0784779c3d9224f
[jigdo.git] / src / cachefile.cc
1 /* $Id: cachefile.cc,v 1.8 2005/07/21 11:31:43 atterer Exp $ -*- C++ -*-
2   __   _
3   |_) /|  Copyright (C) 2001-2003  |  richard@
4   | \/¯|  Richard Atterer          |  atterer.org
5   ¯ '` ¯
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License, version 2. See
8   the file COPYING for details.
9
10   Cache with MD5 sums of file contents - used by JigdoCache in scan.hh
11
12 */
13
14 #include <config.h>
15
16 #include <cachefile.hh>
17 #include <compat.hh>
18 #if HAVE_LIBDB
19
20 #if DEBUG
21 #  include <iostream>
22 #endif
23 #include <new>
24 #include <time.h> /* time() */
25
26 #include <debug.hh>
27 #include <log.hh>
28 #include <serialize.hh>
29 //______________________________________________________________________
30
31 DEBUG_UNIT("cachefile")
32
33 CacheFile::CacheFile(const char* dbName) {
34   memset(&data, 0, sizeof(DBT));
35
36   int e = db_create(&db, 0, 0); // No env/flags
37   if (e != 0) throw DbError(e);
38
39   // Cache of 0GB+4MB, one contiguous chunk
40   db->set_cachesize(db, 0, 4*1024*1024, 1);
41
42   // Use a btree, create database file if not yet present
43   e = compat_dbOpen(db, dbName, "jigdo filecache v0", DB_BTREE, DB_CREATE,
44                     0666);
45   if (e != 0) {
46     // Re-close, in case it is necessary
47     db->close(db, 0);
48     if (e != DB_OLD_VERSION && e != DB_RUNRECOVERY)
49       throw DbError(e);
50     /* If the DB file is old or corrupted, just regenerate it from
51        scratch, otherwise throw error. */
52     debug("Cache file corrupt, recreating it");
53     if (compat_dbOpen(db, dbName, "jigdo filecache v0", DB_BTREE,
54                       DB_CREATE | DB_TRUNCATE, 0666) != 0)
55       throw DbError(e);
56   }
57
58   data.flags |= DB_DBT_REALLOC;
59 }
60 //______________________________________________________________________
61
62 namespace {
63
64   /** Local struct: Wrapper which calls close() for any DBC cursor at end of
65       scope */
66   struct AutoCursor {
67     AutoCursor() : c(0) { }
68     ~AutoCursor() { close(); }
69     int close() {
70       if (c == 0) return 0;
71       int r = c->c_close(c);
72       c = 0;
73       return r;
74     }
75     int get(DBT *key, DBT *data, u_int32_t flags) {
76       return c->c_get(c, key, data, flags);
77     }
78     int put(DBT *key, DBT *data, u_int32_t flags) {
79       return c->c_put(c, key, data, flags);
80     }
81     int del(u_int32_t flags) {
82       return c->c_del(c, flags);
83     }
84     DBC* c;
85   };
86
87 }
88 //________________________________________
89
90 Status CacheFile::find(const byte*& resultData, size_t& resultSize,
91                        const string& fileName, uint64 fileSize, time_t mtime) {
92   DBT key; memset(&key, 0, sizeof(DBT));
93   key.data = const_cast<char*>(fileName.c_str());
94   key.size = fileName.size();
95
96   AutoCursor cursor;
97   // Cursor with no transaction id, no flags
98   if (db->cursor(db, 0, &cursor.c, 0) != 0) return FAILED;
99
100   if (cursor.get(&key, &data, DB_SET) == DB_NOTFOUND
101       || data.data == 0) return FAILED;
102
103   // Check whether mtime and size matches
104   Paranoid(data.size >= USER_DATA);
105   byte* d = static_cast<byte*>(data.data);
106   Paranoid(d != 0);
107   time_t cacheMtime;
108   unserialize4(cacheMtime, d + MTIME);
109   if (cacheMtime != mtime) return FAILED;
110   uint64 cacheFileSize;
111   unserialize6(cacheFileSize, d + SIZE);
112   if (cacheFileSize != fileSize) return FAILED;
113
114   // Match - update access time
115   time_t now = time(0);
116   Paranoid(now != static_cast<time_t>(-1));
117   serialize4(now, d + ACCESS);
118   DBT partial; memset(&partial, 0, sizeof(DBT));
119   partial.data = d + ACCESS;
120   partial.size = 4;
121   partial.flags |= DB_DBT_PARTIAL;
122   partial.doff = ACCESS;
123   partial.dlen = 4;
124   //cerr << "CacheFile lookup successfull for "<<fileName<<endl;
125   cursor.put(&key, &partial, DB_CURRENT);
126
127   resultData = d + USER_DATA;
128   resultSize = data.size - USER_DATA;
129   return OK;
130 }
131 //________________________________________
132
133 Status CacheFile::findName(const byte*& resultData, size_t& resultSize,
134     const string& fileName, off_t& resultFileSize,
135     time_t& resultMtime) {
136   DBT key; memset(&key, 0, sizeof(DBT));
137   key.data = const_cast<char*>(fileName.c_str());
138   key.size = fileName.size();
139
140   AutoCursor cursor;
141   // Cursor with no transaction id, no flags
142   if (db->cursor(db, 0, &cursor.c, 0) != 0) return FAILED;
143
144   if (cursor.get(&key, &data, DB_SET) == DB_NOTFOUND
145       || data.data == 0) return FAILED;
146
147   // get mtime and size
148   Paranoid(data.size >= USER_DATA);
149   byte* d = static_cast<byte*>(data.data);
150   Paranoid(d != 0);
151   time_t cacheMtime;
152   unserialize4(cacheMtime, d + MTIME);
153   resultMtime = cacheMtime;
154   uint64 cacheFileSize;
155   unserialize6(cacheFileSize, d + SIZE);
156   resultFileSize = cacheFileSize;
157
158   // Match - update access time
159   time_t now = time(0);
160   Paranoid(now != static_cast<time_t>(-1));
161   serialize4(now, d + ACCESS);
162   DBT partial; memset(&partial, 0, sizeof(DBT));
163   partial.data = d + ACCESS;
164   partial.size = 4;
165   partial.flags |= DB_DBT_PARTIAL;
166   partial.doff = ACCESS;
167   partial.dlen = 4;
168   //cerr << "CacheFile lookup successfull for "<<fileName<<endl;
169   cursor.put(&key, &partial, DB_CURRENT);
170
171   resultData = d + USER_DATA;
172   resultSize = data.size - USER_DATA;
173   return OK;
174 }
175 //______________________________________________________________________
176
177 void CacheFile::expire(time_t t) {
178   DBT key; memset(&key, 0, sizeof(DBT));
179   DBT data; memset(&data, 0, sizeof(DBT));
180   AutoCursor cursor;
181   // Cursor with no transaction id, no flags
182   if (db->cursor(db, 0, &cursor.c, 0) != 0) return;
183
184   int status;
185   while ((status = cursor.get(&key, &data, DB_NEXT)) == 0) {
186     time_t lastAccess = 0;
187     // If data.data == 0, expire entry by leaving lastAccess at 0
188     if (data.data != 0)
189       unserialize4(lastAccess, static_cast<byte*>(data.data) + ACCESS);
190     // Same as 'if (lastAccess<t)', but deals with wraparound:
191     if (static_cast<signed>(t - lastAccess) > 0) {
192       debug("Cache: expiring %1",
193             string(static_cast<char*>(key.data), key.size));
194       cursor.del(0);
195     }
196   }
197   if (status != DB_NOTFOUND)
198     throw DbError(status);
199 }
200 //______________________________________________________________________
201
202 /* Prepare for an insertion of data, by allocating a sufficient amount
203    of memory and returning a pointer to it. */
204 byte* CacheFile::insert_prepare(size_t inSize) {
205   // Allocate enough memory for the new entry
206   void* tmp = realloc(data.data, USER_DATA + inSize);
207   if (tmp == 0) throw bad_alloc();
208   data.data = tmp;
209   data.size = USER_DATA + inSize;
210   return static_cast<byte*>(tmp) + USER_DATA;
211 }
212
213 /* ASSUMES THAT insert_prepare() HAS JUST BEEN CALLED and that the
214    data had been copied to the memory region it returned. This
215    function commits the data to the db. */
216 void CacheFile::insert_perform(const string& fileName, time_t mtime,
217                                uint64 fileSize) {
218   byte* buf = static_cast<byte*>(data.data);
219
220   // Write our data members
221   time_t now = time(0);
222   serialize4(now, buf + ACCESS);
223   serialize4(mtime, buf + MTIME);
224   serialize6(fileSize, buf + SIZE);
225
226   // Insert in database
227   DBT key; memset(&key, 0, sizeof(DBT));
228   key.data = const_cast<char*>(fileName.c_str());
229   key.size = fileName.size();
230   db->put(db, 0, &key, &data, 0); // No transaction, overwrite
231
232 //   cerr << "CacheFile write `"<<fileName<<'\'';
233 //   for (size_t i = USER_DATA; i < data.get_size(); ++i)
234 //     cerr << ' '<< hex << (int)(buf[i]);
235 //   cerr << endl;
236 }
237 //______________________________________________________________________
238
239 #endif /* HAVE_LIBDB */