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