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