xref: /haiku/src/servers/registrar/RecentEntries.cpp (revision 4e3137c085bae361922078f123dceb92da700640)
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 	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 */
160 status_t
161 RecentEntries::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 */
230 status_t
231 RecentEntries::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 */
244 status_t
245 RecentEntries::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 
259 status_t
260 RecentEntries::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 */
346 status_t
347 RecentEntries::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