1//---------------------------------------------------------------------- 2// This software is part of the Haiku distribution and is covered 3// by the MIT License. 4//--------------------------------------------------------------------- 5/*! 6 \file AssociatedTypes.cpp 7 AssociatedTypes class implementation 8*/ 9 10#include <mime/AssociatedTypes.h> 11 12#include <stdio.h> 13 14#include <new> 15 16#include <Directory.h> 17#include <Entry.h> 18#include <Message.h> 19#include <mime/database_support.h> 20#include <mime/DatabaseDirectory.h> 21#include <mime/DatabaseLocation.h> 22#include <mime/MimeSniffer.h> 23#include <MimeType.h> 24#include <Path.h> 25#include <String.h> 26#include <storage_support.h> 27 28 29#define DBG(x) x 30//#define DBG(x) 31#define OUT printf 32 33namespace BPrivate { 34namespace Storage { 35namespace Mime { 36 37/*! 38 \class AssociatedTypes 39 \brief Information about file extensions and their associated types 40*/ 41 42// Constructor 43//! Constructs a new AssociatedTypes object 44AssociatedTypes::AssociatedTypes(DatabaseLocation* databaseLocation, 45 MimeSniffer* mimeSniffer) 46 : 47 fDatabaseLocation(databaseLocation), 48 fMimeSniffer(mimeSniffer), 49 fHaveDoneFullBuild(false) 50{ 51} 52 53// Destructor 54//! Destroys the AssociatedTypes object 55AssociatedTypes::~AssociatedTypes() 56{ 57} 58 59// GetAssociatedTypes 60/*! \brief Returns a list of mime types associated with the given file 61 extension in the pre-allocated \c BMessage pointed to by \c types. 62 63 See \c BMimeType::GetAssociatedTypes() for more information. 64*/ 65status_t 66AssociatedTypes::GetAssociatedTypes(const char *extension, BMessage *types) 67{ 68 status_t err = extension && types ? B_OK : B_BAD_VALUE; 69 std::string extStr; 70 71 // See if we need to do our initial build still 72 if (!err && !fHaveDoneFullBuild) { 73 err = BuildAssociatedTypesTable(); 74 } 75 // Format the extension 76 if (!err) { 77 extStr = PrepExtension(extension); 78 err = extStr.length() > 0 ? B_OK : B_BAD_VALUE; 79 } 80 // Build the message 81 if (!err) { 82 // Clear the message, as we're just going to add to it 83 types->MakeEmpty(); 84 85 // Add the types associated with this extension 86 std::set<std::string> &assTypes = fAssociatedTypes[extStr]; 87 std::set<std::string>::const_iterator i; 88 for (i = assTypes.begin(); i != assTypes.end() && !err; i++) { 89 err = types->AddString(kTypesField, i->c_str()); 90 } 91 } 92 return err; 93} 94 95// GuessMimeType 96/*! \brief Guesses a MIME type for the given filename based on its extension 97 98 \param filename The filename of interest 99 \param result Pointer to a pre-allocated \c BString object into which 100 the result is stored. If the function returns a value other 101 than \c B_OK, \a result will not be modified. 102 \return 103 - \c B_OK: success 104 - \c other error code: failure 105*/ 106status_t 107AssociatedTypes::GuessMimeType(const char *filename, BString *result) 108{ 109 status_t err = filename && result ? B_OK : B_BAD_VALUE; 110 if (!err && !fHaveDoneFullBuild) 111 err = BuildAssociatedTypesTable(); 112 113 // if we have a mime sniffer, let's give it a shot first 114 if (!err && fMimeSniffer != NULL) { 115 BMimeType mimeType; 116 float priority = fMimeSniffer->GuessMimeType(filename, &mimeType); 117 if (priority >= 0) { 118 *result = mimeType.Type(); 119 return B_OK; 120 } 121 } 122 123 if (!err) { 124 // Extract the extension from the file 125 const char *rawExtension = strrchr(filename, '.'); 126 127 // If there was an extension, grab it and look up its associated 128 // type(s). Otherwise, the best guess we can offer is 129 // "application/octect-stream" 130 if (rawExtension && rawExtension[1] != '\0') { 131 std::string extension = PrepExtension(rawExtension + 1); 132 133 /*! \todo I'm just grabbing the first item in the set here. Should we perhaps 134 do something different? 135 */ 136 std::set<std::string> &types = fAssociatedTypes[extension]; 137 std::set<std::string>::const_iterator i = types.begin(); 138 if (i != types.end()) 139 result->SetTo(i->c_str()); 140 else 141 err = kMimeGuessFailureError; 142 } else { 143 err = kMimeGuessFailureError; 144 } 145 } 146 return err; 147} 148 149// GuessMimeType 150/*! \brief Guesses a MIME type for the given \c entry_ref based on its filename extension 151 152 \param filename The entry_ref of interest 153 \param result Pointer to a pre-allocated \c BString object into which 154 the result is stored. If the function returns a value other 155 than \c B_OK, \a result will not be modified. 156 \return 157 - \c B_OK: success 158 - \c other error code: failure 159*/ 160status_t 161AssociatedTypes::GuessMimeType(const entry_ref *ref, BString *result) 162{ 163 // Convert the entry_ref to a filename and then do the check 164 if (!ref) 165 return B_BAD_VALUE; 166 BPath path; 167 status_t err = path.SetTo(ref); 168 if (!err) 169 err = GuessMimeType(path.Path(), result); 170 return err; 171} 172 173// SetFileExtensions 174/*! \brief Sets the list of file extensions for the given type and 175 updates the associated types mappings. 176 177 All listed extensions will including the given mime type in 178 their list of associated types following this call. 179 180 All extensions previously but no longer associated with this 181 mime type will no longer list this mime type as an associated 182 type. 183 184 \param app The mime type whose associated file extensions you are setting 185 \param types Pointer to a \c BMessage containing an array of associated 186 file extensions in its \c Mime::kExtensionsField field. 187*/ 188status_t 189AssociatedTypes::SetFileExtensions(const char *type, const BMessage *extensions) 190{ 191 status_t err = type && extensions ? B_OK : B_BAD_VALUE; 192 if (!fHaveDoneFullBuild) 193 return err; 194 195 std::set<std::string> oldExtensions; 196 std::set<std::string> &newExtensions = fFileExtensions[type]; 197 // Make a copy of the previous extensions 198 if (!err) { 199 oldExtensions = newExtensions; 200 201 // Read through the list of new extensions, creating the new 202 // file extensions list and adding the type as an associated type 203 // for each extension 204 newExtensions.clear(); 205 const char *extension; 206 for (int32 i = 0; 207 extensions->FindString(kTypesField, i, &extension) == B_OK; 208 i++) 209 { 210 newExtensions.insert(extension); 211 AddAssociatedType(extension, type); 212 } 213 214 // Remove any extensions that are still associated from the list 215 // of previously associated extensions 216 for (std::set<std::string>::const_iterator i = newExtensions.begin(); 217 i != newExtensions.end(); 218 i++) 219 { 220 oldExtensions.erase(*i); 221 } 222 223 // Now remove the type as an associated type for any of its previously 224 // but no longer associated extensions 225 for (std::set<std::string>::const_iterator i = oldExtensions.begin(); 226 i != oldExtensions.end(); 227 i++) 228 { 229 RemoveAssociatedType(i->c_str(), type); 230 } 231 } 232 return err; 233} 234 235// DeleteFileExtensions 236/*! \brief Clears the given types's file extensions list and removes the 237 types from each of said extensions' associated types list. 238 \param app The mime type whose file extensions you are clearing 239*/ 240status_t 241AssociatedTypes::DeleteFileExtensions(const char *type) 242{ 243 BMessage extensions; 244 return SetFileExtensions(type, &extensions); 245} 246 247// PrintToStream 248//! Dumps the associated types mapping to standard output 249void 250AssociatedTypes::PrintToStream() const 251{ 252 printf("\n"); 253 printf("-----------------\n"); 254 printf("Associated Types:\n"); 255 printf("-----------------\n"); 256 257 for (std::map<std::string, std::set<std::string> >::const_iterator i = fAssociatedTypes.begin(); 258 i != fAssociatedTypes.end(); 259 i++) 260 { 261 printf("%s: ", i->first.c_str()); 262 fflush(stdout); 263 bool first = true; 264 for (std::set<std::string>::const_iterator type = i->second.begin(); 265 type != i->second.end(); 266 type++) 267 { 268 if (first) 269 first = false; 270 else 271 printf(", "); 272 printf("%s", type->c_str()); 273 fflush(stdout); 274 } 275 printf("\n"); 276 } 277} 278 279// AddAssociatedType 280/*! \brief Adds the given mime type to the set of associated types 281 for the given extension. 282 283 \param extension The file extension 284 \param type The associated mime type 285 \return 286 - B_OK: success, even if the type was already in the associated types list 287 - "error code": failure 288*/ 289status_t 290AssociatedTypes::AddAssociatedType(const char *extension, const char *type) 291{ 292 status_t err = extension && type ? B_OK : B_BAD_VALUE; 293 std::string extStr; 294 if (!err) { 295 extStr = PrepExtension(extension); 296 err = extStr.length() > 0 ? B_OK : B_BAD_VALUE; 297 } 298 if (!err) 299 fAssociatedTypes[extStr].insert(type); 300 return err; 301} 302 303// RemoveAssociatedType 304/*! \brief Removes the given mime type from the set of associated types 305 for the given extension. 306 307 \param extension The file extension 308 \param type The associated mime type 309 \return 310 - B_OK: success, even if the type was not found in the associated types list 311 - "error code": failure 312*/ 313status_t 314AssociatedTypes::RemoveAssociatedType(const char *extension, const char *type) 315{ 316 status_t err = extension && type ? B_OK : B_BAD_VALUE; 317 std::string extStr; 318 if (!err) { 319 extStr = PrepExtension(extension); 320 err = extStr.length() > 0 ? B_OK : B_BAD_VALUE; 321 } 322 if (!err) 323 fAssociatedTypes[extension].erase(type); 324 return err; 325} 326 327// BuildAssociatedTypesTable 328/*! \brief Crawls the mime database and builds a list of associated types 329 for every associated file extension. 330*/ 331status_t 332AssociatedTypes::BuildAssociatedTypesTable() 333{ 334 fFileExtensions.clear(); 335 fAssociatedTypes.clear(); 336 337 DatabaseDirectory root; 338 status_t err = root.Init(fDatabaseLocation); 339 if (!err) { 340 root.Rewind(); 341 while (true) { 342 BEntry entry; 343 err = root.GetNextEntry(&entry); 344 if (err) { 345 // If we've come to the end of list, it's not an error 346 if (err == B_ENTRY_NOT_FOUND) 347 err = B_OK; 348 break; 349 } else { 350 // Check that this entry is both a directory and a valid MIME string 351 char supertype[B_PATH_NAME_LENGTH]; 352 if (entry.IsDirectory() 353 && entry.GetName(supertype) == B_OK 354 && BMimeType::IsValid(supertype)) 355 { 356 // Make sure the supertype string is all lowercase 357 BPrivate::Storage::to_lower(supertype); 358 359 // First, iterate through this supertype directory and process 360 // all of its subtypes 361 DatabaseDirectory dir; 362 if (dir.Init(fDatabaseLocation, supertype) == B_OK) { 363 dir.Rewind(); 364 while (true) { 365 BEntry subEntry; 366 err = dir.GetNextEntry(&subEntry); 367 if (err) { 368 // If we've come to the end of list, it's not an error 369 if (err == B_ENTRY_NOT_FOUND) 370 err = B_OK; 371 break; 372 } else { 373 // Get the subtype's name 374 char subtype[B_PATH_NAME_LENGTH]; 375 if (subEntry.GetName(subtype) == B_OK) { 376 BPrivate::Storage::to_lower(subtype); 377 378 char fulltype[B_PATH_NAME_LENGTH]; 379 snprintf(fulltype, B_PATH_NAME_LENGTH, "%s/%s", 380 supertype, subtype); 381 382 // Process the subtype 383 ProcessType(fulltype); 384 } 385 } 386 } 387 } else { 388 DBG(OUT("Mime::AssociatedTypes::BuildAssociatedTypesTable(): " 389 "Failed opening supertype directory '%s'\n", 390 supertype)); 391 } 392 393 // Second, process the supertype 394 ProcessType(supertype); 395 } 396 } 397 } 398 } else { 399 DBG(OUT("Mime::AssociatedTypes::BuildAssociatedTypesTable(): " 400 "Failed opening mime database directory\n")); 401 } 402 if (!err) { 403 fHaveDoneFullBuild = true; 404// PrintToStream(); 405 } else { 406 DBG(OUT("Mime::AssociatedTypes::BuildAssociatedTypesTable() failed, " 407 "error code == 0x%" B_PRIx32 "\n", err)); 408 } 409 return err; 410 411} 412 413// ProcessType 414/*! \brief Handles a portion of the initial associated types table construction for 415 the given mime type. 416 417 \note To be called by BuildAssociatedTypesTable() *ONLY*. :-) 418 419 \param type The mime type of interest. The mime string is expected to be valid 420 and lowercase. Both "supertype" and "supertype/subtype" mime types 421 are allowed. 422*/ 423status_t 424AssociatedTypes::ProcessType(const char *type) 425{ 426 status_t err = type ? B_OK : B_BAD_VALUE; 427 if (!err) { 428 // Read in the list of file extension types 429 BMessage msg; 430 if (fDatabaseLocation->ReadMessageAttribute(type, kFileExtensionsAttr, 431 msg) == B_OK) { 432 // Iterate through the file extesions, adding them to the list of 433 // file extensions for the mime type and adding the mime type 434 // to the list of associated types for each file extension 435 const char *extension; 436 std::set<std::string> &fileExtensions = fFileExtensions[type]; 437 for (int i = 0; msg.FindString(kExtensionsField, i, &extension) == B_OK; i++) { 438 std::string extStr = PrepExtension(extension); 439 if (extStr.length() > 0) { 440 fileExtensions.insert(extStr); 441 AddAssociatedType(extStr.c_str(), type); 442 } 443 } 444 } 445 } 446 return err; 447} 448 449// PrepExtension 450/*! \brief Strips any leading '.' chars from the given extension and 451 forces all chars to lowercase. 452*/ 453std::string 454AssociatedTypes::PrepExtension(const char *extension) const 455{ 456 if (extension) { 457 uint i = 0; 458 while (extension[i] == '.') 459 i++; 460 return BPrivate::Storage::to_lower(&(extension[i])); 461 } else { 462 return ""; // This shouldn't ever happen, but if it does, an 463 // empty string is considered an invalid extension 464 } 465} 466 467} // namespace Mime 468} // namespace Storage 469} // namespace BPrivate 470 471