//============================================================================
//
//  project:    freedb
//  file:       process-archive.c
//  author:     Andrew Smith
//  date:       2004-12-28
//  language:   C
//
//  NOTES
//
//  FreeDB music CD database tarball conversion.
//
//  Copyright 2004, Andrew Smith <freedb@asmith.id.au>
//
//
//----------------------------------------------------------------------------
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published
//  by the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//  General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software Foundation,
//  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
//
//============================================================================

#include <stdio.h>

enum tTarSizes
{
    kMaxName = 100,
    kMaxMode = 8,
    kMaxUid = 8,
    kMaxGid = 8,
    kMaxSize = 12,
    kMaxTime = 12,
    kMaxChkSum = 8,
    kMaxTypeFlag = 1,
    kMaxLinkName = 100,
    kMaxMagic = 6,
    kMaxVersion = 2,
    kMaxUName = 32,
    kMaxGName = 32,
    kMaxDevMajor = 8,
    kMaxDevMinor = 8,
    kMaxPrefix = 155
};

enum tTarTypes
{
    kARegType = 0,
    kRegType = '0',
    kLnkType = '1',
    kSymType = '2',
    kChrType = '3',
    kBlkType = '4',
    kDirType = '5',
    kFifoType = '6',
    kContType = '7'
};

typedef struct
{
    char fName[kMaxName];
    char fMode[kMaxMode];
    char fUid[kMaxUid];
    char fGid[kMaxGid];
    char fSize[kMaxSize];
    char fTime[kMaxTime];
    char fChkSum[kMaxChkSum];
    char fTypeFlag;
    char fLinkName[kMaxLinkName];
    char fMagic[kMaxMagic];
    char fVersion[kMaxVersion];
    char fUName[kMaxUName];
    char fGName[kMaxGName];
    char fDevMajor[kMaxDevMajor];
    char fDevMinor[kMaxDevMinor];
    char fPrefix[kMaxPrefix];
    char fFiller[12];
} tTarRec;

#define kBlockSize sizeof(tTarRec)

static int vFileSize,vFileTime;

static tTarRec rTar;


//----------------------------------------------------------------------------

#define kMaxBuffer (2048 * kBlockSize)

enum tBoolean {FALSE, TRUE};

enum tFields
{
    kLength, kRevision, kProcessor, kSubmitter,
    kDiscId, kDArtist, kDTitle, kDYear, kDGenre, kExtD,
    kFrame, kTArtist, kTTitle, kExtT,
    kMaxField
};

enum tErrors
{
    kLineError = 1,
    kFileError,
    kArchiveError,
    kLengthError,
    kFieldError,
    kBufferError,
    kCommentError
};

enum tTolerance {kNeeded, kEolOk, kEofOk};

static char vBuffer[kMaxBuffer], vInput[kMaxBuffer];
static char *vNext, *vFrom, *vFields[kMaxField];
static int  vField, vTrack, vCount = 0;


//----------------------------------------------------------------------------
// initialise variables

static void
Initialise(void)
{
    for (vField = 0; vField < kMaxField; ++vField)
        vFields[vField] = NULL;
    vNext = vBuffer;
    vFrom = vInput;
    vTrack = -1;
    vCount++;
}


//----------------------------------------------------------------------------
// output records
// disc records have an empty track field

static void
Output(void)
{
    int aTrack;
    printf("\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%d\t%d\n",
           vCount,
           vFields[kLength] ? vFields[kLength] : "",
           vFields[kRevision] ? vFields[kRevision] : "0",
           vFields[kProcessor] ? vFields[kProcessor] : "",
           vFields[kSubmitter] ? vFields[kSubmitter] : "",
           vFields[kDiscId] ? vFields[kDiscId] : "",
           vFields[kDTitle] ? vFields[kDTitle] : "",
           vFields[kDYear] ? vFields[kDYear] : "",
           vFields[kDGenre] ? vFields[kDGenre] : "",
           vFields[kExtD] ? vFields[kExtD] : "",
           rTar.fName,
           vFileTime,
           vFileSize);

    for (aTrack = 0; aTrack <= vTrack; ++aTrack)
    {
        printf("%d\t%d\t%s\t%s\t%s\n",
               aTrack,
               vCount,
               vFields[kFrame] ? vFields[kFrame] : "",
               vFields[kTTitle] ? vFields[kTTitle] : "",
               vFields[kExtT] ? vFields[kExtT] : "");
        if (vFields[kFrame]) while (*vFields[kFrame]++);
        if (vFields[kTTitle]) while (*vFields[kTTitle]++);
        if (vFields[kExtT]) while (*vFields[kExtT]++);
    }
}


//----------------------------------------------------------------------------
// handle an error

static void
HandleError(Error)
    enum tErrors Error;
{
    if (Error)
    {
        char *vThis;
        for (vThis = vBuffer; vThis < vNext; ++vThis)
            putchar(*vThis);
    }
    exit(Error);
}


//----------------------------------------------------------------------------
// retrieve the next character

static int
GetNext(vTolerance)
    enum tTolerance vTolerance;
{
    int vChar = *vFrom++;

    switch (vChar)
    {
        case 0:
            HandleError(kFileError);

        case '\n':
            if (vTolerance < kEolOk)
                HandleError(kLineError);

        default:
            *vNext = vChar;
            break;
    }
    return vChar;
}


//----------------------------------------------------------------------------
// commit a character, escaping special characters

static void
PutNext(void)
{
    static int vSlashF = FALSE;

    if (vNext >= vBuffer + kMaxBuffer - 2)
    {
        Output();
        HandleError(kBufferError);
    }

    // put out a pending back slash

    if (vSlashF)
    {
        vSlashF = FALSE;
        switch (*vNext)
        {
            case 't':
            case 'r':
            case 'n':
            case '\\':
                ++vNext;
                return;

            default:
                *(vNext+1) = *vNext;
                *vNext++ = '\\';
                break;
        }
    }

    // escape tabs, returns and backslashes
    // null terminate lines

    switch (*vNext)
    {
        case '\n':
            *vNext++ = 0;
            break;

        case '\t':
            *vNext++ = '\\';
            *vNext++ = 't';
            break;

        case '\r':
            *vNext++ = '\\';
            *vNext++ = 'r';
            break;

        case '\\':
            vSlashF = TRUE;

        default:
            ++vNext;
            break;
    }
}


//----------------------------------------------------------------------------
// save a line, escaping special characters

static void
CopyLine(void)
{
    do PutNext();
    while (GetNext(kEolOk) != '\n');
    PutNext();
}


//----------------------------------------------------------------------------
// discard a line

static void
SkipLine(void)
{
    while (GetNext(kEolOk) != '\n');
}


//----------------------------------------------------------------------------
// save a field

static void
CopyField(void)
{
    if (*vNext == '\n')
        PutNext();
    else
        CopyLine();
}


//----------------------------------------------------------------------------
// skip a disc field and output the rest of the line

static void
SkipDisc(aField)
    enum tFields aField;
{
    while (GetNext(kNeeded) != '=');

    if (aField == vField)
        --vNext;
    else
    {
        vFields[aField] = vNext;
        vField = aField;
    }

    GetNext(kEolOk);
    CopyField();
}


//----------------------------------------------------------------------------
// skip a track field and output the rest of the line

static void
SkipTrack(aField)
    enum tFields aField;
{
    int aTrack = 0;

    while (GetNext(kNeeded), *vNext < '0' || *vNext > '9');

    do aTrack = aTrack * 10 + (*vNext - '0');
    while (GetNext(kNeeded), *vNext >= '0' && *vNext <= '9');

    if (*vNext != '=')
        HandleError(kFieldError);

    if (aField == vField && aTrack == vTrack)
        --vNext;
    else
    {
        if (aField != vField)
        {
            vFields[aField] = vNext;
            vField = aField;
        }
        vTrack = aTrack;
    }

    GetNext(kEolOk);
    CopyField();
}


//----------------------------------------------------------------------------
// skip past a colon and white space*/

static int
SkipColon(aField)
    enum tFields aField;
{
    while (GetNext(kEolOk), *vNext != ':' && *vNext != '\n');

    if (*vNext == '\n')
        return FALSE;

    while (GetNext(kEolOk), *vNext == ' ' || *vNext == '\t');

    vFields[aField] = vNext;
    vField = aField;
    return TRUE;
}


//----------------------------------------------------------------------------
// send only a number

static void
CopyNumber(void)
{
    do PutNext();
    while (GetNext(kEolOk), *vNext >= '0' && *vNext <= '9');

    while (*vNext != '\n')
        GetNext(kEolOk);

    PutNext();
}


//----------------------------------------------------------------------------
// skip blanks and analyse a comment line

static void
SkipComment(void)
{
    while (GetNext(kEolOk), *vNext == ' ' || *vNext == '\t');
    switch (*vNext)
    {
        case 'D': //# Disc length:
            if (SkipColon(kLength))
                CopyNumber();
            break;

        case 'R': //# Revision:
            if (SkipColon(kRevision))
                CopyNumber();
            break;

        case 'P': //# Processed by:
            if (SkipColon(kProcessor))
                CopyField();
            break;

        case 'S': //# Submitted via:
            if (SkipColon(kSubmitter))
                CopyField();
            break;

        case '\n':
            break;

        default: //# [0-9]+
            if (*vNext >= '0' && *vNext <= '9')
            {
                if (vField != kFrame)
                {
                    vFields[kFrame] = vNext;
                    vField = kFrame;
                }
                CopyNumber();
            }
            else
                SkipLine();
            break;
    }
}


//----------------------------------------------------------------------------
// process a cddb file

static void
CopyFile(void)
{
    while (TRUE)
        switch (GetNext(kEofOk))
        {
            case '#':
                SkipComment();
                break;

            case 'D':
                switch (GetNext(kNeeded))
                {
                    case 'I': //DISCID=
                        SkipDisc(kDiscId);
                        break;

                    case 'T': //DTITLE[0-9]+=
                        SkipDisc(kDTitle);
                        break;

                    case 'Y': //DYEAR=
                        SkipDisc(kDYear);
                        break;

                    case 'G': //DGENRE=
                        SkipDisc(kDGenre);
                        break;

                    default:
                        HandleError(kFieldError);
                }
                break;

            case 'T': //TTITLE[0-9]+=
                SkipTrack(kTTitle);
                break;

            case 'E':
                GetNext(kNeeded);
                GetNext(kNeeded);
                switch (GetNext(kNeeded))
                {
                    case 'D': //EXTD=
                        SkipDisc(kExtD);
                        break;

                    case 'T': //EXTT=
                        SkipTrack(kExtT);
                        break;

                    default:
                        HandleError(kFieldError);
                }
                break;

            case 'P': //PLAYORDER=
                Output();
                return;

            default:
                SkipLine();
                break;
        }
}


//----------------------------------------------------------------------------
// convert octal digits to an integer

static int
GetOctal(DigitP)
    char *DigitP;
{
    int Value = 0;
    while (*DigitP >= '0' && *DigitP <= '7')
        Value = Value * 8 + *DigitP++ - '0';
    return Value;
}


//----------------------------------------------------------------------------
// read bytes into a buffer

static void
GetBlocks(vTolerance,BufferP,Blocks)
    enum tTolerance vTolerance;
    char *BufferP;
    size_t Blocks;
{
    if (fread(BufferP,kBlockSize,Blocks,stdin) < Blocks)
        HandleError(vTolerance < kEofOk || ferror(stdin) ? kLengthError : 0);
}


//----------------------------------------------------------------------------
// read a file header and contents from a tar archive on stdin

static void
GetFile(void)
{
    Initialise();
    GetBlocks(kEofOk,&rTar,1);
    vFileSize = GetOctal(rTar.fSize);
    vFileTime = GetOctal(rTar.fTime);
    switch (rTar.fTypeFlag)
    {
        case kARegType:
        case kRegType:
        case kContType:
            if (vFileSize >= kMaxBuffer)
                HandleError(kBufferError);
            if (vFileSize)
            {
                size_t vBlocks = (vFileSize + kBlockSize - 1) / kBlockSize;
                GetBlocks(kNeeded,vInput,vBlocks);
                vInput[vFileSize] = 0;
                if (GetNext(kEofOk) == '#' &&
                    GetNext(kEofOk) == ' ' &&
                    GetNext(kEofOk) == 'x' &&
                    GetNext(kEofOk) == 'm' &&
                    GetNext(kEofOk) == 'c' &&
                    GetNext(kEofOk) == 'd')
                {
                    SkipLine();
                    CopyFile();
                }
            }
            break;

        case kLnkType:
        case kSymType:
            printf("%s\t%d\t%s\n",rTar.fName,vFileTime,rTar.fLinkName);
            break;

        case kDirType:
            break;

        case kChrType:
        case kBlkType:
        case kFifoType:
        default:
            fprintf(stderr,"bad archive type %s %x\n",rTar.fName,rTar.fTypeFlag);
            HandleError(kArchiveError);
            break;
    }
}


//----------------------------------------------------------------------------
// search for the start of cddb file marker

extern int
main(void)
{
    while (TRUE)
        GetFile();
}


//============================================================================
