1 /* bzflag
2 * Copyright (c) 1993-2021 Tim Riker
3 *
4 * This package is free software; you can redistribute it and/or
5 * modify it under the terms of the license found in the file
6 * named COPYING that should have accompanied this file.
7 *
8 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11 */
12
13 #include "common.h"
14
15 // implementation header
16 #include "CacheManager.h"
17
18 // system headers
19 #include <string>
20 #include <vector>
21 #include <iostream>
22 #include <algorithm>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #ifndef _WIN32
30 # include <unistd.h>
31 #endif
32
33 // common headers
34 #include "md5.h"
35 #include "bzfio.h"
36 #include "TextUtils.h"
37 #include "FileManager.h"
38 #include "StateDatabase.h"
39 #include "DirectoryNames.h"
40
41
42 // function prototypes
43 static bool fileExists(const std::string& name);
44 static void removeDirs(const std::string& path);
45 static void removeNewlines(char* c);
46 static std::string partialEncoding(const std::string& string);
47 static bool compareUsedDate(const CacheManager::CacheRecord& a,
48 const CacheManager::CacheRecord& b);
49
50
51 CacheManager CACHEMGR;
52
53
CacheManager()54 CacheManager::CacheManager() : indexName("CacheIndex.txt")
55 {
56 }
57
58
~CacheManager()59 CacheManager::~CacheManager()
60 {
61 }
62
63
getIndexPath()64 std::string CacheManager::getIndexPath()
65 {
66 return getCacheDirName() + indexName;
67 }
68
69
isCacheFileType(const std::string & name) const70 bool CacheManager::isCacheFileType(const std::string &name) const
71 {
72 if (strncasecmp(name.c_str(), "http://", 7) == 0)
73 return true;
74 if (strncasecmp(name.c_str(), "ftp://", 6) == 0)
75 return true;
76 return false;
77 }
78
79
getLocalName(const std::string & name) const80 std::string CacheManager::getLocalName(const std::string &name) const
81 {
82 std::string local = "";
83 if (strncasecmp(name.c_str(), "http://", 7) == 0)
84 {
85 local = getCacheDirName() + "http/";
86 local += partialEncoding(name.substr(7));
87 }
88 else if (strncasecmp(name.c_str(), "ftp://", 6) == 0)
89 {
90 local = getCacheDirName() + "ftp/";
91 local += partialEncoding(name.substr(6));
92 }
93 #ifdef _WIN32
94 std::replace(local.begin(), local.end(), '/', '\\');
95 #endif
96 return local;
97 }
98
99
findURL(const std::string & url,CacheRecord & record)100 bool CacheManager::findURL(const std::string& url, CacheRecord& record)
101 {
102 int pos = findRecord(url);
103 if (pos >= 0)
104 {
105 CacheRecord* rec = &records[pos];
106 rec->usedDate = time(NULL); // update the timestamp
107 record = *rec;
108 return true;
109 }
110 return false;
111 }
112
113
addFile(CacheRecord & record,const void * data)114 bool CacheManager::addFile(CacheRecord& record, const void* data)
115 {
116 if (((data == NULL) && (record.size != 0)) || (record.url.size() <= 0))
117 return false;
118
119 record.name = getLocalName(record.url);
120 std::ostream* out = FILEMGR.createDataOutStream(record.name, true /* binary*/);
121 if (out == NULL)
122 return false;
123
124 bool replacement = false;
125 CacheRecord* rec = &record;
126
127 int pos = findRecord(record.url);
128 if (pos >= 0)
129 {
130 records[pos] = record;
131 rec = &records[pos];
132 replacement = true;
133 }
134
135 out->write((const char *)data, rec->size);
136
137 rec->usedDate = time(NULL); // update the timestamp
138
139 MD5 md5;
140 md5.update((const unsigned char *)data, rec->size);
141 md5.finalize();
142 rec->key = md5.hexdigest();
143
144 if (!replacement)
145 records.push_back(*rec);
146
147 delete out;
148 return true;
149 }
150
151
findRecord(const std::string & url)152 int CacheManager::findRecord(const std::string& url)
153 {
154 for (unsigned int i = 0; i < records.size(); i++)
155 {
156 CacheRecord* rec = &(records[i]);
157 if (url == rec->url)
158 return i;
159 }
160 return -1;
161 }
162
163
loadIndex()164 bool CacheManager::loadIndex()
165 {
166 records.clear();
167
168 FILE* file = fopen(getIndexPath().c_str(), "r");
169 if (file == NULL)
170 return false;
171
172 char buffer[1024];
173 while (fgets(buffer, 1024, file) != NULL)
174 {
175 removeNewlines(buffer);
176 if ((buffer[0] == '\0') || (buffer[0] == '#'))
177 continue;
178
179 CacheRecord rec;
180 rec.url = buffer;
181 rec.name = getLocalName(rec.url);
182
183 if (fgets(buffer, 1024, file) == NULL)
184 break;
185 else
186 removeNewlines(buffer);
187 std::string line = buffer;
188 std::vector<std::string> tokens = TextUtils::tokenize(line, " ");
189 if (tokens.size() != 4)
190 {
191 logDebugMessage(1,"loadCacheIndex (bad line): %s\n", buffer);
192 continue;
193 }
194 rec.size = strtoul(tokens[0].c_str(), NULL, 10);
195 rec.date = strtoul(tokens[1].c_str(), NULL, 10);
196 rec.usedDate = strtoul(tokens[2].c_str(), NULL, 10);
197 rec.key = tokens[3];
198 if (fileExists(rec.name))
199 records.push_back(rec);
200 }
201
202 fclose(file);
203 return true;
204 }
205
206
saveIndex()207 bool CacheManager::saveIndex()
208 {
209 std::sort(records.begin(), records.end(), compareUsedDate);
210
211 std::string indexPath = getIndexPath();
212 std::string tmpIndexName = indexPath + ".tmp";
213
214 FILE* file = fopen(tmpIndexName.c_str(), "w");
215 if (file == NULL)
216 return false;
217
218 const time_t nowTime = time(NULL);
219 fprintf(file, "#\n");
220 fprintf(file, "# BZFlag Cache Index - %s", ctime(&nowTime));
221 fprintf(file, "# <filesize> <filetime> <lastused> <md5check>\n");
222 fprintf(file, "#\n\n");
223
224 for (unsigned int i = 0; i < records.size(); i++)
225 {
226 const CacheRecord& rec = records[i];
227 fprintf(file, "%s\n%u %llu %llu ", rec.url.c_str(), rec.size, (long long unsigned)rec.date,
228 (long long unsigned)rec.usedDate);
229 fprintf(file, "%s\n\n", rec.key.c_str());
230 }
231
232 fclose(file);
233
234 #ifdef _WIN32
235 // Windows sucks yet again. You can't rename a file to a file that
236 // already exists, you have to remove the existing file first. No
237 // atomic transactions.
238 remove(indexPath.c_str());
239 #endif
240
241 return (rename(tmpIndexName.c_str(), indexPath.c_str()) == 0);
242 }
243
244
limitCacheSize()245 void CacheManager::limitCacheSize()
246 {
247 int maxSize = BZDB.evalInt("maxCacheMB") * 1024 * 1024;
248 if (maxSize < 0)
249 maxSize = 0;
250
251 int currentSize = 0;
252 for (unsigned int i = 0; i < records.size(); i++)
253 currentSize += records[i].size;
254
255 std::sort(records.begin(), records.end(), compareUsedDate);
256
257 while ((currentSize > maxSize) && (records.size() > 0))
258 {
259 CacheManager::CacheRecord& rec = records.back();
260 currentSize -= rec.size;
261 remove(rec.name.c_str());
262 removeDirs(rec.name);
263 records.pop_back();
264 }
265
266 return;
267 }
268
269
getCacheList() const270 std::vector<CacheManager::CacheRecord> CacheManager::getCacheList() const
271 {
272 return records;
273 }
274
275
fileExists(const std::string & name)276 static bool fileExists (const std::string& name)
277 {
278 struct stat buf;
279 #ifndef _WIN32
280 return (stat(name.c_str(), &buf) == 0);
281 #else
282 // Windows sucks yet again, if there is a trailing "\"
283 // at the end of the filename, _stat will return -1.
284 std::string dirname = name;
285 while (dirname.find_last_of('\\') == (dirname.size() - 1))
286 dirname.resize(dirname.size() - 1);
287 return (_stat(dirname.c_str(), (struct _stat *) &buf) == 0);
288 #endif
289 }
290
291
removeDirs(const std::string & path)292 static void removeDirs(const std::string& path)
293 {
294 unsigned int minLen = (unsigned int)getConfigDirName().size();
295 std::string tmp = path;
296 while (tmp.size() > minLen)
297 {
298 #ifndef _WIN32
299 unsigned int i = (unsigned int)tmp.find_last_of('/');
300 #else
301 unsigned int i = (unsigned int)tmp.find_last_of('\\');
302 #endif
303 tmp = tmp.substr(0, i);
304 if (remove(tmp.c_str()) != 0)
305 break;
306 }
307 return;
308 }
309
310
removeNewlines(char * c)311 static void removeNewlines(char* c)
312 {
313 while (*c != '\0')
314 {
315 if ((*c == '\n') || (*c == '\r'))
316 *c = '\0';
317 c++;
318 }
319 return;
320 }
321
322
partialEncoding(const std::string & string)323 static std::string partialEncoding(const std::string& string)
324 {
325 // URL encoding removes the '/' and '.', which is
326 // not acceptable. It is nice to have the directory
327 // structure, and to be able to point and click your
328 // way through it to view ".png"s.
329 std::string tmp;
330 char hex[5];
331 for (unsigned int i = 0; i < string.size(); i++)
332 {
333 const char c = string[i];
334 if (TextUtils::isWhitespace(c))
335 tmp += "%20";
336 else if ((c == '%') || (c == '*') || (c == '?') ||
337 (c == ':') || (c == '"') || (c == '\\'))
338 {
339 tmp += '%';
340 sprintf(hex, "%-2.2X", c);
341 tmp += hex;
342 }
343 else
344 tmp += c;
345 }
346 return tmp;
347 }
348
349
compareUsedDate(const CacheManager::CacheRecord & a,const CacheManager::CacheRecord & b)350 static bool compareUsedDate(const CacheManager::CacheRecord& a,
351 const CacheManager::CacheRecord& b)
352 {
353 // oldest last
354 return (a.usedDate > b.usedDate);
355 }
356
357
358 /*
359 * Local Variables: ***
360 * mode: C ***
361 * tab-width: 8 ***
362 * c-basic-offset: 2 ***
363 * indent-tabs-mode: t ***
364 * End: ***
365 * ex: shiftwidth=2 tabstop=8
366 */
367