1/**
2* The CPageCache class allows you to store pages in memory.
3*
4* @param {int} maxSize - The maximum number of pages to store in memory.
5* @private
6* @class
7*/
8function CPageCache(maxSize, batchSize, debug) {
9	var m_queue = [];
10	var m_p1Queue = []; // Priority 1 queue. These can only be bumped by other p1 pages.
11	var m_pages = {}, m_p1Ids = {};
12	var m_maxSize = maxSize;
13	var m_batchSize = batchSize;
14	var m_maxP1Size = 10;
15
16	if (debug) {
17		window.cpagecache_pages = m_pages;
18		window.cpagecache_queue = m_queue;
19	}
20
21	// @param {Boolean} p1 - Pages the user actually visited are stored longer than preloads.
22	this.add = function(id, data, p1) {
23		if (p1)
24			_addPage(id, m_p1Queue, m_p1Ids, 1, m_maxP1Size);
25		_addPage(id, m_queue, m_pages, data, m_maxSize, m_p1Queue);
26	};
27	this.remove = function(id) {
28		if (id in m_pages) {
29			m_queue.splice(m_queue.indexOf(id), 1);
30			delete m_pages[id];
31
32			var p1Idx = m_p1Queue.indexOf(id);
33			if (p1Idx >= 0) {
34				m_queue.splice(p1Idx, 1);
35				delete m_p1Ids[id];
36			}
37		}
38	};
39	this.get = function(id) {
40		if (id in m_pages) {
41			// If it's accessed, it goes to the front.
42			_pushToFront(id, m_queue);
43			_pushToFront(id, m_p1Queue);
44			return m_pages[id];
45		}
46		return null;
47	};
48	this.has = function(id) {
49		return id in m_pages;
50	};
51
52	// Load initial cache, based on hrefs in an element
53	this.load = function(elt, history) {
54		var self = this;
55		var ids = {};
56		$('a', elt).each(function(idx, a) {
57			var href = a.getAttribute('href'); // Use getAttribute because some browsers make href appear to be canonical.
58			if (href && href.indexOf('://') < 0) {
59				var numParams = href.split('=').length;
60				if (href.indexOf('id=') >= 0)
61					numParams--;
62				if (numParams == 1) {
63					var pageinfo = history.getSwitchId(href);
64					if (pageinfo && !m_cache.has(pageinfo.id))
65						ids[pageinfo.id] = 1;
66				}
67			}
68		});
69
70		var idsA = [];
71		for (var id in ids)
72			idsA.push(id);
73
74		if (idsA.length > m_maxSize) {
75			// There are so many links that the chances of preloading the right one are basically zero.
76			// TODO: Sort by vertical position and preload near the top of the page?
77		}
78		else if (idsA.length > 0) {
79			if (idsA.length > m_maxSize)
80				idsA.length = m_maxSize;
81
82			// Split pages into at least 4 batches if possible.
83			var batchSize = m_batchSize;
84			if (idsA.length / batchSize < 4)
85				batchSize = Math.ceil(idsA.length / 4);
86			var requests = [];
87			for (var x=0; x<Math.ceil(idsA.length / batchSize); x++) {
88				var sublist = idsA.slice(x*batchSize, (x+1)*batchSize);
89				var params = {partial: 1};
90				params['do'] = 'fastwiki_preload';
91				params.fastwiki_preload_pages = sublist.join(',');
92				requests.push(params);
93			}
94
95			function doPost(params) {
96				m_debug && console.log("Preloading " + params.fastwiki_preload_pages);
97				$.post(DOKU_BASE + 'doku.php', params, function(data) {
98					var pages = data.split(JSINFO.fastwiki.preload_head);
99					for (var p=0; p<pages.length; p++) {
100						var line1End = pages[p].indexOf('\n');
101						var id = pages[p].substr(0, line1End);
102						pages[p] = pages[p].substr(line1End+1);
103						m_debug && console.log("Loaded " + [id, pages[p].length]);
104						// If a bug causes a whole page to be loaded, don't cache it.
105						if (pages[p].indexOf('<body') >= 0)
106							m_debug && console.log("ERROR: Body found!");
107						else
108							self.add(id, pages[p]);
109					}
110
111					if (requests.length > 0)
112						doPost(requests.shift());
113				}, 'text');
114			}
115
116			// Make the first 4 requests. Limit to 4 so as not to monopolize all the browser's sockets (there are 6 in modern browsers).
117			for (var x=0; x<Math.min(4, requests.length); x++)
118				doPost(requests.shift());
119		}
120	};
121
122	function _pushToFront(id, queue) {
123		var idx = queue.indexOf(id);
124		if (idx >= 0) {
125			queue.splice(idx, 1);
126			queue.push(id);
127		}
128	}
129	function _addPage(id, queue, hash, data, maxSize, exclude) {
130		if (id in hash)
131			_pushToFront(id, queue);
132		else if (data) {
133			if (queue.length > maxSize) {
134				if (exclude) {
135					for (var x=0; x<queue.length; x++) {
136						if (!exclude[queue[x]]) {
137							delete hash[queue[x]];
138							queue.splice(x, 1);
139						}
140					}
141				}
142				else
143					delete hash[queue.shift()];
144			}
145			queue.push(id);
146		}
147
148		if (data)
149			hash[id] = data;
150	}
151}
152