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