root/src/bin/setmime.cpp
/*
 * Copyright 2011 Aleksas Pantechovskis, <alexp.frl@gmail.com>
 * Copyright 2011 Siarzhuk Zharski, <imker@gmx.li>
 * All rights reserved. Distributed under the terms of the MIT License.
 */

#include <iomanip>
#include <iostream>
#include <map>
#include <math.h>
#include <set>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <vector>

#include <Application.h>
#include <Bitmap.h>
#include <Entry.h>
#include <InterfaceDefs.h>
#include <Message.h>
#include <Mime.h>
#include <Path.h>
#include <String.h>


using namespace std;


const char* kUsageMessage = "# setmime:\n"
        "# usage: setmime ((-dump | -dumpSniffRule | -dumpIcon | -dumpAll) "
                                                                                                        "[ <signatureString> ] )\n"
        "#      | (-remove <signatureString> )\n"
        "#      | ( (-set | -force | -add)  <signatureString>\n"
        "#          [ -short <short description> ] [ -long <long description> ]\n"
        "#          [ -preferredApp <preferred app path> ]\n"
        "#          [ -preferredAppSig <preferred app signature> ]\n"
        "#          [ -sniffRule <sniffRule> ]\n"
        "#          [ -extension <file suffix> ]\n"
        "#          [ -attribute <internal name>\n"
        "#             [ -attrName <public name> ] [ -attrType <type code> ]\n"
        "#             [ -attrWidth <display width> ][ -attrAlignment <position>]\n"
        "#             [ -attrViewable <bool flag> ][ -attrEditable <bool flag> ]\n"
        "#             [ -attrExtra <bool flag> ] ]\n"
        "#          [ -miniIcon <256 hex bytes> ]\n"
        "#          [ -largeIcon <1024 hex bytes> ]\n"
        "#          [ -vectorIcon <icon hex bytes> ] ... )\n"
        "#      | (-checkSniffRule <sniffRule>\n"
        "#      | -includeApps)\n";

const char* kHelpMessage = "#  -dump prints a specified metamime\n"
        "#  -remove removes specified metamime\n"
        "#  -add adds specified metamime and specified metamime attributes\n"
        "#      that have not yet been defined\n"
        "#  -set adds specified metamime and specified metamime attributes, \n"
        "#      overwrites the existing values of specified metamime attributes\n"
        "#  -force adds specified metamime and specified metamime attributes\n"
        "#      after first erasing all the existing attributes\n"
        "#  -dumpSniffRule prints just the MIME sniffer rule of a "
                                                                                                                "specified metamime\n"
        "#     -dumpIcon prints just the icon information of a specified metamime\n"
        "#  -dumpAll prints all the information, including icons of a "
                                                                                                                "specified metamime\n"
        "#  -checkSniffRule parses a MIME sniffer rule and reports any errors\n"
        "#  -includeApps will include applications\n";

const char* kNeedArgMessage =   "you have to specify any of "
                                                                "-dump[All|Icon|SnifferRule], -add, -set, "
                                                                "-force or -remove";

const char* kWrongModeMessage = "can only specify one of -dump, -dumpAll, "
                                                                "-dumpIcon, -dumpSnifferRule, -remove, "
                                                                "-add, -set, -force or -checkSnifferRule";

const char* kHelpReq            =       "--help";
const char* kDump                       =       "-dump";
const char* kDumpSniffRule      =       "-dumpSniffRule";
const char* kDumpIcon           =       "-dumpIcon";
const char* kDumpAll            =       "-dumpAll";
const char* kAdd                        =       "-add";
const char* kSet                        =       "-set";
const char* kForce                      =       "-force";
const char* kRemove                     =       "-remove";
const char* kCheckSniffRule     =       "-checkSniffRule";
const char* kShort                      =       "-short";
const char* kLong                       =       "-long";
const char* kPreferredApp       =       "-preferredApp";
const char* kPreferredAppSig =  "-preferredAppSig";
const char* kSniffRule          =       "-sniffRule";
const char* kMiniIcon           =       "-miniIcon";
const char* kLargeIcon          =       "-largeIcon";
const char* kVectorIcon         =       "-vectorIcon";
const char* kIncludeApps        =       "-includeApps";
const char* kExtension          =       "-extension";
const char* kAttribute          =       "-attribute";
const char* kAttrName           =       "-attrName";
const char* kAttrType           =       "-attrType";
const char* kAttrWidth          =       "-attrWidth";
const char* kAttrAlignment      =       "-attrAlignment";
const char* kAttrViewable       =       "-attrViewable";
const char* kAttrEditable       =       "-attrEditable";
const char* kAttrExtra          =       "-attrExtra";


const uint32 hash_function(const char* str)
{
        uint32 h = 0;
        uint32 g = 0;
        for (const char* p = str; *p; p++) {
                h = (h << 4) + (*p & 0xFF);
                g = h & 0xF0000000;
                if (g != 0) {
                        h ^= g >> 24;
                        h ^= g;
                }
        }
        return h;
}


// the list of all acceptable command-line options
struct CmdOption {

        enum Type {
                kMode,
                kOption,
                kAttrRoot,
                kAttrib,
                kHelp
        };

        const char*     fName;
        Type            fType;
        bool            fNeedArg;
        bool            fNonExclusive;

} gCmdOptions[] = {

        { kHelpReq,                     CmdOption::kHelp },

        { kDump,                        CmdOption::kMode },
        { kDumpSniffRule,       CmdOption::kMode },
        { kDumpIcon,            CmdOption::kMode },
        { kDumpAll,                     CmdOption::kMode },
        { kAdd,                         CmdOption::kMode },
        { kSet,                         CmdOption::kMode },
        { kForce,                       CmdOption::kMode },
        { kRemove,                      CmdOption::kMode },
        { kCheckSniffRule,      CmdOption::kMode, true },

        { kShort,                       CmdOption::kOption, true },
        { kLong,                        CmdOption::kOption, true },
        { kPreferredApp,        CmdOption::kOption, true },
        { kPreferredAppSig,     CmdOption::kOption, true },
        { kSniffRule,           CmdOption::kOption, true },
        { kMiniIcon     ,               CmdOption::kOption, true },
        { kLargeIcon,           CmdOption::kOption, true },
        { kVectorIcon,          CmdOption::kOption, true },
        { kIncludeApps,         CmdOption::kOption, false },
        { kExtension,           CmdOption::kOption, true, true },
        { kAttribute,           CmdOption::kAttrRoot, true, true },

        { kAttrName,            CmdOption::kAttrib, true },
        { kAttrType,            CmdOption::kAttrib, true },
        { kAttrWidth,           CmdOption::kAttrib, true },
        { kAttrAlignment,       CmdOption::kAttrib, true },
        { kAttrViewable,        CmdOption::kAttrib, true },
        { kAttrEditable,        CmdOption::kAttrib, true },
        { kAttrExtra,           CmdOption::kAttrib, true }
};

// the 'hash -> value' map of arguments provided by user
typedef multimap<uint32, const char*>                           TUserArgs;
typedef multimap<uint32, const char*>::iterator         TUserArgsI;

// user provided attributes are grouped separately in vector
typedef vector<TUserArgs>                       TUserAttrs;
typedef vector<TUserArgs>::iterator     TUserAttrsI;

const uint32 kOpModeUndefined = 0;


// #pragma mark -

class Error : public std::exception
{
                        BString         fWhat;
public:
                                                Error(const char* what, ...);
        virtual                         ~Error() throw() {}
        virtual const char*     what() const throw() { return fWhat.String(); }
};


Error::Error(const char* what, ...)
{
        const int size = 1024;
        va_list args;
        va_start(args, what);
        vsnprintf(fWhat.LockBuffer(size), size, what, args);
        fWhat.UnlockBuffer();
        va_end(args);
}


// #pragma mark -

// encapsulate the single attribute params
//
struct MimeAttribute
{
        status_t        fStatus;
        BString         fName;
        BString         fPublicName;
        int32           fType;
        bool            fViewable;
        bool            fEditable;
        bool            fExtra;
        int32           fWidth;
        int32           fAlignment;

                                MimeAttribute(BMessage& msg, int32 index);
                                MimeAttribute(TUserArgs& args);
                                MimeAttribute(const MimeAttribute& src);

        status_t        InitCheck() { return fStatus; }

        MimeAttribute& operator=(const MimeAttribute& src);

        void            Dump();
        void            SyncWith(TUserArgs& args);
        void            StoreInto(BMessage* target);
        const char*     UserArgValue(TUserArgs& map, const char* name);

        bool            IsPrintableChar(char c)
                                        { return c >= ' ' && c < 127 && c != '\'' && c != '\\'; }
};


MimeAttribute::MimeAttribute(BMessage& msg, int32 index)
                :
                fStatus(B_NO_INIT),
                fType('CSTR'),
                fViewable(true),
                fEditable(false),
                fExtra(false),
                fWidth(0),
                fAlignment(0)
{
        BString rawPublicName;
        struct attrEntry {
                const char* name;
                type_code       type;
                bool            required;
                void*           data;
        } attrEntries[] = {
                { "attr:name",                  B_STRING_TYPE,  true, &fName },
                { "attr:public_name",   B_STRING_TYPE,  true, &rawPublicName },
                { "attr:type",                  B_INT32_TYPE,   true, &fType },
                { "attr:viewable",              B_BOOL_TYPE,    false, &fViewable },
                { "attr:editable",              B_BOOL_TYPE,    false, &fEditable },
                { "attr:extra",                 B_BOOL_TYPE,    false, &fExtra },
                { "attr:width",                 B_INT32_TYPE,   false, &fWidth },
                { "attr:alignment",             B_INT32_TYPE,   false, &fAlignment }
        };

        for (size_t i = 0; i < sizeof(attrEntries) / sizeof(attrEntries[0]); i++) {
                switch (attrEntries[i].type) {
                        case B_STRING_TYPE:
                                fStatus = msg.FindString(attrEntries[i].name, index,
                                                                (BString*)attrEntries[i].data);
                                break;
                        case B_BOOL_TYPE:
                                fStatus = msg.FindBool(attrEntries[i].name, index,
                                                                (bool*)attrEntries[i].data);
                                break;
                        case B_INT32_TYPE:
                                fStatus = msg.FindInt32(attrEntries[i].name, index,
                                                                (int32*)attrEntries[i].data);
                                break;
                }

                if (attrEntries[i].required && fStatus != B_OK)
                        return;
        }

        fPublicName.CharacterEscape(rawPublicName, "\'", '\\');
        fStatus = B_OK;
}


MimeAttribute::MimeAttribute(TUserArgs& args)
                :
                fStatus(B_NO_INIT),
                fType('CSTR'),
                fViewable(true),
                fEditable(false),
                fExtra(false),
                fWidth(0),
                fAlignment(0)
{
        SyncWith(args);
        fStatus = B_OK;
}


MimeAttribute::MimeAttribute(const MimeAttribute& src)
{
        *this = src;
}


MimeAttribute&
MimeAttribute::operator=(const MimeAttribute& src)
{
        fStatus = src.fStatus;
        fName = src.fName;
        fPublicName = src.fPublicName;
        fType = src.fType;
        fViewable = src.fViewable;
        fEditable = src.fEditable;
        fExtra = src.fExtra;
        fWidth = src.fWidth;
        fAlignment = src.fAlignment;

        return *this;
}


void
MimeAttribute::SyncWith(TUserArgs& args)
{
        const char* value = UserArgValue(args, kAttribute);
        if (value != NULL)
                fName.SetTo(value, B_MIME_TYPE_LENGTH);

        value = UserArgValue(args, kAttrName);
        if (value != NULL)
                fPublicName.SetTo(value, B_MIME_TYPE_LENGTH);

        value = UserArgValue(args, kAttrType);
        if (value != NULL) {
                fType = 0;
                if (strlen(value) > 2 && value[0] == '0' && value[1] == 'x') {
                        stringstream ss;
                        ss << setbase(16) << value + 2;
                        ss >> fType;
                } else if (strlen(value) == 4) {
                        for (int i = 0; i < 4 && value[i] != '\0'; i++) {
                                fType <<= 8;
                                fType |= (value[i] != '\0' ? value[i] : ' ');
                        }

                } else
                        throw Error("Invalid data for %s", kAttrType);

                fType = B_LENDIAN_TO_HOST_INT32(fType);
        }

        value = UserArgValue(args, kAttrWidth);
        if (value != NULL)
                fWidth = atoi(value);

        value = UserArgValue(args, kAttrAlignment);
        if (value != NULL) {
                if (strcasecmp(value, "right") == 0) {
                        fAlignment = B_ALIGN_RIGHT;
                } else if (strcasecmp(value, "left") == 0) {
                        fAlignment = B_ALIGN_LEFT;
                } else if (strcasecmp(value, "center") == 0) {
                        fAlignment = B_ALIGN_CENTER;
                } else
                        fAlignment = atoi(value);
        }

        value = UserArgValue(args, kAttrViewable);
        if (value != NULL)
                fViewable = atoi(value) != 0;

        value = UserArgValue(args, kAttrEditable);
        if (value != NULL)
                fEditable = atoi(value) != 0;

        value = UserArgValue(args, kAttrExtra);
        if (value != NULL)
                fExtra = atoi(value) != 0;
}


void
MimeAttribute::Dump()
{
        uint32 type = B_HOST_TO_LENDIAN_INT32(fType);
        const char* alignment = fAlignment == B_ALIGN_RIGHT ? "right"
                                        : (fAlignment == B_ALIGN_LEFT ? "left" : "center");

        cout << " \\" << endl << "\t" << kAttribute << " \"" << fName << "\" "
                                << kAttrName << " \"" << fPublicName << "\"";

        char c1 = (char)((type >> 24) & 0xFF);
        char c2 = (char)((type >> 16) & 0xFF);
        char c3 = (char)((type >> 8) & 0xFF);
        char c4 = (char)(type & 0xFF);

        ios::fmtflags flags = cout.flags();

        cout << " \\" << endl << "\t\t" << kAttrType;
        if (IsPrintableChar(c1) && IsPrintableChar(c2) &&
                IsPrintableChar(c3) && IsPrintableChar(c4))
                cout << " '" << c1 << c2 << c3 << c4 << "' ";
        else
                cout << "0x" << hex << type;

        cout << " " << kAttrWidth << " " << fWidth
                        << " " << kAttrAlignment << " " << alignment;

        cout << " \\" << endl << "\t\t" << kAttrViewable << " " << fViewable
                        << " " << kAttrEditable << " " << fEditable
                        << " " << kAttrExtra << " " << fExtra;

        cout.flags(flags);
}


void
MimeAttribute::StoreInto(BMessage* target)
{
        struct attrEntry {
                const char* name;
                type_code       type;
                const void*     data;
        } attrEntries[] = {
                { "attr:name",                  B_STRING_TYPE,  fName.String() },
                { "attr:public_name",   B_STRING_TYPE,  fPublicName.String() },
                { "attr:type",                  B_INT32_TYPE,   &fType },
                { "attr:viewable",              B_BOOL_TYPE,    &fViewable },
                { "attr:editable",              B_BOOL_TYPE,    &fEditable },
                { "attr:extra",                 B_BOOL_TYPE,    &fExtra },
                { "attr:width",                 B_INT32_TYPE,   &fWidth },
                { "attr:alignment",             B_INT32_TYPE,   &fAlignment }
        };

        for (size_t i = 0; i < sizeof(attrEntries) / sizeof(attrEntries[0]); i++) {
                switch (attrEntries[i].type) {
                        case B_STRING_TYPE:
                                fStatus = target->AddString(attrEntries[i].name,
                                                                (const char*)attrEntries[i].data);
                                break;
                        case B_BOOL_TYPE:
                                fStatus = target->AddBool(attrEntries[i].name,
                                                                (bool*)attrEntries[i].data);
                                break;
                        case B_INT32_TYPE:
                                fStatus = target->AddInt32(attrEntries[i].name,
                                                                *(int32*)attrEntries[i].data);
                                break;
                }

                if (fStatus != B_OK)
                        return;
        }
}


const char*
MimeAttribute::UserArgValue(TUserArgs& map, const char* name)
{
        TUserArgsI i = map.find(hash_function(name));
        if (i == map.end())
                return NULL;
        return i->second != NULL ? i->second : "";
}


// #pragma mark -

// the work-horse of the app - the class encapsulates extended info readed
// from the mime type and do all edit and dump operations
//
class MimeType : public BMimeType {

public:
                                        MimeType(char** argv);
                                        ~MimeType();

        void                    Process();

private:
        status_t                _InitCheck();
        void                    _SetTo(const char* mimetype);
        void                    _PurgeProperties();
        void                    _Init(char** argv);
        void                    _DumpIcon(uint8 *iconData, size_t iconSize);
        void                    _Dump(const char* mimetype);
        void                    _DoEdit();
        void                    _SetIcon(const char* iconData, int32 iconSize);

        const char*             _UserArgValue(const char* name);

        status_t                fStatus;
        const char*             fToolName;

        // configurable MimeType properties
        BString                 fShort;
        BString                 fLong;
        BString                 fPrefApp;
        BString                 fPrefAppSig;
        BString                 fSniffRule;
        BBitmap*                fSmallIcon;
        BBitmap*                fBigIcon;
        uint8*                  fVectorIcon;
        size_t                  fVectorIconSize;

        map<uint32, BString>            fExtensions;
        map<uint32, MimeAttribute>      fAttributes;

        // user provided arguments
        TUserArgs               fUserArguments;
        TUserAttrs              fUserAttributes;

        // operation mode switches and flags
        uint32                  fOpMode;
        bool                    fDumpNormal;
        bool                    fDumpRule;
        bool                    fDumpIcon;
        bool                    fDumpAll;
        bool                    fDoAdd;
        bool                    fDoSet;
        bool                    fDoForce;
        bool                    fDoRemove;
        bool                    fCheckSniffRule;
};


MimeType::MimeType(char** argv)
                :
                fStatus(B_NO_INIT),
                fToolName(argv[0]),
                fSmallIcon(NULL),
                fBigIcon(NULL),
                fVectorIcon(NULL),
                fVectorIconSize(0),
                fOpMode(kOpModeUndefined),
                fDumpNormal(false),
                fDumpRule(false),
                fDumpIcon(false),
                fDumpAll(false),
                fDoAdd(false),
                fDoSet(false),
                fDoForce(false),
                fDoRemove(false),
                fCheckSniffRule(false)
{
        fToolName = strrchr(argv[0], '/');
        fToolName = fToolName == NULL ? argv[0] : fToolName + 1;

        _Init(++argv);
}


MimeType::~MimeType()
{
        delete fSmallIcon;
        delete fBigIcon;
        free(fVectorIcon);
}


void
MimeType::_Init(char** argv)
{
        // fill the helper map of options - for quick lookup of arguments
        map<uint32, const CmdOption*> cmdOptionsMap;
        for (size_t i = 0; i < sizeof(gCmdOptions) / sizeof(gCmdOptions[0]); i++)
                cmdOptionsMap.insert(pair<uint32, CmdOption*>(
                                                        hash_function(gCmdOptions[i].fName), &gCmdOptions[i]));

        // parse the command line arguments
        for (char** arg = argv; *arg; arg++) {
                // non-option arguments are assumed as signature
                if (**arg != '-') {
                        if (Type() != NULL)
                                throw Error("mime signature already specified: '%s'", Type());

                        SetTo(*arg);
                        continue;
                }

                // check op.modes, options and attribs
                uint32 key = hash_function(*arg);

                map<uint32, const CmdOption*>::iterator I = cmdOptionsMap.find(key);
                if (I == cmdOptionsMap.end())
                        throw Error("unknown option '%s'", *arg);

                switch (I->second->fType) {
                        case CmdOption::kHelp:
                                cerr << kUsageMessage;
                                throw Error(kHelpMessage);

                        case CmdOption::kMode:
                                // op.modes are exclusive - no simultaneous possible
                                if (fOpMode != kOpModeUndefined)
                                        throw Error(kWrongModeMessage);
                                fOpMode = key;

                                if (hash_function(I->second->fName) != hash_function(kCheckSniffRule))
                                        break;
                                // else -> fallthrough, CheckRule works both as mode and Option
                        case CmdOption::kOption:
                                {
                                        const char* name = *arg;
                                        const char* param = NULL;
                                        if (I->second->fNeedArg) {
                                                if (!*++arg)
                                                        throw Error("argument required for '%s'", name);
                                                param = *arg;
                                        }

                                        TUserArgsI A = fUserArguments.find(key);
                                        if (A != fUserArguments.end() && !I->second->fNonExclusive)
                                                throw Error("option '%s' already specified", name);

                                        fUserArguments.insert(
                                                        pair<uint32, const char*>(key, param));
                                }
                                break;

                        case CmdOption::kAttrRoot:
                                if (!*++arg)
                                        throw Error("attribute name should be specified");

                                fUserAttributes.resize(fUserAttributes.size() + 1);
                                fUserAttributes.back().insert(
                                                        pair<uint32, const char*>(key, *arg));
                                break;

                        case CmdOption::kAttrib:
                                {
                                        const char* name = *arg;
                                        if (fUserAttributes.size() <= 0)
                                                throw Error("'%s' allowed only after the '%s' <name>",
                                                                name, kAttribute);

                                        if (!*++arg || **arg == '-')
                                                throw Error("'%s', argument should be specified", name);

                                        TUserArgsI A = fUserAttributes.back().find(key);
                                        if (A != fUserAttributes.back().end())
                                                throw Error("'%s' for attribute '%s' already specified",
                                                                name, A->second);

                                        fUserAttributes.back().insert(
                                                        pair<uint32, const char*>(key, *arg));
                                }
                                break;

                        default:
                                throw Error("internal error. wrong mode: %d", I->second->fType);
                }
        }

        // check some mutual exclusive conditions
        if (fOpMode == kOpModeUndefined)
                throw Error(kNeedArgMessage);

        if (Type() != NULL && InitCheck() != B_OK)
                throw Error("error instantiating mime for '%s': %s",
                                                        Type(), strerror(InitCheck()));

        fDoAdd = fOpMode == hash_function(kAdd);
        fDoSet = fOpMode == hash_function(kSet);
        fDoForce = fOpMode == hash_function(kForce);
        fDoRemove = fOpMode == hash_function(kRemove);
        fDumpNormal = fOpMode == hash_function(kDump);
        fDumpRule = fOpMode == hash_function(kDumpSniffRule);
        fDumpIcon = fOpMode == hash_function(kDumpIcon);
        fDumpAll = fOpMode == hash_function(kDumpAll);
        fCheckSniffRule = fOpMode == hash_function(kCheckSniffRule);

        if (fDoAdd || fDoSet || fDoForce || fDoRemove) {
                if (Type() == NULL)
                        throw Error("signature should be specified");

                if (!IsValid())
                        throw Error("mime for '%s' is not valid", Type());

        } else if (fDumpNormal || fDumpRule || fDumpIcon || fDumpAll) {
                if (Type() != NULL) {
                        if (!IsValid())
                                throw Error("mime for '%s' is not valid", Type());

                        if (!IsInstalled())
                                throw Error("mime for '%s' is not installed", Type());
                }
        }

        // finally force to load mime-specific fileds
        _SetTo(Type());
}


status_t
MimeType::_InitCheck()
{
        return fStatus != B_OK ? fStatus : BMimeType::InitCheck();
}


void
MimeType::_PurgeProperties()
{
        fShort.Truncate(0);
        fLong.Truncate(0);
        fPrefApp.Truncate(0);
        fPrefAppSig.Truncate(0);
        fSniffRule.Truncate(0);

        delete fSmallIcon;
        fSmallIcon = NULL;

        delete fBigIcon;
        fBigIcon = NULL;

        free(fVectorIcon);
        fVectorIcon = NULL;

        fExtensions.clear();
        fAttributes.clear();
}


void
MimeType::_DumpIcon(uint8 *iconData, size_t iconSize)
{
        // bitmap icons ASCII art :)
        int lineLimit = iconSize == B_MINI_ICON * B_MINI_ICON
                                                ? B_MINI_ICON : B_LARGE_ICON;

        ios::fmtflags flags = cout.flags();

        for (size_t i = 0; i < iconSize; i++) {
                if (i % lineLimit == 0 && i != iconSize - 1)
                        cout << "\\" << endl;

                cout << hex << setfill('0') << setw(2) << (uint16) iconData[i];
        }

        cout.flags(flags);
}


void
MimeType::_SetIcon(const char* iconData, int32 iconSize)
{
        uint8* bits = NULL;
        BRect rect(0, 0, iconSize - 1, iconSize - 1);

        switch (iconSize) {
                case B_MINI_ICON:
                        if (fSmallIcon == NULL)
                                fSmallIcon = new BBitmap(rect, B_COLOR_8_BIT);
                        bits = (uint8*) fSmallIcon->Bits();
                        break;
                case B_LARGE_ICON:
                        if (fBigIcon == NULL)
                                fBigIcon = new BBitmap(rect, B_COLOR_8_BIT);
                        bits = (uint8*) fBigIcon->Bits();
                        break;
                default:
                        if (iconSize >= 0)
                                break;
                        free(fVectorIcon);
                        fVectorIconSize = -iconSize;
                        bits = fVectorIcon = (uint8*) malloc(fVectorIconSize);
                        break;
        }

        if (bits == NULL)
                throw Error("cannot create icon of size %d", iconSize);

        size_t dataSize = iconSize < 0 ? -iconSize / 2 : iconSize * iconSize;

        for (size_t i = 0; i < dataSize; i++) {
                stringstream ss;
                uint16 val;
                ss << setbase(16) << iconData[i * 2] << iconData[i * 2 + 1];
                ss >> val;
                bits[i] = uint8(val & 0xff);
        }

        if (iconSize < 0)
                SetIcon(fVectorIcon, dataSize);
        else
                SetIcon(iconSize == B_MINI_ICON ? fSmallIcon : fBigIcon,
                                        (icon_size) iconSize);
}


void
MimeType::_SetTo(const char* mimetype)
{
        if (mimetype == NULL)
                return; // iterate all types - nothing to load ATM

        if (BMimeType::SetTo(mimetype) != B_OK)
                throw Error("failed to set mimetype to '%s'", mimetype);

        _PurgeProperties();

        char buffer[B_MIME_TYPE_LENGTH] = { 0 };
        if (GetShortDescription(buffer) == B_OK)
                fShort.SetTo(buffer, B_MIME_TYPE_LENGTH);

        if (GetLongDescription(buffer) == B_OK)
                fLong.SetTo(buffer, B_MIME_TYPE_LENGTH);

        entry_ref ref;
        if (GetAppHint(&ref) == B_OK) {
                BPath path(&ref);
                fPrefApp.SetTo(path.Path(), B_MIME_TYPE_LENGTH);
        }

        if (GetPreferredApp(buffer, B_OPEN) == B_OK)
                fPrefAppSig.SetTo(buffer, B_MIME_TYPE_LENGTH);

        BString rule;
        if (GetSnifferRule(&rule) == B_OK)
                fSniffRule.CharacterEscape(rule.String(), "\'", '\\');

        BMessage exts;
        fExtensions.clear();
        if (GetFileExtensions(&exts) == B_OK) {
                uint32 i = 0;
                const char* ext = NULL;
                while (exts.FindString("extensions", i++, &ext) == B_OK)
                        fExtensions.insert(pair<uint32, BString>(hash_function(ext), ext));
        }

        BMessage attrs;
        fAttributes.clear();
        if (GetAttrInfo(&attrs) == B_OK) {
                for (int index = 0; ; index++) {
                        MimeAttribute attr(attrs, index);
                        if (attr.InitCheck() != B_OK)
                                break;

                        fAttributes.insert(
                                        pair<uint32, MimeAttribute>(hash_function(attr.fName), attr));
                }
        }

        fSmallIcon = new BBitmap(BRect(0, 0, 15, 15), B_COLOR_8_BIT);
        if (GetIcon(fSmallIcon, B_MINI_ICON) != B_OK) {
                delete fSmallIcon;
                fSmallIcon = NULL;
        }

        fBigIcon = new BBitmap(BRect(0, 0, 31, 31), B_COLOR_8_BIT);
        if (GetIcon(fBigIcon, B_LARGE_ICON) != B_OK) {
                delete fBigIcon;
                fBigIcon = NULL;
        }

        if (GetIcon(&fVectorIcon, &fVectorIconSize) != B_OK)
                fVectorIcon = NULL;
}


const char*
MimeType::_UserArgValue(const char* name)
{
        TUserArgsI i = fUserArguments.find(hash_function(name));
        if (i == fUserArguments.end())
                return NULL;

        return i->second != NULL ? i->second : "";
}


void
MimeType::_Dump(const char* mimetype)
{
        // _Dump can be called as part of all types iteration - so set to required
        if (Type() == NULL || strcasecmp(Type(), mimetype) != 0)
                _SetTo(mimetype);

        // apps have themself as preferred app - use it to handle
        // -includeApps option - do not dump applications info
        if (!fPrefApp.IsEmpty()
                && fPrefApp.ICompare(mimetype) == 0
                && _UserArgValue(kIncludeApps) == NULL)
                        return;

        if (fDumpIcon && fSmallIcon == NULL && fBigIcon == NULL)
                return;

        if (fDumpRule && fSniffRule.IsEmpty())
                return;

        cout << fToolName << " -set " << mimetype;

        if (fDumpNormal || fDumpAll) {
                if (!fShort.IsEmpty())
                        cout << " " << kShort << " \"" << fShort << "\"";
                if (!fLong.IsEmpty())
                        cout << " " << kLong << " \"" << fLong << "\"";
                if (!fPrefApp.IsEmpty())
                        cout << " " << kPreferredApp << " " << fPrefApp;
                if (!fPrefAppSig.IsEmpty())
                        cout << " " << kPreferredAppSig << " " << fPrefAppSig;
        }

        if (!fDumpIcon && !fSniffRule.IsEmpty())
                cout << " " << kSniffRule << " '" << fSniffRule << "'";

        if (fDumpNormal || fDumpAll)
                for (map<uint32, BString>::iterator i = fExtensions.begin();
                                i != fExtensions.end(); i++)
                        cout << " " << kExtension << " " << i->second;

        if (fDumpAll)
                for (map<uint32, MimeAttribute>::iterator i = fAttributes.begin();
                                i != fAttributes.end(); i++)
                        i->second.Dump();

        if (fDumpIcon || fDumpAll) {
                if (fSmallIcon != NULL && fSmallIcon->Bits() != NULL) {
                        cout << " \\" << endl << "\t" << kMiniIcon << " ";
                        _DumpIcon((uint8*) fSmallIcon->Bits(), fSmallIcon->BitsLength());
                }

                if (fBigIcon != NULL && fBigIcon->Bits() != NULL) {
                        cout << " \\" << endl << "\t" << kLargeIcon << " ";
                        _DumpIcon((uint8*) fBigIcon->Bits(), fBigIcon->BitsLength());
                }

                if (fVectorIcon != NULL && fVectorIconSize != 0) {
                        cout << " \\" << endl << "\t" << kVectorIcon << " ";
                        _DumpIcon((uint8*) fVectorIcon, fVectorIconSize);
                }
        }

        cout << endl;
}


void
MimeType::_DoEdit()
{
        if (fDoRemove || fDoForce) {
                status_t result = Delete();
                if (result != B_OK)
                        throw Error(strerror(result), result);

                if (fDoRemove)
                        return;

                _PurgeProperties();
        }

        if (!IsInstalled() && Install() != B_OK)
                throw Error("could not install mimetype '%s'", Type());

        const char* value = _UserArgValue(kShort);
        if (value != NULL && (!fDoAdd || fShort.IsEmpty()))
                if (SetShortDescription(value) != B_OK)
                        throw Error("cannot set %s to %s for '%s'", kShort, value, Type());

        value = _UserArgValue(kLong);
        if (value != NULL && (!fDoAdd || fLong.IsEmpty()))
                if (SetLongDescription(value) != B_OK)
                        throw Error("cannot set %s to %s for '%s'", kLong, value, Type());

        value = _UserArgValue(kPreferredApp);
        if (value != NULL && (!fDoAdd || fPrefApp.IsEmpty())) {
                entry_ref appHint;
                if (get_ref_for_path(value, &appHint) != B_OK)
                        throw Error("%s ref_entry for '%s' couldn't be found for '%s'",
                                                kPreferredApp, value, Type());

                if (SetAppHint(&appHint) != B_OK)
                        throw Error("cannot set %s to %s for '%s'",
                                        kPreferredApp, value, Type());
        }

        value = _UserArgValue(kPreferredAppSig);
        if (value != NULL && (!fDoAdd || fPrefAppSig.IsEmpty()))
                if (SetPreferredApp(value) != B_OK)
                        throw Error("cannot set %s to %s for '%s'",
                                        kPreferredAppSig, value, Type());

        value = _UserArgValue(kSniffRule);
        if (value != NULL && (!fDoAdd || fSniffRule.IsEmpty()))
                if (SetSnifferRule(value) != B_OK)
                        throw Error("cannot set %s to %s for '%s'",
                                        kSniffRule, value, Type());

        value = _UserArgValue(kMiniIcon);
        if (value != NULL && (!fDoAdd || fSmallIcon == NULL)) {
                int32 iconSize = strlen(value);
                if (iconSize / 2 != B_MINI_ICON * B_MINI_ICON)
                        throw Error("cannot set %s for '%s'. Hex data size %d is invalid",
                                        kMiniIcon, Type(), iconSize);

                _SetIcon(value, B_MINI_ICON);
        }

        value = _UserArgValue(kLargeIcon);
        if (value != NULL && (!fDoAdd || fBigIcon == NULL)) {
                int32 iconSize = strlen(value);
                if (iconSize / 2 != B_LARGE_ICON * B_LARGE_ICON)
                        throw Error("cannot set %s for '%s'. Hex data size %d is invalid",
                                        kLargeIcon, Type(), iconSize);

                _SetIcon(value, B_LARGE_ICON);
        }

        value = _UserArgValue(kVectorIcon);
        if (value != NULL && (!fDoAdd || fVectorIcon == NULL)) {
                int32 iconSize = strlen(value);
                if ((iconSize % 2) != 0)
                        throw Error("cannot set %s for '%s'. Hex data size %d is invalid",
                                        kVectorIcon, Type(), iconSize);

                // vector icon size is negative intended
                _SetIcon(value, -iconSize);
        }

        // handle extensions update
        pair<TUserArgsI, TUserArgsI> exts
                                                        = fUserArguments.equal_range(hash_function(kExtension));
        for (TUserArgsI i = exts.first; i != exts.second; i++) {
                uint32 key = hash_function(i->second);
                if (fExtensions.find(key) == fExtensions.end())
                        fExtensions.insert(pair<uint32, BString>(key, i->second));
        }

        if (exts.first != exts.second) {
                BMessage msg;
                for (map<uint32, BString>::iterator i = fExtensions.begin();
                                i != fExtensions.end(); i++)
                        if (msg.AddString("extensions", i->second.String()) != B_OK)
                                throw Error("extension '%s' couldn't be added",
                                        i->second.String());

                if (SetFileExtensions(&msg) != B_OK)
                        throw Error("set file extensions failed");
        }

        // take care about attribute trees
        for (TUserAttrsI userAttr = fUserAttributes.begin();
                        userAttr != fUserAttributes.end(); userAttr++ )
        {
                // search for -attribute "name" in args map
                TUserArgsI attrArgs = userAttr->find(hash_function(kAttribute));
                if (attrArgs == userAttr->end())
                        throw Error("internal error: %s arg not found", kAttribute);

                // check if we already have this attribute cached
                map<uint32, MimeAttribute>::iterator
                                                                attr = fAttributes.find(hash_function(attrArgs->second));
                if (attr == fAttributes.end()) {
                        // add new one
                        MimeAttribute mimeAttr(*userAttr);
                        fAttributes.insert(
                                pair<uint32, MimeAttribute>(hash_function(mimeAttr.fName), mimeAttr));
                } else if (!fDoAdd)
                        attr->second.SyncWith(*userAttr);
        }

        if (fAttributes.size() > 0) {
                BMessage msg;
                for (map<uint32, MimeAttribute>::iterator i = fAttributes.begin();
                                i != fAttributes.end(); i++)
                {
                        i->second.StoreInto(&msg);
                        if (i->second.InitCheck() != B_OK)
                                throw Error("storing attributes in message failed");
                }

                if (SetAttrInfo(&msg) != B_OK)
                        throw Error("set mimetype attributes failed");
        }
}


void
MimeType::Process()
{
        if (fCheckSniffRule) {
                TUserArgsI I = fUserArguments.find(fOpMode);
                if (I == fUserArguments.end())
                        throw Error("Sniffer rule is empty");

                BString error;
                status_t result = BMimeType::CheckSnifferRule(I->second, &error);
                if (result == B_OK)
                        cerr << I->second << endl << "Sniffer rule is correct" << endl;
                else
                        cerr <<  error.String() << endl;

                return;
        }

        if (fDoAdd || fDoSet || fDoForce || fDoRemove) {
                _DoEdit();
                return;
        }

        if (fDumpNormal || fDumpRule || fDumpIcon || fDumpAll) {
                if (Type() != NULL) {
                        _Dump(Type());
                        return;
                }

                BMessage superTypes;
                int32 superCount = 0;
                type_code type = B_INT32_TYPE;
                if (BMimeType::GetInstalledSupertypes(&superTypes) != B_OK
                        || superTypes.GetInfo("super_types", &type, &superCount) != B_OK)
                        throw Error("super types enumeration failed");

                for (int32 si = 0; si < superCount; si++) {
                        const char* superName = NULL;
                        if (superTypes.FindString("super_types", si, &superName) != B_OK)
                                throw Error("name for supertype #%d not found", si);

                        BMessage types;
                        if (BMimeType::GetInstalledTypes(superName, &types) != B_OK)
                                throw Error("mimetypes of supertype '%s' not found", superName);

                        int32 count = 0;
                        if (types.GetInfo("types", &type, &count) != B_OK)
                                continue; // no sub-types?

                        for (int32 i = 0; i < count; i++) {
                                const char* name = NULL;
                                if (types.FindString("types", i, &name) != B_OK)
                                        throw Error("name for type %s/#%d not found", superName, i);

                                _Dump(name);
                        }
                }
        }
}


int
main(int argc, char** argv)
{
        // AppServer link is required to work with bitmaps
        BApplication app("application/x-vnd.haiku.setmime");

        try {

                if (argc < 2)
                        throw Error(kNeedArgMessage);

                MimeType mimetype(argv);

                mimetype.Process();

        } catch(exception& exc) {
                cerr << argv[0] << " : " << exc.what() << endl;
                cerr << kUsageMessage;
                return B_ERROR;
        }

        return B_OK;
}