xref: /haiku/src/servers/registrar/RecentEntries.cpp (revision 51978af14a173e7fae0563b562be5603bc652aeb)
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 	return B_OK;
264 }
265 
266 // Save
267 status_t
268 RecentEntries::Save(FILE* file, const char *description, const char *tag)
269 {
270 	status_t error = file ? B_OK : B_BAD_VALUE;
271 	if (!error) {
272 		fprintf(file, "# %s\n", description);
273 
274 		/*	In order to write our entries out in the format used by the
275 			Roster settings file, we need to collect all the signatures
276 			for each entry in one place, while at the same time updating
277 			the index values for each entry/sig pair to reflect the current
278 			ordering of the list. I believe this is the data structure
279 			R5 actually maintains all the time, as their indices do not
280 			change over time (whereas ours will). If our implementation
281 			proves to be slower that R5, we may want to consider using
282 			the data structure pervasively.
283 		*/
284 		std::map<entry_ref, std::list<recent_entry*> > map;
285 		uint32 count = fEntryList.size();
286 
287 		for (std::list<recent_entry*>::iterator item = fEntryList.begin();
288 		       item != fEntryList.end();
289 		         count--, item++)
290 		{
291 			recent_entry *entry = *item;
292 			if (entry) {
293 				entry->index = count;
294 				map[entry->ref].push_back(entry);
295 			} else {
296 				DBG(OUT("WARNING: RecentEntries::Save(): The entry %ld entries "
297 				        "from the front of fEntryList was found to be NULL\n",
298 				        fEntryList.size() - count));
299 			}
300 		}
301 
302 		for (std::map<entry_ref, std::list<recent_entry*> >::iterator mapItem
303 		     = map.begin();
304 		       mapItem != map.end();
305 		         mapItem++)
306 		{
307 			// We're going to need to properly escape the path name we
308 			// get, which will at absolute worst double the length of
309 			// the string.
310 			char path[B_PATH_NAME_LENGTH];
311 			char escapedPath[B_PATH_NAME_LENGTH*2];
312 			status_t outputError = BPrivate::Storage::entry_ref_to_path(&mapItem->first,
313 			                       path, B_PATH_NAME_LENGTH);
314 			if (!outputError) {
315 				BPrivate::Storage::escape_path(path, escapedPath);
316 				fprintf(file, "%s %s", tag, escapedPath);
317 				std::list<recent_entry*> &list = mapItem->second;
318 				int32 i = 0;
319 				for (std::list<recent_entry*>::iterator item = list.begin();
320 				       item != list.end();
321 				         i++, item++)
322 				{
323 					recent_entry *entry = *item;
324 					if (entry)
325 						fprintf(file, " \"%s\" %ld", entry->sig.c_str(), entry->index);
326 					else {
327 						DBG(OUT("WARNING: RecentEntries::Save(): The entry %ld entries "
328 						        "from the front of the compiled recent_entry* list for the "
329 						        "entry ref (%ld, %lld, '%s') was found to be NULL\n",
330 						        i, mapItem->first.device, mapItem->first.directory,
331 						        mapItem->first.name));
332 					}
333 				}
334 				fprintf(file, "\n");
335 			} else {
336 				DBG(OUT("WARNING: RecentEntries::Save(): entry_ref_to_path() failed on "
337 				        "the entry_ref (%ld, %lld, '%s') with error 0x%lx\n",
338 				        mapItem->first.device, mapItem->first.directory,
339 				        mapItem->first.name, outputError));
340 			}
341 		}
342 		fprintf(file, "\n");
343 	}
344 	return error;
345 }
346 
347 
348 // GetTypeForRef
349 /*! \brief Fetches the file type of the given file.
350 
351 	If the file has no type, an empty string is returned. The file
352 	is *not* sniffed.
353 */
354 status_t
355 RecentEntries::GetTypeForRef(const entry_ref *ref, char *result)
356 {
357 	BNode node;
358 	status_t error = ref && result ? B_OK : B_BAD_VALUE;
359 	// Read the type and force to lowercase
360 	if (!error)
361 		error = node.SetTo(ref);
362 	if (!error) {
363 		ssize_t bytes = node.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE,
364 		                0, result, B_MIME_TYPE_LENGTH-1);
365 		if (bytes < 0)
366 			error = bytes;
367 		else
368 			result[bytes] = '\0';
369 	}
370 	if (!error)
371 		BPrivate::Storage::to_lower(result);
372 	return error;
373 }
374