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