1/*
2 * Copyright 2002-2006, Haiku.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Tyler Dauwalder
7 *		Ingo Weinhold, bonefish@users.sf.net
8 *		Axel D��rfler, axeld@pinc-software.de
9 */
10
11
12#include <mime/InstalledTypes.h>
13
14#include <stdio.h>
15
16#include <new>
17
18#include <Directory.h>
19#include <Entry.h>
20#include <Message.h>
21#include <MimeType.h>
22#include <String.h>
23
24#include <mime/database_support.h>
25#include <mime/DatabaseDirectory.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 InstalledTypes
39	\brief Installed types information for the entire database
40*/
41
42//! Constructs a new InstalledTypes object
43InstalledTypes::InstalledTypes(DatabaseLocation* databaseLocation)
44	:
45	fDatabaseLocation(databaseLocation),
46	fCachedMessage(NULL),
47	fCachedSupertypesMessage(NULL),
48	fHaveDoneFullBuild(false)
49{
50}
51
52
53//! Destroys the InstalledTypes object
54InstalledTypes::~InstalledTypes()
55{
56	delete fCachedSupertypesMessage;
57	delete fCachedMessage;
58}
59
60
61/*! \brief Returns a list of all currently installed types in the
62	pre-allocated \c BMessage pointed to by \c types.
63
64	See \c BMimeType::GetInstalledTypes(BMessage*) for more information.
65*/
66status_t
67InstalledTypes::GetInstalledTypes(BMessage *types)
68{
69	status_t err = types ? B_OK : B_BAD_VALUE;
70	// See if we need to do our initial build still
71	if (!err && !fHaveDoneFullBuild)
72		err = _BuildInstalledTypesList();
73
74	// See if we need to fill up a new message
75	if (!err && !fCachedMessage)
76		err = _CreateMessageWithTypes(&fCachedMessage);
77
78	// If we get this far, there a cached message waiting
79	if (!err)
80		*types = *fCachedMessage;
81
82	return err;
83}
84
85
86/*! \brief Returns a list of all currently installed types of the given
87	supertype in the pre-allocated \c BMessage pointed to by \c types.
88
89	See \c BMimeType::GetInstalledTypes(const char*, BMessage*) for more
90	information.
91*/
92status_t
93InstalledTypes::GetInstalledTypes(const char *supertype, BMessage *types)
94{
95	if (supertype == NULL || types == NULL)
96		return B_BAD_VALUE;
97
98	// Verify the supertype is valid *and* is a supertype
99
100	BMimeType mime;
101	BMimeType super;
102	// Make sure the supertype is valid
103	status_t err = mime.SetTo(supertype);
104	// Make sure it's really a supertype
105	if (!err && !mime.IsSupertypeOnly())
106		err = B_BAD_VALUE;
107	// See if we need to do our initial build still
108	if (!err && !fHaveDoneFullBuild)
109		err = _BuildInstalledTypesList();
110
111	// Ask the appropriate supertype for its list
112	if (!err) {
113		std::map<std::string, Supertype>::iterator i = fSupertypes.find(supertype);
114		if (i != fSupertypes.end())
115			err = i->second.GetInstalledSubtypes(types);
116		else
117			err = B_NAME_NOT_FOUND;
118	}
119	return err;
120}
121
122
123/*! \brief Returns a list of all currently installed supertypes in the
124	pre-allocated \c BMessage pointed to by \c types.
125
126	See \c BMimeType::GetInstalledSupertypes() for more information.
127*/
128status_t
129InstalledTypes::GetInstalledSupertypes(BMessage *types)
130{
131	if (types == NULL)
132		return B_BAD_VALUE;
133
134	status_t err = B_OK;
135
136	// See if we need to do our initial build still
137	if (!fHaveDoneFullBuild)
138		err = _BuildInstalledTypesList();
139
140	// See if we need to fill up a new message
141	if (!err && !fCachedSupertypesMessage)
142		err = _CreateMessageWithSupertypes(&fCachedSupertypesMessage);
143
144	// If we get this far, there's a cached message waiting
145	if (!err)
146		*types = *fCachedSupertypesMessage;
147
148	return err;
149}
150
151
152/*! \brief Adds the given type to the appropriate lists of installed types.
153
154	If cached messages exist, the type is simply appended to the end of
155	the current type list.
156*/
157status_t
158InstalledTypes::AddType(const char *type)
159{
160	if (!fHaveDoneFullBuild)
161		return B_OK;
162
163	BMimeType mime(type);
164	if (type == NULL || mime.InitCheck() != B_OK)
165		return B_BAD_VALUE;
166
167	// Find the / in the string, if one exists
168	uint i;
169	size_t len = strlen(type);
170	for (i = 0; i < len; i++) {
171		if (type[i] == '/')
172			break;
173	}
174	if (i == len) {
175		// Supertype only
176		std::map<std::string, Supertype>::iterator i;
177		return _AddSupertype(type, i);
178	}
179
180	// Copy the supertype
181	char super[B_PATH_NAME_LENGTH];
182	strncpy(super, type, i);
183	super[i] = 0;
184
185	// Get a pointer to the subtype
186	const char *sub = &(type[i+1]);
187
188	// Add the subtype (which will add the supertype if necessary)
189	return _AddSubtype(super, sub);
190}
191
192
193/*! \brief Removes the given type from the appropriate installed types lists.
194
195	Any corresponding cached messages are invalidated.
196*/
197status_t
198InstalledTypes::RemoveType(const char *type)
199{
200	if (!fHaveDoneFullBuild)
201		return B_OK;
202
203	BMimeType mime(type);
204	if (type == NULL || mime.InitCheck() != B_OK)
205		return B_BAD_VALUE;
206
207	// Find the / in the string, if one exists
208	uint i;
209	size_t len = strlen(type);
210	for (i = 0; i < len; i++) {
211		if (type[i] == '/')
212			break;
213	}
214	if (i == len) {
215		// Supertype only
216		return _RemoveSupertype(type);
217	}
218
219	// Copy the supertype
220	char super[B_PATH_NAME_LENGTH];
221	strncpy(super, type, i);
222	super[i] = 0;
223
224	// Get a pointer to the subtype
225	const char *sub = &(type[i+1]);
226
227	// Remove the subtype
228	return _RemoveSubtype(super, sub);
229}
230
231
232/*! \brief Adds the given supertype to the supertype map.
233	\return
234	- B_OK: success, even if the supertype already existed in the map
235	- "error code": failure
236*/
237status_t
238InstalledTypes::_AddSupertype(const char *super,
239	std::map<std::string, Supertype>::iterator &i)
240{
241	if (super == NULL)
242		return B_BAD_VALUE;
243
244	status_t err = B_OK;
245
246	i = fSupertypes.find(super);
247	if (i == fSupertypes.end()) {
248		Supertype &supertype = fSupertypes[super];
249		supertype.SetName(super);
250		if (fCachedMessage)
251			err = fCachedMessage->AddString(kTypesField, super);
252		if (!err && fCachedSupertypesMessage)
253			err = fCachedSupertypesMessage->AddString(kSupertypesField, super);
254	}
255
256	return err;
257}
258
259
260/*! \brief Adds the given subtype to the given supertype's lists of installed types.
261
262	If the supertype does not yet exist, it is created.
263
264	\param super The supertype
265	\param sub The subtype (subtype only; no "supertype/subtype" types please)
266	\return
267	- B_OK: success
268	- B_NAME_IN_USE: The subtype already exists in the subtype list
269	- "error code": failure
270*/
271status_t
272InstalledTypes::_AddSubtype(const char *super, const char *sub)
273{
274	if (super == NULL || sub == NULL)
275		return B_BAD_VALUE;
276
277	std::map<std::string, Supertype>::iterator i;
278	status_t err = _AddSupertype(super, i);
279	if (!err)
280		err = _AddSubtype(i->second, sub);
281
282	return err;
283}
284
285
286/*! \brief Adds the given subtype to the given supertype's lists of installed types.
287
288	\param super The supertype object
289	\param sub The subtype (subtype only; no "supertype/subtype" types please)
290	\return
291	- B_OK: success
292	- B_NAME_IN_USE: The subtype already exists in the subtype list
293	- "error code": failure
294*/
295status_t
296InstalledTypes::_AddSubtype(Supertype &super, const char *sub)
297{
298	if (sub == NULL)
299		return B_BAD_VALUE;
300
301	status_t err = super.AddSubtype(sub);
302	if (!err && fCachedMessage) {
303		char type[B_PATH_NAME_LENGTH];
304		sprintf(type, "%s/%s", super.GetName(), sub);
305		err = fCachedMessage->AddString("types", type);
306	}
307	return err;
308}
309
310
311/*! \brief Removes the given supertype and any corresponding subtypes.
312*/
313status_t
314InstalledTypes::_RemoveSupertype(const char *super)
315{
316	if (super == NULL)
317		return B_BAD_VALUE;
318
319	status_t err = fSupertypes.erase(super) == 1 ? B_OK : B_NAME_NOT_FOUND;
320	if (!err)
321		_ClearCachedMessages();
322	return err;
323}
324
325
326/*! \brief Removes the given subtype from the given supertype.
327*/
328status_t
329InstalledTypes::_RemoveSubtype(const char *super, const char *sub)
330{
331	if (super == NULL || sub == NULL)
332		return B_BAD_VALUE;
333
334	status_t err = B_NAME_NOT_FOUND;
335
336	std::map<std::string, Supertype>::iterator i = fSupertypes.find(super);
337	if (i != fSupertypes.end()) {
338		err = i->second.RemoveSubtype(sub);
339		if (!err)
340			_ClearCachedMessages();
341	}
342
343	return err;
344
345}
346
347
348//! Clears any cached messages and empties the supertype map
349void
350InstalledTypes::_Unset()
351{
352	_ClearCachedMessages();
353	fSupertypes.clear();
354}
355
356
357//! Frees any cached messages and sets their pointers to NULL
358void
359InstalledTypes::_ClearCachedMessages()
360{
361	delete fCachedSupertypesMessage;
362	delete fCachedMessage;
363	fCachedSupertypesMessage = NULL;
364	fCachedMessage = NULL;
365}
366
367
368/*! \brief Reads through the database and builds a complete set of installed types lists.
369
370	An initial set of cached messages are also created.
371*/
372status_t
373InstalledTypes::_BuildInstalledTypesList()
374{
375	status_t err = B_OK;
376	_Unset();
377
378	// Create empty "cached messages" so proper messages
379	// will be built up as we add new types
380	try {
381		fCachedMessage = new BMessage();
382		fCachedSupertypesMessage = new BMessage();
383	} catch (std::bad_alloc&) {
384		err = B_NO_MEMORY;
385	}
386
387	DatabaseDirectory root;
388	if (!err)
389		err = root.Init(fDatabaseLocation);
390	if (!err) {
391		root.Rewind();
392		while (true) {
393			BEntry entry;
394			err = root.GetNextEntry(&entry);
395			if (err) {
396				// If we've come to the end of list, it's not an error
397				if (err == B_ENTRY_NOT_FOUND)
398					err = B_OK;
399				break;
400			} else {
401				// Check that this entry is both a directory and a valid MIME string
402				char supertype[B_PATH_NAME_LENGTH];
403				if (entry.IsDirectory()
404				      && entry.GetName(supertype) == B_OK
405				         && BMimeType::IsValid(supertype))
406				{
407					// Make sure our string is all lowercase
408					BPrivate::Storage::to_lower(supertype);
409
410					// Add this supertype
411					std::map<std::string, Supertype>::iterator i;
412					if (_AddSupertype(supertype, i) != B_OK)
413						DBG(OUT("Mime::InstalledTypes::BuildInstalledTypesList()"
414							" -- Error adding supertype '%s': 0x%" B_PRIx32 "\n",
415							supertype, err));
416					Supertype &supertypeRef = fSupertypes[supertype];
417
418					// Now iterate through this supertype directory and add
419					// all of its subtypes
420					DatabaseDirectory dir;
421					if (dir.Init(fDatabaseLocation, supertype) == B_OK) {
422						dir.Rewind();
423						while (true) {
424							BEntry subEntry;
425							err = dir.GetNextEntry(&subEntry);
426							if (err) {
427								// If we've come to the end of list, it's not an error
428								if (err == B_ENTRY_NOT_FOUND)
429									err = B_OK;
430								break;
431							} else {
432								// We need to preserve the case of the type name for
433								// queries, so we can't use the file name directly
434								BString type;
435								int32 subStart;
436								BNode node(&subEntry);
437								if (node.InitCheck() == B_OK
438									&& node.ReadAttrString(kTypeAttr, &type) >= B_OK
439									&& (subStart = type.FindFirst('/')) > 0) {
440									// Add the subtype
441									if (_AddSubtype(supertypeRef, type.String()
442											+ subStart + 1) != B_OK) {
443										DBG(OUT("Mime::InstalledTypes::BuildInstalledTypesList()"
444											" -- Error adding subtype '%s/%s': 0x%" B_PRIx32 "\n",
445											supertype, type.String() + subStart + 1, err));
446									}
447								}
448							}
449						}
450					} else {
451						DBG(OUT("Mime::InstalledTypes::BuildInstalledTypesList(): "
452						          "Failed opening supertype directory '%s'\n",
453						            supertype));
454					}
455				}
456			}
457		}
458	} else {
459		DBG(OUT("Mime::InstalledTypes::BuildInstalledTypesList(): "
460		          "Failed opening mime database directory.\n"));
461	}
462	fHaveDoneFullBuild = true;
463	return err;
464
465}
466
467
468/*! \brief Allocates a new BMessage into the BMessage pointer pointed to by \c result
469	and fills it with a complete list of installed types.
470*/
471status_t
472InstalledTypes::_CreateMessageWithTypes(BMessage **_result) const
473{
474	if (_result == NULL)
475		return B_BAD_VALUE;
476
477	status_t err = B_OK;
478
479	// Alloc the message
480	try {
481		*_result = new BMessage();
482	} catch (std::bad_alloc&) {
483		err = B_NO_MEMORY;
484	}
485
486	// Fill with types
487	if (!err) {
488		BMessage &msg = **_result;
489		std::map<std::string, Supertype>::const_iterator i;
490		for (i = fSupertypes.begin(); i != fSupertypes.end() && !err; i++) {
491			err = msg.AddString(kTypesField, i->first.c_str());
492			if (!err)
493				err = i->second.FillMessageWithTypes(msg);
494		}
495	}
496	return err;
497}
498
499
500/*! \brief Allocates a new BMessage into the BMessage pointer pointed to by \c result
501	and fills it with a complete list of installed supertypes.
502*/
503status_t
504InstalledTypes::_CreateMessageWithSupertypes(BMessage **_result) const
505{
506	if (_result == NULL)
507		return B_BAD_VALUE;
508
509	status_t err = B_OK;
510
511	// Alloc the message
512	try {
513		*_result = new BMessage();
514	} catch (std::bad_alloc&) {
515		err = B_NO_MEMORY;
516	}
517
518	// Fill with types
519	if (!err) {
520		BMessage &msg = **_result;
521		std::map<std::string, Supertype>::const_iterator i;
522		for (i = fSupertypes.begin(); i != fSupertypes.end() && !err; i++) {
523			err = msg.AddString(kSupertypesField, i->first.c_str());
524		}
525	}
526	return err;
527}
528
529} // namespace Mime
530} // namespace Storage
531} // namespace BPrivate
532
533