xref: /haiku/src/servers/registrar/RecentEntries.cpp (revision 084e24d0bf3808808e2bf58d4d65f493bd2b8f49)
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 
36 using 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 */
50 recent_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 */
81 RecentEntries::RecentEntries()
82 {
83 }
84 
85 
86 /*!	\brief Frees all resources associated with the object.
87 */
88 RecentEntries::~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 */
109 status_t
110 RecentEntries::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 */
166 status_t
167 RecentEntries::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 */
236 status_t
237 RecentEntries::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 */
250 status_t
251 RecentEntries::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 
265 status_t
266 RecentEntries::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 */
352 status_t
353 RecentEntries::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