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