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	int32 remove = fEntryList.size() - kMaxRecentEntries;
136	while (remove > 0) {
137		fEntryList.pop_back();
138		remove--;
139	}
140
141	return B_OK;
142}
143
144
145/*! \brief Returns the first \a maxCount recent apps in the \c BMessage
146	pointed to by \a list.
147
148	The message is cleared first, and \c entry_refs for the the apps are
149	stored in the \c "refs" field of the message (\c B_REF_TYPE).
150
151	If there are fewer than \a maxCount items in the list, the entire
152	list is returned.
153
154	Duplicate entries are never returned, i.e. if two instances of the
155	same entry were added under different app sigs, and both instances
156	match the given filter criterion, only the most recent instance is
157	returned; the latter instance is ignored and not counted towards
158	the \a maxCount number of entries to return.
159
160	Since BRoster::GetRecentEntries() returns \c void, the message pointed
161	to by \a list is simply cleared if maxCount is invalid (i.e. <= 0).
162
163	\param fileTypes An array of file type filters. These file types are
164	       expected to be all lowercase.
165*/
166status_t
167RecentEntries::Get(int32 maxCount, const char *fileTypes[],
168	int32 fileTypesCount, const char *appSig, BMessage *result)
169{
170	if (result == NULL
171		|| fileTypesCount < 0
172		|| (fileTypesCount > 0 && fileTypes == NULL))
173		return B_BAD_VALUE;
174
175	result->MakeEmpty();
176
177	std::list<recent_entry*> duplicateList;
178	std::list<recent_entry*>::iterator item;
179	status_t error = B_OK;
180	int count = 0;
181
182	for (item = fEntryList.begin();
183			error == B_OK && count < maxCount && item != fEntryList.end();
184			item++) {
185		// Filter by app sig
186		if (appSig != NULL && strcasecmp((*item)->sig.c_str(), appSig))
187			continue;
188
189		// Filter by file type
190		if (fileTypesCount > 0) {
191			char type[B_MIME_TYPE_LENGTH];
192			if (GetTypeForRef(&(*item)->ref, type) == B_OK) {
193				bool match = false;
194				for (int i = 0; i < fileTypesCount; i++) {
195					if (!strcasecmp(type, fileTypes[i])) {
196						match = true;
197						break;
198					}
199				}
200				if (!match)
201					continue;
202			}
203		}
204
205		// Check for duplicates
206		bool duplicate = false;
207		for (std::list<recent_entry*>::iterator dupItem = duplicateList.begin();
208				dupItem != duplicateList.end(); dupItem++) {
209			if ((*dupItem)->ref == (*item)->ref) {
210				duplicate = true;
211				break;
212			}
213		}
214		if (duplicate)
215			continue;
216
217		// Add the ref to the list used to check
218		// for duplicates, and then to the result
219		try {
220			duplicateList.push_back(*item);
221		} catch (...) {
222			error = B_NO_MEMORY;
223		}
224		if (error == B_OK)
225			error = result->AddRef("refs", &(*item)->ref);
226		if (error == B_OK)
227			count++;
228	}
229
230	return error;
231}
232
233
234/*! \brief Clears the list of recently launched apps
235*/
236status_t
237RecentEntries::Clear()
238{
239	std::list<recent_entry*>::iterator i;
240	for (i = fEntryList.begin(); i != fEntryList.end(); i++) {
241		delete *i;
242	}
243	fEntryList.clear();
244	return B_OK;
245}
246
247
248/*! \brief Dumps the the current list of entries to stdout.
249*/
250status_t
251RecentEntries::Print()
252{
253	std::list<recent_entry*>::iterator item;
254	int counter = 1;
255	for (item = fEntryList.begin(); item != fEntryList.end(); item++) {
256		printf("%d: device == '%" B_PRIdDEV "', dir == '%" B_PRIdINO "', "
257			"name == '%s', app == '%s', index == %" B_PRId32 "\n", counter++,
258			(*item)->ref.device, (*item)->ref.directory, (*item)->ref.name,
259			(*item)->sig.c_str(), (*item)->index);
260	}
261	return B_OK;
262}
263
264
265status_t
266RecentEntries::Save(FILE* file, const char *description, const char *tag)
267{
268	if (file == NULL || description == NULL || tag == NULL)
269		return B_BAD_VALUE;
270
271	fprintf(file, "# %s\n", description);
272
273	/*	In order to write our entries out in the format used by the
274		Roster settings file, we need to collect all the signatures
275		for each entry in one place, while at the same time updating
276		the index values for each entry/sig pair to reflect the current
277		ordering of the list. I believe this is the data structure
278		R5 actually maintains all the time, as their indices do not
279		change over time (whereas ours will). If our implementation
280		proves to be slower that R5, we may want to consider using
281		the data structure pervasively.
282	*/
283	std::map<entry_ref, std::list<recent_entry*> > map;
284	uint32 count = fEntryList.size();
285
286	try {
287		for (std::list<recent_entry*>::iterator item = fEntryList.begin();
288				item != fEntryList.end(); count--, item++) {
289			recent_entry *entry = *item;
290			if (entry) {
291				entry->index = count;
292				map[entry->ref].push_back(entry);
293			} else {
294				D(PRINT("WARNING: RecentEntries::Save(): The entry %ld entries "
295					"from the front of fEntryList was found to be NULL\n",
296					fEntryList.size() - count));
297			}
298		}
299	} catch (...) {
300		return B_NO_MEMORY;
301	}
302
303	for (std::map<entry_ref, std::list<recent_entry*> >::iterator mapItem = map.begin();
304			mapItem != map.end(); mapItem++) {
305		// We're going to need to properly escape the path name we
306		// get, which will at absolute worst double the length of
307		// the string.
308		BPath path;
309		char escapedPath[B_PATH_NAME_LENGTH*2];
310		status_t outputError = path.SetTo(&mapItem->first);
311		if (!outputError) {
312			BPrivate::Storage::escape_path(path.Path(), escapedPath);
313			fprintf(file, "%s %s", tag, escapedPath);
314			std::list<recent_entry*> &list = mapItem->second;
315			int32 i = 0;
316			for (std::list<recent_entry*>::iterator item = list.begin();
317					item != list.end(); i++, item++) {
318				recent_entry *entry = *item;
319				if (entry) {
320					fprintf(file, " \"%s\" %" B_PRId32, entry->sig.c_str(),
321						entry->index);
322				} else {
323					D(PRINT("WARNING: RecentEntries::Save(): The entry %"
324						B_PRId32 " entries from the front of the compiled "
325						"recent_entry* list for the entry ref (%" B_PRId32 ", %"
326						B_PRId64 ", '%s') was found to be NULL\n", i,
327						mapItem->first.device, mapItem->first.directory,
328						mapItem->first.name));
329				}
330			}
331			fprintf(file, "\n");
332		} else {
333			D(PRINT("WARNING: RecentEntries::Save(): entry_ref_to_path() "
334				"failed on the entry_ref (%" B_PRId32", %" B_PRId64 ", '%s') "
335				"with error 0x%" B_PRIx32 "\n",
336				mapItem->first.device, mapItem->first.directory,
337				mapItem->first.name, outputError));
338		}
339	}
340
341	fprintf(file, "\n");
342	return B_OK;
343}
344
345
346// GetTypeForRef
347/*! \brief Fetches the file type of the given file.
348
349	If the file has no type, an empty string is returned. The file
350	is *not* sniffed.
351*/
352status_t
353RecentEntries::GetTypeForRef(const entry_ref *ref, char *result)
354{
355	if (ref == NULL || result == NULL)
356		return B_BAD_VALUE;
357
358	// Read the type
359	BNode node;
360	status_t error = node.SetTo(ref);
361	if (error == B_OK) {
362		ssize_t bytes = node.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE,
363			0, result, B_MIME_TYPE_LENGTH - 1);
364		if (bytes < B_OK)
365			error = bytes;
366		else
367			result[bytes] = '\0';
368	}
369
370	return error;
371}
372