root/src/kits/interface/DecorInfo.cpp
/*
 * Public domain source code.
 *
 * Author:
 *              Joseph "looncraz" Groover <looncraz@satx.rr.com>
 */


#include <DecorInfo.h>

#include <new>
#include <stdio.h>

#include <Autolock.h>
#include <FindDirectory.h>
#include <Path.h>
#include <Resources.h>
#include <SystemCatalog.h>

#include <DecoratorPrivate.h>


#define B_TRANSLATION_CONTEXT "Default decorator about box"


namespace BPrivate {


DecorInfo::DecorInfo()
        :
        fVersion(0),
        fModificationTime(0),
        fInitStatus(B_NO_INIT)
{
}


DecorInfo::DecorInfo(const BString& path)
        :
        fPath(path),
        fVersion(0),
        fModificationTime(0),
        fInitStatus(B_NO_INIT)
{
        BEntry entry(path.String(), true);
        entry.GetRef(&fRef);

        _Init();
}


DecorInfo::DecorInfo(const entry_ref& ref)
        :
        fRef(ref),
        fVersion(0),
        fModificationTime(0),
        fInitStatus(B_NO_INIT)
{
        BPath path(&ref);
        fPath = path.Path();

        _Init();
}


DecorInfo::~DecorInfo()
{
}


status_t
DecorInfo::SetTo(const entry_ref& ref)
{
        Unset();

        BPath path(&ref);
        fPath = path.Path();
        fRef = ref;
        _Init();

        return InitCheck();
}


status_t
DecorInfo::SetTo(BString path)
{
        BEntry entry(path.String(), true);
        entry_ref ref;
        entry.GetRef(&ref);
        return SetTo(ref);
}


status_t
DecorInfo::InitCheck()  const
{
        return fInitStatus;
}


void
DecorInfo::Unset()
{
        fRef = entry_ref();
        fPath = "";
        fName = "";
        fAuthors = "";
        fShortDescription = "";
        fLicenseURL = "";
        fLicenseName = "";
        fSupportURL = "";
        fVersion = 0;
        fModificationTime = 0;
        fInitStatus = B_NO_INIT;
}


bool
DecorInfo::IsDefault() const
{
        return fPath == "Default";
}


BString
DecorInfo::Path() const
{
        return fPath;
}


const entry_ref*
DecorInfo::Ref() const
{
        if (InitCheck() != B_OK || IsDefault())
                return NULL;
        return &fRef;
}


BString
DecorInfo::Name() const
{
        return fName;
}


BString
DecorInfo::ShortcutName() const
{
        if (IsDefault())
                return "Default";
        else if (Ref() != NULL)
                return fRef.name;

        return fName;
}


BString
DecorInfo::Authors() const
{
        return fAuthors;
}


BString
DecorInfo::ShortDescription() const
{
        return fShortDescription;
}


BString
DecorInfo::LongDescription() const
{
        return fLongDescription;
}


BString
DecorInfo::LicenseURL() const
{
        return fLicenseURL;
}


BString
DecorInfo::LicenseName() const
{
        return fLicenseName;
}


BString
DecorInfo::SupportURL() const
{
        return fSupportURL;
}


float
DecorInfo::Version() const
{
        return fVersion;
}


time_t
DecorInfo::ModificationTime() const
{
        return fModificationTime;
}


bool
DecorInfo::CheckForChanges(bool& deleted)
{
        if (InitCheck() != B_OK)
                return false;

        BEntry entry(&fRef);

        if (entry.InitCheck() != B_OK)
                return false;

        if (!entry.Exists()) {
                deleted = true;
                return true;
        }

        time_t modtime = 0;
        if (entry.GetModificationTime(&modtime) != B_OK) {
                fprintf(stderr, "DecorInfo::CheckForChanges()\tERROR: "
                        "BEntry:GetModificationTime() failed\n");
                return false;
        }

        if (fModificationTime != modtime) {
                _Init(true);
                return true;
        }

        return false;
}


void
DecorInfo::_Init(bool isUpdate)
{
        if (!isUpdate && InitCheck() != B_NO_INIT) {
                // TODO: remove after validation
                fprintf(stderr, "DecorInfo::_Init()\tImproper init state\n");
                return;
        }

        BEntry entry;

        if (fPath == "Default") {
                if (isUpdate) {
                        // should never happen
                        fprintf(stderr, "DecorInfo::_Init(true)\tBUG BUG updating default"
                                "decorator!?!?!\n");
                        return;
                }

                fAuthors = "DarkWyrm, Stephan Aßmus, Clemens Zeidler, Ingo Weinhold";
                fLongDescription = "";
                fLicenseURL = "http://";
                fLicenseName = "MIT";
                fSupportURL = "http://www.haiku-os.org/";
                fVersion = 0.5;
                fInitStatus = B_OK;

                fName = gSystemCatalog.GetString(B_TRANSLATE_MARK("Default"),
                        B_TRANSLATION_CONTEXT);
                fShortDescription = gSystemCatalog.GetString(B_TRANSLATE_MARK(
                                "Default Haiku window decorator."),
                        B_TRANSLATION_CONTEXT);

                // The following is to get the modification time of the app_server
                // and, thusly, the Default decorator...
                // If you can make it more simple, please do!
                BPath path;
                find_directory(B_SYSTEM_SERVERS_DIRECTORY, &path);
                path.Append("app_server");
                entry.SetTo(path.Path(), true);
                if (!entry.Exists()) {
                        fprintf(stderr, "Server MIA the world has become its slave! "
                                "Call the CIA!\n");
                        return;
                }

                entry.GetModificationTime(&fModificationTime);
                return;
        }

        // Is a file system object...

        entry.SetTo(&fRef, true);       // follow link
        if (entry.InitCheck() != B_OK) {
                fInitStatus = entry.InitCheck();
                return;
        }

        if (!entry.Exists()) {
                if (isUpdate) {
                        fprintf(stderr, "DecorInfo::_Init()\tERROR: decorator deleted"
                                        " after CheckForChanges() found it!\n");
                        fprintf(stderr, "DecorInfo::_Init()\tERROR: DecorInfo will "
                                        "Unset\n");
                        Unset();
                }
                return;
        }

        // update fRef to match file system object
        entry.GetRef(&fRef);
        entry.GetModificationTime(&fModificationTime);

        BResources resources(&fRef);
        if (resources.InitCheck() != B_OK) {
                fprintf(stderr, "DecorInfo::_Init()\t BResource InitCheck() failure\n");
                return;
        }

        size_t infoSize = 0;
        const void* infoData = resources.LoadResource(B_MESSAGE_TYPE,
                "be:decor:info", &infoSize);
        BMessage infoMessage;

        if (infoData == NULL || infoSize == 0
                || infoMessage.Unflatten((const char*)infoData) != B_OK) {
                fprintf(stderr, "DecorInfo::_init()\tNo extended information found for"
                        " \"%s\"\n", fRef.name);
        } else {
                infoMessage.FindString("name", &fName);
                infoMessage.FindString("authors", &fAuthors);
                infoMessage.FindString("short_descr", &fShortDescription);
                infoMessage.FindString("long_descr", &fLongDescription);
                infoMessage.FindString("lic_url", &fLicenseURL);
                infoMessage.FindString("lic_name", &fLicenseName);
                infoMessage.FindString("support_url", &fSupportURL);
                infoMessage.FindFloat ("version", &fVersion);
        }

        fInitStatus = B_OK;
        fName = fRef.name;
}


// #pragma mark - DecorInfoUtility


DecorInfoUtility::DecorInfoUtility(bool scanNow)
        :
        fHasScanned(false)
{
        // get default decorator from app_server
        DecorInfo* info = new(std::nothrow) DecorInfo("Default");
        if (info == NULL || info->InitCheck() != B_OK)  {
                delete info;
                fprintf(stderr, "DecorInfoUtility::constructor\tdefault decorator's "
                        "DecorInfo failed InitCheck()\n");
                return;
        }

        fList.AddItem(info);

        if (scanNow)
                ScanDecorators();
}


DecorInfoUtility::~DecorInfoUtility()
{
        BAutolock _(fLock);
        for     (int i = fList.CountItems() - 1; i >= 0; --i)
                delete fList.ItemAt(i);
}


status_t
DecorInfoUtility::ScanDecorators()
{
        status_t result;

        BPath systemPath;
        result = find_directory(B_SYSTEM_ADDONS_DIRECTORY, &systemPath);
        if (result == B_OK)
                result = systemPath.Append("decorators");

        if (result == B_OK) {
                BDirectory systemDirectory(systemPath.Path());
                result = systemDirectory.InitCheck();
                if (result == B_OK) {
                        result = _ScanDecorators(systemDirectory);
                        if (result != B_OK) {
                                fprintf(stderr, "DecorInfoUtility::ScanDecorators()\tERROR: %s\n",
                                        strerror(result));
                                return result;
                        }
                }
        }

        BPath systemNonPackagedPath;
        result = find_directory(B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY,
                &systemNonPackagedPath);
        if (result == B_OK)
                result = systemNonPackagedPath.Append("decorators");

        if (result == B_OK) {
                BDirectory systemNonPackagedDirectory(systemNonPackagedPath.Path());
                result = systemNonPackagedDirectory.InitCheck();
                if (result == B_OK) {
                        result = _ScanDecorators(systemNonPackagedDirectory);
                        if (result != B_OK) {
                                fprintf(stderr, "DecorInfoUtility::ScanDecorators()\tERROR: %s\n",
                                        strerror(result));
                                return result;
                        }
                }
        }

        BPath userPath;
        result = find_directory(B_USER_ADDONS_DIRECTORY, &userPath);
        if (result == B_OK)
                result = userPath.Append("decorators");

        if (result == B_OK) {
                BDirectory userDirectory(userPath.Path());
                result = userDirectory.InitCheck();
                if (result == B_OK) {
                        result = _ScanDecorators(userDirectory);
                        if (result != B_OK) {
                                fprintf(stderr, "DecorInfoUtility::ScanDecorators()\tERROR: %s\n",
                                        strerror(result));
                                return result;
                        }
                }
        }

        BPath userNonPackagedPath;
        result = find_directory(B_USER_NONPACKAGED_ADDONS_DIRECTORY,
                &userNonPackagedPath);
        if (result == B_OK)
                result = userNonPackagedPath.Append("decorators");

        if (result == B_OK) {
                BDirectory userNonPackagedDirectory(userNonPackagedPath.Path());
                result = userNonPackagedDirectory.InitCheck();
                if (result == B_OK) {
                        result = _ScanDecorators(userNonPackagedDirectory);
                        if (result != B_OK) {
                                fprintf(stderr, "DecorInfoUtility::ScanDecorators()\tERROR: %s\n",
                                        strerror(result));
                                return result;
                        }
                }
        }

        fHasScanned = true;

        return B_OK;
}


int32
DecorInfoUtility::CountDecorators()
{
        BAutolock _(fLock);
        if (!fHasScanned)
                ScanDecorators();

        return fList.CountItems();
}


DecorInfo*
DecorInfoUtility::DecoratorAt(int32 index)
{
        BAutolock _(fLock);
        return fList.ItemAt(index);
}


DecorInfo*
DecorInfoUtility::FindDecorator(const BString& string)
{
        if (string.Length() == 0)
                return CurrentDecorator();

        if (string == "Default")
                return DefaultDecorator();

        BAutolock _(fLock);
        if (!fHasScanned)
                ScanDecorators();

        // search by path
        DecorInfo* decor = _FindDecor(string);
        if (decor != NULL)
                return decor;

        // search by name
        for (int i = 1; i < fList.CountItems(); ++i) {
                decor = fList.ItemAt(i);
                if (string.ICompare(decor->Name()) == 0)
                        return decor;
        }

        return NULL;
}


DecorInfo*
DecorInfoUtility::CurrentDecorator()
{
        BAutolock _(fLock);
        if (!fHasScanned)
                ScanDecorators();

        BString name;
        get_decorator(name);
        return FindDecorator(name);
}


DecorInfo*
DecorInfoUtility::DefaultDecorator()
{
        BAutolock _(fLock);
        return fList.ItemAt(0);
}


bool
DecorInfoUtility::IsCurrentDecorator(DecorInfo* decor)
{
        BAutolock _(fLock);
        if (decor == NULL)
                 return false;
        return decor->Path() == CurrentDecorator()->Path();
}


status_t
DecorInfoUtility::SetDecorator(DecorInfo* decor)
{
        if (decor == NULL)
                return B_BAD_VALUE;

        BAutolock _(fLock);
        if (decor->IsDefault())
                return set_decorator("Default");

        return set_decorator(decor->Path());
}


status_t
DecorInfoUtility::SetDecorator(int32 index)
{
        BAutolock _(fLock);
        if (!fHasScanned)
                return B_ERROR;

        DecorInfo* decor = DecoratorAt(index);
        if (decor == NULL)
                return B_BAD_INDEX;

        return SetDecorator(decor);
}


status_t
DecorInfoUtility::Preview(DecorInfo* decor, BWindow* window)
{
        if (decor == NULL)
                return B_BAD_VALUE;

        return preview_decorator(decor->Path(), window);
}


// #pargma mark - private


DecorInfo*
DecorInfoUtility::_FindDecor(const BString& pathString)
{
        // find decor by path and path alone!
        if (!fLock.IsLocked()) {
                fprintf(stderr, "DecorInfoUtility::_find_decor()\tfailure to lock! - "
                        "BUG BUG BUG\n");
                return NULL;
        }

        if (pathString == "Default")
                return fList.ItemAt(0);

        for (int i = 1; i < fList.CountItems(); ++i) {
                DecorInfo* decor = fList.ItemAt(i);
                // Find the DecoratorInfo either by its true current location or by
                // what we still think the location is (before we had a chance to
                // update). NOTE: This will only catch the case when the user moved the
                // folder in which the add-on file lives. It will not work when the user
                // moves the add-on file itself or renames it.
                BPath path(decor->Ref());
                if (path.Path() == pathString || decor->Path() == pathString)
                        return decor;
        }

        return NULL;
}


status_t
DecorInfoUtility::_ScanDecorators(BDirectory decoratorDirectory)
{
        BAutolock _(fLock);

        // First, run through our list and DecorInfos CheckForChanges()
        if (fHasScanned) {
                for (int i = fList.CountItems() - 1; i > 0; --i) {
                        DecorInfo* decorInfo = fList.ItemAt(i);

                        bool deleted = false;
                        decorInfo->CheckForChanges(deleted);

                        if (deleted) {
                                fList.RemoveItem(decorInfo);
                                delete decorInfo;
                        }
                }
        }

        entry_ref ref;
        // Now, look at file system, skip the entries for which we already have
        // a DecorInfo in the list.
        while (decoratorDirectory.GetNextRef(&ref) == B_OK) {
                BPath path(&decoratorDirectory);
                status_t result = path.Append(ref.name);
                if (result != B_OK) {
                        fprintf(stderr, "DecorInfoUtility::_ScanDecorators()\tFailed to"
                                "append decorator file to path, skipping: %s.\n", strerror(result));
                        continue;
                }
                if (_FindDecor(path.Path()) != NULL)
                        continue;

                DecorInfo* decorInfo = new(std::nothrow) DecorInfo(ref);
                if (decorInfo == NULL || decorInfo->InitCheck() != B_OK) {
                        fprintf(stderr, "DecorInfoUtility::_ScanDecorators()\tInitCheck() "
                                "failure on decorator, skipping.\n");
                        delete decorInfo;
                        continue;
                }

                fList.AddItem(decorInfo);
        }

        return B_OK;
}


}       // namespace BPrivate