1/*
2 * Copyright 2001-2009, Haiku Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Tyler Dauwalder
7 *		Ingo Weinhold, bonefish@users.sf.net
8 *		Axel Dörfler, axeld@pinc-software.de
9 */
10
11
12//!	Recently launched apps list
13
14
15#include "RecentEntries.h"
16
17#include <new>
18#include <map>
19
20#include <strings.h>
21
22#include <AppFileInfo.h>
23#include <Entry.h>
24#include <File.h>
25#include <Message.h>
26#include <Mime.h>
27#include <Path.h>
28#include <Roster.h>
29#include <String.h>
30
31#include <storage_support.h>
32
33#include "Debug.h"
34
35
36using namespace std;
37
38
39/*!	\struct recent_entry
40
41	\brief A recent entry, the corresponding signature of the application
42	that launched/used/opened/viewed/whatevered it, and an index used for
43	keeping track of orderings when loading/storing the recent entries list
44	from/to disk.
45
46*/
47
48/*! \brief Creates a new recent_entry object.
49*/
50recent_entry::recent_entry(const entry_ref *ref, const char *appSig,
51		uint32 index)
52	:
53	ref(ref ? *ref : entry_ref()),
54	sig(appSig),
55	index(index)
56{
57}
58
59
60//	#pragma mark -
61
62
63/*!	\class RecentEntries
64	\brief Implements the common functionality used by the roster's recent
65	folders and recent documents lists.
66
67*/
68
69/*!	\var std::list<std::string> RecentEntries::fEntryList
70	\brief The list of entries and their corresponding app sigs, most recent first
71
72	The signatures are expected to be stored all lowercase, as MIME
73	signatures are case-independent.
74*/
75
76
77/*!	\brief Creates a new list.
78
79	The list is initially empty.
80*/
81RecentEntries::RecentEntries()
82{
83}
84
85
86/*!	\brief Frees all resources associated with the object.
87*/
88RecentEntries::~RecentEntries()
89{
90	Clear();
91}
92
93
94/*! \brief Places the given entry Places the app with the given signature at the front of
95	the recent apps list.
96
97	If the app already exists elsewhere in the list, that item is
98	removed so only one instance exists in the list at any time.
99
100	\param appSig The application's signature
101	\param appFlags The application's flags. If \a appFlags contains
102	                either \c B_ARGV_ONLY or \c B_BACKGROUND_APP, the
103	                application is \b not added to the list (but \c B_OK
104	                is still returned).
105	\return
106	- \c B_OK: success (even if the app was not added due to appFlags)
107	- error code: failure
108*/
109status_t
110RecentEntries::Add(const entry_ref *ref, const char *appSig)
111{
112	if (ref == NULL || appSig == NULL)
113		return B_BAD_VALUE;
114
115	// Look for a previous instance of this entry
116	std::list<recent_entry*>::iterator item;
117	for (item = fEntryList.begin(); item != fEntryList.end(); item++) {
118		if ((*item)->ref == *ref && !strcasecmp((*item)->sig.c_str(), appSig)) {
119			fEntryList.erase(item);
120			break;
121		}
122	}
123
124	// Add this entry to the front of the list
125	recent_entry *entry = new (nothrow) recent_entry(ref, appSig, 0);
126	if (entry == NULL)
127		return B_NO_MEMORY;
128
129	try {
130		fEntryList.push_front(entry);
131	} catch (...) {
132		return B_NO_MEMORY;
133	}
134
135	return B_OK;
136}
137
138
139/*! \brief Returns the first \a maxCount recent apps in the \c BMessage
140	pointed to by \a list.
141
142	The message is cleared first, and \c entry_refs for the the apps are
143	stored in the \c "refs" field of the message (\c B_REF_TYPE).
144
145	If there are fewer than \a maxCount items in the list, the entire
146	list is returned.
147
148	Duplicate entries are never returned, i.e. if two instances of the
149	same entry were added under different app sigs, and both instances
150	match the given filter criterion, only the most recent instance is
151	returned; the latter instance is ignored and not counted towards
152	the \a maxCount number of entries to return.
153
154	Since BRoster::GetRecentEntries() returns \c void, the message pointed
155	to by \a list is simply cleared if maxCount is invalid (i.e. <= 0).
156
157	\param fileTypes An array of file type filters. These file types are
158	       expected to be all lowercase.
159*/
160status_t
161RecentEntries::Get(int32 maxCount, const char *fileTypes[],
162	int32 fileTypesCount, const char *appSig, BMessage *result)
163{
164	if (result == NULL
165		|| fileTypesCount < 0
166		|| (fileTypesCount > 0 && fileTypes == NULL))
167		return B_BAD_VALUE;
168
169	result->MakeEmpty();
170
171	std::list<recent_entry*> duplicateList;
172	std::list<recent_entry*>::iterator item;
173	status_t error = B_OK;
174	int count = 0;
175
176	for (item = fEntryList.begin();
177			error == B_OK && count < maxCount && item != fEntryList.end();
178			item++) {
179		// Filter by app sig
180		if (appSig != NULL && strcasecmp((*item)->sig.c_str(), appSig))
181			continue;
182
183		// Filter by file type
184		if (fileTypesCount > 0) {
185			char type[B_MIME_TYPE_LENGTH];
186			if (GetTypeForRef(&(*item)->ref, type) == B_OK) {
187				bool match = false;
188				for (int i = 0; i < fileTypesCount; i++) {
189					if (!strcasecmp(type, fileTypes[i])) {
190						match = true;
191						break;
192					}
193				}
194				if (!match)
195					continue;
196			}
197		}
198
199		// Check for duplicates
200		bool duplicate = false;
201		for (std::list<recent_entry*>::iterator dupItem = duplicateList.begin();
202				dupItem != duplicateList.end(); dupItem++) {
203			if ((*dupItem)->ref == (*item)->ref) {
204				duplicate = true;
205				break;
206			}
207		}
208		if (duplicate)
209			continue;
210
211		// Add the ref to the list used to check
212		// for duplicates, and then to the result
213		try {
214			duplicateList.push_back(*item);
215		} catch (...) {
216			error = B_NO_MEMORY;
217		}
218		if (error == B_OK)
219			error = result->AddRef("refs", &(*item)->ref);
220		if (error == B_OK)
221			count++;
222	}
223
224	return error;
225}
226
227
228/*! \brief Clears the list of recently launched apps
229*/
230status_t
231RecentEntries::Clear()
232{
233	std::list<recent_entry*>::iterator i;
234	for (i = fEntryList.begin(); i != fEntryList.end(); i++) {
235		delete *i;
236	}
237	fEntryList.clear();
238	return B_OK;
239}
240
241
242/*! \brief Dumps the the current list of entries to stdout.
243*/
244status_t
245RecentEntries::Print()
246{
247	std::list<recent_entry*>::iterator item;
248	int counter = 1;
249	for (item = fEntryList.begin(); item != fEntryList.end(); item++) {
250		printf("%d: device == '%" B_PRIdDEV "', dir == '%" B_PRIdINO "', "
251			"name == '%s', app == '%s', index == %" B_PRId32 "\n", counter++,
252			(*item)->ref.device, (*item)->ref.directory, (*item)->ref.name,
253			(*item)->sig.c_str(), (*item)->index);
254	}
255	return B_OK;
256}
257
258
259status_t
260RecentEntries::Save(FILE* file, const char *description, const char *tag)
261{
262	if (file == NULL || description == NULL || tag == NULL)
263		return B_BAD_VALUE;
264
265	fprintf(file, "# %s\n", description);
266
267	/*	In order to write our entries out in the format used by the
268		Roster settings file, we need to collect all the signatures
269		for each entry in one place, while at the same time updating
270		the index values for each entry/sig pair to reflect the current
271		ordering of the list. I believe this is the data structure
272		R5 actually maintains all the time, as their indices do not
273		change over time (whereas ours will). If our implementation
274		proves to be slower that R5, we may want to consider using
275		the data structure pervasively.
276	*/
277	std::map<entry_ref, std::list<recent_entry*> > map;
278	uint32 count = fEntryList.size();
279
280	try {
281		for (std::list<recent_entry*>::iterator item = fEntryList.begin();
282				item != fEntryList.end(); count--, item++) {
283			recent_entry *entry = *item;
284			if (entry) {
285				entry->index = count;
286				map[entry->ref].push_back(entry);
287			} else {
288				D(PRINT("WARNING: RecentEntries::Save(): The entry %ld entries "
289					"from the front of fEntryList was found to be NULL\n",
290					fEntryList.size() - count));
291			}
292		}
293	} catch (...) {
294		return B_NO_MEMORY;
295	}
296
297	for (std::map<entry_ref, std::list<recent_entry*> >::iterator mapItem = map.begin();
298			mapItem != map.end(); mapItem++) {
299		// We're going to need to properly escape the path name we
300		// get, which will at absolute worst double the length of
301		// the string.
302		BPath path;
303		char escapedPath[B_PATH_NAME_LENGTH*2];
304		status_t outputError = path.SetTo(&mapItem->first);
305		if (!outputError) {
306			BPrivate::Storage::escape_path(path.Path(), escapedPath);
307			fprintf(file, "%s %s", tag, escapedPath);
308			std::list<recent_entry*> &list = mapItem->second;
309			int32 i = 0;
310			for (std::list<recent_entry*>::iterator item = list.begin();
311					item != list.end(); i++, item++) {
312				recent_entry *entry = *item;
313				if (entry) {
314					fprintf(file, " \"%s\" %" B_PRId32, entry->sig.c_str(),
315						entry->index);
316				} else {
317					D(PRINT("WARNING: RecentEntries::Save(): The entry %"
318						B_PRId32 " entries from the front of the compiled "
319						"recent_entry* list for the entry ref (%" B_PRId32 ", %"
320						B_PRId64 ", '%s') was found to be NULL\n", i,
321						mapItem->first.device, mapItem->first.directory,
322						mapItem->first.name));
323				}
324			}
325			fprintf(file, "\n");
326		} else {
327			D(PRINT("WARNING: RecentEntries::Save(): entry_ref_to_path() "
328				"failed on the entry_ref (%" B_PRId32", %" B_PRId64 ", '%s') "
329				"with error 0x%" B_PRIx32 "\n",
330				mapItem->first.device, mapItem->first.directory,
331				mapItem->first.name, outputError));
332		}
333	}
334
335	fprintf(file, "\n");
336	return B_OK;
337}
338
339
340// GetTypeForRef
341/*! \brief Fetches the file type of the given file.
342
343	If the file has no type, an empty string is returned. The file
344	is *not* sniffed.
345*/
346status_t
347RecentEntries::GetTypeForRef(const entry_ref *ref, char *result)
348{
349	if (ref == NULL || result == NULL)
350		return B_BAD_VALUE;
351
352	// Read the type
353	BNode node;
354	status_t error = node.SetTo(ref);
355	if (error == B_OK) {
356		ssize_t bytes = node.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE,
357			0, result, B_MIME_TYPE_LENGTH - 1);
358		if (bytes < B_OK)
359			error = bytes;
360		else
361			result[bytes] = '\0';
362	}
363
364	return error;
365}
366