1 // ==========================================================
2 // Multi-Page functions
3 //
4 // Design and implementation by
5 // - Floris van den Berg (flvdberg@wxs.nl)
6 // - checkered (checkered@users.sourceforge.net)
7 // - Mihail Naydenov (mnaydenov@users.sourceforge.net)
8 //
9 // This file is part of FreeImage 3
10 //
11 // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
12 // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
13 // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
14 // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
15 // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
16 // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
17 // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
18 // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
19 // THIS DISCLAIMER.
20 //
21 // Use at your own risk!
22 // ==========================================================
23 
24 #ifdef _MSC_VER
25 #pragma warning (disable : 4786) // identifier was truncated to 'number' characters
26 #endif
27 
28 #include "CacheFile.h"
29 
30 // ----------------------------------------------------------
31 
CacheFile()32 CacheFile::CacheFile() :
33 m_file(NULL),
34 m_free_pages(),
35 m_page_cache_mem(),
36 m_page_cache_disk(),
37 m_page_map(),
38 m_page_count(0),
39 m_current_block(NULL),
40 m_keep_in_memory(TRUE) {
41 }
42 
~CacheFile()43 CacheFile::~CacheFile() {
44   close();
45 }
46 
47 BOOL
open(const std::string & filename,BOOL keep_in_memory)48 CacheFile::open(const std::string& filename, BOOL keep_in_memory) {
49 
50   assert(!m_file);
51 
52   m_filename = filename;
53   m_keep_in_memory = keep_in_memory;
54 
55 	if ((!m_filename.empty()) && (!m_keep_in_memory)) {
56 		m_file = fopen(m_filename.c_str(), "w+b");
57 		return (m_file != NULL);
58 	}
59 
60 	return (m_keep_in_memory == TRUE);
61 }
62 
63 void
close()64 CacheFile::close() {
65 	// dispose the cache entries
66 
67 	while (!m_page_cache_disk.empty()) {
68 		Block *block = *m_page_cache_disk.begin();
69 		m_page_cache_disk.pop_front();
70 		delete [] block->data;
71 		delete block;
72 	}
73 	while (!m_page_cache_mem.empty()) {
74 		Block *block = *m_page_cache_mem.begin();
75 		m_page_cache_mem.pop_front();
76 		delete [] block->data;
77 		delete block;
78 	}
79 
80 	if (m_file) {
81 		// close the file
82 		fclose(m_file);
83 		m_file = NULL;
84 
85 		// delete the file
86 		remove(m_filename.c_str());
87 	}
88 }
89 
90 void
cleanupMemCache()91 CacheFile::cleanupMemCache() {
92 	if (!m_keep_in_memory) {
93 		if (m_page_cache_mem.size() > CACHE_SIZE) {
94 			// flush the least used block to file
95 
96 			Block *old_block = m_page_cache_mem.back();
97 			fseek(m_file, old_block->nr * BLOCK_SIZE, SEEK_SET);
98 			fwrite(old_block->data, BLOCK_SIZE, 1, m_file);
99 
100 			// remove the data
101 
102 			delete [] old_block->data;
103 			old_block->data = NULL;
104 
105 			// move the block to another list
106 
107 			m_page_cache_disk.splice(m_page_cache_disk.begin(), m_page_cache_mem, --m_page_cache_mem.end());
108 			m_page_map[old_block->nr] = m_page_cache_disk.begin();
109 		}
110 	}
111 }
112 
113 int
allocateBlock()114 CacheFile::allocateBlock() {
115 	Block *block = new Block;
116 	block->data = new BYTE[BLOCK_SIZE];
117 	block->next = 0;
118 
119 	if (!m_free_pages.empty()) {
120 		block->nr = *m_free_pages.begin();
121 		m_free_pages.pop_front();
122 	} else {
123 		block->nr = m_page_count++;
124 	}
125 
126 	m_page_cache_mem.push_front(block);
127 	m_page_map[block->nr] = m_page_cache_mem.begin();
128 
129 	cleanupMemCache();
130 
131 	return block->nr;
132 }
133 
134 Block *
lockBlock(int nr)135 CacheFile::lockBlock(int nr) {
136 	if (m_current_block == NULL) {
137 		PageMapIt it = m_page_map.find(nr);
138 
139 		if (it != m_page_map.end()) {
140 			m_current_block = *(it->second);
141 
142 			// the block is swapped out to disc. load it back
143 			// and remove the block from the cache. it might get cached
144 			// again as soon as the memory buffer fills up
145 
146 			if (m_current_block->data == NULL) {
147 				m_current_block->data = new BYTE[BLOCK_SIZE];
148 
149 				fseek(m_file, m_current_block->nr * BLOCK_SIZE, SEEK_SET);
150 				fread(m_current_block->data, BLOCK_SIZE, 1, m_file);
151 
152 				m_page_cache_mem.splice(m_page_cache_mem.begin(), m_page_cache_disk, it->second);
153 				m_page_map[nr] = m_page_cache_mem.begin();
154 			}
155 
156 			// if the memory cache size is too large, swap an item to disc
157 
158 			cleanupMemCache();
159 
160 			// return the current block
161 
162 			return m_current_block;
163 		}
164 	}
165 
166 	return NULL;
167 }
168 
169 BOOL
unlockBlock(int nr)170 CacheFile::unlockBlock(int nr) {
171 	if (m_current_block) {
172 		m_current_block = NULL;
173 		return TRUE;
174 	}
175 	return FALSE;
176 }
177 
178 BOOL
deleteBlock(int nr)179 CacheFile::deleteBlock(int nr) {
180 	if (!m_current_block) {
181 		PageMapIt it = m_page_map.find(nr);
182 
183 		// remove block from cache
184 
185 		if (it != m_page_map.end()) {
186 			m_page_map.erase(nr);
187 		}
188 
189 		// add block to free page list
190 
191 		m_free_pages.push_back(nr);
192 
193 		return TRUE;
194 	}
195 
196 	return FALSE;
197 }
198 
199 BOOL
readFile(BYTE * data,int nr,int size)200 CacheFile::readFile(BYTE *data, int nr, int size) {
201 	if ((data) && (size > 0)) {
202 		int s = 0;
203 		int block_nr = nr;
204 
205 		do {
206 			int copy_nr = block_nr;
207 
208 			Block *block = lockBlock(copy_nr);
209 
210 			block_nr = block->next;
211 
212 			memcpy(data + s, block->data, (s + BLOCK_SIZE > size) ? size - s : BLOCK_SIZE);
213 
214 			unlockBlock(copy_nr);
215 
216 			s += BLOCK_SIZE;
217 		} while (block_nr != 0);
218 
219 		return TRUE;
220 	}
221 
222 	return FALSE;
223 }
224 
225 int
writeFile(BYTE * data,int size)226 CacheFile::writeFile(BYTE *data, int size) {
227 	if ((data) && (size > 0)) {
228 		int nr_blocks_required = 1 + (size / BLOCK_SIZE);
229 		int count = 0;
230 		int s = 0;
231 		int stored_alloc;
232 		int alloc;
233 
234 		stored_alloc = alloc = allocateBlock();
235 
236 		do {
237 			int copy_alloc = alloc;
238 
239 			Block *block = lockBlock(copy_alloc);
240 
241 			block->next = 0;
242 
243 			memcpy(block->data, data + s, (s + BLOCK_SIZE > size) ? size - s : BLOCK_SIZE);
244 
245 			if (count + 1 < nr_blocks_required)
246 				alloc = block->next = allocateBlock();
247 
248 			unlockBlock(copy_alloc);
249 
250 			s += BLOCK_SIZE;
251 		} while (++count < nr_blocks_required);
252 
253 		return stored_alloc;
254 	}
255 
256 	return 0;
257 }
258 
259 void
deleteFile(int nr)260 CacheFile::deleteFile(int nr) {
261 	do {
262 		Block *block = lockBlock(nr);
263 
264 		if (block == NULL)
265 			break;
266 
267 		int next = block->next;
268 
269 		unlockBlock(nr);
270 
271 		deleteBlock(nr);
272 
273 		nr = next;
274 	} while (nr != 0);
275 }
276 
277