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 return B_OK; 136 } 137 138 139 /*! \brief Returns the first \a maxCount recent apps in the \c BMessage 140 pointed to by \a list. 141 142 The message is cleared first, and \c entry_refs for the the apps are 143 stored in the \c "refs" field of the message (\c B_REF_TYPE). 144 145 If there are fewer than \a maxCount items in the list, the entire 146 list is returned. 147 148 Duplicate entries are never returned, i.e. if two instances of the 149 same entry were added under different app sigs, and both instances 150 match the given filter criterion, only the most recent instance is 151 returned; the latter instance is ignored and not counted towards 152 the \a maxCount number of entries to return. 153 154 Since BRoster::GetRecentEntries() returns \c void, the message pointed 155 to by \a list is simply cleared if maxCount is invalid (i.e. <= 0). 156 157 \param fileTypes An array of file type filters. These file types are 158 expected to be all lowercase. 159 */ 160 status_t 161 RecentEntries::Get(int32 maxCount, const char *fileTypes[], 162 int32 fileTypesCount, const char *appSig, BMessage *result) 163 { 164 if (result == NULL 165 || fileTypesCount < 0 166 || (fileTypesCount > 0 && fileTypes == NULL)) 167 return B_BAD_VALUE; 168 169 result->MakeEmpty(); 170 171 std::list<recent_entry*> duplicateList; 172 std::list<recent_entry*>::iterator item; 173 status_t error = B_OK; 174 int count = 0; 175 176 for (item = fEntryList.begin(); 177 error == B_OK && count < maxCount && item != fEntryList.end(); 178 item++) { 179 // Filter by app sig 180 if (appSig != NULL && strcasecmp((*item)->sig.c_str(), appSig)) 181 continue; 182 183 // Filter by file type 184 if (fileTypesCount > 0) { 185 char type[B_MIME_TYPE_LENGTH]; 186 if (GetTypeForRef(&(*item)->ref, type) == B_OK) { 187 bool match = false; 188 for (int i = 0; i < fileTypesCount; i++) { 189 if (!strcasecmp(type, fileTypes[i])) { 190 match = true; 191 break; 192 } 193 } 194 if (!match) 195 continue; 196 } 197 } 198 199 // Check for duplicates 200 bool duplicate = false; 201 for (std::list<recent_entry*>::iterator dupItem = duplicateList.begin(); 202 dupItem != duplicateList.end(); dupItem++) { 203 if ((*dupItem)->ref == (*item)->ref) { 204 duplicate = true; 205 break; 206 } 207 } 208 if (duplicate) 209 continue; 210 211 // Add the ref to the list used to check 212 // for duplicates, and then to the result 213 try { 214 duplicateList.push_back(*item); 215 } catch (...) { 216 error = B_NO_MEMORY; 217 } 218 if (error == B_OK) 219 error = result->AddRef("refs", &(*item)->ref); 220 if (error == B_OK) 221 count++; 222 } 223 224 return error; 225 } 226 227 228 /*! \brief Clears the list of recently launched apps 229 */ 230 status_t 231 RecentEntries::Clear() 232 { 233 std::list<recent_entry*>::iterator i; 234 for (i = fEntryList.begin(); i != fEntryList.end(); i++) { 235 delete *i; 236 } 237 fEntryList.clear(); 238 return B_OK; 239 } 240 241 242 /*! \brief Dumps the the current list of entries to stdout. 243 */ 244 status_t 245 RecentEntries::Print() 246 { 247 std::list<recent_entry*>::iterator item; 248 int counter = 1; 249 for (item = fEntryList.begin(); item != fEntryList.end(); item++) { 250 printf("%d: device == '%" B_PRIdDEV "', dir == '%" B_PRIdINO "', " 251 "name == '%s', app == '%s', index == %" B_PRId32 "\n", counter++, 252 (*item)->ref.device, (*item)->ref.directory, (*item)->ref.name, 253 (*item)->sig.c_str(), (*item)->index); 254 } 255 return B_OK; 256 } 257 258 259 status_t 260 RecentEntries::Save(FILE* file, const char *description, const char *tag) 261 { 262 if (file == NULL || description == NULL || tag == NULL) 263 return B_BAD_VALUE; 264 265 fprintf(file, "# %s\n", description); 266 267 /* In order to write our entries out in the format used by the 268 Roster settings file, we need to collect all the signatures 269 for each entry in one place, while at the same time updating 270 the index values for each entry/sig pair to reflect the current 271 ordering of the list. I believe this is the data structure 272 R5 actually maintains all the time, as their indices do not 273 change over time (whereas ours will). If our implementation 274 proves to be slower that R5, we may want to consider using 275 the data structure pervasively. 276 */ 277 std::map<entry_ref, std::list<recent_entry*> > map; 278 uint32 count = fEntryList.size(); 279 280 try { 281 for (std::list<recent_entry*>::iterator item = fEntryList.begin(); 282 item != fEntryList.end(); count--, item++) { 283 recent_entry *entry = *item; 284 if (entry) { 285 entry->index = count; 286 map[entry->ref].push_back(entry); 287 } else { 288 D(PRINT("WARNING: RecentEntries::Save(): The entry %ld entries " 289 "from the front of fEntryList was found to be NULL\n", 290 fEntryList.size() - count)); 291 } 292 } 293 } catch (...) { 294 return B_NO_MEMORY; 295 } 296 297 for (std::map<entry_ref, std::list<recent_entry*> >::iterator mapItem = map.begin(); 298 mapItem != map.end(); mapItem++) { 299 // We're going to need to properly escape the path name we 300 // get, which will at absolute worst double the length of 301 // the string. 302 BPath path; 303 char escapedPath[B_PATH_NAME_LENGTH*2]; 304 status_t outputError = path.SetTo(&mapItem->first); 305 if (!outputError) { 306 BPrivate::Storage::escape_path(path.Path(), escapedPath); 307 fprintf(file, "%s %s", tag, escapedPath); 308 std::list<recent_entry*> &list = mapItem->second; 309 int32 i = 0; 310 for (std::list<recent_entry*>::iterator item = list.begin(); 311 item != list.end(); i++, item++) { 312 recent_entry *entry = *item; 313 if (entry) { 314 fprintf(file, " \"%s\" %" B_PRId32, entry->sig.c_str(), 315 entry->index); 316 } else { 317 D(PRINT("WARNING: RecentEntries::Save(): The entry %" 318 B_PRId32 " entries from the front of the compiled " 319 "recent_entry* list for the entry ref (%" B_PRId32 ", %" 320 B_PRId64 ", '%s') was found to be NULL\n", i, 321 mapItem->first.device, mapItem->first.directory, 322 mapItem->first.name)); 323 } 324 } 325 fprintf(file, "\n"); 326 } else { 327 D(PRINT("WARNING: RecentEntries::Save(): entry_ref_to_path() " 328 "failed on the entry_ref (%" B_PRId32", %" B_PRId64 ", '%s') " 329 "with error 0x%" B_PRIx32 "\n", 330 mapItem->first.device, mapItem->first.directory, 331 mapItem->first.name, outputError)); 332 } 333 } 334 335 fprintf(file, "\n"); 336 return B_OK; 337 } 338 339 340 // GetTypeForRef 341 /*! \brief Fetches the file type of the given file. 342 343 If the file has no type, an empty string is returned. The file 344 is *not* sniffed. 345 */ 346 status_t 347 RecentEntries::GetTypeForRef(const entry_ref *ref, char *result) 348 { 349 if (ref == NULL || result == NULL) 350 return B_BAD_VALUE; 351 352 // Read the type 353 BNode node; 354 status_t error = node.SetTo(ref); 355 if (error == B_OK) { 356 ssize_t bytes = node.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 357 0, result, B_MIME_TYPE_LENGTH - 1); 358 if (bytes < B_OK) 359 error = bytes; 360 else 361 result[bytes] = '\0'; 362 } 363 364 return error; 365 } 366