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