Support multiple MIME DB directories

Each installation location (system, common, common/non-packaged,
~/config, ~/config/non-package) can now have a read-only data/mime_db
directory. ~/config/settings/beos_mime is now named mime_db as well. The
contents of all directories makes up the MIME DB. Entries in more
specific locations shadow entries in more general locations. Only the
directory in ~/config/settings is where the registrar writes changes to.

The new layout allows packages to contribute entries to the MIME DB by
simply providing the respective files in data/mime_db. Consequently the
user settings directory is supposed to contain only the things the user
has actually changed.

Seems to work fine as far as tested. A few issues, though:
* The registrar doesn't monitor the directories yet, so it doesn't
  notice entry changes due to package de-/activation.
* ATM it is not possible to remove a MIME type that is not in the user
  settings directory, although the FileTypes GUI suggests that it is.
  We'd have to work with white-outs, since we cannot remove the files in
  the data/mime_db directories. Or, alternatively, the API has to be
  extended and the FileTypes GUI adjusted to disable the "Remove" button
  in such a case.
This commit is contained in:
Ingo Weinhold 2013-05-07 04:43:15 +02:00
parent 38e3973ecf
commit 59a653b51c
10 changed files with 367 additions and 120 deletions

View File

@ -7,12 +7,12 @@
#include <StorageDefs.h>
#include <String.h>
#include <string>
class BNode;
class BMessage;
class BString;
class BStringList;
namespace BPrivate {
@ -20,8 +20,8 @@ namespace Storage {
namespace Mime {
// Database directory
const std::string get_database_directory();
const std::string get_application_database_directory();
const BStringList& get_database_directories();
BString get_writable_database_directory();
// Attribute Prefixes
extern const char *kMiniIconAttrPrefix;
@ -75,7 +75,7 @@ extern const char *kMetaMimeType;
// Error codes (to be used only by BPrivate::Storage::Mime members)
extern const status_t kMimeGuessFailureError;
std::string type_to_filename(const char *type);
BString type_to_writable_filename(const char *type);
status_t open_type(const char *type, BNode *result);
status_t open_or_create_type(const char *type, BNode *result, bool *didCreate);

View File

@ -4,6 +4,7 @@
*
* Authors:
* Tyler Dauwalder
* Ingo Weinhold <ingo_weinhold@gmx.de>
*/
/*!
@ -14,18 +15,23 @@
#include <AppMisc.h>
#include <DataIO.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <Entry.h>
#include <Message.h>
#include <Node.h>
#include <Path.h>
#include <storage_support.h>
#include <StringList.h>
#include <TypeConstants.h>
#include <fs_attr.h> // For struct attr_info
#include <new> // For new(nothrow)
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <AutoDeleter.h>
#include "mime/database_support.h"
@ -94,49 +100,187 @@ const char *kMetaMimeType = "application/x-vnd.Be-meta-mime";
// Error codes
const status_t kMimeGuessFailureError = B_ERRORS_END+1;
static const directory_which kBaseDirectoryConstants[] = {
B_USER_SETTINGS_DIRECTORY,
B_USER_NONPACKAGED_DATA_DIRECTORY,
B_USER_DATA_DIRECTORY,
B_COMMON_NONPACKAGED_DATA_DIRECTORY,
B_COMMON_DATA_DIRECTORY,
B_SYSTEM_DATA_DIRECTORY
};
static pthread_once_t sDatabaseDirectoryInitOnce = PTHREAD_ONCE_INIT;
static std::string sDatabaseDirectory;
static std::string sApplicationDatabaseDirectory;
static BStringList sDatabaseDirectories;
static void
init_database_directories()
{
BPath path;
if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK)
sDatabaseDirectory = path.Path();
for (size_t i = 0;
i < sizeof(kBaseDirectoryConstants)
/ sizeof(kBaseDirectoryConstants[0]); i++) {
BString directoryPath;
BPath path;
if (find_directory(kBaseDirectoryConstants[i], &path) == B_OK)
directoryPath = path.Path();
else if (i == 0)
directoryPath = "/boot/home/config/settings";
else
continue;
directoryPath += "/mime_db";
sDatabaseDirectories.Add(directoryPath);
}
}
const BStringList&
get_database_directories()
{
pthread_once(&sDatabaseDirectoryInitOnce, &init_database_directories);
return sDatabaseDirectories;
}
BString
get_writable_database_directory()
{
return get_database_directories().StringAt(0);
}
static BString
type_to_filename(const char* type, int32 index)
{
BString path = get_database_directories().StringAt(index);
return path << '/' << BPrivate::Storage::to_lower(type).c_str();
}
/*! Converts the given MIME type to an absolute path in the user writeable MIME
database directory.
*/
BString
type_to_writable_filename(const char* type)
{
return type_to_filename(type, 0);
}
static status_t
open_type(const char* type, BNode* _node, int32& _index)
{
const BStringList& directories = get_database_directories();
int32 count = directories.CountStrings();
for (int32 i = 0; i < count; i++) {
status_t error = _node->SetTo(type_to_filename(type, i));
attr_info attrInfo;
if (error == B_OK && _node->GetAttrInfo(kTypeAttr, &attrInfo) == B_OK) {
_index = i;
return B_OK;
}
}
return B_ENTRY_NOT_FOUND;
}
static status_t
create_type_node(const char* type, BNode& _result)
{
const char* slash = strchr(type, '/');
BString superTypeName;
if (slash != NULL)
superTypeName.SetTo(type, slash - type);
else
sDatabaseDirectory = "/boot/home/config/settings";
superTypeName = type;
superTypeName.ToLower();
sDatabaseDirectory += "/beos_mime";
sApplicationDatabaseDirectory = sDatabaseDirectory + "/application";
// open/create the directory for the supertype
BDirectory parent(get_writable_database_directory());
status_t error = parent.InitCheck();
if (error != B_OK)
return error;
BDirectory superTypeDirectory;
if (BEntry(&parent, superTypeName).Exists())
error = superTypeDirectory.SetTo(&parent, superTypeName);
else
error = parent.CreateDirectory(superTypeName, &superTypeDirectory);
if (error != B_OK)
return error;
// create the subtype
BFile subTypeFile;
if (slash != NULL) {
error = superTypeDirectory.CreateFile(BString(slash + 1).ToLower(),
&subTypeFile);
if (error != B_OK)
return error;
}
// assign the result
if (slash != NULL)
_result = subTypeFile;
else
_result = superTypeDirectory;
return _result.InitCheck();
}
const std::string
get_database_directory()
static status_t
copy_type_node(BNode& source, const char* type, BNode& _target)
{
pthread_once(&sDatabaseDirectoryInitOnce, &init_database_directories);
return sDatabaseDirectory;
status_t error = create_type_node(type, _target);
if (error != B_OK)
return error;
// copy the attributes
MemoryDeleter bufferDeleter;
size_t bufferSize = 0;
source.RewindAttrs();
char attribute[B_ATTR_NAME_LENGTH];
while (source.GetNextAttrName(attribute) == B_OK) {
attr_info info;
error = source.GetAttrInfo(attribute, &info);
if (error != B_OK) {
syslog(LOG_ERR, "Failed to get info for attribute \"%s\" of MIME "
"type \"%s\": %s", attribute, type, strerror(error));
continue;
}
// resize our buffer, if necessary
if (info.size > bufferSize) {
bufferDeleter.SetTo(malloc(info.size));
if (bufferDeleter.Get() == NULL)
return B_NO_MEMORY;
bufferSize = info.size;
}
ssize_t bytesRead = source.ReadAttr(attribute, info.type, 0,
bufferDeleter.Get(), info.size);
if (bytesRead != info.size) {
syslog(LOG_ERR, "Failed to read attribute \"%s\" of MIME "
"type \"%s\": %s", attribute, type,
bytesRead < 0 ? strerror(bytesRead) : "short read");
continue;
}
ssize_t bytesWritten = _target.WriteAttr(attribute, info.type, 0,
bufferDeleter.Get(), info.size);
if (bytesWritten < 0) {
syslog(LOG_ERR, "Failed to write attribute \"%s\" of MIME "
"type \"%s\": %s", attribute, type,
bytesWritten < 0 ? strerror(bytesWritten) : "short write");
continue;
}
}
return B_OK;
}
const std::string
get_application_database_directory()
{
pthread_once(&sDatabaseDirectoryInitOnce, &init_database_directories);
return sApplicationDatabaseDirectory;
}
// type_to_filename
//! Converts the given MIME type to an absolute path in the MIME database.
std::string
type_to_filename(const char *type)
{
return get_database_directory() + "/" + BPrivate::Storage::to_lower(type);
}
// open_type
/*! \brief Opens a BNode on the given type, failing if the type has no
corresponding file in the database.
@ -150,20 +294,8 @@ open_type(const char *type, BNode *result)
if (type == NULL || result == NULL)
return B_BAD_VALUE;
status_t status = result->SetTo(type_to_filename(type).c_str());
// TODO: this can be removed again later - we just didn't write this
// attribute is before at all...
#if 1
if (status == B_OK) {
// check if the MIME:TYPE attribute exist, and create it if not
attr_info info;
if (result->GetAttrInfo(kTypeAttr, &info) != B_OK)
result->WriteAttr(kTypeAttr, B_STRING_TYPE, 0, type, strlen(type) + 1);
}
#endif
return status;
int32 index;
return open_type(type, result, index);
}
// open_or_create_type
@ -180,44 +312,47 @@ open_or_create_type(const char *type, BNode *result, bool *didCreate)
{
if (didCreate)
*didCreate = false;
std::string filename;
std::string typeLower = BPrivate::Storage::to_lower(type);
status_t err = (type && result ? B_OK : B_BAD_VALUE);
if (!err) {
filename = type_to_filename(type);
err = result->SetTo(filename.c_str());
}
if (err == B_ENTRY_NOT_FOUND) {
// Figure out what type of node we need to create
// + Supertype == directory
// + Non-supertype == file
size_t pos = typeLower.find_first_of('/');
if (pos == std::string::npos) {
// Supertype == directory
BDirectory parent(get_database_directory().c_str());
err = parent.InitCheck();
if (!err)
err = parent.CreateDirectory(typeLower.c_str(), NULL);
} else {
// Non-supertype == file
std::string super(typeLower, 0, pos);
std::string sub(typeLower, pos+1);
BDirectory parent((get_database_directory() + "/" + super).c_str());
err = parent.InitCheck();
if (!err)
err = parent.CreateFile(sub.c_str(), NULL);
// See, if the type already exists.
int32 index;
status_t error = open_type(type, result, index);
if (error == B_OK) {
if (index == 0)
return B_OK;
// The caller wants a editable node, but the node found is not in the
// user's settings directory. Copy the node.
BNode nodeToClone(*result);
if (nodeToClone.InitCheck() != B_OK)
return nodeToClone.InitCheck();
error = copy_type_node(nodeToClone, type, *result);
if (error != B_OK) {
result->Unset();
return error;
}
// Now try opening again
if (err == B_OK)
err = result->SetTo(filename.c_str());
if (err == B_OK && didCreate)
if (didCreate != NULL)
*didCreate = true;
if (err == B_OK) {
// write META:TYPE attribute
result->WriteAttr(kTypeAttr, B_STRING_TYPE, 0, type, strlen(type) + 1);
}
return error;
}
return err;
// type doesn't exist yet -- create the respective node
error = create_type_node(type, *result);
if (error != B_OK)
return error;
// write the type attribute
error = result->WriteAttr(kTypeAttr, B_STRING_TYPE, 0, type,
strlen(type) + 1);
if (error != B_OK) {
result->Unset();
return error;
}
if (didCreate != NULL)
*didCreate = true;
return B_OK;
}
// read_mime_attr

View File

@ -43,6 +43,7 @@ Server registrar
AssociatedTypes.cpp
CreateAppMetaMimeThread.cpp
Database.cpp
DatabaseDirectory.cpp
InstalledTypes.cpp
MimeSnifferAddon.cpp
MimeSnifferAddonManager.cpp

View File

@ -8,7 +8,10 @@
*/
#include "AssociatedTypes.h"
#include "MimeSnifferAddonManager.h"
#include <stdio.h>
#include <new>
#include <Directory.h>
#include <Entry.h>
@ -19,8 +22,9 @@
#include <mime/database_support.h>
#include <storage_support.h>
#include <new>
#include <stdio.h>
#include "DatabaseDirectory.h"
#include "MimeSnifferAddonManager.h"
#define DBG(x) x
//#define DBG(x)
@ -330,8 +334,8 @@ AssociatedTypes::BuildAssociatedTypesTable()
fFileExtensions.clear();
fAssociatedTypes.clear();
BDirectory root;
status_t err = root.SetTo(get_database_directory().c_str());
DatabaseDirectory root;
status_t err = root.Init();
if (!err) {
root.Rewind();
while (true) {
@ -354,8 +358,8 @@ AssociatedTypes::BuildAssociatedTypesTable()
// First, iterate through this supertype directory and process
// all of its subtypes
BDirectory dir;
if (dir.SetTo(&entry) == B_OK) {
DatabaseDirectory dir;
if (dir.Init(supertype) == B_OK) {
dir.Rewind();
while (true) {
BEntry subEntry;
@ -392,8 +396,7 @@ AssociatedTypes::BuildAssociatedTypesTable()
}
} else {
DBG(OUT("Mime::AssociatedTypes::BuildAssociatedTypesTable(): "
"Failed opening mime database directory '%s'\n",
get_database_directory().c_str()));
"Failed opening mime database directory\n"));
}
if (!err) {
fHaveDoneFullBuild = true;

View File

@ -65,9 +65,9 @@ Database::Database()
fDeferredInstallNotificationsLocker("deferred install notifications"),
fDeferredInstallNotifications()
{
// Do some really minor error checking
BEntry entry(get_database_directory().c_str());
fStatus = entry.Exists() ? B_OK : B_BAD_VALUE;
// make sure the user's MIME DB directory exists
fStatus = create_directory(get_writable_database_directory(),
S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
}
// destructor
@ -107,7 +107,7 @@ Database::Install(const char *type)
return B_BAD_VALUE;
BEntry entry;
status_t err = entry.SetTo(type_to_filename(type).c_str());
status_t err = entry.SetTo(type_to_writable_filename(type));
if (!err) {
if (entry.Exists())
err = B_FILE_EXISTS;
@ -139,7 +139,7 @@ Database::Delete(const char *type)
// Open the type
BEntry entry;
status_t status = entry.SetTo(type_to_filename(type).c_str());
status_t status = entry.SetTo(type_to_writable_filename(type));
if (status != B_OK)
return status;

View File

@ -0,0 +1,69 @@
/*
* Copyright 2013, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Ingo Weinhold <ingo_weinhold@gmx.de>
*/
#include "DatabaseDirectory.h"
#include <fs_attr.h>
#include <Node.h>
#include <StringList.h>
#include <mime/database_support.h>
DatabaseDirectory::DatabaseDirectory()
:
BMergedDirectory(B_COMPARE)
{
}
DatabaseDirectory::~DatabaseDirectory()
{
}
status_t
DatabaseDirectory::Init(const char* superType)
{
status_t error = BMergedDirectory::Init();
if (error != B_OK)
return error;
const BStringList& directories
= BPrivate::Storage::Mime::get_database_directories();
int32 count = directories.CountStrings();
for (int32 i = 0; i < count; i++) {
BString directory = directories.StringAt(i);
if (superType != NULL)
directory << '/' << superType;
AddDirectory(directory);
}
return B_OK;
}
bool
DatabaseDirectory::ShallPreferFirstEntry(const entry_ref& entry1, int32 index1,
const entry_ref& entry2, int32 index2)
{
return _IsValidMimeTypeEntry(entry1) || !_IsValidMimeTypeEntry(entry2);
}
bool
DatabaseDirectory::_IsValidMimeTypeEntry(const entry_ref& entry)
{
// check whether the MIME:TYPE attribute exists
BNode node;
attr_info info;
return node.SetTo(&entry) == B_OK
&& node.GetAttrInfo(BPrivate::Storage::Mime::kTypeAttr, &info) == B_OK;
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2013, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Ingo Weinhold <ingo_weinhold@gmx.de>
*/
#ifndef DATABASE_DIRECTORY_H
#define DATABASE_DIRECTORY_H
#include <MergedDirectory.h>
class DatabaseDirectory : public BMergedDirectory {
public:
DatabaseDirectory();
virtual ~DatabaseDirectory();
status_t Init(const char* superType = NULL);
protected:
virtual bool ShallPreferFirstEntry(const entry_ref& entry1,
int32 index1, const entry_ref& entry2,
int32 index2);
private:
bool _IsValidMimeTypeEntry(const entry_ref& entry);
};
#endif // DATABASE_DIRECTORY_H

View File

@ -11,8 +11,9 @@
#include "InstalledTypes.h"
#include <mime/database_support.h>
#include <storage_support.h>
#include <stdio.h>
#include <new>
#include <Directory.h>
#include <Entry.h>
@ -20,8 +21,11 @@
#include <MimeType.h>
#include <String.h>
#include <new>
#include <stdio.h>
#include <mime/database_support.h>
#include <storage_support.h>
#include "DatabaseDirectory.h"
#define DBG(x) x
//#define DBG(x)
@ -380,9 +384,9 @@ InstalledTypes::_BuildInstalledTypesList()
err = B_NO_MEMORY;
}
BDirectory root;
DatabaseDirectory root;
if (!err)
err = root.SetTo(get_database_directory().c_str());
err = root.Init();
if (!err) {
root.Rewind();
while (true) {
@ -413,8 +417,8 @@ InstalledTypes::_BuildInstalledTypesList()
// Now iterate through this supertype directory and add
// all of its subtypes
BDirectory dir;
if (dir.SetTo(&entry) == B_OK) {
DatabaseDirectory dir;
if (dir.Init(supertype) == B_OK) {
dir.Rewind();
while (true) {
BEntry subEntry;
@ -453,8 +457,7 @@ InstalledTypes::_BuildInstalledTypesList()
}
} else {
DBG(OUT("Mime::InstalledTypes::BuildInstalledTypesList(): "
"Failed opening mime database directory '%s'\n",
get_database_directory().c_str()));
"Failed opening mime database directory.\n"));
}
fHaveDoneFullBuild = true;
return err;

View File

@ -28,6 +28,7 @@
#include <storage_support.h>
#include <String.h>
#include "DatabaseDirectory.h"
#include "MimeSnifferAddonManager.h"
#define DBG(x) x
@ -332,9 +333,9 @@ SnifferRules::BuildRuleList()
ssize_t maxBytesNeeded = 0;
ssize_t bytesNeeded = 0;
BDirectory root;
DatabaseDirectory root;
status_t err = root.SetTo(get_database_directory().c_str());
status_t err = root.Init();
if (!err) {
root.Rewind();
while (true) {
@ -357,8 +358,8 @@ SnifferRules::BuildRuleList()
// First, iterate through this supertype directory and process
// all of its subtypes
BDirectory dir;
if (dir.SetTo(&entry) == B_OK) {
DatabaseDirectory dir;
if (dir.Init(supertype) == B_OK) {
dir.Rewind();
while (true) {
BEntry subEntry;
@ -399,8 +400,7 @@ SnifferRules::BuildRuleList()
}
} else {
DBG(OUT("Mime::SnifferRules::BuildRuleList(): "
"Failed opening mime database directory '%s'\n",
get_database_directory().c_str()));
"Failed opening mime database directory.\n"));
}
if (!err) {

View File

@ -10,9 +10,11 @@
#include "SupportingApps.h"
#include <mime/database_support.h>
#include <storage_support.h>
#include <stdio.h>
#include <new>
#include <iostream>
#include <Directory.h>
#include <Message.h>
@ -20,9 +22,11 @@
#include <Path.h>
#include <String.h>
#include <new>
#include <stdio.h>
#include <iostream>
#include <mime/database_support.h>
#include <storage_support.h>
#include "DatabaseDirectory.h"
#define DBG(x) x
//#define DBG(x)
@ -262,8 +266,8 @@ SupportingApps::BuildSupportingAppsTable()
fSupportingApps.clear();
fStrandedTypes.clear();
BDirectory dir;
status_t status = dir.SetTo(get_application_database_directory().c_str());
DatabaseDirectory dir;
status_t status = dir.Init("application");
// Build the supporting apps table based on the mime database
if (status == B_OK) {