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 */ 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 */ 81 RecentEntries::RecentEntries() 82 { 83 } 84 85 86 /*! \brief Frees all resources associated with the object. 87 */ 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 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 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 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 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 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 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