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