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