diff --git a/headers/private/storage/mime/AssociatedTypes.h b/headers/private/storage/mime/AssociatedTypes.h new file mode 100644 index 0000000000..cb6cd7baf5 --- /dev/null +++ b/headers/private/storage/mime/AssociatedTypes.h @@ -0,0 +1,58 @@ +//---------------------------------------------------------------------- +// This software is part of the OpenBeOS distribution and is covered +// by the OpenBeOS license. +//--------------------------------------------------------------------- +/*! + \file AssociatedTypes.h + AssociatedTypes class declarations +*/ + +#ifndef _MIME_ASSOCIATED_TYPES_H +#define _MIME_ASSOCIATED_TYPES_H + +#include + +#include +#include +#include + +class BMessage; +class BString; + +namespace BPrivate { +namespace Storage { +namespace Mime { + +class AssociatedTypes { +public: + AssociatedTypes(); + ~AssociatedTypes(); + + status_t GetAssociatedTypes(const char *extension, BMessage *types); + status_t GuessMimeType(const char *filename, BString *result); + status_t GuessMimeType(const entry_ref *ref, BString *result); + + status_t SetFileExtensions(const char *type, const BMessage *extensions); + status_t DeleteFileExtensions(const char *type); + + void PrintToStream() const; +private: + status_t AddAssociatedType(const char *extension, const char *type); + status_t RemoveAssociatedType(const char *extension, const char *type); + + status_t BuildAssociatedTypesTable(); + + status_t ProcessType(const char *type); + std::string PrepExtension(const char *extension) const; + + std::map > fFileExtensions; // mime type => set of associated file extensions + std::map > fAssociatedTypes; // file extension => set of associated mime types + + bool fHaveDoneFullBuild; +}; + +} // namespace Mime +} // namespace Storage +} // namespace BPrivate + +#endif // _MIME_ASSOCIATED_TYPES_H diff --git a/src/kits/storage/mime/AssociatedTypes.cpp b/src/kits/storage/mime/AssociatedTypes.cpp new file mode 100644 index 0000000000..2621f8975f --- /dev/null +++ b/src/kits/storage/mime/AssociatedTypes.cpp @@ -0,0 +1,451 @@ +//---------------------------------------------------------------------- +// This software is part of the OpenBeOS distribution and is covered +// by the OpenBeOS license. +//--------------------------------------------------------------------- +/*! + \file AssociatedTypes.cpp + AssociatedTypes class implementation +*/ + +#include "mime/AssociatedTypes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DBG(x) x +//#define DBG(x) +#define OUT printf + +namespace BPrivate { +namespace Storage { +namespace Mime { + +/*! + \class AssociatedTypes + \brief Information about file extensions and their associated types +*/ + +// Constructor +//! Constructs a new AssociatedTypes object +AssociatedTypes::AssociatedTypes() + : fHaveDoneFullBuild(false) +{ +} + +// Destructor +//! Destroys the AssociatedTypes object +AssociatedTypes::~AssociatedTypes() +{ +} + +// GetAssociatedTypes +/*! \brief Returns a list of mime types associated with the given file + extension in the pre-allocated \c BMessage pointed to by \c types. + + See \c BMimeType::GetAssociatedTypes() for more information. +*/ +status_t +AssociatedTypes::GetAssociatedTypes(const char *extension, BMessage *types) +{ + status_t err = extension && types ? B_OK : B_BAD_VALUE; + std::string extStr; + + // See if we need to do our initial build still + if (!err && !fHaveDoneFullBuild) { + err = BuildAssociatedTypesTable(); + } + // Format the extension + if (!err) { + extStr = PrepExtension(extension); + err = extStr.length() > 0 ? B_OK : B_BAD_VALUE; + } + // Build the message + if (!err) { + // Clear the message, as we're just going to add to it + types->MakeEmpty(); + + // Add the types associated with this extension + std::set &assTypes = fAssociatedTypes[extStr]; + std::set::const_iterator i; + for (i = assTypes.begin(); i != assTypes.end() && !err; i++) { + err = types->AddString(kTypesField, i->c_str()); + } + } + return err; +} + +// GuessMimeType +/*! \brief Guesses a MIME type for the given filename based on its extension + + \param filename The filename of interest + \param result Pointer to a pre-allocated \c BString object into which + the result is stored. If the function returns a value other + than \c B_OK, \a result will not be modified. + \return + - \c B_OK: success + - \c other error code: failure +*/ +status_t +AssociatedTypes::GuessMimeType(const char *filename, BString *result) +{ + status_t err = filename && result ? B_OK : B_BAD_VALUE; + if (!err && !fHaveDoneFullBuild) + err = BuildAssociatedTypesTable(); + if (!err) { + // Extract the extension from the file + uint i = strlen(filename); + while (i-1 >= 0 && filename[i-1] != '.') + i--; + + // If there was an extension, grab it and look up its associated + // type(s). Otherwise, the best guess we can offer is + // "application/octect-stream" + if (i > 0) { + std::string extension = PrepExtension(&(filename[i])); + + /*! \todo I'm just grabbing the first item in the set here. Should we perhaps + do something different? + */ + std::set &types = fAssociatedTypes[extension]; + std::set::const_iterator i = types.begin(); + if (i != types.end()) + result->SetTo(i->c_str()); + else + err = kMimeGuessFailureError; + } else { + err = kMimeGuessFailureError; + } + } + return err; +} + +// GuessMimeType +/*! \brief Guesses a MIME type for the given \c entry_ref based on its filename extension + + \param filename The entry_ref of interest + \param result Pointer to a pre-allocated \c BString object into which + the result is stored. If the function returns a value other + than \c B_OK, \a result will not be modified. + \return + - \c B_OK: success + - \c other error code: failure +*/ +status_t +AssociatedTypes::GuessMimeType(const entry_ref *ref, BString *result) +{ + // Convert the entry_ref to a filename and then do the check + /*! \todo If the ref is invalid, */ + char path[B_PATH_NAME_LENGTH]; + status_t err = entry_ref_to_path(ref, path, B_PATH_NAME_LENGTH); + if (!err) + err = GuessMimeType(path, result); + return err; +} + +// SetFileExtensions +/*! \brief Sets the list of file extensions for the given type and + updates the associated types mappings. + + All listed extensions will including the given mime type in + their list of associated types following this call. + + All extensions previously but no longer associated with this + mime type will no longer list this mime type as an associated + type. + + \param app The mime type whose associated file extensions you are setting + \param types Pointer to a \c BMessage containing an array of associated + file extensions in its \c Mime::kExtensionsField field. +*/ +status_t +AssociatedTypes::SetFileExtensions(const char *type, const BMessage *extensions) +{ + status_t err = type && extensions ? B_OK : B_BAD_VALUE; + if (!fHaveDoneFullBuild) + return err; + + std::set oldExtensions; + std::set &newExtensions = fFileExtensions[type]; + // Make a copy of the previous extensions + if (!err) + oldExtensions = newExtensions; + + if (!err) { + // Read through the list of new extensions, creating the new + // file extensions list and adding the type as an associated type + // for each extension + newExtensions.clear(); + const char *extension; + for (int32 i = 0; + extensions->FindString(kTypesField, i, &extension) == B_OK; + i++) + { + newExtensions.insert(extension); + AddAssociatedType(extension, type); + } + + // Remove any extensions that are still associated from the list + // of previously associated extensions + for (std::set::const_iterator i = newExtensions.begin(); + i != newExtensions.end(); + i++) + { + oldExtensions.erase(*i); + } + + // Now remove the type as an associated type for any of its previously + // but no longer associated extensions + for (std::set::const_iterator i = oldExtensions.begin(); + i != oldExtensions.end(); + i++) + { + RemoveAssociatedType(i->c_str(), type); + } + } + return err; +} + +// DeleteFileExtensions +/*! \brief Clears the given types's file extensions list and removes the + types from each of said extensions' associated types list. + \param app The mime type whose file extensions you are clearing +*/ +status_t +AssociatedTypes::DeleteFileExtensions(const char *type) +{ + BMessage extensions; + return SetFileExtensions(type, &extensions); +} + +// PrintToStream +//! Dumps the associated types mapping to standard output +void +AssociatedTypes::PrintToStream() const +{ + printf("\n"); + printf("-----------------\n"); + printf("Associated Types:\n"); + printf("-----------------\n"); + + for (std::map >::const_iterator i = fAssociatedTypes.begin(); + i != fAssociatedTypes.end(); + i++) + { + printf("%s: ", i->first.c_str()); + fflush(stdout); + bool first = true; + for (std::set::const_iterator type = i->second.begin(); + type != i->second.end(); + type++) + { + if (first) + first = false; + else + printf(", "); + printf("%s", type->c_str()); + fflush(stdout); + } + printf("\n"); + } +} + +// AddAssociatedType +/*! \brief Adds the given mime type to the set of associated types + for the given extension. + + \param extension The file extension + \param type The associated mime type + \return + - B_OK: success, even if the type was already in the associated types list + - "error code": failure +*/ +status_t +AssociatedTypes::AddAssociatedType(const char *extension, const char *type) +{ + status_t err = extension && type ? B_OK : B_BAD_VALUE; + std::string extStr; + if (!err) { + extStr = PrepExtension(extension); + err = extStr.length() > 0 ? B_OK : B_BAD_VALUE; + } + if (!err) + fAssociatedTypes[extStr].insert(type); + return err; +} + +// RemoveAssociatedType +/*! \brief Removes the given mime type from the set of associated types + for the given extension. + + \param extension The file extension + \param type The associated mime type + \return + - B_OK: success, even if the type was not found in the associated types list + - "error code": failure +*/ +status_t +AssociatedTypes::RemoveAssociatedType(const char *extension, const char *type) +{ + status_t err = extension && type ? B_OK : B_BAD_VALUE; + std::string extStr; + if (!err) { + extStr = PrepExtension(extension); + err = extStr.length() > 0 ? B_OK : B_BAD_VALUE; + } + if (!err) + fAssociatedTypes[extension].erase(type); + return err; +} + +// BuildAssociatedTypesTable +/*! \brief Crawls the mime database and builds a list of associated types + for every associated file extension. +*/ +status_t +AssociatedTypes::BuildAssociatedTypesTable() +{ + fFileExtensions.clear(); + fAssociatedTypes.clear(); + + BDirectory root; + status_t err = root.SetTo(kDatabaseDir.c_str()); + if (!err) { + root.Rewind(); + while (true) { + BEntry entry; + err = root.GetNextEntry(&entry); + if (err) { + // If we've come to the end of list, it's not an error + if (err == B_ENTRY_NOT_FOUND) + err = B_OK; + break; + } else { + // Check that this entry is both a directory and a valid MIME string + char supertype[B_PATH_NAME_LENGTH]; + if (entry.IsDirectory() + && entry.GetName(supertype) == B_OK + && BMimeType::IsValid(supertype)) + { + // Make sure the supertype string is all lowercase + BPrivate::Storage::to_lower(supertype); + + // First, iterate through this supertype directory and process + // all of its subtypes + BDirectory dir; + if (dir.SetTo(&entry) == B_OK) { + dir.Rewind(); + while (true) { + BEntry subEntry; + err = dir.GetNextEntry(&subEntry); + if (err) { + // If we've come to the end of list, it's not an error + if (err == B_ENTRY_NOT_FOUND) + err = B_OK; + break; + } else { + // Get the subtype's name + char subtype[B_PATH_NAME_LENGTH]; + if (subEntry.GetName(subtype) == B_OK) { + BPrivate::Storage::to_lower(subtype); + + char fulltype[B_PATH_NAME_LENGTH]; + sprintf(fulltype, "%s/%s", supertype, subtype); + + // Process the subtype + ProcessType(fulltype); + } + } + } + } else { + DBG(OUT("Mime::AssociatedTypes::BuildAssociatedTypesTable(): " + "Failed opening supertype directory '%s'\n", + supertype)); + } + + // Second, process the supertype + ProcessType(supertype); + } + } + } + } else { + DBG(OUT("Mime::AssociatedTypes::BuildAssociatedTypesTable(): " + "Failed opening mime database directory '%s'\n", + kDatabaseDir.c_str())); + } + if (!err) { + fHaveDoneFullBuild = true; +// PrintToStream(); + } + else + DBG(OUT("Mime::AssociatedTypes::BuildAssociatedTypesTable() failed, error code == 0x%lx\n", err)); + return err; + +} + +// ProcessType +/*! \brief Handles a portion of the initial associated types table construction for + the given mime type. + + \note To be called by BuildAssociatedTypesTable() *ONLY*. :-) + + \param type The mime type of interest. The mime string is expected to be valid + and lowercase. Both "supertype" and "supertype/subtype" mime types + are allowed. +*/ +status_t +AssociatedTypes::ProcessType(const char *type) +{ + status_t err = type ? B_OK : B_BAD_VALUE; + if (!err) { + // Read in the list of file extension types + BMessage msg; + if (read_mime_attr_message(type, kFileExtensionsAttr, &msg) == B_OK) { + // Iterate through the file extesions, adding them to the list of + // file extensions for the mime type and adding the mime type + // to the list of associated types for each file extension + const char *extension; + std::set &fileExtensions = fFileExtensions[type]; + for (int i = 0; msg.FindString(kExtensionsField, i, &extension) == B_OK; i++) { + std::string extStr = PrepExtension(extension); + if (extStr.length() > 0) { + fileExtensions.insert(extStr); + AddAssociatedType(extStr.c_str(), type); + } + } + } + } + return err; +} + +// PrepExtension +/*! \brief Strips any leading '.' chars from the given extension and + forces all chars to lowercase. +*/ +std::string +AssociatedTypes::PrepExtension(const char *extension) const +{ + if (extension) { + uint i = 0; + while (extension[i] == '.') + i++; + return BPrivate::Storage::to_lower(&(extension[i])); + } else { + return ""; // This shouldn't ever happen, but if it does, an + // empty string is considered an invalid extension + } +} + +} // namespace Mime +} // namespace Storage +} // namespace BPrivate +