1/*
2 * Copyright 2002-2024, Haiku, Inc. All rights reserved.
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/SupportingApps.h>
13
14#include <stdio.h>
15
16#include <new>
17
18#include <Directory.h>
19#include <Message.h>
20#include <MimeType.h>
21#include <Path.h>
22#include <String.h>
23
24#include <mime/database_support.h>
25#include <mime/DatabaseDirectory.h>
26#include <mime/DatabaseLocation.h>
27#include <storage_support.h>
28
29
30#define DBG(x) x
31//#define DBG(x)
32#define OUT printf
33
34namespace BPrivate {
35namespace Storage {
36namespace Mime {
37
38
39/*!
40	\class SupportingApps
41	\brief Supporting apps information for the entire MIME database
42*/
43
44
45SupportingApps::SupportingApps(DatabaseLocation* databaseLocation)
46	:
47	fDatabaseLocation(databaseLocation),
48	fHaveDoneFullBuild(false)
49{
50}
51
52
53SupportingApps::~SupportingApps()
54{
55}
56
57
58/*! \brief Returns a list of signatures of supporting applications for the
59	given type in the pre-allocated \c BMessage pointed to by \c apps.
60
61	See \c BMimeType::GetSupportingApps() for more information.
62*/
63status_t
64SupportingApps::GetSupportingApps(const char *type, BMessage *apps)
65{
66	if (type == NULL || apps == NULL)
67		return B_BAD_VALUE;
68
69	// See if we need to do our initial build still
70	if (!fHaveDoneFullBuild) {
71		status_t status = BuildSupportingAppsTable();
72		if (status != B_OK)
73			return status;
74	}
75
76	// Clear the message, as we're just going to add to it
77	apps->MakeEmpty();
78
79	BString typeString(type);
80	typeString.ToLower();
81
82	BMimeType mime(type);
83	status_t status = mime.InitCheck();
84	if (status != B_OK)
85		return status;
86
87	if (mime.IsSupertypeOnly()) {
88		// Add the apps that support this supertype (plus their count)
89		std::set<std::string> &superApps = fSupportingApps[typeString.String()];
90		int32 count = 0;
91		std::set<std::string>::const_iterator i;
92		for (i = superApps.begin(); i != superApps.end() && status == B_OK; i++) {
93			status = apps->AddString(kApplicationsField, (*i).c_str());
94			count++;
95		}
96		if (status == B_OK)
97			status = apps->AddInt32(kSupportingAppsSuperCountField, count);
98	} else {
99		// Add the apps that support this subtype (plus their count)
100		std::set<std::string> &subApps = fSupportingApps[typeString.String()];
101		int32 count = 0;
102		std::set<std::string>::const_iterator i;
103		for (i = subApps.begin(); i != subApps.end() && status == B_OK; i++) {
104			status = apps->AddString(kApplicationsField, (*i).c_str());
105			count++;
106		}
107		if (status == B_OK)
108			status = apps->AddInt32(kSupportingAppsSubCountField, count);
109
110		// Now add any apps that support the supertype, but not the
111		// subtype (plus their count).
112		BMimeType superMime;
113		status = mime.GetSupertype(&superMime);
114		if (status == B_OK)
115			status = superMime.InitCheck();
116		if (status == B_OK) {
117			std::set<std::string> &superApps = fSupportingApps[superMime.Type()];
118			count = 0;
119			for (i = superApps.begin(); i != superApps.end() && status == B_OK; i++) {
120				if (subApps.find(*i) == subApps.end()) {
121					status = apps->AddString(kApplicationsField, (*i).c_str());
122					count++;
123				}
124			}
125			if (status == B_OK)
126				status = apps->AddInt32(kSupportingAppsSuperCountField, count);
127		}
128	}
129
130	return status;
131}
132
133
134/*! \brief Sets the list of supported types for the given application and
135	updates the supporting apps mappings.
136
137	All types listed as being supported types will including the given
138	app signature in their list of supporting apps following this call.
139
140	If \a fullSync is true, all types previously but no longer supported
141	by this application with no longer list this application as a
142	supporting app.
143
144	If \a fullSync is false, said previously supported types will be
145	saved to a "stranded types" mapping and appropriately synchronized
146	the next time SetSupportedTypes() is called with a \c true \a fullSync
147	parameter.
148
149	The stranded types mapping is properly maintained even in the event
150	of types being removed and then re-added to the list of supporting
151	types with consecutive \c false \a fullSync parameters.
152
153	\param app The application whose supported types you are setting
154	\param types Pointer to a \c BMessage containing an array of supported
155	             mime types in its \c Mime::kTypesField field.
156	\param fullSync If \c true, \c app is removed as a supporting application
157	                for any types for which it is no longer a supporting application
158	                (including types which were removed as supporting types with
159	                previous callsto SetSupportedTypes(..., false)). If \c false,
160	                said mappings are not updated until the next SetSupportedTypes(..., true)
161	                call.
162*/
163status_t
164SupportingApps::SetSupportedTypes(const char *app, const BMessage *types, bool fullSync)
165{
166	if (app == NULL || types == NULL)
167		return B_BAD_VALUE;
168
169	if (!fHaveDoneFullBuild)
170		return B_OK;
171
172	std::set<std::string> oldTypes;
173	std::set<std::string> &newTypes = fSupportedTypes[app];
174	std::set<std::string> &strandedTypes = fStrandedTypes[app];
175
176	// Make a copy of the previous types if we're doing a full sync
177	oldTypes = newTypes;
178
179	// Read through the list of new supported types, creating the new
180	// supported types list and adding the app as a supporting app for
181	// each type.
182	newTypes.clear();
183	BString type;
184	for (int32 i = 0; types->FindString(kTypesField, i, &type) == B_OK; i++) {
185		type.ToLower();
186		newTypes.insert(type.String());
187		AddSupportingApp(type.String(), app);
188	}
189
190	// Update the list of stranded types by removing any types that are newly
191	// re-supported and adding any types that are newly un-supported
192	for (std::set<std::string>::const_iterator i = newTypes.begin();
193			i != newTypes.end(); i++) {
194		strandedTypes.erase(*i);
195	}
196	for (std::set<std::string>::const_iterator i = oldTypes.begin();
197			i != oldTypes.end(); i++) {
198		if (newTypes.find(*i) == newTypes.end())
199			strandedTypes.insert(*i);
200	}
201
202	// Now, if we're doing a full sync, remove the app as a supporting
203	// app for any of its stranded types and then clear said list of
204	// stranded types.
205	if (fullSync) {
206		for (std::set<std::string>::const_iterator i = strandedTypes.begin();
207				i != strandedTypes.end(); i++) {
208			RemoveSupportingApp((*i).c_str(), app);
209		}
210		strandedTypes.clear();
211	}
212
213	return B_OK;
214}
215
216
217/*! \brief Clears the given application's supported types list and optionally
218	removes the application from each of said types' supporting apps list.
219	\param app The application whose supported types you are clearing
220	\param fullSync See SupportingApps::SetSupportedTypes()
221*/
222status_t
223SupportingApps::DeleteSupportedTypes(const char *app, bool fullSync)
224{
225	BMessage types;
226	return SetSupportedTypes(app, &types, fullSync);
227}
228
229
230/*! \brief Adds the given application signature to the set of supporting
231	apps for the given type.
232
233	\param type The full mime type
234	\param app The full application signature (i.e. "application/app-subtype")
235	\return
236	- B_OK: success, even if the app was already in the supporting apps list
237	- "error code": failure
238*/
239status_t
240SupportingApps::AddSupportingApp(const char *type, const char *app)
241{
242	if (type == NULL || app == NULL)
243		return B_BAD_VALUE;
244
245	fSupportingApps[type].insert(app);
246	return B_OK;
247}
248
249
250/*! \brief Removes the given application signature from the set of supporting
251	apps for the given type.
252
253	\param type The full mime type
254	\param app The full application signature (i.e. "application/app-subtype")
255	\return
256	- B_OK: success, even if the app was not found in the supporting apps list
257	- "error code": failure
258*/
259status_t
260SupportingApps::RemoveSupportingApp(const char *type, const char *app)
261{
262	if (type == NULL || app == NULL)
263		return B_BAD_VALUE;
264
265	fSupportingApps[type].erase(app);
266	return B_OK;
267}
268
269
270/*! \brief Crawls the mime database and builds a list of supporting application
271	signatures for every supported type.
272*/
273status_t
274SupportingApps::BuildSupportingAppsTable()
275{
276	fSupportedTypes.clear();
277	fSupportingApps.clear();
278	fStrandedTypes.clear();
279
280	DatabaseDirectory dir;
281	status_t status = dir.Init(fDatabaseLocation, "application");
282	if (status != B_OK)
283		return status;
284
285	// Build the supporting apps table based on the mime database
286	dir.Rewind();
287
288	// Handle each application type
289	while (true) {
290		entry_ref ref;
291		status = dir.GetNextRef(&ref);
292		if (status < B_OK) {
293			// If we've come to the end of list, it's not an error
294			if (status == B_ENTRY_NOT_FOUND)
295				status = B_OK;
296			break;
297		}
298
299		// read application signature from file
300		BString appSignature;
301		BNode node(&ref);
302		if (node.InitCheck() == B_OK && node.ReadAttrString(kTypeAttr,
303				&appSignature) >= B_OK) {
304			// Read in the list of supported types
305			BMessage msg;
306			if (fDatabaseLocation->ReadMessageAttribute(appSignature,
307					kSupportedTypesAttr, msg) == B_OK) {
308				// Iterate through the supported types, adding them to the list of
309				// supported types for the application and adding the application's
310				// signature to the list of supporting apps for each type
311				BString type;
312				std::set<std::string> &supportedTypes = fSupportedTypes[appSignature.String()];
313				for (int i = 0; msg.FindString(kTypesField, i, &type) == B_OK; i++) {
314					type.ToLower();
315						// MIME types are case insensitive, so we lowercase everything
316					supportedTypes.insert(type.String());
317					AddSupportingApp(type.String(), appSignature.String());
318				}
319			}
320		}
321	}
322
323	if (status == B_OK)
324		fHaveDoneFullBuild = true;
325	else
326		DBG(OUT("SupportingApps::BuildSupportingAppsTable() failed: %s\n", strerror(status)));
327
328	return status;
329}
330
331
332} // namespace Mime
333} // namespace Storage
334} // namespace BPrivate
335