1/*
2 * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de>
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "BrowsingHistory.h"
29
30#include <new>
31#include <stdio.h>
32
33#include <Autolock.h>
34#include <Entry.h>
35#include <File.h>
36#include <FindDirectory.h>
37#include <Message.h>
38#include <Path.h>
39
40#include "BrowserApp.h"
41
42
43BrowsingHistoryItem::BrowsingHistoryItem(const BString& url)
44	:
45	fURL(url),
46	fDateTime(BDateTime::CurrentDateTime(B_LOCAL_TIME)),
47	fInvokationCount(0)
48{
49}
50
51
52BrowsingHistoryItem::BrowsingHistoryItem(const BrowsingHistoryItem& other)
53{
54	*this = other;
55}
56
57
58BrowsingHistoryItem::BrowsingHistoryItem(const BMessage* archive)
59{
60	if (!archive)
61		return;
62	BMessage dateTimeArchive;
63	if (archive->FindMessage("date time", &dateTimeArchive) == B_OK)
64		fDateTime = BDateTime(&dateTimeArchive);
65	archive->FindString("url", &fURL);
66	archive->FindUInt32("invokations", &fInvokationCount);
67}
68
69
70BrowsingHistoryItem::~BrowsingHistoryItem()
71{
72}
73
74
75status_t
76BrowsingHistoryItem::Archive(BMessage* archive) const
77{
78	if (!archive)
79		return B_BAD_VALUE;
80	BMessage dateTimeArchive;
81	status_t status = fDateTime.Archive(&dateTimeArchive);
82	if (status == B_OK)
83		status = archive->AddMessage("date time", &dateTimeArchive);
84	if (status == B_OK)
85		status = archive->AddString("url", fURL.String());
86	if (status == B_OK)
87		status = archive->AddUInt32("invokations", fInvokationCount);
88	return status;
89}
90
91
92BrowsingHistoryItem&
93BrowsingHistoryItem::operator=(const BrowsingHistoryItem& other)
94{
95	if (this == &other)
96		return *this;
97
98	fURL = other.fURL;
99	fDateTime = other.fDateTime;
100	fInvokationCount = other.fInvokationCount;
101
102	return *this;
103}
104
105
106bool
107BrowsingHistoryItem::operator==(const BrowsingHistoryItem& other) const
108{
109	if (this == &other)
110		return true;
111
112	return fURL == other.fURL && fDateTime == other.fDateTime
113		&& fInvokationCount == other.fInvokationCount;
114}
115
116
117bool
118BrowsingHistoryItem::operator!=(const BrowsingHistoryItem& other) const
119{
120	return !(*this == other);
121}
122
123
124bool
125BrowsingHistoryItem::operator<(const BrowsingHistoryItem& other) const
126{
127	if (this == &other)
128		return false;
129
130	return fDateTime < other.fDateTime || fURL < other.fURL;
131}
132
133
134bool
135BrowsingHistoryItem::operator<=(const BrowsingHistoryItem& other) const
136{
137	return (*this == other) || (*this < other);
138}
139
140
141bool
142BrowsingHistoryItem::operator>(const BrowsingHistoryItem& other) const
143{
144	if (this == &other)
145		return false;
146
147	return fDateTime > other.fDateTime || fURL > other.fURL;
148}
149
150
151bool
152BrowsingHistoryItem::operator>=(const BrowsingHistoryItem& other) const
153{
154	return (*this == other) || (*this > other);
155}
156
157
158void
159BrowsingHistoryItem::Invoked()
160{
161	// Eventually, we may overflow...
162	uint32 count = fInvokationCount + 1;
163	if (count > fInvokationCount)
164		fInvokationCount = count;
165	fDateTime = BDateTime::CurrentDateTime(B_LOCAL_TIME);
166}
167
168
169// #pragma mark - BrowsingHistory
170
171
172BrowsingHistory
173BrowsingHistory::sDefaultInstance;
174
175
176BrowsingHistory::BrowsingHistory()
177	:
178	BLocker("browsing history"),
179	fHistoryItems(64),
180	fMaxHistoryItemAge(7),
181	fSettingsLoaded(false)
182{
183}
184
185
186BrowsingHistory::~BrowsingHistory()
187{
188	_SaveSettings();
189	_Clear();
190}
191
192
193/*static*/ BrowsingHistory*
194BrowsingHistory::DefaultInstance()
195{
196	if (sDefaultInstance.Lock()) {
197		sDefaultInstance._LoadSettings();
198		sDefaultInstance.Unlock();
199	}
200	return &sDefaultInstance;
201}
202
203
204bool
205BrowsingHistory::AddItem(const BrowsingHistoryItem& item)
206{
207	BAutolock _(this);
208
209	return _AddItem(item, false);
210}
211
212
213int32
214BrowsingHistory::BrowsingHistory::CountItems() const
215{
216	BAutolock _(const_cast<BrowsingHistory*>(this));
217
218	return fHistoryItems.CountItems();
219}
220
221
222BrowsingHistoryItem
223BrowsingHistory::HistoryItemAt(int32 index) const
224{
225	BAutolock _(const_cast<BrowsingHistory*>(this));
226
227	BrowsingHistoryItem* existingItem = reinterpret_cast<BrowsingHistoryItem*>(
228		fHistoryItems.ItemAt(index));
229	if (!existingItem)
230		return BrowsingHistoryItem(BString());
231
232	return BrowsingHistoryItem(*existingItem);
233}
234
235
236void
237BrowsingHistory::Clear()
238{
239	BAutolock _(this);
240	_Clear();
241	_SaveSettings();
242}
243
244
245void
246BrowsingHistory::SetMaxHistoryItemAge(int32 days)
247{
248	BAutolock _(this);
249	if (fMaxHistoryItemAge != days) {
250		fMaxHistoryItemAge = days;
251		_SaveSettings();
252	}
253}
254
255
256int32
257BrowsingHistory::MaxHistoryItemAge() const
258{
259	return fMaxHistoryItemAge;
260}
261
262
263// #pragma mark - private
264
265
266void
267BrowsingHistory::_Clear()
268{
269	int32 count = CountItems();
270	for (int32 i = 0; i < count; i++) {
271		BrowsingHistoryItem* item = reinterpret_cast<BrowsingHistoryItem*>(
272			fHistoryItems.ItemAtFast(i));
273		delete item;
274	}
275	fHistoryItems.MakeEmpty();
276}
277
278
279bool
280BrowsingHistory::_AddItem(const BrowsingHistoryItem& item, bool internal)
281{
282	int32 count = CountItems();
283	int32 insertionIndex = count;
284	for (int32 i = 0; i < count; i++) {
285		BrowsingHistoryItem* existingItem
286			= reinterpret_cast<BrowsingHistoryItem*>(
287			fHistoryItems.ItemAtFast(i));
288		if (item.URL() == existingItem->URL()) {
289			if (!internal) {
290				existingItem->Invoked();
291				_SaveSettings();
292			}
293			return true;
294		}
295		if (item < *existingItem)
296			insertionIndex = i;
297	}
298	BrowsingHistoryItem* newItem = new(std::nothrow) BrowsingHistoryItem(item);
299	if (!newItem || !fHistoryItems.AddItem(newItem, insertionIndex)) {
300		delete newItem;
301		return false;
302	}
303
304	if (!internal) {
305		newItem->Invoked();
306		_SaveSettings();
307	}
308
309	return true;
310}
311
312
313void
314BrowsingHistory::_LoadSettings()
315{
316	if (fSettingsLoaded)
317		return;
318
319	fSettingsLoaded = true;
320
321	BFile settingsFile;
322	if (_OpenSettingsFile(settingsFile, B_READ_ONLY)) {
323		BMessage settingsArchive;
324		settingsArchive.Unflatten(&settingsFile);
325		if (settingsArchive.FindInt32("max history item age",
326				&fMaxHistoryItemAge) != B_OK) {
327			fMaxHistoryItemAge = 7;
328		}
329		BDateTime oldestAllowedDateTime
330			= BDateTime::CurrentDateTime(B_LOCAL_TIME);
331		oldestAllowedDateTime.Date().AddDays(-fMaxHistoryItemAge);
332
333		BMessage historyItemArchive;
334		for (int32 i = 0; settingsArchive.FindMessage("history item", i,
335				&historyItemArchive) == B_OK; i++) {
336			BrowsingHistoryItem item(&historyItemArchive);
337			if (oldestAllowedDateTime < item.DateTime())
338				_AddItem(item, true);
339			historyItemArchive.MakeEmpty();
340		}
341	}
342}
343
344
345void
346BrowsingHistory::_SaveSettings()
347{
348	BFile settingsFile;
349	if (_OpenSettingsFile(settingsFile,
350			B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY)) {
351		BMessage settingsArchive;
352		settingsArchive.AddInt32("max history item age", fMaxHistoryItemAge);
353		BMessage historyItemArchive;
354		int32 count = CountItems();
355		for (int32 i = 0; i < count; i++) {
356			BrowsingHistoryItem item = HistoryItemAt(i);
357			if (item.Archive(&historyItemArchive) != B_OK)
358				break;
359			if (settingsArchive.AddMessage("history item",
360					&historyItemArchive) != B_OK) {
361				break;
362			}
363			historyItemArchive.MakeEmpty();
364		}
365		settingsArchive.Flatten(&settingsFile);
366	}
367}
368
369
370bool
371BrowsingHistory::_OpenSettingsFile(BFile& file, uint32 mode)
372{
373	BPath path;
374	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK
375		|| path.Append(kApplicationName) != B_OK
376		|| path.Append("BrowsingHistory") != B_OK) {
377		return false;
378	}
379	return file.SetTo(path.Path(), mode) == B_OK;
380}
381
382