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