root/src/apps/aboutsystem/Utilities.cpp
/*
 * Copyright 2008-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT license.
 */

#include "Utilities.h"

#include <ctype.h>
#include <stdlib.h>

#if __GNUC__ < 4
#define va_copy         __va_copy
#endif

#include <Catalog.h>
#include <Locale.h>
#include <Message.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Utilities"


BString
trim_string(const char* string, size_t len)
{
        BString trimmed;
        size_t i = 0;
        bool addSpace = false;

        while (i < len) {
                // skip space block
                while (i < len && isspace(string[i]))
                        i++;

                // write non-spaced (if any)
                if (i < len && !isspace(string[i])) {
                        // pad with a single space, for all but the first block
                        if (addSpace)
                                trimmed << ' ';
                        else
                                addSpace = true;

                        // append chars
                        while (i < len && !isspace(string[i]))
                                trimmed << string[i++];
                }
        }

        return trimmed;
}


void
parse_named_url(const BString& namedURL, BString& name, BString& url)
{
        int32 urlStart = namedURL.FindFirst('<');
        int32 urlEnd = namedURL.FindLast('>');
        if (urlStart < 0 || urlEnd < 0 || urlStart + 1 >= urlEnd) {
                name = namedURL;
                url = namedURL;
                return;
        }

        url.SetTo(namedURL.String() + urlStart + 1, urlEnd - urlStart - 1);

        if (urlStart > 0)
                name = trim_string(namedURL, urlStart);
        else
                name = url;
}


// #pragma mark - StringVector


StringVector::StringVector()
        :
        fStrings(NULL),
        fCount(0)
{
}


StringVector::StringVector(const char* string,...)
        :
        fStrings(NULL),
        fCount(0)
{
        if (string == NULL)
                return;

        va_list list;
        va_start(list, string);
        SetTo(string, list);
        va_end(list);
}


StringVector::StringVector(const BMessage& strings, const char* fieldName)
        :
        fStrings(NULL),
        fCount(0)
{
        SetTo(strings, fieldName);
}


StringVector::StringVector(const StringVector& other)
        :
        fStrings(NULL),
        fCount(0)
{
        if (other.fCount == 0)
                return;

        fStrings = new BString[other.fCount];
        fCount = other.fCount;

        for (int32 i = 0; i < fCount; i++)
                fStrings[i] = other.fStrings[i];
}


StringVector::~StringVector()
{
        Unset();
}


void
StringVector::SetTo(const char* string,...)
{
        va_list list;
        va_start(list, string);
        SetTo(string, list);
        va_end(list);
}


void
StringVector::SetTo(const char* string, va_list _list)
{
        // free old strings
        Unset();

        if (string == NULL)
                return;

        // count strings
        va_list list;
        va_copy(list, _list);
        fCount = 1;
        while (va_arg(list, const char*) != NULL)
                fCount++;
        va_end(list);

        // create array and copy them
        fStrings = new BString[fCount];
        fStrings[0] = string;

        va_copy(list, _list);
        for (int32 i = 1; i < fCount; i++)
                fStrings[i] = va_arg(list, const char*);
        va_end(list);

}


void
StringVector::SetTo(const BMessage& strings, const char* fieldName,
        const char* prefix)
{
        Unset();

        type_code type;
        int32 count;
        if (strings.GetInfo(fieldName, &type, &count) != B_OK
                || type != B_STRING_TYPE) {
                return;
        }

        fStrings = new BString[count];

        for (int32 i = 0; i < count; i++) {
                if (strings.FindString(fieldName, i, &fStrings[i]) != B_OK)
                        return;

                if (prefix != NULL)
                        fStrings[i].Prepend(prefix);

                fCount++;
        }
}


void
StringVector::Unset()
{
        delete[] fStrings;
        fStrings = NULL;
        fCount = 0;
}


const char*
StringVector::StringAt(int32 index) const
{
        return (index >= 0 && index < fCount ? fStrings[index].String() : NULL);
}


// #pragma mark - PackageCredit


PackageCredit::PackageCredit(const char* packageName)
        :
        fPackageName(packageName)
{
}


PackageCredit::PackageCredit(const BMessage& packageDescription)
{
        const char* package;
        const char* copyright;
        const char* url;

        // package and copyright are mandatory
        if (packageDescription.FindString("Package", &package) != B_OK
                || packageDescription.FindString("Copyright", &copyright) != B_OK) {
                return;
        }

        // URL is optional
        if (packageDescription.FindString("URL", &url) != B_OK)
                url = NULL;

        fPackageName = package;
        fCopyrights.SetTo(packageDescription, "Copyright", COPYRIGHT_STRING);
        fLicenses.SetTo(packageDescription, "License");
        fSources.SetTo(packageDescription, "SourceURL");
        fURL = url;
}


PackageCredit::PackageCredit(const PackageCredit& other)
        :
        fPackageName(other.fPackageName),
        fCopyrights(other.fCopyrights),
        fLicenses(other.fLicenses),
        fSources(other.fSources),
        fURL(other.fURL)
{
}


PackageCredit::~PackageCredit()
{
}


bool
PackageCredit::IsValid() const
{
        // should have at least a package name and a copyright
        return fPackageName.Length() > 0 && !fCopyrights.IsEmpty();
}


bool
PackageCredit::IsBetterThan(const PackageCredit& other) const
{
        // We prefer credits with licenses.
        if (CountLicenses() > 0 && other.CountLicenses() == 0)
                return true;

        // Scan the copyrights for year numbers and let the greater one win.
        return _MaxCopyrightYear() > other._MaxCopyrightYear();
}


PackageCredit&
PackageCredit::SetCopyrights(const char* copyright,...)
{
        va_list list;
        va_start(list, copyright);
        fCopyrights.SetTo(copyright, list);
        va_end(list);

        return *this;
}


PackageCredit&
PackageCredit::SetCopyright(const char* copyright)
{
        return SetCopyrights(copyright, NULL);
}


PackageCredit&
PackageCredit::SetLicenses(const char* license,...)
{
        va_list list;
        va_start(list, license);
        fLicenses.SetTo(license, list);
        va_end(list);

        return *this;
}


PackageCredit&
PackageCredit::SetLicense(const char* license)
{
        return SetLicenses(license, NULL);
}


PackageCredit&
PackageCredit::SetSources(const char* source,...)
{
        va_list list;
        va_start(list, source);
        fSources.SetTo(source, list);
        va_end(list);

        return *this;
}


PackageCredit&
PackageCredit::SetSource(const char* source)
{
        return SetSources(source, NULL);
}


PackageCredit&
PackageCredit::SetURL(const char* url)
{
        fURL = url;

        return *this;
}


const char*
PackageCredit::PackageName() const
{
        return fPackageName.String();
}


const StringVector&
PackageCredit::Copyrights() const
{
        return fCopyrights;
}


int32
PackageCredit::CountCopyrights() const
{
        return fCopyrights.CountStrings();
}


const char*
PackageCredit::CopyrightAt(int32 index) const
{
        return fCopyrights.StringAt(index);
}


const StringVector&
PackageCredit::Licenses() const
{
        return fLicenses;
}


int32
PackageCredit::CountLicenses() const
{
        return fLicenses.CountStrings();
}


const char*
PackageCredit::LicenseAt(int32 index) const
{
        return fLicenses.StringAt(index);
}


const StringVector&
PackageCredit::Sources() const
{
        return fSources;
}


int32
PackageCredit::CountSources() const
{
        return fSources.CountStrings();
}


const char*
PackageCredit::SourceAt(int32 index) const
{
        return fSources.StringAt(index);
}


const char*
PackageCredit::URL() const
{
        return fURL.Length() > 0 ? fURL.String() : NULL;
}


/*static*/ bool
PackageCredit::NameLessInsensitive(const PackageCredit* a,
        const PackageCredit* b)
{
        return a->fPackageName.ICompare(b->fPackageName) < 0;
}


int
PackageCredit::_MaxCopyrightYear() const
{
        int maxYear = 0;

        for (int32 i = 0; const char* string = CopyrightAt(i); i++) {
                // iterate through the numbers
                int32 start = 0;
                while (true) {
                        // find the next number start
                        while (string[start] != '\0' && !isdigit(string[start]))
                                start++;

                        if (string[start] == '\0')
                                break;

                        // find the end
                        int32 end = start + 1;
                        while (string[end] != '\0' && isdigit(string[end]))
                                end++;

                        if (end - start == 4) {
                                int year = atoi(string + start);
                                if (year > 1900 && year < 2200 && year > maxYear)
                                        maxYear = year;
                        }

                        start = end;
                }
        }

        return maxYear;
}