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