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