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 return B_OK; 264 } 265 266 // Save 267 status_t 268 RecentEntries::Save(FILE* file, const char *description, const char *tag) 269 { 270 status_t error = file ? B_OK : B_BAD_VALUE; 271 if (!error) { 272 fprintf(file, "# %s\n", description); 273 274 /* In order to write our entries out in the format used by the 275 Roster settings file, we need to collect all the signatures 276 for each entry in one place, while at the same time updating 277 the index values for each entry/sig pair to reflect the current 278 ordering of the list. I believe this is the data structure 279 R5 actually maintains all the time, as their indices do not 280 change over time (whereas ours will). If our implementation 281 proves to be slower that R5, we may want to consider using 282 the data structure pervasively. 283 */ 284 std::map<entry_ref, std::list<recent_entry*> > map; 285 uint32 count = fEntryList.size(); 286 287 for (std::list<recent_entry*>::iterator item = fEntryList.begin(); 288 item != fEntryList.end(); 289 count--, item++) 290 { 291 recent_entry *entry = *item; 292 if (entry) { 293 entry->index = count; 294 map[entry->ref].push_back(entry); 295 } else { 296 DBG(OUT("WARNING: RecentEntries::Save(): The entry %ld entries " 297 "from the front of fEntryList was found to be NULL\n", 298 fEntryList.size() - count)); 299 } 300 } 301 302 for (std::map<entry_ref, std::list<recent_entry*> >::iterator mapItem 303 = map.begin(); 304 mapItem != map.end(); 305 mapItem++) 306 { 307 // We're going to need to properly escape the path name we 308 // get, which will at absolute worst double the length of 309 // the string. 310 char path[B_PATH_NAME_LENGTH]; 311 char escapedPath[B_PATH_NAME_LENGTH*2]; 312 status_t outputError = BPrivate::Storage::entry_ref_to_path(&mapItem->first, 313 path, B_PATH_NAME_LENGTH); 314 if (!outputError) { 315 BPrivate::Storage::escape_path(path, escapedPath); 316 fprintf(file, "%s %s", tag, escapedPath); 317 std::list<recent_entry*> &list = mapItem->second; 318 int32 i = 0; 319 for (std::list<recent_entry*>::iterator item = list.begin(); 320 item != list.end(); 321 i++, item++) 322 { 323 recent_entry *entry = *item; 324 if (entry) 325 fprintf(file, " \"%s\" %ld", entry->sig.c_str(), entry->index); 326 else { 327 DBG(OUT("WARNING: RecentEntries::Save(): The entry %ld entries " 328 "from the front of the compiled recent_entry* list for the " 329 "entry ref (%ld, %lld, '%s') was found to be NULL\n", 330 i, mapItem->first.device, mapItem->first.directory, 331 mapItem->first.name)); 332 } 333 } 334 fprintf(file, "\n"); 335 } else { 336 DBG(OUT("WARNING: RecentEntries::Save(): entry_ref_to_path() failed on " 337 "the entry_ref (%ld, %lld, '%s') with error 0x%lx\n", 338 mapItem->first.device, mapItem->first.directory, 339 mapItem->first.name, outputError)); 340 } 341 } 342 fprintf(file, "\n"); 343 } 344 return error; 345 } 346 347 348 // GetTypeForRef 349 /*! \brief Fetches the file type of the given file. 350 351 If the file has no type, an empty string is returned. The file 352 is *not* sniffed. 353 */ 354 status_t 355 RecentEntries::GetTypeForRef(const entry_ref *ref, char *result) 356 { 357 BNode node; 358 status_t error = ref && result ? B_OK : B_BAD_VALUE; 359 // Read the type and force to lowercase 360 if (!error) 361 error = node.SetTo(ref); 362 if (!error) { 363 ssize_t bytes = node.ReadAttr("BEOS:TYPE", B_MIME_STRING_TYPE, 364 0, result, B_MIME_TYPE_LENGTH-1); 365 if (bytes < 0) 366 error = bytes; 367 else 368 result[bytes] = '\0'; 369 } 370 if (!error) 371 BPrivate::Storage::to_lower(result); 372 return error; 373 } 374